页面实现
hall.html
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>游戏大厅</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/hall.css">
</head>
<body>
<div class="nav">五子棋匹配大厅</div>
<div class="container">
<div class="dialog">
<!-- 展示用户信息 -->
<div id="screen"></div>
<!-- 开始匹配 -->
<button id="match" onclick="findMatch()">开始匹配</button>
</div>
</div>
<script src="js/jquery.min.js"></script>
</body>
</html>
hall.html
.container {
height: calc(100% - 50px);
display: flex;
justify-content: center;
align-items: center;
}
.container .dialog {
height: 350px;
width: 299px;
background-color: white;
border-radius: 20px;
padding-top: 30px;
display: flex;
justify-content: center;
/* align-items: center; */
flex-wrap: wrap
}
.dialog *{
display: flex;
justify-content: center;
align-items: center;
}
.dialog #screen {
width: 250px;
height: 150px;
background-color: wheat;
border-radius: 10px;
}
.dialog #match {
width: 150px;
height: 40px;
background-color: rgb(255, 159, 33);
border-radius: 10px;
}
.dialog #match:active {
background-color: rgb(204, 128, 21);
}
获取用户信息接口
当用户进入 游戏大厅时,就应该获取到登录用户的信息显示到页面上,我们使用js代码从访问后端接口获取信息
<script src="js/jquery.min.js"></script>
<script>
$.ajax({
url:"/user/getUserInfo",
type:"get",
success: function(result) {
if(result.username != null) {
let screen = document.querySelector("#screen");
screen.innerHTML = '当前玩家:' + result.username + '<br>天梯积分:' +
result.score + '<br>比赛场次:' + result.totalCount +
'<br>获胜场次:' + result.winCount;
}else{
alert("获取用户信息失败,请重新登录");
location.href = "/login.html";
}
},
error: function() {
alert("获取用户信息失败");
}
})
</script>
WebSocket前端代码
当用户点击匹配按钮时,需要告知服务器该用户要进行匹配,服务器如果接收到则立即回复表示正在匹配,当匹配成功服务器则又需要发送匹配信息给客户端。这里涉及到服务器主动给客户端发送消息的场景,所以我们使用websocket实现
初始化websocket
var webSocket= new WebSocket("ws://localhost:8080/game");
webSocket.onopen = function() {
console.log("连接成功");
}
webSocket.onclose = function() {
console.log("连接关闭");
}
webSocket.onerror = function() {
console.log("error");
}
//页面关闭时释放webSocket
window.onbeforeunload = function() {
webSocket.close();
}
//处理服务器发送的消息
webSocket.onmessage = function(e) {
}
实现findMatch()方法
点击开始匹配按钮后就会执行findMatch方法,进入匹配状态,此时我们可以把开始匹配按钮替换成取消匹配按钮,再次点击则会向服务器发送取消匹配请求
function findMatch() {
//检查websocket连接
if(webSocket.readyState == webSocket.OPEN) {
if($("#match").text() == '开始匹配') {
console.log("开始匹配");
webSocket.send(JSON.stringify({
message: 'startMatch' //约定startMatch表示开始匹配
}));
}else if($("#match").text() == '匹配中...') {
console.log("停止匹配");
webSocket.send(JSON.stringify({
message: 'stopMatch' //约定stopMatch表示停止匹配
}));
}
}else{
alert("连接断开,请重新登录");
location.href = "/login.html";
}
}
实现onmessage
我们约定服务器返回的响应为包含以下三个字段的json:
ok: true/false, //表示请求成功还是失败
errMsg: "错误信息", //请求失败返回错误信息
message: 'startMatch' 开始匹配 / 'stopMatch' 停止匹配/ 'success' 匹配成功 / 'no_login' 用户未登录 / ’repeat_login'该账号重复登录
webSocket.onmessage = function(e) {
//解析json字符串为js对象
let resp = JSON.parse(e.data);
if(resp.message == 'startMatch') {
//开始匹配请求发送成功正在匹配
//替换按钮描述
$("#match").text("匹配中...");
}else if(resp.message == 'stopMatch') {
//取消匹配请求发送成功已取消匹配
//替换按钮描述
$("#match").text("开始匹配");
}else if(resp.message == 'success'){
//匹配成功
console.log("匹配成功! 进入游戏房间");
location.assign("/room.html");
console.log("进入游戏房间");
}else if(resp.message == 'repeat_login') {
alert("该账号已在别处登录");
location.href = "/login.html";
}else if(resp.message == 'no_login') {
alert("当前还未登录");
location.href = "/login.html";
}else {
alert("非法响应 errMsg:" + resp.errMsg);
}
}
WebSocket后端代码
注册websocket
创建TextWebSocketHandler子类,重写如下方法:
package org.ting.j20250110_gobang.websocket;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Component
public class MatchWebSocket extends TextWebSocketHandler {
//连接成功后执行
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
}
//接收到请求后执行
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
}
//连接异常时执行
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
}
//连接正常断开后执行
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
}
}
注册socket:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TextWebSocketHandler textWebSocketHandler;
@Autowired
private MatchWebSocket matchWebSocket;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(textWebSocketHandler, "/test");
registry.addHandler(matchWebSocket, "/findMatch") //注意路径和前端对应
//添加拦截器获取到session,方便获取session中的用户信息
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
维护在线用户
在用户登录成功后,我们可以维护好用户的websocket会话,把用户表示为在线状态,方便获取到用户的websocket会话
@Component
public class OnlineUserManager {
//使用ConcurrentHashMap保证线程安全
private Map<Integer, WebSocketSession> onlineUser = new ConcurrentHashMap<>();
public void enterGameHall(int userId, WebSocketSession session) {
//用户上线
onlineUser.put(userId, session);
}
public void exitGameHall(int userId) {
//用户下线
onlineUser.remove(userId);
}
public WebSocketSession getFromHall(int userId) {
//获取用户的websocket会话
return onlineUser.get(userId);
}
}
实现webSocket相关方法
上期我们定义了webSocket的处理类,但是并没有完成重写的方法,接下来我们借助维护的在线用户具体实现如下方法
在实现这些方法之前,我们还需要按照上期约定好的信息交互形式定义两个实体类,代表请求和响应:
public class MatchRequest {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@Data
public class MatchResponse {
private boolean ok;
private String errMsg;
private String message;
}
连接成功
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
try {
User user = (User) session.getAttributes().get("user");
if (onlineUserManager.getFromHall(user.getUserId()) != null) {
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setErrMsg("已经在别处登录");
response.setMessage("repeat_login");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
// 此处直接关闭有些太激进了, 还是返回一个特殊的 message , 供客户端来进行判定, 由客户端负责进行处理
// session.close();
return;
} else {
onlineUserManager.enterGameHall(user.getUserId(), session);
System.out.println("用户:" + user.getUsername() + " 已上线");
}
}catch (NullPointerException e){
System.out.println("[MatchAPI.afterConnectionEstablished] 当前用户未登录!");
// e.printStackTrace();
// 出现空指针异常, 说明当前用户的身份信息是空, 用户未登录呢.
// 把当前用户尚未登录这个信息给返回回去~~
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setErrMsg("您尚未登录! 不能进行后续匹配功能!");
response.setMessage("no_login");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
连接断开
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
try {
User user = (User)session.getAttributes().get("user");
//防止重复登录时删除正常登录的在线信息
if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {
onlineUserManager.exitGameHall(user.getUserId());
System.out.println("用户:" + user.getUsername() + " 已下线");
}
}catch (NullPointerException e) {
System.out.println("[MatchAPI.handleTransportError] 当前用户未登录!");
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setErrMsg("用户未登录");
response.setMessage("no_login");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
//连接正常断开后执行
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
try {
User user = (User)session.getAttributes().get("user");
//防止重复登录时删除正常登录的在线信息
if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {
onlineUserManager.exitGameHall(user.getUserId());
System.out.println("用户:" + user.getUsername() + " 已下线");
}
}catch (NullPointerException e) {
System.out.println("[MatchAPI.afterConnectionClosed] 当前用户未登录!");
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setErrMsg("用户未登录");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
}
定义Mather类
@Component
public class Matcher {
// 创建三个匹配队列
private Queue<User> normalQueue = new LinkedList<>();
private Queue<User> highQueue = new LinkedList<>();
private Queue<User> veryHighQueue = new LinkedList<>();
@Autowired
private OnlineUserManager onlineUserManager;
private ObjectMapper objectMapper = new ObjectMapper();
// 操作匹配队列的方法.
// 把玩家放到匹配队列中
public void add(User user) {
if (user.getScore() < 2000) {
synchronized (normalQueue) {
normalQueue.offer(user);
normalQueue.notify();
}
System.out.println("把玩家 " + user.getUsername() + " 加入到了 normalQueue 中!");
} else if (user.getScore() >= 2000 && user.getScore() < 3000) {
synchronized (highQueue) {
highQueue.offer(user);
highQueue.notify();
}
System.out.println("把玩家 " + user.getUsername() + " 加入到了 highQueue 中!");
} else {
synchronized (veryHighQueue) {
veryHighQueue.offer(user);
veryHighQueue.notify();
}
System.out.println("把玩家 " + user.getUsername() + " 加入到了 veryHighQueue 中!");
}
}
// 当玩家点击停止匹配的时候, 就需要把玩家从匹配队列中删除
public void remove(User user) {
if (user.getScore() < 2000) {
synchronized (normalQueue) {
normalQueue.remove(user);
}
System.out.println("把玩家 " + user.getUsername() + " 移除了 normalQueue!");
} else if (user.getScore() >= 2000 && user.getScore() < 3000) {
synchronized (highQueue) {
highQueue.remove(user);
}
System.out.println("把玩家 " + user.getUsername() + " 移除了 highQueue!");
} else {
synchronized (veryHighQueue) {
veryHighQueue.remove(user);
}
System.out.println("把玩家 " + user.getUsername() + " 移除了 veryHighQueue!");
}
}
}
处理匹配请求
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
User user = (User) session.getAttributes().get("user");
// 获取到客户端给服务器发送的数据
String payload = message.getPayload();
// 当前这个数据载荷是一个 JSON 格式的字符串, 就需要把它转成 Java 对象. MatchRequest
MatchRequest request = objectMapper.readValue(payload, MatchRequest.class);
MatchResponse response = new MatchResponse();
if (request.getMessage().equals("startMatch")) {
// 进入匹配队列
matcher.add(user);
// 把玩家信息放入匹配队列之后, 就可以返回一个响应给客户端了.
response.setOk(true);
response.setMessage("startMatch");
} else if (request.getMessage().equals("stopMatch")) {
// 退出匹配队列
matcher.remove(user);
// 移除之后, 就可以返回一个响应给客户端了.
response.setOk(true);
response.setMessage("stopMatch");
} else {
response.setOk(false);
response.setErrMsg("非法的匹配请求");
}
String jsonString = objectMapper.writeValueAsString(response);
session.sendMessage(new TextMessage(jsonString));
}
游戏房间实体类
package com.example.demo;
import com.example.demo.dao.User;
import java.util.UUID;
//每个房间都是不通的,所以不能给spring管理
public class Room {
private String roomId;
private User user1;
private User user2;
// 先手方的玩家 id
private int whiteUser;
public int getWhiteUser() {
return whiteUser;
}
public void setWhiteUser(int whiteUser) {
this.whiteUser = whiteUser;
}
public Room() {
roomId = UUID.randomUUID().toString();
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public User getUser1() {
return user1;
}
public void setUser1(User user1) {
this.user1 = user1;
}
public User getUser2() {
return user2;
}
public void setUser2(User user2) {
this.user2 = user2;
}
}
实现匹配功能
创建线程扫描队列
我们为每个匹配队列创建一个线程,用来实现匹配功能,我们在构造方法中创建线程:
public Matcher() {
// 创建三个线程, 分别针对这三个匹配队列, 进行操作.
Thread t1 = new Thread() {
@Override
public void run() {
// 扫描 normalQueue
while (true) {
handlerMatch(normalQueue);
}
}
};
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
while (true) {
handlerMatch(highQueue);
}
}
};
t2.start();
Thread t3 = new Thread() {
@Override
public void run() {
while (true) {
handlerMatch(veryHighQueue);
}
}
};
t3.start();
}
实现handlerMatch()方法进行匹配
public void handlerMatch(Queue<User> matchQueue) {
try {
//对操作的队列加锁保证线程安全
synchronized (matchQueue) {
//1.检测队列中是否有两个元素
while(matchQueue.size() < 2) {
matchQueue.wait();
}
//2.从队列中取出两个玩家
User user1 = matchQueue.poll();
User user2 = matchQueue.poll();
//3.获取到两个玩家的会话信息
WebSocketSession session1 = onlineUserManager.getFromHall(user1.getUserId());
WebSocketSession session2 = onlineUserManager.getFromHall(user2.getUserId());
//4.todo 把两个玩家放到一个游戏房间中
//5.给用户返回匹配成功的响应
MatchResponse response = new MatchResponse();
response.setOk(true);
response.setMessage("success");
String json = objectMapper.writeValueAsString(response);
session1.sendMessage(new TextMessage(json));
session2.sendMessage(new TextMessage(json));
}
}catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
修改websocket后端代码
@Component
public class MatchWebSocket extends TextWebSocketHandler {
@Autowired
private Matcher matcher;
@Autowired
private OnlineUserManager onlineUserManager;
ObjectMapper objectMapper=new ObjectMapper();
//连接成功后执行
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
try {
User user = (User) session.getAttributes().get("user");
if (onlineUserManager.getFromHall(user.getUserId()) != null) {
MatchResponse response = new MatchResponse();
response.setOk(true);
response.setErrMsg("已经在别处登录");
response.setMessage("repeat_login");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
// 此处直接关闭有些太激进了, 还是返回一个特殊的 message , 供客户端来进行判定, 由客户端负责进行处理
// session.close();
return;
} else {
onlineUserManager.enterGameHall(user.getUserId(), session);
System.out.println("用户:" + user.getUsername() + " 已上线");
}
}catch (NullPointerException e){
System.out.println("[MatchAPI.afterConnectionEstablished] 当前用户未登录!");
// e.printStackTrace();
// 出现空指针异常, 说明当前用户的身份信息是空, 用户未登录呢.
// 把当前用户尚未登录这个信息给返回回去~~
MatchResponse response = new MatchResponse();
response.setOk(true);
response.setErrMsg("您尚未登录! 不能进行后续匹配功能!");
response.setMessage("no_login");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
//接收到请求后执行
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
User user = (User) session.getAttributes().get("user");
// 获取到客户端给服务器发送的数据
String payload = message.getPayload();
// 当前这个数据载荷是一个 JSON 格式的字符串, 就需要把它转成 Java 对象. MatchRequest
MatchRequest request = objectMapper.readValue(payload, MatchRequest.class);
MatchResponse response = new MatchResponse();
if (request.getMessage().equals("startMatch")) {
// 进入匹配队列
matcher.add(user);
// 把玩家信息放入匹配队列之后, 就可以返回一个响应给客户端了.
response.setOk(true);
response.setMessage("startMatch");
} else if (request.getMessage().equals("stopMatch")) {
// 退出匹配队列
matcher.remove(user);
// 移除之后, 就可以返回一个响应给客户端了.
response.setOk(true);
response.setMessage("stopMatch");
} else {
response.setOk(false);
response.setErrMsg("非法的匹配请求");
}
String jsonString = objectMapper.writeValueAsString(response);
session.sendMessage(new TextMessage(jsonString));
}
//连接异常时执行
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
try {
User user = (User)session.getAttributes().get("user");
//防止重复登录时删除正常登录的在线信息
if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {
onlineUserManager.exitGameHall(user.getUserId());
System.out.println("用户:" + user.getUsername() + " 已下线");
matcher.remove(user);
}
}catch (NullPointerException e) {
System.out.println("[MatchAPI.handleTransportError] 当前用户未登录!");
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setErrMsg("用户未登录");
response.setMessage("no_login");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
//连接正常断开后执行
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
try {
User user = (User)session.getAttributes().get("user");
//防止重复登录时删除正常登录的在线信息
if(onlineUserManager.getFromHall(user.getUserId()).equals(session)) {
onlineUserManager.exitGameHall(user.getUserId());
System.out.println("用户:" + user.getUsername() + " 已下线");
matcher.remove(user);
}
}catch (NullPointerException e) {
System.out.println("[MatchAPI.afterConnectionClosed] 当前用户未登录!");
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setErrMsg("用户未登录");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
}
房间管理器实体类
这里我们创建了两个哈希表,一个维护房间id到游戏房间的映射,一个维护用户id到游戏房间的映射,此时我们就可以通过add方法把两个用户加入一个游戏房间内
package com.example.demo;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class RoomManager {
//通过房间id来获得房间
private ConcurrentHashMap<String, Room> roomIdToRoom = new ConcurrentHashMap<>();
//通过用户id来获取房间id,然后再获取房间
private ConcurrentHashMap<Integer, String> userIdToRoomId = new ConcurrentHashMap<>();
public void add(String roomId, Room room, Integer userId1, Integer userId2) {
roomIdToRoom.put(roomId, room);
userIdToRoomId.put(userId1, roomId);
userIdToRoomId.put(userId2, roomId);
}
public void remove(String roomId, int userId1, int userId2) {
roomIdToRoom.remove(roomId);
userIdToRoomId.remove(userId1);
userIdToRoomId.remove(userId2);
}
public Room getRoomByRoomId(String roomId) {
return roomIdToRoom.get(roomId);
}
public Room getRoomByUserId(Integer userId) {
return roomIdToRoom.get(userIdToRoomId.get(userId));
}
}
进入房间代码
Room room = new Room();
roomManager.add(room.getRoomId(), room, user1.getUserId(), user2.getUserId());
游戏房间前端代码
room.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>游戏房间</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/game_room.css">
</head>
<body>
<div class="nav">五子棋对战</div>
<div class="container">
<div>
<!-- 棋盘区域, 需要基于 canvas 进行实现 -->
<canvas id="chess" width="450px" height="450px">
</canvas>
<!-- 显示区域 -->
<div id="screen"> 等待玩家连接中... </div>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>
common.css
/* 公共样式 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
background-image: url(../img/kk.png);
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.nav {
height: 50px;
background-color: gray;
color: white;
font-size: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 100%;
height: calc(100% - 50px);
display: flex;
align-items: center;
justify-content: center;
}
game_room.css
#screen {
width: 450px;
height: 50px;
margin-top: 10px;
background-color: #fff;
font-size: 22px;
line-height: 50px;
text-align: center;
}
.return-btn {
width: 450px;
height: 50px;
margin-top: 5px;
background-color: orange;
color: #fff;
font-size: 22px;
line-height: 50px;
text-align: center;
}
script.js
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 = "img/sky.jpeg";
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) {
// TODO 发送坐标给服务器, 服务器要返回结果
oneStep(col, row, gameInfo.isWhite);
chessBoard[row][col] = 1;
}
}
}
initGame();
下篇文章我们来写对战的相关代码