一、约定前后端交互接口
1. 匹配是否成功的 接口
匹配成功返回数据 1. message消息类别 2. ok 3. reson 4. 房间id 5. 双方id 6.白色玩家
2. 用户发送落子的请求 以及 响应 接口
文章来源:https://www.toymoban.com/news/detail-636870.html
二、实现前端页面
文章来源地址https://www.toymoban.com/news/detail-636870.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>
游戏大厅里的 java script
<script>
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
//
// 此处写的路径要写作 /game, 不要写作 /game/
let websocketUrl = "ws://" + location.host + "/game";
let websocket = new WebSocket(websocketUrl);
websocket.onopen = function() {
console.log("连接游戏房间成功!");
}
websocket.close = function() {
console.log("和游戏服务器断开连接!");
}
websocket.onerror = function() {
console.log("和服务器的连接出现异常!");
}
window.onbeforeunload = function() {
websocket.close();
}
// 处理服务器返回的响应数据
websocket.onmessage = function(event) {
console.log("[handlerGameReady] " + event.data);
let resp = JSON.parse(event.data);
if (!resp.ok) {
alert("连接游戏失败! reason: " + resp.reason);
// 如果出现连接失败的情况, 回到游戏大厅
location.assign("/game_hall.html");
return;
}
if (resp.message == 'gameReady') {
gameInfo.roomId = resp.roomId;
gameInfo.thisUserId = resp.thisUserId;
gameInfo.thatUserId = resp.thatUserId;
gameInfo.isWhite = (resp.whiteUser == resp.thisUserId);
// 初始化棋盘
initGame();
// 设置显示区域的内容
setScreenText(gameInfo.isWhite);
} else if (resp.message == 'repeatConnection') {
alert("检测到游戏多开! 请使用其他账号登录!");
location.assign("/login.html");
}
}
//
// 初始化一局游戏
//
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/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) {
// 发送坐标给服务器, 服务器要返回结果
send(row, col);
// 留到浏览器收到落子响应的时候再处理(收到响应再来画棋子)
// oneStep(col, row, gameInfo.isWhite);
// chessBoard[row][col] = 1;
}
}
function send(row, col) {
let req = {
message: 'putChess',
userId: gameInfo.thisUserId,
row: row,
col: col
};
websocket.send(JSON.stringify(req));
}
// 之前 websocket.onmessage 主要是用来处理了游戏就绪响应. 在游戏就绪之后, 初始化完毕之后, 也就不再有这个游戏就绪响应了.
// 就在这个 initGame 内部, 修改 websocket.onmessage 方法~~, 让这个方法里面针对落子响应进行处理!
websocket.onmessage = function(event) {
console.log("[handlerPutChess] " + event.data);
let resp = JSON.parse(event.data);
if (resp.message != 'putChess') {
console.log("响应类型错误!");
return;
}
// 先判定当前这个响应是自己落的子, 还是对方落的子.
if (resp.userId == gameInfo.thisUserId) {
// 我自己落的子
// 根据我自己子的颜色, 来绘制一个棋子
oneStep(resp.col, resp.row, gameInfo.isWhite);
} else if (resp.userId == gameInfo.thatUserId) {
// 我的对手落的子
oneStep(resp.col, resp.row, !gameInfo.isWhite);
} else {
// 响应错误! userId 是有问题的!
console.log('[handlerPutChess] resp userId 错误!');
return;
}
// 给对应的位置设为 1, 方便后续逻辑判定当前位置是否已经有子了.
chessBoard[resp.row][resp.col] = 1;
// 交换双方的落子轮次
me = !me;
setScreenText(me);
// 判定游戏是否结束
let screenDiv = document.querySelector('#screen');
if (resp.winner != 0) {
if (resp.winner == gameInfo.thisUserId) {
// alert('你赢了!');
screenDiv.innerHTML = '你赢了!';
} else if (resp.winner = gameInfo.thatUserId) {
// alert('你输了!');
screenDiv.innerHTML = '你输了!';
} else {
alert("winner 字段错误! " + resp.winner);
}
// 回到游戏大厅
// location.assign('/game_hall.html');
// 增加一个按钮, 让玩家点击之后, 再回到游戏大厅~
let backBtn = document.createElement('button');
backBtn.innerHTML = '回到大厅';
backBtn.onclick = function() {
location.replace('/game_hall.html');
}
let fatherDiv = document.querySelector('.container>div');
fatherDiv.appendChild(backBtn);
}
}
}
</script>
一个类记录房间中的信息(房间id,两个用户id,是否为白棋)
信息提示框
处理匹配API
初始化游戏(棋盘,下一个棋子,接受棋子处理响应,判断是否结束)
三、实现后端
一. 建立各种请求和相应的类
1. 客户端连接到游戏房间后, 服务器返回的响应.
GameReadyResponse(返回给用户用的)
package com.example.java_gobang.game;
// 客户端连接到游戏房间后, 服务器返回的响应.
public class GameReadyResponse {
private String message;
private boolean ok;
private String reason;
private String roomId;
private int thisUserId;
private int thatUserId;
private int whiteUser;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public boolean isOk() {
return ok;
}
public void setOk(boolean ok) {
this.ok = ok;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getRoomId() {
return roomId;
}
public void setRoomId(String roomId) {
this.roomId = roomId;
}
public int getThisUserId() {
return thisUserId;
}
public void setThisUserId(int thisUserId) {
this.thisUserId = thisUserId;
}
public int getThatUserId() {
return thatUserId;
}
public void setThatUserId(int thatUserId) {
this.thatUserId = thatUserId;
}
public int getWhiteUser() {
return whiteUser;
}
public void setWhiteUser(int whiteUser) {
this.whiteUser = whiteUser;
}
}
2. 落子请求
GameRequest(用户发送来的)
package com.example.java_gobang.game;
// 这个类表示 落子请求
public class GameRequest {
private String message;
private int userId;
private int row;
private int col;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
}
3. 落子响应
GameResponse(返回给用户)
package com.example.java_gobang.game;
// 这个类表示一个 落子响应
public class GameResponse {
private String message;
private int userId;
private int row;
private int col;
private int winner;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
public int getWinner() {
return winner;
}
public void setWinner(int winner) {
this.winner = winner;
}
}
二. 实现用户管理类 和 房间管理类
OnlineUserManager
package com.example.java_gobang.game;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class OnlineUserManager {
// 这个哈希表就用来表示当前用户在游戏大厅在线状态.
private ConcurrentHashMap<Integer, WebSocketSession> gameHall = new ConcurrentHashMap<>();
// 这个哈希表就用来表示当前用户在游戏房间的在线状态.
private ConcurrentHashMap<Integer, WebSocketSession> gameRoom = new ConcurrentHashMap<>();
public void enterGameHall(int userId, WebSocketSession webSocketSession) {
gameHall.put(userId, webSocketSession);
}
public void exitGameHall(int userId) {
gameHall.remove(userId);
}
public WebSocketSession getFromGameHall(int userId) {
return gameHall.get(userId);
}
public void enterGameRoom(int userId, WebSocketSession webSocketSession) {
gameRoom.put(userId, webSocketSession);
}
public void exitGameRoom(int userId) {
gameRoom.remove(userId);
}
public WebSocketSession getFromGameRoom(int userId) {
return gameRoom.get(userId);
}
}
Room
1. 由于我们需要 多例模式下的 Room,所以我们不能把Room注入Spring
2. 那么我们就需要用context 调用 OnlineUserManager
package com.example.java_gobang.game;
import com.example.java_gobang.JavaGobangApplication;
import com.example.java_gobang.model.User;
import com.example.java_gobang.model.UserMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.UUID;
// 这个类就表示一个游戏房间
public class Room {
// 使用字符串类型来表示, 方便生成唯一值.
private String roomId;
private User user1;
private User user2;
// 先手方的玩家 id
private int whiteUser;
private static final int MAX_ROW = 15;
private static final int MAX_COL = 15;
// 这个二维数组用来表示棋盘
// 约定:
// 1) 使用 0 表示当前位置未落子. 初始化好的 int 二维数组, 就相当于是 全 0
// 2) 使用 1 表示 user1 的落子位置
// 3) 使用 2 表示 user2 的落子位置
private int[][] board = new int[MAX_ROW][MAX_COL];
// 创建 ObjectMapper 用来转换 JSON
private ObjectMapper objectMapper = new ObjectMapper();
// 引入 OnlineUserManager
// @Autowired
private OnlineUserManager onlineUserManager;
// 引入 RoomManager, 用于房间销毁
// @Autowired
private RoomManager roomManager;
private UserMapper userMapper;
// 通过这个方法来处理一次落子操作.
// 要做的事情:
public void putChess(String reqJson) throws IOException {
// 1. 记录当前落子的位置.
GameRequest request = objectMapper.readValue(reqJson, GameRequest.class);
GameResponse response = new GameResponse();
// 当前这个子是玩家1 落的还是玩家2 落的. 根据这个玩家1 和 玩家2 来决定往数组中是写 1 还是 2
int chess = request.getUserId() == user1.getUserId() ? 1 : 2;
int row = request.getRow();
int col = request.getCol();
if (board[row][col] != 0) {
// 在客户端已经针对重复落子进行过判定了. 此处为了程序更加稳健, 在服务器再判定一次.
System.out.println("当前位置 (" + row + ", " + col + ") 已经有子了!");
return;
}
board[row][col] = chess;
// 2. 打印出当前的棋盘信息, 方便来观察局势. 也方便后面验证胜负关系的判定.
printBoard();
// 3. 进行胜负判定
int winner = checkWinner(row, col, chess);
// 4. 给房间中的所有客户端都返回响应.
response.setMessage("putChess");
response.setUserId(request.getUserId());
response.setRow(row);
response.setCol(col);
response.setWinner(winner);
// 要想给用户发送 websocket 数据, 就需要获取到这个用户的 WebSocketSession
WebSocketSession session1 = onlineUserManager.getFromGameRoom(user1.getUserId());
WebSocketSession session2 = onlineUserManager.getFromGameRoom(user2.getUserId());
// 万一当前查到的会话为空(玩家已经下线了) 特殊处理一下
if (session1 == null) {
// 玩家1 已经下线了. 直接认为玩家2 获胜!
response.setWinner(user2.getUserId());
System.out.println("玩家1 掉线!");
}
if (session2 == null) {
// 玩家2 已经下线. 直接认为玩家1 获胜!
response.setWinner(user1.getUserId());
System.out.println("玩家2 掉线!");
}
// 把响应构造成 JSON 字符串, 通过 session 进行传输.
String respJson = objectMapper.writeValueAsString(response);
if (session1 != null) {
session1.sendMessage(new TextMessage(respJson));
}
if (session2 != null) {
session2.sendMessage(new TextMessage(respJson));
}
// 5. 如果当前胜负已分, 此时这个房间就失去存在的意义了. 就可以直接销毁房间. (把房间从房间管理器中给移除)
if (response.getWinner() != 0) {
// 胜负已分
System.out.println("游戏结束! 房间即将销毁! roomId=" + roomId + " 获胜方为: " + response.getWinner());
// 更新获胜方和失败方的信息.
int winUserId = response.getWinner();
int loseUserId = response.getWinner() == user1.getUserId() ? user2.getUserId() : user1.getUserId();
userMapper.userWin(winUserId);
userMapper.userLose(loseUserId);
// 销毁房间
roomManager.remove(roomId, user1.getUserId(), user2.getUserId());
}
}
private void printBoard() {
// 打印出棋盘
System.out.println("[打印棋盘信息] " + roomId);
System.out.println("=====================================================================");
for (int r = 0; r < MAX_ROW; r++) {
for (int c = 0; c < MAX_COL; c++) {
// 针对一行之内的若干列, 不要打印换行
System.out.print(board[r][c] + " ");
}
// 每次遍历完一行之后, 再打印换行.
System.out.println();
}
System.out.println("=====================================================================");
}
// 使用这个方法来判定当前落子是否分出胜负.
// 约定如果玩家1 获胜, 就返回玩家1 的 userId
// 如果玩家2 获胜, 就返回玩家2 的 userId
// 如果胜负未分, 就返回 0
private int checkWinner(int row, int col, int chess) {
// 1. 检查所有的行
// 先遍历这五种情况
for (int c = col - 4; c <= col; c++) {
// 针对其中的一种情况, 来判定这五个子是不是连在一起了~
// 不光是这五个子得连着, 而且还得和玩家落的子是一样~~ (才算是获胜)
try {
if (board[row][c] == chess
&& board[row][c + 1] == chess
&& board[row][c + 2] == chess
&& board[row][c + 3] == chess
&& board[row][c + 4] == chess) {
// 构成了五子连珠! 胜负已分!
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
// 如果出现数组下标越界的情况, 就在这里直接忽略这个异常.
continue;
}
}
// 2. 检查所有列
for (int r = row - 4; r <= row; r++) {
try {
if (board[r][col] == chess
&& board[r + 1][col] == chess
&& board[r + 2][col] == chess
&& board[r + 3][col] == chess
&& board[r + 4][col] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 3. 检查左对角线
for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {
try {
if (board[r][c] == chess
&& board[r + 1][c + 1] == chess
&& board[r + 2][c + 2] == chess
&& board[r + 3][c + 3] == chess
&& board[r + 4][c + 4] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 4. 检查右对角线
for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {
try {
if (board[r][c] == chess
&& board[r + 1][c - 1] == chess
&& board[r + 2][c - 2] == chess
&& board[r + 3][c - 3] == chess
&& board[r + 4][c - 4] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 胜负未分, 就直接返回 0 了.
return 0;
}
public int getWhiteUser() {
return whiteUser;
}
public void setWhiteUser(int whiteUser) {
this.whiteUser = whiteUser;
}
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 Room() {
// 构造 Room 的时候生成一个唯一的字符串表示房间 id.
// 使用 UUID 来作为房间 id
roomId = UUID.randomUUID().toString();
// 通过入口类中记录的 context 来手动获取到前面的 RoomManager 和 OnlineUserManager
onlineUserManager = JavaGobangApplication.context.getBean(OnlineUserManager.class);
roomManager = JavaGobangApplication.context.getBean(RoomManager.class);
userMapper = JavaGobangApplication.context.getBean(UserMapper.class);
}
public static void main(String[] args) {
Room room = new Room();
System.out.println(room.roomId);
}
}
RoomManager
package com.example.java_gobang.game;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
// 房间管理器类.
// 这个类也希望有唯一实例.
@Component
public class RoomManager {
private ConcurrentHashMap<String, Room> rooms = new ConcurrentHashMap<>();
private ConcurrentHashMap<Integer, String> userIdToRoomId = new ConcurrentHashMap<>();
public void add(Room room, int userId1, int userId2) {
rooms.put(room.getRoomId(), room);
userIdToRoomId.put(userId1, room.getRoomId());
userIdToRoomId.put(userId2, room.getRoomId());
}
public void remove(String roomId, int userId1, int userId2) {
rooms.remove(roomId);
userIdToRoomId.remove(userId1);
userIdToRoomId.remove(userId2);
}
public Room getRoomByRoomId(String roomId) {
return rooms.get(roomId);
}
public Room getRoomByUserId(int userId) {
String roomId = userIdToRoomId.get(userId);
if (roomId == null) {
// userId -> roomId 映射关系不存在, 直接返回 null
return null;
}
return rooms.get(roomId);
}
}
三. 实现GameAPI
package com.example.java_gobang.api;
import com.example.java_gobang.game.*;
import com.example.java_gobang.model.User;
import com.example.java_gobang.model.UserMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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;
import javax.annotation.Resource;
import java.io.IOException;
@Component
public class GameAPI extends TextWebSocketHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private RoomManager roomManager;
@Autowired
private OnlineUserManager onlineUserManager;
@Resource
private UserMapper userMapper;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
GameReadyResponse resp = new GameReadyResponse();
// 1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)
User user = (User) session.getAttributes().get("user");
if (user == null) {
resp.setOk(false);
resp.setReason("用户尚未登录!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
return;
}
// 2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)
Room room = roomManager.getRoomByUserId(user.getUserId());
if (room == null) {
// 如果为 null, 当前没有找到对应的房间. 该玩家还没有匹配到.
resp.setOk(false);
resp.setReason("用户尚未匹配到!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
return;
}
// 3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)
// 前面准备了一个 OnlineUserManager
if (onlineUserManager.getFromGameHall(user.getUserId()) != null
|| onlineUserManager.getFromGameRoom(user.getUserId()) != null) {
// 如果一个账号, 一边是在游戏大厅, 一边是在游戏房间, 也视为多开~~
resp.setOk(true);
resp.setReason("禁止多开游戏页面");
resp.setMessage("repeatConnection");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
return;
}
// 4. 设置当前玩家上线!
onlineUserManager.enterGameRoom(user.getUserId(), session);
// 5. 把两个玩家加入到游戏房间中.
// 前面的创建房间/匹配过程, 是在 game_hall.html 页面中完成的.
// 因此前面匹配到对手之后, 需要经过页面跳转, 来到 game_room.html 才算正式进入游戏房间(才算玩家准备就绪)
// 当前这个逻辑是在 game_room.html 页面加载的时候进行的.
// 执行到当前逻辑, 说明玩家已经页面跳转成功了!!
// 页面跳转, 其实是个大活~~ (很有可能出现 "失败" 的情况的)
synchronized (room) {
if (room.getUser1() == null) {
// 第一个玩家还尚未加入房间.
// 就把当前连上 websocket 的玩家作为 user1, 加入到房间中.
room.setUser1(user);
// 把先连入房间的玩家作为先手方.
room.setWhiteUser(user.getUserId());
System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家1");
return;
}
if (room.getUser2() == null) {
// 如果进入到这个逻辑, 说明玩家1 已经加入房间, 现在要给当前玩家作为玩家2 了
room.setUser2(user);
System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家2");
// 当两个玩家都加入成功之后, 就要让服务器, 给这两个玩家都返回 websocket 的响应数据.
// 通知这两个玩家说, 游戏双方都已经准备好了.
// 通知玩家1
noticeGameReady(room, room.getUser1(), room.getUser2());
// 通知玩家2
noticeGameReady(room, room.getUser2(), room.getUser1());
return;
}
}
// 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.
// 这种情况理论上是不存在的, 为了让程序更加的健壮, 还是做一个判定和提示.
resp.setOk(false);
resp.setReason("当前房间已满, 您不能加入房间");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
}
private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {
GameReadyResponse resp = new GameReadyResponse();
resp.setMessage("gameReady");
resp.setOk(true);
resp.setReason("");
resp.setRoomId(room.getRoomId());
resp.setThisUserId(thisUser.getUserId());
resp.setThatUserId(thatUser.getUserId());
resp.setWhiteUser(room.getWhiteUser());
// 把当前的响应数据传回给玩家.
WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thisUser.getUserId());
webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 1. 先从 session 里拿到当前用户的身份信息
User user = (User) session.getAttributes().get("user");
if (user == null) {
System.out.println("[handleTextMessage] 当前玩家尚未登录! ");
return;
}
// 2. 根据玩家 id 获取到房间对象
Room room = roomManager.getRoomByUserId(user.getUserId());
// 3. 通过 room 对象来处理这次具体的请求
room.putChess(message.getPayload());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
User user = (User) session.getAttributes().get("user");
if (user == null) {
// 此处就简单处理, 在断开连接的时候就不给客户端返回响应了.
return;
}
WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
if (session == exitSession) {
// 加上这个判定, 目的是为了避免在多开的情况下, 第二个用户退出连接动作, 导致第一个用户的会话被删除.
onlineUserManager.exitGameRoom(user.getUserId());
}
System.out.println("当前用户 " + user.getUsername() + " 游戏房间连接异常!");
// 通知对手获胜了
noticeThatUserWin(user);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
User user = (User) session.getAttributes().get("user");
if (user == null) {
// 此处就简单处理, 在断开连接的时候就不给客户端返回响应了.
return;
}
WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
if (session == exitSession) {
// 加上这个判定, 目的是为了避免在多开的情况下, 第二个用户退出连接动作, 导致第一个用户的会话被删除.
onlineUserManager.exitGameRoom(user.getUserId());
}
System.out.println("当前用户 " + user.getUsername() + " 离开游戏房间!");
// 通知对手获胜了
noticeThatUserWin(user);
}
private void noticeThatUserWin(User user) throws IOException {
// 1. 根据当前玩家, 找到玩家所在的房间
Room room = roomManager.getRoomByUserId(user.getUserId());
if (room == null) {
// 这个情况意味着房间已经被释放了, 也就没有 "对手" 了
System.out.println("当前房间已经释放, 无需通知对手!");
return;
}
// 2. 根据房间找到对手
User thatUser = (user == room.getUser1()) ? room.getUser2() : room.getUser1();
// 3. 找到对手的在线状态
WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thatUser.getUserId());
if (webSocketSession == null) {
// 这就意味着对手也掉线了!
System.out.println("对手也已经掉线了, 无需通知!");
return;
}
// 4. 构造一个响应, 来通知对手, 你是获胜方
GameResponse resp = new GameResponse();
resp.setMessage("putChess");
resp.setUserId(thatUser.getUserId());
resp.setWinner(thatUser.getUserId());
webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
// 5. 更新玩家的分数信息
int winUserId = thatUser.getUserId();
int loseUserId = user.getUserId();
userMapper.userWin(winUserId);
userMapper.userLose(loseUserId);
// 6. 释放房间对象
roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());
}
}
1. afterConnectionEstablished
1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)
2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)
3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)
4. 设置当前玩家上线!
5. 把两个玩家加入到游戏房间中.
6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
GameReadyResponse resp = new GameReadyResponse();
// 1. 先获取到用户的身份信息. (从 HttpSession 里拿到当前用户的对象)
User user = (User) session.getAttributes().get("user");
if (user == null) {
resp.setOk(false);
resp.setReason("用户尚未登录!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
return;
}
// 2. 判定当前用户是否已经进入房间. (拿着房间管理器进行查询)
Room room = roomManager.getRoomByUserId(user.getUserId());
if (room == null) {
// 如果为 null, 当前没有找到对应的房间. 该玩家还没有匹配到.
resp.setOk(false);
resp.setReason("用户尚未匹配到!");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
return;
}
// 3. 判定当前是不是多开 (该用户是不是已经在其他地方进入游戏了)
// 前面准备了一个 OnlineUserManager
if (onlineUserManager.getFromGameHall(user.getUserId()) != null
|| onlineUserManager.getFromGameRoom(user.getUserId()) != null) {
// 如果一个账号, 一边是在游戏大厅, 一边是在游戏房间, 也视为多开~~
resp.setOk(true);
resp.setReason("禁止多开游戏页面");
resp.setMessage("repeatConnection");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
return;
}
// 4. 设置当前玩家上线!
onlineUserManager.enterGameRoom(user.getUserId(), session);
// 5. 把两个玩家加入到游戏房间中.
// 前面的创建房间/匹配过程, 是在 game_hall.html 页面中完成的.
// 因此前面匹配到对手之后, 需要经过页面跳转, 来到 game_room.html 才算正式进入游戏房间(才算玩家准备就绪)
// 当前这个逻辑是在 game_room.html 页面加载的时候进行的.
// 执行到当前逻辑, 说明玩家已经页面跳转成功了!!
// 页面跳转, 其实是个大活~~ (很有可能出现 "失败" 的情况的)
synchronized (room) {
if (room.getUser1() == null) {
// 第一个玩家还尚未加入房间.
// 就把当前连上 websocket 的玩家作为 user1, 加入到房间中.
room.setUser1(user);
// 把先连入房间的玩家作为先手方.
room.setWhiteUser(user.getUserId());
System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家1");
return;
}
if (room.getUser2() == null) {
// 如果进入到这个逻辑, 说明玩家1 已经加入房间, 现在要给当前玩家作为玩家2 了
room.setUser2(user);
System.out.println("玩家 " + user.getUsername() + " 已经准备就绪! 作为玩家2");
// 当两个玩家都加入成功之后, 就要让服务器, 给这两个玩家都返回 websocket 的响应数据.
// 通知这两个玩家说, 游戏双方都已经准备好了.
// 通知玩家1
noticeGameReady(room, room.getUser1(), room.getUser2());
// 通知玩家2
noticeGameReady(room, room.getUser2(), room.getUser1());
return;
}
}
// 6. 此处如果又有玩家尝试连接同一个房间, 就提示报错.
// 这种情况理论上是不存在的, 为了让程序更加的健壮, 还是做一个判定和提示.
resp.setOk(false);
resp.setReason("当前房间已满, 您不能加入房间");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));
}
2. afterConnectionClosed
断开连接
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
User user = (User) session.getAttributes().get("user");
if (user == null) {
// 此处就简单处理, 在断开连接的时候就不给客户端返回响应了.
return;
}
WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());
if (session == exitSession) {
// 加上这个判定, 目的是为了避免在多开的情况下, 第二个用户退出连接动作, 导致第一个用户的会话被删除.
onlineUserManager.exitGameRoom(user.getUserId());
}
System.out.println("当前用户 " + user.getUsername() + " 离开游戏房间!");
// 通知对手获胜了
noticeThatUserWin(user);
}
3. 处理落子请求
后端 handleTextMessage
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 1. 先从 session 里拿到当前用户的身份信息
User user = (User) session.getAttributes().get("user");
if (user == null) {
System.out.println("[handleTextMessage] 当前玩家尚未登录! ");
return;
}
// 2. 根据玩家 id 获取到房间对象
Room room = roomManager.getRoomByUserId(user.getUserId());
// 3. 通过 room 对象来处理这次具体的请求
room.putChess(message.getPayload());
}
后端 Room
package com.example.java_gobang.game;
import com.example.java_gobang.JavaGobangApplication;
import com.example.java_gobang.model.User;
import com.example.java_gobang.model.UserMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import java.io.IOException;
import java.util.UUID;
// 这个类就表示一个游戏房间
public class Room {
// 使用字符串类型来表示, 方便生成唯一值.
private String roomId;
private User user1;
private User user2;
// 先手方的玩家 id
private int whiteUser;
private static final int MAX_ROW = 15;
private static final int MAX_COL = 15;
// 这个二维数组用来表示棋盘
// 约定:
// 1) 使用 0 表示当前位置未落子. 初始化好的 int 二维数组, 就相当于是 全 0
// 2) 使用 1 表示 user1 的落子位置
// 3) 使用 2 表示 user2 的落子位置
private int[][] board = new int[MAX_ROW][MAX_COL];
// 创建 ObjectMapper 用来转换 JSON
private ObjectMapper objectMapper = new ObjectMapper();
// 引入 OnlineUserManager
// @Autowired
private OnlineUserManager onlineUserManager;
// 引入 RoomManager, 用于房间销毁
// @Autowired
private RoomManager roomManager;
private UserMapper userMapper;
// 通过这个方法来处理一次落子操作.
// 要做的事情:
public void putChess(String reqJson) throws IOException {
// 1. 记录当前落子的位置.
GameRequest request = objectMapper.readValue(reqJson, GameRequest.class);
GameResponse response = new GameResponse();
// 当前这个子是玩家1 落的还是玩家2 落的. 根据这个玩家1 和 玩家2 来决定往数组中是写 1 还是 2
int chess = request.getUserId() == user1.getUserId() ? 1 : 2;
int row = request.getRow();
int col = request.getCol();
if (board[row][col] != 0) {
// 在客户端已经针对重复落子进行过判定了. 此处为了程序更加稳健, 在服务器再判定一次.
System.out.println("当前位置 (" + row + ", " + col + ") 已经有子了!");
return;
}
board[row][col] = chess;
// 2. 打印出当前的棋盘信息, 方便来观察局势. 也方便后面验证胜负关系的判定.
printBoard();
// 3. 进行胜负判定
int winner = checkWinner(row, col, chess);
// 4. 给房间中的所有客户端都返回响应.
response.setMessage("putChess");
response.setUserId(request.getUserId());
response.setRow(row);
response.setCol(col);
response.setWinner(winner);
// 要想给用户发送 websocket 数据, 就需要获取到这个用户的 WebSocketSession
WebSocketSession session1 = onlineUserManager.getFromGameRoom(user1.getUserId());
WebSocketSession session2 = onlineUserManager.getFromGameRoom(user2.getUserId());
// 万一当前查到的会话为空(玩家已经下线了) 特殊处理一下
if (session1 == null) {
// 玩家1 已经下线了. 直接认为玩家2 获胜!
response.setWinner(user2.getUserId());
System.out.println("玩家1 掉线!");
}
if (session2 == null) {
// 玩家2 已经下线. 直接认为玩家1 获胜!
response.setWinner(user1.getUserId());
System.out.println("玩家2 掉线!");
}
// 把响应构造成 JSON 字符串, 通过 session 进行传输.
String respJson = objectMapper.writeValueAsString(response);
if (session1 != null) {
session1.sendMessage(new TextMessage(respJson));
}
if (session2 != null) {
session2.sendMessage(new TextMessage(respJson));
}
// 5. 如果当前胜负已分, 此时这个房间就失去存在的意义了. 就可以直接销毁房间. (把房间从房间管理器中给移除)
if (response.getWinner() != 0) {
// 胜负已分
System.out.println("游戏结束! 房间即将销毁! roomId=" + roomId + " 获胜方为: " + response.getWinner());
// 更新获胜方和失败方的信息.
int winUserId = response.getWinner();
int loseUserId = response.getWinner() == user1.getUserId() ? user2.getUserId() : user1.getUserId();
userMapper.userWin(winUserId);
userMapper.userLose(loseUserId);
// 销毁房间
roomManager.remove(roomId, user1.getUserId(), user2.getUserId());
}
}
private void printBoard() {
// 打印出棋盘
System.out.println("[打印棋盘信息] " + roomId);
System.out.println("=====================================================================");
for (int r = 0; r < MAX_ROW; r++) {
for (int c = 0; c < MAX_COL; c++) {
// 针对一行之内的若干列, 不要打印换行
System.out.print(board[r][c] + " ");
}
// 每次遍历完一行之后, 再打印换行.
System.out.println();
}
System.out.println("=====================================================================");
}
// 使用这个方法来判定当前落子是否分出胜负.
// 约定如果玩家1 获胜, 就返回玩家1 的 userId
// 如果玩家2 获胜, 就返回玩家2 的 userId
// 如果胜负未分, 就返回 0
private int checkWinner(int row, int col, int chess) {
// 1. 检查所有的行
// 先遍历这五种情况
for (int c = col - 4; c <= col; c++) {
// 针对其中的一种情况, 来判定这五个子是不是连在一起了~
// 不光是这五个子得连着, 而且还得和玩家落的子是一样~~ (才算是获胜)
try {
if (board[row][c] == chess
&& board[row][c + 1] == chess
&& board[row][c + 2] == chess
&& board[row][c + 3] == chess
&& board[row][c + 4] == chess) {
// 构成了五子连珠! 胜负已分!
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
// 如果出现数组下标越界的情况, 就在这里直接忽略这个异常.
continue;
}
}
// 2. 检查所有列
for (int r = row - 4; r <= row; r++) {
try {
if (board[r][col] == chess
&& board[r + 1][col] == chess
&& board[r + 2][col] == chess
&& board[r + 3][col] == chess
&& board[r + 4][col] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 3. 检查左对角线
for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {
try {
if (board[r][c] == chess
&& board[r + 1][c + 1] == chess
&& board[r + 2][c + 2] == chess
&& board[r + 3][c + 3] == chess
&& board[r + 4][c + 4] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 4. 检查右对角线
for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {
try {
if (board[r][c] == chess
&& board[r + 1][c - 1] == chess
&& board[r + 2][c - 2] == chess
&& board[r + 3][c - 3] == chess
&& board[r + 4][c - 4] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 胜负未分, 就直接返回 0 了.
return 0;
}
public int getWhiteUser() {
return whiteUser;
}
public void setWhiteUser(int whiteUser) {
this.whiteUser = whiteUser;
}
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 Room() {
// 构造 Room 的时候生成一个唯一的字符串表示房间 id.
// 使用 UUID 来作为房间 id
roomId = UUID.randomUUID().toString();
// 通过入口类中记录的 context 来手动获取到前面的 RoomManager 和 OnlineUserManager
onlineUserManager = JavaGobangApplication.context.getBean(OnlineUserManager.class);
roomManager = JavaGobangApplication.context.getBean(RoomManager.class);
userMapper = JavaGobangApplication.context.getBean(UserMapper.class);
}
public static void main(String[] args) {
Room room = new Room();
System.out.println(room.roomId);
}
}
前端
<script>
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
//
// 此处写的路径要写作 /game, 不要写作 /game/
let websocketUrl = "ws://" + location.host + "/game";
let websocket = new WebSocket(websocketUrl);
websocket.onopen = function() {
console.log("连接游戏房间成功!");
}
websocket.close = function() {
console.log("和游戏服务器断开连接!");
}
websocket.onerror = function() {
console.log("和服务器的连接出现异常!");
}
window.onbeforeunload = function() {
websocket.close();
}
// 处理服务器返回的响应数据
websocket.onmessage = function(event) {
console.log("[handlerGameReady] " + event.data);
let resp = JSON.parse(event.data);
if (!resp.ok) {
alert("连接游戏失败! reason: " + resp.reason);
// 如果出现连接失败的情况, 回到游戏大厅
location.assign("/game_hall.html");
return;
}
if (resp.message == 'gameReady') {
gameInfo.roomId = resp.roomId;
gameInfo.thisUserId = resp.thisUserId;
gameInfo.thatUserId = resp.thatUserId;
gameInfo.isWhite = (resp.whiteUser == resp.thisUserId);
// 初始化棋盘
initGame();
// 设置显示区域的内容
setScreenText(gameInfo.isWhite);
} else if (resp.message == 'repeatConnection') {
alert("检测到游戏多开! 请使用其他账号登录!");
location.assign("/login.html");
}
}
//
// 初始化一局游戏
//
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/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) {
// 发送坐标给服务器, 服务器要返回结果
send(row, col);
// 留到浏览器收到落子响应的时候再处理(收到响应再来画棋子)
// oneStep(col, row, gameInfo.isWhite);
// chessBoard[row][col] = 1;
}
}
function send(row, col) {
let req = {
message: 'putChess',
userId: gameInfo.thisUserId,
row: row,
col: col
};
websocket.send(JSON.stringify(req));
}
// 之前 websocket.onmessage 主要是用来处理了游戏就绪响应. 在游戏就绪之后, 初始化完毕之后, 也就不再有这个游戏就绪响应了.
// 就在这个 initGame 内部, 修改 websocket.onmessage 方法~~, 让这个方法里面针对落子响应进行处理!
websocket.onmessage = function(event) {
console.log("[handlerPutChess] " + event.data);
let resp = JSON.parse(event.data);
if (resp.message != 'putChess') {
console.log("响应类型错误!");
return;
}
// 先判定当前这个响应是自己落的子, 还是对方落的子.
if (resp.userId == gameInfo.thisUserId) {
// 我自己落的子
// 根据我自己子的颜色, 来绘制一个棋子
oneStep(resp.col, resp.row, gameInfo.isWhite);
} else if (resp.userId == gameInfo.thatUserId) {
// 我的对手落的子
oneStep(resp.col, resp.row, !gameInfo.isWhite);
} else {
// 响应错误! userId 是有问题的!
console.log('[handlerPutChess] resp userId 错误!');
return;
}
// 给对应的位置设为 1, 方便后续逻辑判定当前位置是否已经有子了.
chessBoard[resp.row][resp.col] = 1;
// 交换双方的落子轮次
me = !me;
setScreenText(me);
// 判定游戏是否结束
let screenDiv = document.querySelector('#screen');
if (resp.winner != 0) {
if (resp.winner == gameInfo.thisUserId) {
// alert('你赢了!');
screenDiv.innerHTML = '你赢了!';
} else if (resp.winner = gameInfo.thatUserId) {
// alert('你输了!');
screenDiv.innerHTML = '你输了!';
} else {
alert("winner 字段错误! " + resp.winner);
}
// 回到游戏大厅
// location.assign('/game_hall.html');
// 增加一个按钮, 让玩家点击之后, 再回到游戏大厅~
let backBtn = document.createElement('button');
backBtn.innerHTML = '回到大厅';
backBtn.onclick = function() {
location.replace('/game_hall.html');
}
let fatherDiv = document.querySelector('.container>div');
fatherDiv.appendChild(backBtn);
}
}
}
</script>
四. 判断胜负
Room
private int checkWinner(int row, int col, int chess) {
// 1. 检查所有的行
// 先遍历这五种情况
for (int c = col - 4; c <= col; c++) {
// 针对其中的一种情况, 来判定这五个子是不是连在一起了~
// 不光是这五个子得连着, 而且还得和玩家落的子是一样~~ (才算是获胜)
try {
if (board[row][c] == chess
&& board[row][c + 1] == chess
&& board[row][c + 2] == chess
&& board[row][c + 3] == chess
&& board[row][c + 4] == chess) {
// 构成了五子连珠! 胜负已分!
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
// 如果出现数组下标越界的情况, 就在这里直接忽略这个异常.
continue;
}
}
// 2. 检查所有列
for (int r = row - 4; r <= row; r++) {
try {
if (board[r][col] == chess
&& board[r + 1][col] == chess
&& board[r + 2][col] == chess
&& board[r + 3][col] == chess
&& board[r + 4][col] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 3. 检查左对角线
for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {
try {
if (board[r][c] == chess
&& board[r + 1][c + 1] == chess
&& board[r + 2][c + 2] == chess
&& board[r + 3][c + 3] == chess
&& board[r + 4][c + 4] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 4. 检查右对角线
for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {
try {
if (board[r][c] == chess
&& board[r + 1][c - 1] == chess
&& board[r + 2][c - 2] == chess
&& board[r + 3][c - 3] == chess
&& board[r + 4][c - 4] == chess) {
return chess == 1 ? user1.getUserId() : user2.getUserId();
}
} catch (ArrayIndexOutOfBoundsException e) {
continue;
}
}
// 胜负未分, 就直接返回 0 了.
return 0;
}
到了这里,关于网页版Java(Spring/Spring Boot/Spring MVC)五子棋项目(四)对战模块的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!