[网页五子棋][对战模块]前后端交互接口(建立连接、连接响应、落子请求/响应),客户端开发(实现棋盘/棋子绘制)
文章目录
- 约定前后端交互接口
- 建立连接
- 建立连接响应
- 针对"落子"的请求和响应
- 客户端开发
- 实现棋盘/棋子绘制
- 部分逻辑解释
约定前后端交互接口
对战模块和匹配模块使用的是两套逻辑,使用不同的
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,就是这个位置有子,不能落子了