2024-06-14 10:51:41 +00:00
|
|
|
|
package com.example.catchTheLetters.service.impl;
|
|
|
|
|
|
|
|
|
|
import com.example.catchTheLetters.entity.*;
|
2024-06-15 16:25:47 +00:00
|
|
|
|
import com.example.catchTheLetters.enums.*;
|
2024-06-14 10:51:41 +00:00
|
|
|
|
import com.example.catchTheLetters.model.vo.Letter;
|
|
|
|
|
import com.example.catchTheLetters.service.AuthService;
|
|
|
|
|
import com.example.catchTheLetters.service.RoomService;
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import jakarta.annotation.Resource;
|
|
|
|
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
import org.springframework.web.socket.TextMessage;
|
|
|
|
|
import org.springframework.web.socket.WebSocketSession;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Random;
|
2024-06-15 16:07:06 +00:00
|
|
|
|
import java.util.UUID;
|
2024-06-14 10:51:41 +00:00
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
2024-06-14 15:58:52 +00:00
|
|
|
|
/**
|
|
|
|
|
* 房间服务实现类
|
|
|
|
|
*
|
|
|
|
|
* @author spyn
|
|
|
|
|
*/
|
2024-06-14 10:51:41 +00:00
|
|
|
|
@Service
|
|
|
|
|
public class RoomServiceImpl implements RoomService {
|
|
|
|
|
|
|
|
|
|
// 房间列表
|
|
|
|
|
private final ConcurrentHashMap<Long, GameRoom> rooms = new ConcurrentHashMap<>();
|
|
|
|
|
|
2024-06-14 15:58:52 +00:00
|
|
|
|
// 玩家列表
|
|
|
|
|
private final ConcurrentHashMap<String, WebSocketSession> playerInGame = new ConcurrentHashMap<>();
|
|
|
|
|
|
2024-06-14 10:51:41 +00:00
|
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
|
|
|
|
private final Random random = new Random();
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private MongoTemplate mongoTemplate;
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
private AuthService authService;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void addPlayer(long roomId, WebSocketSession session, String token) {
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
var players = room.getPlayers();
|
|
|
|
|
var user = authService.verify(token);
|
|
|
|
|
var player = new PlayerInGame(user.getId(), user);
|
|
|
|
|
|
|
|
|
|
// 如果玩家已经在房间中,更新他们的WebSocketSession
|
|
|
|
|
for (var entry : players.entrySet()) {
|
|
|
|
|
if (entry.getValue().getUserId().equals(user.getId())) {
|
|
|
|
|
player = entry.getValue();
|
|
|
|
|
players.remove(entry.getKey());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
players.put(session, player);
|
|
|
|
|
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// 发送消息通知其他玩家有人加入了
|
|
|
|
|
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);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void removePlayer(long roomId, WebSocketSession session) {
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
var players = room.getPlayers();
|
2024-06-15 16:07:06 +00:00
|
|
|
|
var removed = players.remove(session);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
// 如果是房主退出,更换房主,否则关闭房间
|
|
|
|
|
if (session == room.getHost()) {
|
2024-06-15 16:07:06 +00:00
|
|
|
|
if (!players.isEmpty())
|
2024-06-14 10:51:41 +00:00
|
|
|
|
room.setHost(players.keySet().iterator().next());
|
2024-06-15 16:07:06 +00:00
|
|
|
|
else
|
2024-06-14 10:51:41 +00:00
|
|
|
|
rooms.remove(roomId);
|
|
|
|
|
}
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// 发送消息通知其他玩家有人退出了
|
|
|
|
|
var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.LEAVE, removed.getUserId()));
|
|
|
|
|
for (var player : players.keySet())
|
|
|
|
|
if (player != session)
|
|
|
|
|
sendMessage(player, message);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-14 15:58:52 +00:00
|
|
|
|
@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();
|
2024-06-15 16:07:06 +00:00
|
|
|
|
var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.KICK, playerID));
|
|
|
|
|
WebSocketSession aimSession = null;
|
2024-06-14 15:58:52 +00:00
|
|
|
|
for (var player : players.entrySet()) {
|
2024-06-15 16:07:06 +00:00
|
|
|
|
if (player.getValue().getUserId().equals(playerID))
|
|
|
|
|
aimSession = player.getKey();
|
|
|
|
|
// 发送消息通知有玩家被踢出了
|
|
|
|
|
sendMessage(player.getKey(), message);
|
2024-06-14 15:58:52 +00:00
|
|
|
|
}
|
2024-06-15 16:07:06 +00:00
|
|
|
|
players.remove(aimSession);
|
2024-06-14 15:58:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-14 10:51:41 +00:00
|
|
|
|
@Override
|
|
|
|
|
public void startGame(long roomId, WebSocketSession session) {
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
var players = room.getPlayers();
|
|
|
|
|
var readyPlayers = room.getReadyPlayers();
|
|
|
|
|
readyPlayers.add(session);
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// 发送消息通知其他玩家有人准备好了
|
|
|
|
|
var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.START, session.getId()));
|
|
|
|
|
for (var player : players.keySet())
|
|
|
|
|
if (player != session)
|
|
|
|
|
sendMessage(player, message);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
// 如果所有玩家都准备好了,开始游戏
|
|
|
|
|
if (readyPlayers.size() == room.getPlayers().size()) {
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// 发送消息通知所有玩家游戏开始
|
|
|
|
|
var startMessage = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.START, session.getId()));
|
|
|
|
|
for (var player : players.keySet())
|
|
|
|
|
sendMessage(player, startMessage);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
readyPlayers.clear();
|
|
|
|
|
room.setStatus(RoomStatus.PLAYING);
|
|
|
|
|
gameLogic(room);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void cancelStartGame(long roomId, WebSocketSession session) {
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
var readyPlayers = room.getReadyPlayers();
|
|
|
|
|
readyPlayers.remove(session);
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// 发送消息通知其他玩家有人取消准备
|
|
|
|
|
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);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void handleInput(long roomId, WebSocketSession session, PlayerInput input) {
|
|
|
|
|
var message = new GameMessage<>(MessageType.INPUT, input);
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
var players = room.getPlayers();
|
2024-06-15 16:25:47 +00:00
|
|
|
|
|
|
|
|
|
if (input.getKey() == InputKeyType.SPACE) {
|
|
|
|
|
var player = players.get(session);
|
|
|
|
|
// 如果是空格,按下则记录,松开则获取记录时间并判断是否有1秒,有则对玩家发送清空答案的消息,并清空答案
|
|
|
|
|
if (input.getState() == InputState.PRESS)
|
|
|
|
|
player.setDropTime(System.currentTimeMillis());
|
|
|
|
|
else {
|
|
|
|
|
var dropTime = player.getDropTime();
|
|
|
|
|
if (System.currentTimeMillis() - dropTime >= 1000) {
|
|
|
|
|
player.clearCurrentAnswer();
|
|
|
|
|
sendMessage(session, new GameMessage<>(MessageType.LETTER, new LetterAction(LetterActionType.CLEAR)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果是移动,发送消息通知其他玩家有人输入了
|
|
|
|
|
for (var player : players.keySet())
|
|
|
|
|
if (player != session)
|
|
|
|
|
sendMessage(player, message);
|
|
|
|
|
}
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void createRoom(WebSocketSession session, String token) {
|
|
|
|
|
var user = authService.verify(token);
|
|
|
|
|
var player = new PlayerInGame(user.getId(), user);
|
|
|
|
|
var room = new GameRoom(session, player);
|
|
|
|
|
rooms.put(room.getRoomId(), room);
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// 发送消息通知玩家创建房间成功
|
|
|
|
|
sendMessage(session, new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.CREATE, room.getRoomId())));
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void removeRoom(long roomId) {
|
|
|
|
|
// 如果房间内还有玩家,全部移除
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
var players = room.getPlayers();
|
2024-06-15 16:07:06 +00:00
|
|
|
|
for (var player : players.entrySet()) {
|
|
|
|
|
// 发送消息通知所有玩家房间已解散
|
|
|
|
|
sendMessage(player.getKey(), new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.KICK, player.getValue().getUserId())));
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
rooms.remove(roomId);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-14 15:58:52 +00:00
|
|
|
|
@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 <T> 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-15 16:07:06 +00:00
|
|
|
|
@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滴血
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-14 10:51:41 +00:00
|
|
|
|
private void generateLetter(GameRoom room) {
|
|
|
|
|
var players = room.getPlayers();
|
|
|
|
|
var words = room.getWords();
|
|
|
|
|
|
|
|
|
|
// 如果words长度<=5,从数据库中再获取一批随机单词
|
|
|
|
|
if (words.size() <= 5) getWords(words);
|
|
|
|
|
|
|
|
|
|
Letter letter;
|
2024-06-15 16:07:06 +00:00
|
|
|
|
var id = UUID.randomUUID().getLeastSignificantBits();
|
2024-06-14 10:51:41 +00:00
|
|
|
|
|
|
|
|
|
var val = random.nextInt(100);
|
|
|
|
|
|
|
|
|
|
// 在80%概率当前单词的字母中随机选择一个,19%随机生成一个字母,1%是回血爱心。选定后随机赋值2f到6f的下落速度
|
|
|
|
|
if (val == 0)
|
|
|
|
|
// 回血爱心
|
2024-06-15 16:07:06 +00:00
|
|
|
|
letter = new Letter(id, "10", random.nextFloat(1), random.nextFloat(2, 6));
|
2024-06-14 10:52:39 +00:00
|
|
|
|
else if (val <= 81)
|
2024-06-14 10:51:41 +00:00
|
|
|
|
// 从当前单词中随机选择一个字母
|
2024-06-15 16:07:06 +00:00
|
|
|
|
letter = new Letter(id, words.keySet().toArray()[random.nextInt(words.size())].toString(), random.nextFloat(1), random.nextFloat(2, 6));
|
2024-06-14 10:51:41 +00:00
|
|
|
|
else
|
|
|
|
|
// 随机生成一个字母
|
2024-06-15 16:07:06 +00:00
|
|
|
|
letter = new Letter(id, String.valueOf((char) (random.nextInt(26) + 'a')), random.nextFloat(1), random.nextFloat(2, 6));
|
2024-06-14 10:51:41 +00:00
|
|
|
|
|
2024-06-15 16:07:06 +00:00
|
|
|
|
room.getLetters().put(id, letter);
|
2024-06-14 10:51:41 +00:00
|
|
|
|
// 给所有玩家发送字母
|
|
|
|
|
for (var player : players.keySet())
|
2024-06-15 16:07:06 +00:00
|
|
|
|
sendMessage(player, new GameMessage<>(MessageType.LETTER, new LetterAction(LetterActionType.CREATE, letter)));
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void getWords(Map<String, Integer> words) {
|
|
|
|
|
// TODO 从数据库中获取一批随机单词,然后放入words中,并把单词数组推送给所有玩家
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void gameLogic(GameRoom room) {
|
|
|
|
|
getWords(room.getWords());
|
2024-06-15 16:07:06 +00:00
|
|
|
|
// TODO 从300秒开始倒计时,每秒调用一次generateLetter方法,如果时间到了,调用endGame方法
|
|
|
|
|
// 如果当前单词被某玩家拼完,Map对应单词的value++,如果value>=玩家数(有可能中途有人退出),目前单词出队列,继续下一个单词
|
|
|
|
|
// 游戏结束后需要向玩家发送排行榜数据(用户ID、分数、排名)
|
|
|
|
|
// 最大的问题:丢包后如何处理?比如其他玩家按下按键后,自己没有接收到松开消息,导致其他玩家在画面中一直持续运动,网络连接稳定后,不同C端的玩家位置不一致
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|
2024-06-15 16:25:47 +00:00
|
|
|
|
|
|
|
|
|
public void endGame(long roomId) {
|
|
|
|
|
var room = rooms.get(roomId);
|
|
|
|
|
room.setStatus(RoomStatus.WAITING);
|
|
|
|
|
// 发送消息通知所有玩家游戏结束
|
|
|
|
|
var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.END));
|
|
|
|
|
for (var player : room.getPlayers().keySet())
|
|
|
|
|
sendMessage(player, message);
|
|
|
|
|
// TODO 初始化玩家数据
|
|
|
|
|
}
|
2024-06-14 10:51:41 +00:00
|
|
|
|
}
|