[网页五子棋][对战模块]实现游戏房间页面,服务器开发(创建落子请求/响应对象)
实现游戏房间页面
创建 css/game_room.css
#screen
用于显示当前的状态,例如“等待玩家连接中…”,“轮到你落子”,“轮到对方落子”等
#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
,这个页面就是匹配成功之后,要跳转到的新页面
<!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
先处理游戏就绪响应
// 此处写的路径要写作 /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
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
进行注册
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
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
类
- 表示落子请求
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
类
- 表示落子响应
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; }
}