CatchTheLettersBackend/src/main/java/com/example/catchTheLetters/service/impl/RoomServiceImpl.java

307 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.catchTheLetters.service.impl;
import com.example.catchTheLetters.entity.*;
import com.example.catchTheLetters.enums.*;
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;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 房间服务实现类
*
* @author spyn
*/
@Service
public class RoomServiceImpl implements RoomService {
// 房间列表
private final ConcurrentHashMap<Long, GameRoom> rooms = new ConcurrentHashMap<>();
// 玩家列表
private final ConcurrentHashMap<String, WebSocketSession> playerInGame = new ConcurrentHashMap<>();
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);
// 发送消息通知其他玩家有人加入了
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();
var removed = players.remove(session);
// 如果是房主退出,更换房主,否则关闭房间
if (session == room.getHost()) {
if (!players.isEmpty())
room.setHost(players.keySet().iterator().next());
else
rooms.remove(roomId);
}
// 发送消息通知其他玩家有人退出了
var message = new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.LEAVE, removed.getUserId()));
for (var player : players.keySet())
if (player != session)
sendMessage(player, message);
}
@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();
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))
aimSession = player.getKey();
// 发送消息通知有玩家被踢出了
sendMessage(player.getKey(), message);
}
players.remove(aimSession);
}
@Override
public void startGame(long roomId, WebSocketSession session) {
var room = rooms.get(roomId);
var players = room.getPlayers();
var readyPlayers = room.getReadyPlayers();
readyPlayers.add(session);
// 发送消息通知其他玩家有人准备好了
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()) {
// 发送消息通知所有玩家游戏开始
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);
}
}
@Override
public void cancelStartGame(long roomId, WebSocketSession session) {
var room = rooms.get(roomId);
var readyPlayers = room.getReadyPlayers();
readyPlayers.remove(session);
// 发送消息通知其他玩家有人取消准备
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 handleInput(long roomId, WebSocketSession session, PlayerInput input) {
var message = new GameMessage<>(MessageType.INPUT, input);
var room = rooms.get(roomId);
var players = room.getPlayers();
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);
}
}
@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);
// 发送消息通知玩家创建房间成功
sendMessage(session, new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.CREATE, room.getRoomId())));
}
@Override
public void removeRoom(long roomId) {
// 如果房间内还有玩家,全部移除
var room = rooms.get(roomId);
var players = room.getPlayers();
for (var player : players.entrySet()) {
// 发送消息通知所有玩家房间已解散
sendMessage(player.getKey(), new GameMessage<>(MessageType.ROOM, new RoomAction(RoomActionType.KICK, player.getValue().getUserId())));
}
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 <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);
}
}
@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();
// 如果words长度<=5从数据库中再获取一批随机单词
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(id, "10", random.nextFloat(1), random.nextFloat(2, 6));
else if (val <= 81)
// 从当前单词中随机选择一个字母
letter = new Letter(id, words.keySet().toArray()[random.nextInt(words.size())].toString(), random.nextFloat(1), random.nextFloat(2, 6));
else
// 随机生成一个字母
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, new LetterAction(LetterActionType.CREATE, letter)));
}
private void getWords(Map<String, Integer> words) {
// TODO 从数据库中获取一批随机单词然后放入words中并把单词数组推送给所有玩家
}
private void gameLogic(GameRoom room) {
getWords(room.getWords());
// TODO 从300秒开始倒计时每秒调用一次generateLetter方法如果时间到了调用endGame方法
// 如果当前单词被某玩家拼完Map对应单词的value++如果value>=玩家数(有可能中途有人退出),目前单词出队列,继续下一个单词
// 游戏结束后需要向玩家发送排行榜数据用户ID、分数、排名
// 最大的问题丢包后如何处理比如其他玩家按下按键后自己没有接收到松开消息导致其他玩家在画面中一直持续运动网络连接稳定后不同C端的玩家位置不一致
}
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 初始化玩家数据
}
}