文章目錄
- 約定前后端交互接口
- 建立連接
- 建立連接響應
- 針對"落子"的請求和響應
- 客戶端開發
- 實現棋盤/棋子繪制
- 部分邏輯解釋
約定前后端交互接口
對戰模塊和匹配模塊使用的是兩套邏輯,使用不同的
websocket
的路徑進行處理,做到更好的耦合
建立連接
ws://127.0.0. 1:8080/game
建立連接響應
服務器要生成一些游戲的初始信息,通過這個響應告訴客戶端
{message: 'gameReady', // 消息的類別:游戲就緒ok: true,reason: '',roomId: '12345678', // 玩家所處在的房間idthisUserId: 1, // 玩家自己的idthatUserId: 2, // 玩家的對手的idwhiteUser: 1 // 哪個玩家執白字(先手)
};
- 這些都是玩家匹配成功之后,要由服務器生成的,然后把這個內容返回給瀏覽器
針對"落子"的請求和響應
請求:
{message: 'putChess',userId: 1,row: 0, // 落子的坐標,往哪一行,哪一列來落子
}
- 建議使用行和列,而不是
x
和y
row => y
,col => x
- 后面的代碼中,需要使用二維數組來表示這個棋盤,通過下標取二維數組
[row] => [y]
,[col] => [x]
- 如果使用
x
和y
,就很別扭,和我們日常表示相悖
響應:
{message: 'putChess',userId: 1,row: 0,col: 0,winner: 0
}
winner
表示當前是否分出勝負- 如果
winner
為0
,表示勝負未分,還需要繼續往下對戰 - 如果
winner
非0
,表示當前的獲勝方的用戶id
- 如果
以上交互接口的設計,其實也不一定非得按照剛才這樣寫的這種格式來進行約定,也可以有其他的約定方式
- 不管是哪種格式,只要能夠解決我們的問題,并且編寫代碼的時候簡單方便即可
客戶端開發
實現棋盤/棋子繪制
創建 js/app.js
- 我們不需要理解這部分內容,只需要復制粘貼即可
- 使用一個二維數組來表示棋盤。雖然勝負是通過服務器判定的,但是客戶端的棋盤可以避免“一個位置重復落子”這樣的情況
oneStep
函數起到的效果是在一個指定的位置上繪制一個棋子,可以區分出繪制白子還是黑子,參數是橫坐標和縱坐標,分別對應行和列- 用
onlick
來處理用戶點擊事件,當用戶點擊的時候通過這個函數來控制繪制棋子 me
變量用來表示當前是否輪到我落子;over
變量用來表示游戲結束- 這個代碼中會用到一個背景圖,放到
image
目錄中即可
// 定義全局變量,表示游戲初始化信息
let gameInfo = { roomId: null, thisUserId: null, thatUserId: null, isWhite: true,
} //
// 設定界面顯示相關操作
// function setScreenText(me) { let screen = document.querySelector('#screen'); if (me) { screen.innerHTML = "輪到你落子了!"; } else { screen.innerHTML = "輪到對方落子了!"; }
} //
// 初始化 websocket// // TODO //
// 初始化一局游戲
//
function initGame() { // 是我下還是對方下. 根據服務器分配的先后手情況決定 let me = gameInfo.isWhite; // 游戲是否結束 let over = false; let chessBoard = []; //初始化chessBord數組(表示棋盤的數組) for (let i = 0; i < 15; i++) { chessBoard[i] = []; for (let j = 0; j < 15; j++) { chessBoard[i][j] = 0; } } let chess = document.querySelector('#chess'); let context = chess.getContext('2d'); context.strokeStyle = "#BFBFBF"; // 背景圖片 let logo = new Image(); logo.src = "image/五子棋棋盤.jpg" logo.onload = function () { context.drawImage(logo, 0, 0, 450, 450); initChessBoard(); } // 繪制棋盤網格 function initChessBoard() { for (let i = 0; i < 15; i++) { context.moveTo(15 + i * 30, 15); context.lineTo(15 + i * 30, 430); context.stroke(); context.moveTo(15, 15 + i * 30); context.lineTo(435, 15 + i * 30); context.stroke(); } } // 繪制一個棋子, me 為 true function oneStep(i, j, isWhite) { context.beginPath(); context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI); context.closePath(); var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0); if (!isWhite) { gradient.addColorStop(0, "#0A0A0A"); gradient.addColorStop(1, "#636766"); } else { gradient.addColorStop(0, "#D1D1D1"); gradient.addColorStop(1, "#F9F9F9"); } context.fillStyle = gradient; context.fill(); } chess.onclick = function (e) { if (over) { return; } if (!me) { return; } let x = e.offsetX; let y = e.offsetY; // 注意, 橫坐標是列, 縱坐標是行 let col = Math.floor(x / 30); let row = Math.floor(y / 30); if (chessBoard[row][col] == 0) { // 發送坐標給服務器, 服務器要返回結果 send(row, col); // 留到瀏覽器收到落子響應的時候再處理(收到響應再來畫棋子) oneStep(col, row, gameInfo.isWhite); chessBoard[row][col] = 1; } } // TODO 實現發送落子請求邏輯和處理落子響應邏輯
} initGame();
canvas
是 HTML5
引入的一個標簽,畫布
- “可以在畫布上畫畫”
- 此處棋盤和棋子,都是畫上去的。
canvas
這個標簽有一組配套的js
的canvas api
,通過這個api
就可以實現一些“畫畫”的效果- 例如,展示一個棋盤,就畫很多的直線,就能構成棋盤的網格
- 表示一個棋子,就畫一個圓圈,并填充上顏色
- 還需要響應點擊事件,在鼠標落子的地方來畫圓圈
canvas api
里面能做的事情比較多,比較復雜,不是重點
部分邏輯解釋
- 表示當前游戲中的棋盤,通過這個棋盤來表示當前哪個位置上有子了
- 當玩家點擊的時候,如果有子的位置就不能再繼續落子了
0
用來表示是空閑位置,非0
表示已經有子了
- 針對
chess
(棋盤canvas
) 設定了點擊回調 e
是點擊回調中的事件參數,這里就會記錄點擊的實際位置 (坐標)Math.floor(x/30)
是為了讓點擊操作能夠對應到網格線上- 總體的棋盤尺寸是
450px * 450px
,整個棋盤上面是15行
,15列
- 每一行每一列占用的尺寸就是
30px
- 總體的棋盤尺寸是
oneStep
就是走一步 (里面繪制一個棋子)- 標記為 1,就是這個位置有子,不能落子了