实现游戏房间页面
创建 css/game_room.css
#screen
用于显示当前的状态,例如"等待玩家连接中...","轮到你落子","轮到对方落子"等
css
#screen {
width: 450px;
height: 50px;
margin-top: 10px;
color: #8f4e19;
font-size: 28px;
font-weight: 700;
line-height: 50px;
text-align: center;
}
实现 game_room.html
,这个页面就是匹配成功之后,要跳转到的新页面
java
<!DOCTYPE html>
<html lang="en">
<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/game_room.css">
</head>
<body>
<div class="nav">五子棋对战</div>
<div class="container">
<div>
<!-- 棋盘区域,需要基于 canva 进行实现 -->
<canvas id="chess" width="450px" height="450px">
</canvas>
<!-- 显示区域 -->
<div id="screen">等待玩家连接中...</div>
</div>
</div>
<script src="js/app.js"></script>
</body>
</html>
页面效果

初始化 websocket
在 js/app.js
中,加入 websocket
的连接代码,实现前后端交互
- 先删掉原来
initGame()
函数的调用,一会在获取到服务器反馈的就绪响应之后,再初始化棋盘 - 创建
websocket
对象,并注册onopen/onclose/onerror
函数- 其中在
onopen
中做一个跳转到大厅的逻辑,当网络断开时,则返回大厅
- 其中在
- 实现
onmessage
方法,onmessage
先处理游戏就绪响应
js
// 此处写的路径要写作 /game,不要写作 /game/let websocket = new WebSocket("ws://127.0.0.1:8080/game");
websocket.onopen = function() {
console.log("连接游戏房间成功!");
}
websocket.onclose = 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.message != 'gameReady') {
console.log("响应类型错误!");
return;
}
if(!resp.ok) {
alert("连接游戏失败! reason: " + resp.reason);
// 如果出现连接失败的情况,就回到游戏大厅
location.assign("/game_hall.html");
return;
}
gameInfo.roomId = resp.roomId;
gameInfo.thisUserId = resp.thisUserId;
gameInfo.thatUserId = resp.thatUserId;
gameInfo.isWhite = resp.isWhite;
// 初始化棋盘
initGame();
// 设置显示区域的内容
setScreenText(gameInfo.isWhite);
}
服务器开发
创建并注册 GameAPI 类
创建 api.GameAPI
,处理 websocket
请求
- 这里准备一个
ObjectMapper
类,让后面进行消息发送的时候进行序列化 - 同时注入一个
RoomManager
和OnlineUserManager
java
package org.example.java_gobang.api;
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;
@Component
public class GameAPI extends TextWebSocketHandler {
@Autowired
private ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private RoomManager roomManager;
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
}
修改 WebSocketConfig
,将 GameAPI
进行注册
java
package org.example.java_gobang.config;
import org.example.java_gobang.api.GameAPI;
import org.example.java_gobang.api.MatchAPI;
import org.example.java_gobang.api.TestAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
* 这个类是用来注册 WebSocketHandler 的配置类
* 这个类是来告诉 Spring(websocket),哪一个类是和哪一个路径相匹配的
*/
@Configuration
@EnableWebSocket // 让 Spring 框架知道这个类是用来配置 websocket 的
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TestAPI testAPI;
@Autowired
private MatchAPI matchAPI;
@Autowired
private GameAPI gameAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
// 当我们的客户端连接到 /test 路径的时候,就会触发到当前的 TestAPI,然后调用执行里面的方法
webSocketHandlerRegistry.addHandler(testAPI, "/test");
// 通过 .addInterceptors(new HttpSessionHandshakeInterceptor() 这个操作,
// 来把 HttpSession ⾥的属性放到 WebSocket 的 session 中
// 然后就可以在 WebSocket 代码中 WebSocketSession ⾥拿到 HttpSession 中的 attribute webSocketHandlerRegistry.addHandler(matchAPI,"/findMatch")
.addInterceptors(new HttpSessionHandshakeInterceptor());
webSocketHandlerRegistry.addHandler(gameAPI, "/game")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
创建落子请求/响应对象
这部分内容要和约定的前后端交互接口匹配
创建 game.GameReadyResponse
java
package org.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 boolean isOk() {
return ok;
}
public void setOk(boolean ok) {
this.ok = ok;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
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 getThatUserId() {
return thatUserId;
}
public void setThatUserId(int thatUserId) {
this.thatUserId = thatUserId;
}
public int getThisUserId() {
return thisUserId;
}
public void setThisUserId(int thisUserId) {
this.thisUserId = thisUserId;
}
public int getWhiteUser() {
return whiteUser;
}
public void setWhiteUser(int whiteUser) {
this.whiteUser = whiteUser;
}
}
创建 game.GameRequest
类
- 表示落子请求
java
package org.example.java_gobang.game;
// 这个类表示落子请求
public class GameRequest {
// 如果不给 message 设置 getter / setter, 则不会被 jackson 序列化
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;
}
}
创建 game.Response
类
- 表示落子响应
java
package org.example.java_gobang.game;
// 这个类表示 落子响应
public class GameResponse {
// 如果不给 message 设置 getter / setter, 则不会被 jackson 序列化
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;
}
}