完善游戏开展流程

This commit is contained in:
2025-12-30 17:43:35 +08:00
parent 082e00c41b
commit 7ca5ce189d
12 changed files with 947 additions and 70 deletions
-13
View File
@@ -1,13 +0,0 @@
### 该问题是怎么引起的?
### 重现步骤
### 报错信息
+56 -2
View File
@@ -1,4 +1,4 @@
.idea/ # Maven
target/ target/
pom.xml.tag pom.xml.tag
pom.xml.releaseBackup pom.xml.releaseBackup
@@ -8,5 +8,59 @@ release.properties
dependency-reduced-pom.xml dependency-reduced-pom.xml
buildNumber.properties buildNumber.properties
.mvn/timing.properties .mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar .mvn/wrapper/maven-wrapper.jar
# IDE
.idea/
*.iml
*.iws
*.ipr
.vscode/
.settings/
.classpath
.project
.factorypath
# Spring Boot
*.log
logs/
# OS
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Java
*.class
*.jar
*.war
*.ear
hs_err_pid*
replay_pid*
# Eclipse
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.loadpath
# NetBeans
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
# Application
application-local.yml
application-dev.yml
application-prod.yml
-36
View File
@@ -1,36 +0,0 @@
# draw-and-guess
#### Description
你画我猜 SpringBoot Websocket
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
+102 -8
View File
@@ -4,23 +4,117 @@
你画我猜 SpringBoot Websocket 你画我猜 SpringBoot Websocket
#### 软件架构 #### 软件架构
- 后端:Java21、Maven、SpringBoot - 后端:Java21、Maven、SpringBoot 3.2.4
- 前端:HTML + jQuery - 前端:HTML + jQuery
- 通讯:HTTP + Websocket - 通讯:HTTP + Websocket
- 工具库:Lombok、Hutool、FastJSON2
#### 项目结构
```
draw-and-guess/
├── src/main/java/cn/airnan/dg/
│ ├── configs/ # 配置类(CORS、WebSocket、全局配置)
│ ├── controller/ # 控制器(玩家、房间、题目、首页)
│ ├── entity/ # 实体类(玩家、房间、画板、画笔、点、题目)
│ ├── pojo/ # 数据传输对象
│ ├── service/ # 业务逻辑层
│ └── utils/ # 工具类(WebSocket、消息封装、通用响应)
├── src/main/resources/
│ ├── static/ # 静态资源(HTML、CSS、JS、图标)
│ ├── application.yml # 应用配置
│ └── topic.json # 题库文件
└── pom.xml # Maven 依赖配置
```
#### 安装教程 #### 安装教程
1. 修改前端`index.html`中的后端地址 1. **修改前端配置**
2. 运行命令: 打开 `src/main/resources/static/index.html`,修改 `backendAddr` 变量为实际的服务器地址:
```javascript
const backendAddr = "192.168.31.222:8080" // 修改为你的服务器IP和端口
``` ```
2. **编译打包**
```bash
# 使用 Maven Wrapper 编译打包
mvnw clean package
# 或使用本地 Maven
mvn clean package
```
打包完成后,在 `target` 目录下会生成 `draw-and-guess-0.0.1-SNAPSHOT.jar`
3. **运行应用**
```bash
# 直接运行 # 直接运行
java -jar xxx.jar java -jar target/draw-and-guess-0.0.1-SNAPSHOT.jar
# 后台运行
nohup java -jar draw-and-guess-0.0.1-SNAPSHOT.jar >> ./draw.log 2>&1 & # 后台运行(Linux/Mac)
nohup java -jar target/draw-and-guess-0.0.1-SNAPSHOT.jar >> ./draw.log 2>&1 &
# Windows 后台运行
start /B java -jar target/draw-and-guess-0.0.1-SNAPSHOT.jar
``` ```
3. 访问`http://IP:8080`进行游玩
4. **访问应用**
打开浏览器访问 `http://IP:8080` 进行游玩
#### 使用说明 #### 使用说明
1. 题库在`resources/topic.json`中,可自行添加。 1. **创建玩家**
- 在首页输入玩家昵称
- 点击"创建"按钮注册玩家
2. **房间管理**
- 查看房间列表,点击"刷新列表"获取最新房间
- 点击"创建房间"创建新房间并成为房主
- 点击"加入房间"加入已有房间
3. **游戏玩法**
- 进入房间后,玩家可以实时聊天
- 房主或其他玩家可以在画板上绘画
- 支持多种颜色、画笔粗细调节
- 支持橡皮擦功能
- 可以清空画板重新绘画
4. **题库配置**
- 题库文件位于 `resources/topic.json`
- 格式为 JSON 数组,每个元素包含题目和提示
- 可自行添加或修改题目:
```json
[
{"题目名称":"提示信息"},
{"空调":"日常物品、2个字"}
]
```
#### API 接口说明
**玩家接口**
- `POST /api/player/create` - 创建玩家
- `GET /api/player/list` - 获取玩家列表
- `POST /api/player/isExist` - 检查玩家是否存在
**房间接口**
- `GET /api/room/list` - 获取房间列表
- `POST /api/room/create` - 创建房间
- `POST /api/room/canJoin` - 检查是否可加入房间
**题目接口**
- `GET /api/topic/random` - 获取随机题目
**WebSocket 连接**
- 连接地址:`ws://IP:8080/websocket/{playerId}/{roomId}`
- 消息类型:
- `room-msg` - 房间通知消息
- `room-chat` - 聊天消息
- `room-draw` - 绘画数据
- `room-draw-clear` - 清空画板
#### 技术特点
- 基于 Spring Boot 3.2.4 + Java 21 开发
- 使用 WebSocket 实现实时双向通信
- 支持多人在线绘画和聊天
- 前端使用 Canvas 实现绘画功能
- 支持移动端和 PC 端访问
@@ -31,6 +31,61 @@ public class Global {
*/ */
public static final String MSG_TYPE_ROOM_MSG = "room-msg"; public static final String MSG_TYPE_ROOM_MSG = "room-msg";
/**
* 消息类型-游戏开始
*/
public static final String MSG_TYPE_GAME_START = "game-start";
/**
* 消息类型-游戏结束
*/
public static final String MSG_TYPE_GAME_END = "game-end";
/**
* 消息类型-轮次切换
*/
public static final String MSG_TYPE_ROUND_CHANGE = "round-change";
/**
* 消息类型-题目分配
*/
public static final String MSG_TYPE_TOPIC_ASSIGN = "topic-assign";
/**
* 消息类型-答题正确
*/
public static final String MSG_TYPE_ANSWER_CORRECT = "answer-correct";
/**
* 消息类型-答题错误
*/
public static final String MSG_TYPE_ANSWER_WRONG = "answer-wrong";
/**
* 消息类型-分数更新
*/
public static final String MSG_TYPE_SCORE_UPDATE = "score-update";
/**
* 消息类型-倒计时
*/
public static final String MSG_TYPE_COUNTDOWN = "countdown";
/**
* 消息类型-绘画者切换
*/
public static final String MSG_TYPE_DRAWER_CHANGE = "drawer-change";
/**
* 消息类型-绘画权限控制
*/
public static final String MSG_TYPE_DRAW_PERMISSION = "draw-permission";
/**
* 消息类型-游戏状态更新
*/
public static final String MSG_TYPE_GAME_STATE = "game-state";
/** /**
* 题库 * 题库
*/ */
@@ -0,0 +1,22 @@
package cn.airnan.dg.configs;
import cn.airnan.dg.service.GameService;
import cn.airnan.dg.utils.WebSocket;
import org.springframework.context.annotation.Configuration;
import jakarta.annotation.PostConstruct;
@Configuration
public class WebSocketConfig {
private final GameService gameService;
public WebSocketConfig(GameService gameService) {
this.gameService = gameService;
}
@PostConstruct
public void init() {
WebSocket.setGameService(gameService);
}
}
@@ -0,0 +1,89 @@
package cn.airnan.dg.controller;
import cn.airnan.dg.configs.Global;
import cn.airnan.dg.service.GameService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/game")
public class GameController {
@Autowired
private GameService gameService;
@PostMapping("/start")
public Map<String, Object> startGame(@RequestBody Map<String, String> request) {
String roomId = request.get("roomId");
String playerId = request.get("playerId");
Map<String, Object> response = new HashMap<>();
if (!Global.ROOMS.containsKey(roomId)) {
response.put("success", false);
response.put("message", "房间不存在");
return response;
}
if (!Global.ROOMS.get(roomId).getMaster().equals(playerId)) {
response.put("success", false);
response.put("message", "只有房主可以开始游戏");
return response;
}
if (Global.ROOMS.get(roomId).getPlayers().size() < 2) {
response.put("success", false);
response.put("message", "至少需要2名玩家才能开始游戏");
return response;
}
try {
gameService.startGame(roomId);
response.put("success", true);
response.put("message", "游戏开始");
} catch (Exception e) {
log.error("开始游戏失败", e);
response.put("success", false);
response.put("message", "游戏开始失败");
}
return response;
}
@PostMapping("/end")
public Map<String, Object> endGame(@RequestBody Map<String, String> request) {
String roomId = request.get("roomId");
String playerId = request.get("playerId");
Map<String, Object> response = new HashMap<>();
if (!Global.ROOMS.containsKey(roomId)) {
response.put("success", false);
response.put("message", "房间不存在");
return response;
}
if (!Global.ROOMS.get(roomId).getMaster().equals(playerId)) {
response.put("success", false);
response.put("message", "只有房主可以结束游戏");
return response;
}
try {
gameService.endGame(roomId);
response.put("success", true);
response.put("message", "游戏结束");
} catch (Exception e) {
log.error("结束游戏失败", e);
response.put("success", false);
response.put("message", "游戏结束失败");
}
return response;
}
}
@@ -35,19 +35,24 @@ public class Player {
*/ */
private Integer score = 0; private Integer score = 0;
/**
* 本轮是否已猜对
*/
private boolean hasGuessed = false;
/** /**
* 加分 * 加分
* *
* @param add 要增加的分数 * @param add 要增加的分数
*/ */
private void addScore(Integer add) { public void addScore(Integer add) {
this.score += add; this.score += add;
} }
/** /**
* 重置分数 * 重置分数
*/ */
private void clearScore() { public void clearScore() {
this.score = 0; this.score = 0;
} }
+100
View File
@@ -44,6 +44,16 @@ public class Room {
*/ */
private Topic topic; private Topic topic;
/**
* 游戏题目列表
*/
private List<Topic> topics = new ArrayList<>();
/**
* 当前题目
*/
private Topic currentTopic;
/** /**
* 游戏开始状态 * 游戏开始状态
*/ */
@@ -54,6 +64,36 @@ public class Room {
*/ */
private Integer countDown = 0; private Integer countDown = 0;
/**
* 当前绘画者ID
*/
private String currentDrawer;
/**
* 当前轮次
*/
private Integer currentRound = 0;
/**
* 总轮次
*/
private Integer totalRounds = 5;
/**
* 游戏状态:0-准备,1-绘画,2-结算
*/
private Integer gameState = 0;
/**
* 本轮已猜对人数
*/
private Integer guessedCount = 0;
/**
* 绘画时间(秒)
*/
private Integer drawTime = 60;
/** /**
* 添加玩家 * 添加玩家
* *
@@ -104,4 +144,64 @@ public class Room {
} }
/**
* 获取下一个绘画者
*
* @return 下一个绘画者ID
*/
public String getNextDrawer() {
if (players.isEmpty()) {
return null;
}
if (currentDrawer == null) {
return players.get(0).getId();
}
for (int i = 0; i < players.size(); i++) {
if (players.get(i).getId().equals(currentDrawer)) {
int nextIndex = (i + 1) % players.size();
return players.get(nextIndex).getId();
}
}
return players.get(0).getId();
}
/**
* 检查玩家是否是当前绘画者
*
* @param playerId 玩家ID
* @return 是否是绘画者
*/
public boolean isCurrentDrawer(String playerId) {
return currentDrawer != null && currentDrawer.equals(playerId);
}
/**
* 重置游戏状态
*/
public void resetGame() {
this.currentRound = 0;
this.gameState = 0;
this.currentDrawer = null;
this.guessedCount = 0;
this.countDown = 0;
for (Player player : players) {
player.clearScore();
}
}
/**
* 获取玩家对象
*
* @param playerId 玩家ID
* @return 玩家对象
*/
public Player getPlayer(String playerId) {
for (Player player : players) {
if (player.getId().equals(playerId)) {
return player;
}
}
return null;
}
} }
@@ -0,0 +1,258 @@
package cn.airnan.dg.service;
import cn.airnan.dg.configs.Global;
import cn.airnan.dg.entity.Player;
import cn.airnan.dg.entity.Room;
import cn.airnan.dg.entity.Topic;
import cn.airnan.dg.utils.WebSocket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class GameService {
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);
private static final Map<String, Runnable> countdownTasks = new HashMap<>();
private final TopicService topicService;
public GameService(TopicService topicService) {
this.topicService = topicService;
}
public void startGame(String roomId) {
Room room = Global.ROOMS.get(roomId);
if (room == null || room.isBegin()) {
return;
}
room.setBegin(true);
room.setCurrentRound(0);
room.setGameState(0);
room.getPlayers().forEach(Player::clearScore);
List<Topic> topics = topicService.randomTopics(room.getTotalRounds());
room.setTopics(topics);
sendGameStartMessage(room);
startNextRound(room);
}
public void endGame(String roomId) {
Room room = Global.ROOMS.get(roomId);
if (room == null) {
return;
}
room.setBegin(false);
room.setGameState(0);
stopCountdown(roomId);
Map<String, Object> data = new HashMap<>();
data.put("roomId", roomId);
data.put("winner", getWinner(room));
data.put("finalScores", getFinalScores(room));
WebSocket.sendToRoom(roomId, Global.MSG_TYPE_GAME_END, data);
}
public void startNextRound(Room room) {
if (room.getCurrentRound() >= room.getTotalRounds()) {
endGame(room.getId());
return;
}
room.setCurrentRound(room.getCurrentRound() + 1);
room.setGameState(1);
room.setGuessedCount(0);
room.getPlayers().forEach(p -> p.setHasGuessed(false));
String nextDrawer = room.getNextDrawer();
room.setCurrentDrawer(nextDrawer);
Topic currentTopic = room.getTopics().get(room.getCurrentRound() - 1);
room.setCurrentTopic(currentTopic);
sendRoundChangeMessage(room);
sendDrawerChangeMessage(room);
sendTopicAssignMessage(room);
sendDrawPermissionMessage(room);
startCountdown(room);
}
public boolean checkAnswer(String roomId, String playerId, String answer) {
Room room = Global.ROOMS.get(roomId);
if (room == null || room.getGameState() != 1) {
return false;
}
Player player = room.getPlayer(playerId);
if (player == null || player.isHasGuessed() || player.getId().equals(room.getCurrentDrawer())) {
return false;
}
Topic currentTopic = room.getCurrentTopic();
if (currentTopic == null) {
return false;
}
if (currentTopic.getName().equalsIgnoreCase(answer.trim())) {
player.setHasGuessed(true);
room.setGuessedCount(room.getGuessedCount() + 1);
int score = calculateScore(room);
player.addScore(score);
sendAnswerCorrectMessage(room, player, score);
sendScoreUpdateMessage(room);
if (room.getGuessedCount() >= room.getPlayers().size() - 1) {
scheduler.schedule(() -> startNextRound(room), 2, TimeUnit.SECONDS);
}
return true;
} else {
sendAnswerWrongMessage(room, player);
return false;
}
}
private int calculateScore(Room room) {
int baseScore = 100;
int timeBonus = room.getDrawTime() / 2;
return baseScore + timeBonus;
}
private Player getWinner(Room room) {
return room.getPlayers().stream()
.max(Comparator.comparingInt(Player::getScore))
.orElse(null);
}
private Map<String, Integer> getFinalScores(Room room) {
Map<String, Integer> scores = new HashMap<>();
room.getPlayers().forEach(p -> scores.put(p.getId(), p.getScore()));
return scores;
}
private void startCountdown(Room room) {
stopCountdown(room.getId());
Runnable countdownTask = new CountdownTask(room, this);
countdownTasks.put(room.getId(), countdownTask);
scheduler.scheduleAtFixedRate(countdownTask, 1, 1, TimeUnit.SECONDS);
}
private void stopCountdown(String roomId) {
Runnable task = countdownTasks.remove(roomId);
if (task instanceof CountdownTask) {
((CountdownTask) task).stop();
}
}
private void sendGameStartMessage(Room room) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("totalRounds", room.getTotalRounds());
data.put("drawTime", room.getDrawTime());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_GAME_START, data);
}
private void sendRoundChangeMessage(Room room) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("currentRound", room.getCurrentRound());
data.put("totalRounds", room.getTotalRounds());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_ROUND_CHANGE, data);
}
private void sendDrawerChangeMessage(Room room) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("drawerId", room.getCurrentDrawer());
data.put("drawerName", room.getPlayer(room.getCurrentDrawer()).getId());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_DRAWER_CHANGE, data);
}
private void sendTopicAssignMessage(Room room) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("topic", room.getCurrentTopic());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_TOPIC_ASSIGN, data);
}
private void sendDrawPermissionMessage(Room room) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("drawerId", room.getCurrentDrawer());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_DRAW_PERMISSION, data);
}
private void sendAnswerCorrectMessage(Room room, Player player, int score) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("playerId", player.getId());
data.put("playerName", player.getId());
data.put("score", score);
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_ANSWER_CORRECT, data);
}
private void sendAnswerWrongMessage(Room room, Player player) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("playerId", player.getId());
data.put("playerName", player.getId());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_ANSWER_WRONG, data);
}
private void sendScoreUpdateMessage(Room room) {
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("scores", getFinalScores(room));
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_SCORE_UPDATE, data);
}
private static class CountdownTask implements Runnable {
private final Room room;
private final GameService gameService;
private volatile boolean running = true;
public CountdownTask(Room room, GameService gameService) {
this.room = room;
this.gameService = gameService;
}
@Override
public void run() {
if (!running) {
return;
}
if (room.getDrawTime() > 0) {
room.setDrawTime(room.getDrawTime() - 1);
Map<String, Object> data = new HashMap<>();
data.put("roomId", room.getId());
data.put("timeLeft", room.getDrawTime());
WebSocket.sendToRoom(room.getId(), Global.MSG_TYPE_COUNTDOWN, data);
} else {
stop();
scheduler.schedule(() -> gameService.startNextRound(room), 1, TimeUnit.SECONDS);
}
}
public void stop() {
running = false;
}
}
}
@@ -3,6 +3,7 @@ package cn.airnan.dg.utils;
import cn.airnan.dg.configs.Global; import cn.airnan.dg.configs.Global;
import cn.airnan.dg.entity.Player; import cn.airnan.dg.entity.Player;
import cn.airnan.dg.entity.Room; import cn.airnan.dg.entity.Room;
import cn.airnan.dg.service.GameService;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference; import com.alibaba.fastjson2.TypeReference;
import jakarta.websocket.OnClose; import jakarta.websocket.OnClose;
@@ -21,6 +22,12 @@ import java.util.Map;
@ServerEndpoint("/websocket/{playerId}/{roomId}") @ServerEndpoint("/websocket/{playerId}/{roomId}")
public class WebSocket { public class WebSocket {
private static GameService gameService;
public static void setGameService(GameService gameService) {
WebSocket.gameService = gameService;
}
/** /**
* 加入房间时触发WebSocket事件 * 加入房间时触发WebSocket事件
@@ -105,6 +112,13 @@ public class WebSocket {
// 解析消息为 SocketMsg 结构体 // 解析消息为 SocketMsg 结构体
SocketMsg<String> socketMsg = JSON.parseObject(message, new TypeReference<>() { SocketMsg<String> socketMsg = JSON.parseObject(message, new TypeReference<>() {
}); });
// 处理答题消息
if ("room-chat-answer".equals(socketMsg.getType())) {
handleAnswer(socketMsg.getRoomId(), socketMsg.getPlayerId(), socketMsg.getData());
return;
}
// 匹配房间并转发消息 // 匹配房间并转发消息
Global.ROOMS.get(socketMsg.getRoomId()).sendMessage(socketMsg.getType(), socketMsg.getPlayerId(), socketMsg.getData()); Global.ROOMS.get(socketMsg.getRoomId()).sendMessage(socketMsg.getType(), socketMsg.getPlayerId(), socketMsg.getData());
} catch (Exception e) { } catch (Exception e) {
@@ -112,6 +126,66 @@ public class WebSocket {
} }
} }
private void handleAnswer(String roomId, String playerId, String answer) {
Room room = Global.ROOMS.get(roomId);
if (room == null) {
return;
}
// 使用 GameService 检查答案
boolean isCorrect = gameService.checkAnswer(roomId, playerId, answer);
if (isCorrect) {
// 答对了,发送正确消息
String message = playerId + " 猜对了!";
room.sendMessage(Global.MSG_TYPE_ROOM_MSG, playerId, message);
} else {
// 答错了,发送错误消息
String message = playerId + " 猜错了";
room.sendMessage(Global.MSG_TYPE_ROOM_MSG, playerId, message);
}
}
private boolean checkAnswer(Room room, String playerId, String answer) {
if (room.getCurrentTopic() == null) {
return false;
}
return room.getCurrentTopic().getName().equalsIgnoreCase(answer.trim());
}
/**
* 发送消息到指定房间
*
* @param roomId 房间ID
* @param type 消息类型
* @param data 消息数据
*/
public static void sendToRoom(String roomId, String type, Object data) {
Room room = Global.ROOMS.get(roomId);
if (room == null) {
return;
}
SocketMsg<Object> socketMsg = new SocketMsg<>();
socketMsg.setType(type);
socketMsg.setRoomId(roomId);
socketMsg.setData(data);
String message = JSON.toJSONString(socketMsg);
for (Player player : room.getPlayers()) {
if (player.getSession() != null && player.getSession().isOpen()) {
try {
synchronized (player.getSession()) {
player.getSession().getAsyncRemote().sendText(message);
}
} catch (Exception e) {
log.error("发送房间消息失败:{}", e.getMessage());
}
}
}
}
/** /**
* 此为广播消息 * 此为广播消息
* *
+184 -9
View File
@@ -41,11 +41,15 @@
<div id="game-top-box"> <div id="game-top-box">
<button id="back-btn" class="button"><span class="iconfont">&#xe65d;</span></button> <button id="back-btn" class="button"><span class="iconfont">&#xe65d;</span></button>
<button id="userIcon" class="button">玩家:admin</button> <button id="userIcon" class="button">玩家:admin</button>
<div id="game-info-box">
<div id="round-info">第 <span id="current-round">0</span> / <span id="total-rounds">5</span> 轮</div>
<div id="countdown-box">倒计时:<span id="countdown">60</span>秒</div>
</div>
</div> </div>
<div id="draw-box"> <div id="draw-box">
<div id="topic-bar"> <div id="topic-bar">
<div>题目:<span id="topic-name">坐井观天</span></div> <div>题目:<span id="topic-name">等待游戏开始</span></div>
<div>提示:<span id="topic-tip">成语,四个字</span></div> <div>提示:<span id="topic-tip">--</span></div>
</div> </div>
<canvas id="board" width="600" height="600"></canvas> <canvas id="board" width="600" height="600"></canvas>
<div style="display: flex;width: 100%;justify-content: space-between;padding: 0 15px 10px 15px;box-sizing: border-box;"> <div style="display: flex;width: 100%;justify-content: space-between;padding: 0 15px 10px 15px;box-sizing: border-box;">
@@ -108,11 +112,11 @@
</div> </div>
<div id="player-box"> <div id="player-box">
<div class="player-item">张</div> <div class="player-item" id="player-0">张</div>
<div class="player-item">王</div> <div class="player-item" id="player-1">王</div>
<div class="player-item"></div> <div class="player-item" id="player-2"></div>
<div class="player-item"></div> <div class="player-item" id="player-3"></div>
<div class="player-item"></div> <div class="player-item" id="player-4"></div>
</div> </div>
<div id="chat-box"> <div id="chat-box">
@@ -121,6 +125,10 @@
<input id="chat-input" type="text" autocomplete="off" placeholder="输入文字以发送" class="input"/> <input id="chat-input" type="text" autocomplete="off" placeholder="输入文字以发送" class="input"/>
<button class="button" onclick="sendInput()">发送</button> <button class="button" onclick="sendInput()">发送</button>
</div> </div>
<div id="game-control-box">
<button id="start-game-btn" class="button" onclick="startGame()">开始游戏</button>
<button id="end-game-btn" class="button" onclick="endGame()">结束游戏</button>
</div>
</div> </div>
</div> </div>
@@ -136,7 +144,11 @@
let playerId = ""; let playerId = "";
// 当前房间id // 当前房间id
let roomId = ""; let roomId = "";
const backendAddr = "192.168.31.222:8080" // 当前是否是绘画者
let isDrawer = false;
// 当前题目
let currentTopic = "";
const backendAddr = window.location.host
// const backendAddr = "draw.airnan.cn" // const backendAddr = "draw.airnan.cn"
const api = "http://" + backendAddr + "/api" const api = "http://" + backendAddr + "/api"
// const api = "https://" + backendAddr + "/api" // const api = "https://" + backendAddr + "/api"
@@ -390,6 +402,46 @@
if (result.type === "room-draw-clear") { if (result.type === "room-draw-clear") {
clearDraw(); clearDraw();
} }
// 游戏开始
if (result.type === "game-start") {
handleGameStart(result.data);
}
// 游戏结束
if (result.type === "game-end") {
handleGameEnd(result.data);
}
// 轮次切换
if (result.type === "round-change") {
handleRoundChange(result.data);
}
// 题目分配
if (result.type === "topic-assign") {
handleTopicAssign(result.data);
}
// 答题正确
if (result.type === "answer-correct") {
handleAnswerCorrect(result.data);
}
// 答题错误
if (result.type === "answer-wrong") {
handleAnswerWrong(result.data);
}
// 分数更新
if (result.type === "score-update") {
handleScoreUpdate(result.data);
}
// 倒计时
if (result.type === "countdown") {
handleCountdown(result.data);
}
// 绘画者切换
if (result.type === "drawer-change") {
handleDrawerChange(result.data);
}
// 绘画权限控制
if (result.type === "draw-permission") {
handleDrawPermission(result.data);
}
}; };
ws.onclose = function () { ws.onclose = function () {
@@ -459,7 +511,11 @@
function sendInput() { function sendInput() {
let txt = $("#chat-input").val().trim(); let txt = $("#chat-input").val().trim();
if (txt !== "") { if (txt !== "") {
sendMessage("room-chat", playerId + ":" + txt); if (currentTopic && !isDrawer) {
sendMessage("room-chat-answer", txt);
} else {
sendMessage("room-chat", playerId + ":" + txt);
}
$("#chat-input").val(""); $("#chat-input").val("");
} }
} }
@@ -490,6 +546,125 @@
$("#page-box").css("left", "0px"); $("#page-box").css("left", "0px");
} }
function startGame() {
$.ajax({
url: api + "/game/start",
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({roomId: roomId, playerId: playerId}),
success: function (data) {
if (data.success) {
showMsg(data.message);
} else {
showMsg(data.message);
}
},
error: function (xhr, status, error) {
console.error('请求失败:', status, error);
}
});
}
function endGame() {
$.ajax({
url: api + "/game/end",
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({roomId: roomId, playerId: playerId}),
success: function (data) {
if (data.success) {
showMsg(data.message);
} else {
showMsg(data.message);
}
},
error: function (xhr, status, error) {
console.error('请求失败:', status, error);
}
});
}
function handleGameStart(data) {
showMsg("游戏开始!");
$("#total-rounds").text(data.totalRounds);
$("#start-game-btn").hide();
}
function handleGameEnd(data) {
showMsg("游戏结束!");
let winner = data.winner;
let finalScores = data.finalScores;
let scoreText = "最终得分:";
for (let playerName in finalScores) {
scoreText += playerName + ":" + finalScores[playerName] + "分 ";
}
showMsg(scoreText);
$("#start-game-btn").show();
$("#topic-name").text("等待游戏开始");
$("#topic-tip").text("--");
}
function handleRoundChange(data) {
$("#current-round").text(data.currentRound);
showMsg("第 " + data.currentRound + " 轮开始");
}
function handleTopicAssign(data) {
let topic = data.topic;
currentTopic = topic.name;
if (isDrawer) {
$("#topic-name").text(topic.name);
$("#topic-tip").text(topic.tip);
} else {
$("#topic-name").text("????");
$("#topic-tip").text(topic.tip);
}
}
function handleAnswerCorrect(data) {
showMsg(data.playerName + " 猜对了!+" + data.score + "分");
}
function handleAnswerWrong(data) {
showMsg(data.playerName + " 猜错了");
}
function handleScoreUpdate(data) {
let scores = data.scores;
for (let i = 0; i < 5; i++) {
let playerItem = $("#player-" + i);
if (playerItem.length > 0) {
let playerName = playerItem.text();
if (scores[playerName]) {
playerItem.text(playerName + ":" + scores[playerName] + "分");
}
}
}
}
function handleCountdown(data) {
$("#countdown").text(data.timeLeft);
}
function handleDrawerChange(data) {
isDrawer = (data.drawerId === playerId);
showMsg(data.drawerName + " 开始绘画");
if (isDrawer) {
showMsg("你是绘画者,请开始绘画!");
} else {
showMsg("请猜答案!");
}
}
function handleDrawPermission(data) {
isDrawer = (data.drawerId === playerId);
if (isDrawer) {
$("#board").css("pointer-events", "auto");
} else {
$("#board").css("pointer-events", "none");
}
}
</script> </script>