完善游戏开展流程
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
### 该问题是怎么引起的?
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 重现步骤
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 报错信息
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
+56
-2
@@ -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
|
||||||
|
|||||||
@@ -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/)
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 此为广播消息
|
* 此为广播消息
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -41,11 +41,15 @@
|
|||||||
<div id="game-top-box">
|
<div id="game-top-box">
|
||||||
<button id="back-btn" class="button"><span class="iconfont"></span></button>
|
<button id="back-btn" class="button"><span class="iconfont"></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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user