上期我们基本完成了匹配功能,这期我们来实现游戏房间相关的代码
由于同时会存在多个游戏房间,我们实现游戏房间时可以再实现一个房间管理器,用于管理游戏房间,我们约定房间管理器通过哈希表来管理游戏房间。
1. 游戏房间实体类
java
public class Room {
private String roomId;
private User user1;
private User user2;
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;
}
}
这里我们使用UUID来作为房间ID,方便存进哈希表时作为键
2. 房间管理器实体类
java
package org.ting.j20250110_gobang.game;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class RoomManager {
private ConcurrentHashMap<String, Room> roomIdToRoom = new ConcurrentHashMap<>();
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 Room getRoomByRoomId(String roomId) {
return roomIdToRoom.get(roomId);
}
public Room getRoomByUserId(Integer userId) {
return roomIdToRoom.get(userIdToRoomId.get(userId));
}
}
这里我们创建了两个哈希表,一个维护房间id到游戏房间的映射,一个维护用户id到游戏房间的映射,此时我们就可以通过add方法把两个用户加入一个游戏房间内
java
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());
System.out.println("匹配成功," + user1.getUsername() + "," + user2.getUsername() + "正在进入游戏房间");
//4.把两个玩家放到一个游戏房间中
Room room = new Room();
roomManager.add(room.getRoomId(), room, user1.getUserId(), user2.getUserId());
//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();
}
}
3. 游戏房间前端代码
room.html:
html
<!DOCTYPE html>
<html lang="chch">
<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/room.css">
</head>
<body>
<div class="nav">五子棋</div>
<div class="container">
<div>
<!-- 棋盘区域 -->
<canvas id="chess" width="450px" height="450px">
</canvas>
<div id="screen">等待玩家连接</div>
</div>
</div>
<script src="js/script.js"></script>
<!-- <script src="js/jquery.min.js"></script> -->
</body>
</html>
script.js:
javascript
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/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();