From d83e912d5bf1f7731249795178e43d4088f1b0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=B3=E7=9A=AE=E5=B9=BC=E9=B8=9F?= <2960474346@qq.com> Date: Sun, 16 Jun 2024 00:07:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E8=81=94=E6=9C=BA=E6=A1=86?= =?UTF-8?q?=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../catchTheLetters/entity/GameRoom.java | 3 + .../catchTheLetters/entity/LetterAction.java | 15 +++ .../catchTheLetters/entity/PlayerInGame.java | 16 +++ .../catchTheLetters/entity/RoomAction.java | 24 +++++ .../catchTheLetters/enums/MessageType.java | 4 +- .../catchTheLetters/enums/RoomActionType.java | 4 +- .../handler/WebSocketHandler.java | 47 ++++++++- .../catchTheLetters/model/vo/Letter.java | 8 +- .../catchTheLetters/service/RoomService.java | 8 ++ .../service/impl/RoomServiceImpl.java | 99 ++++++++++++++----- .../utils/GameMessageDeserializer.java | 3 + 11 files changed, 199 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/example/catchTheLetters/entity/GameRoom.java b/src/main/java/com/example/catchTheLetters/entity/GameRoom.java index 8819ef4..afb2248 100644 --- a/src/main/java/com/example/catchTheLetters/entity/GameRoom.java +++ b/src/main/java/com/example/catchTheLetters/entity/GameRoom.java @@ -2,6 +2,7 @@ package com.example.catchTheLetters.entity; import cn.hutool.core.collection.ConcurrentHashSet; import com.example.catchTheLetters.enums.RoomStatus; +import com.example.catchTheLetters.model.vo.Letter; import lombok.Data; import org.springframework.web.socket.WebSocketSession; @@ -23,6 +24,8 @@ public class GameRoom { private WebSocketSession host; // 单词 : 目前在拼这个单词的玩家数 private final Map words = new ConcurrentSkipListMap<>(); + // 字母id : 字母 + private final Map letters = new ConcurrentHashMap<>(); public GameRoom(WebSocketSession host, PlayerInGame player) { this.roomId = UUID.randomUUID().getLeastSignificantBits(); diff --git a/src/main/java/com/example/catchTheLetters/entity/LetterAction.java b/src/main/java/com/example/catchTheLetters/entity/LetterAction.java index 7c4df1b..a000bfa 100644 --- a/src/main/java/com/example/catchTheLetters/entity/LetterAction.java +++ b/src/main/java/com/example/catchTheLetters/entity/LetterAction.java @@ -16,4 +16,19 @@ public class LetterAction implements Serializable { private LetterActionType type; private Letter letter; private String userId; + private long letterId; + + public LetterAction() { + } + + public LetterAction(LetterActionType type, Letter letter, String userId) { + this.type = type; + this.letter = letter; + this.userId = userId; + } + + public LetterAction(LetterActionType type, Letter letter) { + this.type = type; + this.letter = letter; + } } diff --git a/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java b/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java index 14334e0..02d98fb 100644 --- a/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java +++ b/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java @@ -26,4 +26,20 @@ public class PlayerInGame implements Serializable { this.currentWord = ""; this.currentAnswer = ""; } + + public void addScore(int score) { + this.score += score; + } + + public void reduceHealth(int health) { + this.health = Math.max(0, this.health - health); + } + + public void setCurrentAnswer(String letter) { + this.currentAnswer += letter; + } + + public void clearCurrentAnswer() { + this.currentAnswer = ""; + } } diff --git a/src/main/java/com/example/catchTheLetters/entity/RoomAction.java b/src/main/java/com/example/catchTheLetters/entity/RoomAction.java index c491696..7ddc258 100644 --- a/src/main/java/com/example/catchTheLetters/entity/RoomAction.java +++ b/src/main/java/com/example/catchTheLetters/entity/RoomAction.java @@ -13,4 +13,28 @@ public class RoomAction implements Serializable { // 房主踢出玩家时或被邀请的玩家ID private String userID; private long roomID; + + public RoomAction() { + } + + public RoomAction(RoomActionType type, String token, String userID, long roomID) { + this.type = type; + this.token = token; + this.userID = userID; + this.roomID = roomID; + } + + public RoomAction(RoomActionType type, String userID) { + this.type = type; + this.userID = userID; + } + + public RoomAction(RoomActionType type, long roomID) { + this.type = type; + this.roomID = roomID; + } + + public RoomAction(RoomActionType type) { + this.type = type; + } } diff --git a/src/main/java/com/example/catchTheLetters/enums/MessageType.java b/src/main/java/com/example/catchTheLetters/enums/MessageType.java index c82d0fe..fb5ddd2 100644 --- a/src/main/java/com/example/catchTheLetters/enums/MessageType.java +++ b/src/main/java/com/example/catchTheLetters/enums/MessageType.java @@ -11,5 +11,7 @@ public enum MessageType { LETTER, SCORE, HEALTH, - ERROR + ERROR, + LOGIN, + LOGOUT } diff --git a/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java b/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java index 8001ef4..939b97c 100644 --- a/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java +++ b/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java @@ -11,5 +11,7 @@ public enum RoomActionType { KICK, LEAVE, START, - INVITE + CANCEL_START, + INVITE, + END } diff --git a/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java b/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java index 3383a39..dd1076f 100644 --- a/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java +++ b/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java @@ -1,7 +1,10 @@ package com.example.catchTheLetters.handler; import com.example.catchTheLetters.entity.GameMessage; +import com.example.catchTheLetters.entity.LetterAction; import com.example.catchTheLetters.entity.PlayerInput; +import com.example.catchTheLetters.entity.RoomAction; +import com.example.catchTheLetters.enums.LetterActionType; import com.example.catchTheLetters.enums.MessageType; import com.example.catchTheLetters.service.RoomService; import com.fasterxml.jackson.core.JsonProcessingException; @@ -13,7 +16,7 @@ import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; -import java.io.IOException; +import java.util.Objects; /** * WebSocket 处理器 @@ -46,15 +49,49 @@ public class WebSocketHandler extends TextWebSocketHandler { break; case ROOM: // 处理房间消息 + var roomData = (RoomAction) gameMessage.getData(); + switch (roomData.getType()) { + case CREATE: + roomService.createRoom(session, roomData.getToken()); + break; + case JOIN: + roomService.addPlayer(gameMessage.getRoomId(), session, roomData.getToken()); + break; + case LEAVE: + roomService.removePlayer(gameMessage.getRoomId(), session); + break; + case KICK: + roomService.kickPlayer(gameMessage.getRoomId(), session, roomData.getUserID()); + break; + case START: + roomService.startGame(gameMessage.getRoomId(), session); + break; + case CANCEL_START: + roomService.cancelStartGame(gameMessage.getRoomId(), session); + break; + case END: + roomService.endGame(gameMessage.getRoomId()); + break; + default: + roomService.sendMessage(session, new GameMessage<>(MessageType.ERROR, "未知的房间操作")); + } break; case LETTER: // 处理字母消息 + var letterData = (LetterAction) gameMessage.getData(); + if (Objects.requireNonNull(letterData.getType()) == LetterActionType.GET) + // 获取字母 + roomService.getLetter(gameMessage.getRoomId(), session, letterData.getLetterId()); + else + roomService.sendMessage(session, new GameMessage<>(MessageType.ERROR, "未知的字母操作")); break; - case SCORE: - // 处理分数消息 + case LOGIN: + // 处理登录消息 + roomService.connect(session, (String) gameMessage.getData()); break; - case HEALTH: - // 处理生命值消息 + case LOGOUT: + // 处理登出消息 + roomService.disconnect((String) gameMessage.getData()); break; default: roomService.sendMessage(session, new GameMessage<>(MessageType.ERROR, "未知的消息类型")); diff --git a/src/main/java/com/example/catchTheLetters/model/vo/Letter.java b/src/main/java/com/example/catchTheLetters/model/vo/Letter.java index 14c45eb..5154615 100644 --- a/src/main/java/com/example/catchTheLetters/model/vo/Letter.java +++ b/src/main/java/com/example/catchTheLetters/model/vo/Letter.java @@ -9,6 +9,8 @@ import lombok.Data; */ @Data public class Letter { + private long id; + // 字母值,如果是10则是加血 private String letterVal; @@ -18,12 +20,14 @@ public class Letter { // 字母的x坐标,0-1之间 private float x; - public Letter(String letterVal, float x) { + public Letter(long id, String letterVal, float x) { + this.id = id; this.letterVal = letterVal; this.x = x; } - public Letter(String letterVal, float x, float speed) { + public Letter(long id, String letterVal, float x, float speed) { + this.id = id; this.letterVal = letterVal; this.x = x; this.speed = speed; diff --git a/src/main/java/com/example/catchTheLetters/service/RoomService.java b/src/main/java/com/example/catchTheLetters/service/RoomService.java index 050548f..1bae185 100644 --- a/src/main/java/com/example/catchTheLetters/service/RoomService.java +++ b/src/main/java/com/example/catchTheLetters/service/RoomService.java @@ -101,4 +101,12 @@ public interface RoomService { * @param 消息类型 */ void sendMessage(WebSocketSession session, T message); + + /** + * 玩家获得了字母 + * @param roomId 房间号 + * @param session WebSocket 会话 + * @param letterId 字母ID + */ + void getLetter(long roomId, WebSocketSession session, long letterId); } diff --git a/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java b/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java index 3ca31b0..8a3f042 100644 --- a/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java +++ b/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java @@ -1,6 +1,7 @@ package com.example.catchTheLetters.service.impl; import com.example.catchTheLetters.entity.*; +import com.example.catchTheLetters.enums.LetterActionType; import com.example.catchTheLetters.enums.MessageType; import com.example.catchTheLetters.enums.RoomActionType; import com.example.catchTheLetters.enums.RoomStatus; @@ -17,6 +18,7 @@ import org.springframework.web.socket.WebSocketSession; import java.io.IOException; import java.util.Map; import java.util.Random; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** @@ -60,23 +62,30 @@ public class RoomServiceImpl implements RoomService { } players.put(session, player); - // TODO 发送消息通知其他玩家有人加入了 + // 发送消息通知其他玩家有人加入了 + var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.JOIN, player.getUserId())); + for (var playerEntry : players.entrySet()) + if (playerEntry.getKey() != session) + sendMessage(playerEntry.getKey(), message); } @Override public void removePlayer(long roomId, WebSocketSession session) { var room = rooms.get(roomId); var players = room.getPlayers(); - players.remove(session); + var removed = players.remove(session); // 如果是房主退出,更换房主,否则关闭房间 if (session == room.getHost()) { - if (!players.isEmpty()) { + if (!players.isEmpty()) room.setHost(players.keySet().iterator().next()); - } else { + else rooms.remove(roomId); - } } - // TODO 发送消息通知其他玩家有人退出了 + // 发送消息通知其他玩家有人退出了 + var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.LEAVE, removed.getUserId())); + for (var player : players.keySet()) + if (player != session) + sendMessage(player, message); } @Override @@ -88,13 +97,15 @@ public class RoomServiceImpl implements RoomService { return; } var players = room.getPlayers(); + var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.KICK, playerID)); + WebSocketSession aimSession = null; for (var player : players.entrySet()) { - if (player.getValue().getUserId().equals(playerID)) { - players.remove(player.getKey()); - // TODO 发送消息通知有玩家被踢出了 - break; - } + if (player.getValue().getUserId().equals(playerID)) + aimSession = player.getKey(); + // 发送消息通知有玩家被踢出了 + sendMessage(player.getKey(), message); } + players.remove(aimSession); } @Override @@ -103,10 +114,17 @@ public class RoomServiceImpl implements RoomService { var players = room.getPlayers(); var readyPlayers = room.getReadyPlayers(); readyPlayers.add(session); - // TODO 发送消息通知其他玩家有人准备好了 + // 发送消息通知其他玩家有人准备好了 + var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.START, session.getId())); + for (var player : players.keySet()) + if (player != session) + sendMessage(player, message); // 如果所有玩家都准备好了,开始游戏 if (readyPlayers.size() == room.getPlayers().size()) { - // TODO 发送消息通知所有玩家游戏开始 + // 发送消息通知所有玩家游戏开始 + var startMessage = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.START, session.getId())); + for (var player : players.keySet()) + sendMessage(player, startMessage); readyPlayers.clear(); room.setStatus(RoomStatus.PLAYING); gameLogic(room); @@ -118,14 +136,21 @@ public class RoomServiceImpl implements RoomService { var room = rooms.get(roomId); var readyPlayers = room.getReadyPlayers(); readyPlayers.remove(session); - // TODO 发送消息通知其他玩家有人取消准备 + // 发送消息通知其他玩家有人取消准备 + var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.CANCEL_START, session.getId())); + for (var player : room.getPlayers().keySet()) + if (player != session) + sendMessage(player, message); } @Override public void endGame(long roomId) { var room = rooms.get(roomId); room.setStatus(RoomStatus.WAITING); - // TODO 发送消息通知所有玩家游戏结束 + // 发送消息通知所有玩家游戏结束 + var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.END)); + for (var player : room.getPlayers().keySet()) + sendMessage(player, message); } @Override @@ -133,8 +158,10 @@ public class RoomServiceImpl implements RoomService { var message = new GameMessage<>(MessageType.INPUT, input); var room = rooms.get(roomId); var players = room.getPlayers(); + // 发送消息通知其他玩家有人输入了 for (var player : players.keySet()) - if (player != session) sendMessage(player, message); + if (player != session) + sendMessage(player, message); } @Override @@ -143,6 +170,8 @@ public class RoomServiceImpl implements RoomService { var player = new PlayerInGame(user.getId(), user); var room = new GameRoom(session, player); rooms.put(room.getRoomId(), room); + // 发送消息通知玩家创建房间成功 + sendMessage(session, new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.CREATE, room.getRoomId()))); } @Override @@ -150,8 +179,9 @@ public class RoomServiceImpl implements RoomService { // 如果房间内还有玩家,全部移除 var room = rooms.get(roomId); var players = room.getPlayers(); - for (var player : players.keySet()) { - // TODO 发送消息通知所有玩家房间已解散 + for (var player : players.entrySet()) { + // 发送消息通知所有玩家房间已解散 + sendMessage(player.getKey(), new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.KICK, player.getValue().getUserId()))); } rooms.remove(roomId); } @@ -203,6 +233,24 @@ public class RoomServiceImpl implements RoomService { } } + @Override + public void getLetter(long roomId, WebSocketSession session, long letterId) { + var room = rooms.get(roomId); + var letter = room.getLetters().get(letterId); + // 如果没有这个字母,代表被其他玩家拿走了 + if (letter == null) return; + + var players = room.getPlayers(); + var player = players.get(session); + // 发送消息通知其他玩家有人获得了字母 + var message = new GameMessage<>(MessageType.LETTER, new LetterAction(LetterActionType.GET, letter, player.getUserId())); + for (var playerEntry : players.entrySet()) + sendMessage(playerEntry.getKey(), message); + // TODO 字母校验和加分、改变血量逻辑 + // 玩家接取字母时,向玩家当前答案中添加字母,如果单词拼完,给玩家加5*word.length的分和10滴血,如果单词拼错,每秒扣5滴血,如果玩家血量为0,则死亡 + // 如果接取的是回血爱心,给玩家加10滴血 + } + private void generateLetter(GameRoom room) { var players = room.getPlayers(); var words = room.getWords(); @@ -211,23 +259,25 @@ public class RoomServiceImpl implements RoomService { if (words.size() <= 5) getWords(words); Letter letter; + var id = UUID.randomUUID().getLeastSignificantBits(); var val = random.nextInt(100); // 在80%概率当前单词的字母中随机选择一个,19%随机生成一个字母,1%是回血爱心。选定后随机赋值2f到6f的下落速度 if (val == 0) // 回血爱心 - letter = new Letter("10", random.nextFloat(1), random.nextFloat(2, 6)); + letter = new Letter(id, "10", random.nextFloat(1), random.nextFloat(2, 6)); else if (val <= 81) // 从当前单词中随机选择一个字母 - letter = new Letter(words.keySet().toArray()[random.nextInt(words.size())].toString(), random.nextFloat(1), random.nextFloat(2, 6)); + letter = new Letter(id, words.keySet().toArray()[random.nextInt(words.size())].toString(), random.nextFloat(1), random.nextFloat(2, 6)); else // 随机生成一个字母 - letter = new Letter(String.valueOf((char) (random.nextInt(26) + 'a')), random.nextFloat(1), random.nextFloat(2, 6)); + letter = new Letter(id, String.valueOf((char) (random.nextInt(26) + 'a')), random.nextFloat(1), random.nextFloat(2, 6)); + room.getLetters().put(id, letter); // 给所有玩家发送字母 for (var player : players.keySet()) - sendMessage(player, new GameMessage<>(MessageType.LETTER, letter)); + sendMessage(player, new GameMessage<>(MessageType.LETTER, new LetterAction(LetterActionType.CREATE, letter))); } private void getWords(Map words) { @@ -236,6 +286,9 @@ public class RoomServiceImpl implements RoomService { private void gameLogic(GameRoom room) { getWords(room.getWords()); - // TODO 从300秒开始倒计时,每秒调用一次generateLetter方法,如果时间到了,调用endGame方法,如果当前单词被某玩家拼完,Map对应单词的value++,如果value>=玩家数(有可能中途有人退出),目前单词出队列,继续下一个单词 + // TODO 从300秒开始倒计时,每秒调用一次generateLetter方法,如果时间到了,调用endGame方法 + // 如果当前单词被某玩家拼完,Map对应单词的value++,如果value>=玩家数(有可能中途有人退出),目前单词出队列,继续下一个单词 + // 游戏结束后需要向玩家发送排行榜数据(用户ID、分数、排名) + // 最大的问题:丢包后如何处理?比如其他玩家按下按键后,自己没有接收到松开消息,导致其他玩家在画面中一直持续运动,网络连接稳定后,不同C端的玩家位置不一致 } } diff --git a/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java b/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java index b693124..c9ca160 100644 --- a/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java +++ b/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java @@ -30,6 +30,9 @@ public class GameMessageDeserializer extends JsonDeserializer> { case "LETTER" -> new GameMessage<>(MessageType.LETTER, objectMapper.treeToValue(data, LetterAction.class)); case "SCORE" -> new GameMessage<>(MessageType.SCORE, objectMapper.treeToValue(data, ScoreAction.class)); case "HEALTH" -> new GameMessage<>(MessageType.HEALTH, objectMapper.treeToValue(data, HealthAction.class)); + case "ERROR" -> new GameMessage<>(MessageType.ERROR, objectMapper.treeToValue(data, String.class)); + case "LOGIN" -> new GameMessage<>(MessageType.LOGIN, objectMapper.treeToValue(data, String.class)); + case "LOGOUT" -> new GameMessage<>(MessageType.LOGOUT, objectMapper.treeToValue(data, String.class)); default -> throw new IllegalArgumentException("未知的消息类型"); }; }