diff --git a/src/main/java/com/example/catchTheLetters/entity/GameMessage.java b/src/main/java/com/example/catchTheLetters/entity/GameMessage.java index 1245597..64ebd51 100644 --- a/src/main/java/com/example/catchTheLetters/entity/GameMessage.java +++ b/src/main/java/com/example/catchTheLetters/entity/GameMessage.java @@ -1,6 +1,8 @@ package com.example.catchTheLetters.entity; import com.example.catchTheLetters.enums.MessageType; +import com.example.catchTheLetters.utils.GameMessageDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; import java.io.Serializable; @@ -8,11 +10,12 @@ import java.io.Serializable; /** * 游戏消息 * - * @auther spyn + * @author spyn */ @Data +@JsonDeserialize(using = GameMessageDeserializer.class) public class GameMessage implements Serializable { - private String roomId; + private long roomId; private MessageType type; private T data; @@ -21,9 +24,12 @@ public class GameMessage implements Serializable { this.data = data; } - public GameMessage(String roomId, MessageType type, T data) { + public GameMessage(long roomId, MessageType type, T data) { this.roomId = roomId; this.type = type; this.data = data; } + + public GameMessage() { + } } diff --git a/src/main/java/com/example/catchTheLetters/entity/HealthAction.java b/src/main/java/com/example/catchTheLetters/entity/HealthAction.java new file mode 100644 index 0000000..8e5975d --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/entity/HealthAction.java @@ -0,0 +1,18 @@ +package com.example.catchTheLetters.entity; + +import com.example.catchTheLetters.enums.HealthActionType; +import lombok.Data; + +import java.io.Serializable; + +/** + * 血量操作 + * + * @auther spyn + */ +@Data +public class HealthAction implements Serializable { + private HealthActionType type; + private int health; + private String userId; +} diff --git a/src/main/java/com/example/catchTheLetters/entity/LetterAction.java b/src/main/java/com/example/catchTheLetters/entity/LetterAction.java new file mode 100644 index 0000000..7c4df1b --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/entity/LetterAction.java @@ -0,0 +1,19 @@ +package com.example.catchTheLetters.entity; + +import com.example.catchTheLetters.enums.LetterActionType; +import com.example.catchTheLetters.model.vo.Letter; +import lombok.Data; + +import java.io.Serializable; + +/** + * 字母操作 + * + * @author spyn + */ +@Data +public class LetterAction implements Serializable { + private LetterActionType type; + private Letter letter; + private String userId; +} diff --git a/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java b/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java index ee6e3ce..14334e0 100644 --- a/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java +++ b/src/main/java/com/example/catchTheLetters/entity/PlayerInGame.java @@ -7,7 +7,7 @@ import java.io.Serializable; /** * 游戏中的玩家 * - * @auther spyn + * @author spyn */ @Data public class PlayerInGame implements Serializable { diff --git a/src/main/java/com/example/catchTheLetters/entity/PlayerInput.java b/src/main/java/com/example/catchTheLetters/entity/PlayerInput.java index 93281eb..9ef33fe 100644 --- a/src/main/java/com/example/catchTheLetters/entity/PlayerInput.java +++ b/src/main/java/com/example/catchTheLetters/entity/PlayerInput.java @@ -15,4 +15,7 @@ public class PlayerInput implements Serializable { this.key = key; this.state = state; } + + public PlayerInput() { + } } diff --git a/src/main/java/com/example/catchTheLetters/entity/RoomAction.java b/src/main/java/com/example/catchTheLetters/entity/RoomAction.java new file mode 100644 index 0000000..c491696 --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/entity/RoomAction.java @@ -0,0 +1,16 @@ +package com.example.catchTheLetters.entity; + +import com.example.catchTheLetters.enums.RoomActionType; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class RoomAction implements Serializable { + private RoomActionType type; + // 创建或加入房间时的token + private String token; + // 房主踢出玩家时或被邀请的玩家ID + private String userID; + private long roomID; +} diff --git a/src/main/java/com/example/catchTheLetters/entity/ScoreAction.java b/src/main/java/com/example/catchTheLetters/entity/ScoreAction.java new file mode 100644 index 0000000..80d5b3c --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/entity/ScoreAction.java @@ -0,0 +1,16 @@ +package com.example.catchTheLetters.entity; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 分数操作 + * + * @author spyn + */ +@Data +public class ScoreAction implements Serializable { + private String userId; + private int score; +} diff --git a/src/main/java/com/example/catchTheLetters/enums/HealthActionType.java b/src/main/java/com/example/catchTheLetters/enums/HealthActionType.java new file mode 100644 index 0000000..4471114 --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/enums/HealthActionType.java @@ -0,0 +1,11 @@ +package com.example.catchTheLetters.enums; + +/** + * 血量操作类型 + * + * @author spyn + */ +public enum HealthActionType { + CHANGE, + DEAD +} diff --git a/src/main/java/com/example/catchTheLetters/enums/InputKeyType.java b/src/main/java/com/example/catchTheLetters/enums/InputKeyType.java index 8f58707..1c4f59b 100644 --- a/src/main/java/com/example/catchTheLetters/enums/InputKeyType.java +++ b/src/main/java/com/example/catchTheLetters/enums/InputKeyType.java @@ -1,21 +1,13 @@ package com.example.catchTheLetters.enums; -import lombok.Getter; - /** * 输入类型 * * @author spyn */ -@Getter public enum InputKeyType { - LEFT("left"), - RIGHT("right"), - SHIFT("shift"); - - private final String key; - - InputKeyType(String key) { - this.key = key; - } + LEFT, + RIGHT, + SHIFT, + SPACE } diff --git a/src/main/java/com/example/catchTheLetters/enums/InputState.java b/src/main/java/com/example/catchTheLetters/enums/InputState.java index a984f00..8b146dd 100644 --- a/src/main/java/com/example/catchTheLetters/enums/InputState.java +++ b/src/main/java/com/example/catchTheLetters/enums/InputState.java @@ -1,20 +1,11 @@ package com.example.catchTheLetters.enums; -import lombok.Getter; - /** * 输入状态 * * @author spyn */ -@Getter public enum InputState { - PRESS("press"), - RELEASE("release"); - - private final String state; - - InputState(String state) { - this.state = state; - } + PRESS, + RELEASE } diff --git a/src/main/java/com/example/catchTheLetters/enums/LetterActionType.java b/src/main/java/com/example/catchTheLetters/enums/LetterActionType.java new file mode 100644 index 0000000..18932b3 --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/enums/LetterActionType.java @@ -0,0 +1,11 @@ +package com.example.catchTheLetters.enums; + +/** + * 字母操作类型 + * + * @author spyn + */ +public enum LetterActionType { + CREATE, + GET +} diff --git a/src/main/java/com/example/catchTheLetters/enums/MessageType.java b/src/main/java/com/example/catchTheLetters/enums/MessageType.java index 53ebbaa..c82d0fe 100644 --- a/src/main/java/com/example/catchTheLetters/enums/MessageType.java +++ b/src/main/java/com/example/catchTheLetters/enums/MessageType.java @@ -3,18 +3,13 @@ package com.example.catchTheLetters.enums; /** * WebSocket 消息类型 * - * @auther spyn + * @author spyn */ public enum MessageType { - INPUT("input"), - ROOM("room"), - LETTER("letter"), - SCORE("score"), - HEALTH("health"); - - private final String type; - - MessageType(String type) { - this.type = type; - } + INPUT, + ROOM, + LETTER, + SCORE, + HEALTH, + ERROR } diff --git a/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java b/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java new file mode 100644 index 0000000..8001ef4 --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/enums/RoomActionType.java @@ -0,0 +1,15 @@ +package com.example.catchTheLetters.enums; + +/** + * 房间操作 + * + * @author spyn + */ +public enum RoomActionType { + CREATE, + JOIN, + KICK, + LEAVE, + START, + INVITE +} diff --git a/src/main/java/com/example/catchTheLetters/enums/RoomStatus.java b/src/main/java/com/example/catchTheLetters/enums/RoomStatus.java index 291f5bc..f416e59 100644 --- a/src/main/java/com/example/catchTheLetters/enums/RoomStatus.java +++ b/src/main/java/com/example/catchTheLetters/enums/RoomStatus.java @@ -6,12 +6,6 @@ package com.example.catchTheLetters.enums; * @author spyn */ public enum RoomStatus { - WAITING("waiting"), - PLAYING("playing"); - - private final String status; - - RoomStatus(String status) { - this.status = status; - } + WAITING, + PLAYING } diff --git a/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java b/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java index c43c696..3383a39 100644 --- a/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java +++ b/src/main/java/com/example/catchTheLetters/handler/WebSocketHandler.java @@ -1,10 +1,20 @@ package com.example.catchTheLetters.handler; +import com.example.catchTheLetters.entity.GameMessage; +import com.example.catchTheLetters.entity.PlayerInput; +import com.example.catchTheLetters.enums.MessageType; +import com.example.catchTheLetters.service.RoomService; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Controller; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; +import java.io.IOException; + /** * WebSocket 处理器 * @@ -13,8 +23,41 @@ import org.springframework.web.socket.handler.TextWebSocketHandler; @Controller public class WebSocketHandler extends TextWebSocketHandler { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Resource + private RoomService roomService; + @Override - public void handleTextMessage(WebSocketSession session, TextMessage message) { + public void handleTextMessage(@NotNull WebSocketSession session, TextMessage message) { // 处理接收到的消息 + GameMessage gameMessage; + try { + gameMessage = objectMapper.readValue(message.getPayload(), GameMessage.class); + } catch (JsonProcessingException e) { + roomService.sendMessage(session, new GameMessage<>(MessageType.ERROR, "消息解析失败")); + return; + } + + switch (gameMessage.getType()) { + case INPUT: + // 处理输入消息 + roomService.handleInput(gameMessage.getRoomId(), session, (PlayerInput) gameMessage.getData()); + break; + case ROOM: + // 处理房间消息 + break; + case LETTER: + // 处理字母消息 + break; + case SCORE: + // 处理分数消息 + break; + case HEALTH: + // 处理生命值消息 + break; + default: + roomService.sendMessage(session, new GameMessage<>(MessageType.ERROR, "未知的消息类型")); + } } } diff --git a/src/main/java/com/example/catchTheLetters/service/RoomService.java b/src/main/java/com/example/catchTheLetters/service/RoomService.java index 6bc4053..050548f 100644 --- a/src/main/java/com/example/catchTheLetters/service/RoomService.java +++ b/src/main/java/com/example/catchTheLetters/service/RoomService.java @@ -24,6 +24,14 @@ public interface RoomService { */ void removePlayer(long roomId, WebSocketSession session); + /** + * 踢出玩家 + * @param roomId 房间号 + * @param session 房主的 WebSocket 会话 + * @param playerID 要踢出的玩家 ID + */ + void kickPlayer(long roomId, WebSocketSession session, String playerID); + /** * 开始游戏 * @param roomId 房间号 @@ -64,4 +72,33 @@ public interface RoomService { * @param roomId 房间号 */ void removeRoom(long roomId); + + /** + * 邀请玩家 + * @param roomId 房间号 + * @param session WebSocket 会话 + * @param playerID 被邀请的玩家ID + */ + void invitePlayer(long roomId, WebSocketSession session, String playerID); + + /** + * 连接ws + * @param session WebSocket 会话 + * @param token 玩家 token + */ + void connect(WebSocketSession session, String token); + + /** + * 断开ws + * @param token 玩家 token + */ + void disconnect(String token); + + /** + * 发送消息 + * @param session WebSocket 会话 + * @param message 消息 + * @param 消息类型 + */ + void sendMessage(WebSocketSession session, T message); } 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 223374f..3ca31b0 100644 --- a/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java +++ b/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java @@ -2,6 +2,7 @@ package com.example.catchTheLetters.service.impl; import com.example.catchTheLetters.entity.*; import com.example.catchTheLetters.enums.MessageType; +import com.example.catchTheLetters.enums.RoomActionType; import com.example.catchTheLetters.enums.RoomStatus; import com.example.catchTheLetters.model.vo.Letter; import com.example.catchTheLetters.service.AuthService; @@ -18,12 +19,20 @@ import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; +/** + * 房间服务实现类 + * + * @author spyn + */ @Service public class RoomServiceImpl implements RoomService { // 房间列表 private final ConcurrentHashMap rooms = new ConcurrentHashMap<>(); + // 玩家列表 + private final ConcurrentHashMap playerInGame = new ConcurrentHashMap<>(); + private final ObjectMapper objectMapper = new ObjectMapper(); private final Random random = new Random(); @@ -70,6 +79,24 @@ public class RoomServiceImpl implements RoomService { // TODO 发送消息通知其他玩家有人退出了 } + @Override + public void kickPlayer(long roomId, WebSocketSession session, String playerID) { + var room = rooms.get(roomId); + // 如果不是房主,返回错误信息 + if (session != room.getHost()) { + sendMessage(session, new GameMessage<>(MessageType.ERROR, "你不是房主")); + return; + } + var players = room.getPlayers(); + for (var player : players.entrySet()) { + if (player.getValue().getUserId().equals(playerID)) { + players.remove(player.getKey()); + // TODO 发送消息通知有玩家被踢出了 + break; + } + } + } + @Override public void startGame(long roomId, WebSocketSession session) { var room = rooms.get(roomId); @@ -129,6 +156,53 @@ public class RoomServiceImpl implements RoomService { rooms.remove(roomId); } + @Override + public void invitePlayer(long roomId, WebSocketSession session, String playerID) { + var roomAction = new RoomAction(); + roomAction.setType(RoomActionType.INVITE); + roomAction.setRoomID(roomId); + roomAction.setUserID(playerID); + var aimSession = playerInGame.get(playerID); + + // 如果玩家在线,发送邀请消息 + if (aimSession != null) sendMessage(session, new GameMessage<>(MessageType.ROOM, roomAction)); + else sendMessage(session, new GameMessage<>(MessageType.ERROR, "玩家不在线")); + } + + @Override + public void connect(WebSocketSession session, String token) { + var player = authService.verify(token); + playerInGame.put(player.getId(), session); + } + + @Override + public void disconnect(String token) { + var player = authService.verify(token); + var session = playerInGame.remove(player.getId()); + try { + session.close(); + } catch (IOException e) { + throw new RuntimeException("关闭WebSocket会话失败", e); + } + } + + @Override + public void sendMessage(WebSocketSession session, T message) { + String json; + if (!(message instanceof String)) { + try { + json = objectMapper.writeValueAsString(message); + } catch (Exception e) { + throw new RuntimeException("消息转为JSON字符串操作失败", e); + } + } else json = (String) message; + try { + session.sendMessage(new TextMessage(json)); + } catch (IOException e) { + throw new RuntimeException("发送消息失败", e); + } + } + private void generateLetter(GameRoom room) { var players = room.getPlayers(); var words = room.getWords(); @@ -156,22 +230,6 @@ public class RoomServiceImpl implements RoomService { sendMessage(player, new GameMessage<>(MessageType.LETTER, letter)); } - private void sendMessage(WebSocketSession session, T message) { - String json; - if (!(message instanceof String)) { - try { - json = objectMapper.writeValueAsString(message); - } catch (Exception e) { - throw new RuntimeException("消息转为JSON字符串操作失败", e); - } - } else json = (String) message; - try { - session.sendMessage(new TextMessage(json)); - } catch (IOException e) { - throw new RuntimeException("发送消息失败", e); - } - } - private void getWords(Map words) { // TODO 从数据库中获取一批随机单词,然后放入words中,并把单词数组推送给所有玩家 } diff --git a/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java b/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java new file mode 100644 index 0000000..b693124 --- /dev/null +++ b/src/main/java/com/example/catchTheLetters/utils/GameMessageDeserializer.java @@ -0,0 +1,36 @@ +package com.example.catchTheLetters.utils; + +import com.example.catchTheLetters.entity.*; +import com.example.catchTheLetters.enums.MessageType; +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +/** + * 游戏消息反序列化 + * + * @author spyn + */ +public class GameMessageDeserializer extends JsonDeserializer> { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public GameMessage deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException { + var node = jsonParser.getCodec().readTree(jsonParser); + var type = objectMapper.convertValue(node.get("type"), JsonNode.class); + var data = objectMapper.convertValue(node.get("data"), JsonNode.class); + return switch (type.asText()) { + case "INPUT" -> new GameMessage<>(MessageType.INPUT, objectMapper.treeToValue(data, PlayerInput.class)); + case "ROOM" -> new GameMessage<>(MessageType.ROOM, objectMapper.treeToValue(data, RoomAction.class)); + 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)); + default -> throw new IllegalArgumentException("未知的消息类型"); + }; + } +} diff --git a/src/test/java/com/example/catchTheLetters/JSONTest.java b/src/test/java/com/example/catchTheLetters/JSONTest.java index 04b7010..ea2f239 100644 --- a/src/test/java/com/example/catchTheLetters/JSONTest.java +++ b/src/test/java/com/example/catchTheLetters/JSONTest.java @@ -1,8 +1,10 @@ package com.example.catchTheLetters; +import com.example.catchTheLetters.entity.GameMessage; import com.example.catchTheLetters.entity.PlayerInput; import com.example.catchTheLetters.enums.InputKeyType; import com.example.catchTheLetters.enums.InputState; +import com.example.catchTheLetters.enums.MessageType; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; @@ -13,8 +15,17 @@ class JSONTest { private final ObjectMapper objectMapper = new ObjectMapper(); @Test - void contextLoads() throws JsonProcessingException { + void enumToJSON() throws JsonProcessingException { var input = new PlayerInput(InputKeyType.LEFT, InputState.PRESS); - System.out.println(objectMapper.writeValueAsString(input)); + System.out.println(objectMapper.writeValueAsString(new GameMessage<>(MessageType.INPUT, input))); + } + + @Test + void jsonToEnum() throws JsonProcessingException { + var json = "{\"type\":\"INPUT\",\"data\":{\"key\":\"LEFT\",\"state\":\"PRESS\"}}"; + var message = objectMapper.readValue(json, GameMessage.class); + System.out.println(message); + System.out.println(message.getType() == MessageType.INPUT); + System.out.println(message.getData()); } }