[网页五子棋][匹配模块]实现胜负判定,处理玩家掉线
异常处理
手动注入对象
当前发现此处有一个空指针异常问题,我们怀疑 onlineUserManager
是空的
- 这个
OnlineUserManager
是我们之前通过Autowired
注入进来的对象 - 并且不能给
Room
注释@Component
,如果这么写,这就成了单例,但很明显Room
不是单例,是多例(有多个实例,有很多房间,每一个对局都是一个房间)
当前 Room
不能设为 Spring
组件,但是我们有需要拿到 OnlineUserManager
和 RoomManager
,我们就需要通过手动处理的方式来获取到实例
在入口类中记录
package org.example.java_gobang; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext; @SpringBootApplication
public class JavaGobangApplication { public static ConfigurableApplicationContext context; public static void main(String[] args) { context = SpringApplication.run(JavaGobangApplication.class, args); } }
完善 Room 类
我们在 Room
类中的构造方法里面,通过入口类中记录的 context
来手动获取到 RoomManager
和 OnlineUserManager
public Room() { onlineUserManager = JavaGobangApplication.context.getBean(OnlineUserManager.class); roomManager = JavaGobangApplication.context.getBean(RoomManager.class);
}
实现胜负判定
实现胜负判定:判定棋面上是否出现物资连珠
- 一行、一列、一个对角线…
如果棋盘上出现了五子连珠,那就一定是和这个新落子的位置是相关的
- 进行判定的时候,不需判定整个棋盘
- 只需要以
row
,col
这个位置为中心,判定周围若干个格子即可
行
先以一行为例来,考虑判定过程
- 一共有有五种可能性
- 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
- 第一种情况:
r = row
,c = col - 4
- 第二种情况:
r = row
,c = col - 3
- 第三种情况:
r = row
,c = col - 2
- 第四种情况:
r = row
,c = col - 1
- 第五种情况:
r = row
,c = col
- 第一种情况:
列
- 一共有有五种可能性
- 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
- 第一种情况:
r = row - 4
,c
- 第二种情况:
r = row - 3
,c
- 第三种情况:
r = row - 2
,c
- 第四种情况:
r = row - 1
,c
- 第五种情况:
r = row
,c
- 第一种情况:
右对角线
- 一共有有五种可能性
- 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
- 第一种情况:
r = row - 4
,c = c + 4
- 第二种情况:
r = row - 3
,c = c + 3
- 第三种情况:
r = row - 2
,c = c + 2
- 第四种情况:
r = row - 1
,c = c + 1
- 第五种情况:
r = row
,c
- 第一种情况:
左对角线
- 一共有有五种可能性
- 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
- 第一种情况:
r = row
,c = col
- 第二种情况:
r = row + 1
,c = col + 1
- 第三种情况:
r = row + 2
,c = col + 2
- 第四种情况:
r = row + 3
,c = col + 3
- 第五种情况:
r = row + 4
,c = col + 4
- 第一种情况:
完整代码
- 如果游戏分出胜负,则返回玩家的 id;如果未分出胜负,则返回 0
- 棋盘中值为 1,表示是玩家 1 的落子;值为 2,表示是玩家 2 的落子
- 检查胜负的时候,以当前落子位置为中心,检查所有相关的行、列、对角线即可,不必遍历整个棋盘
private int checkWinner(int row, int col, int chess) { // 1. 检查所有的行 // 先遍历这五种情况 for (int c = col - 4; c <= col; c++) { // 针对其中的一种情况,来判定中这五个子是不是连在一起了 // 不管这个五个子得连着,而且还得和玩家落的子是一样的 try { if (board[row][c] == chess && board[row][c + 1] == chess && board[row][c + 2] == chess && board[row][c + 3] == chess && board[row][c + 4] == chess) { // 构成了五子连珠,胜负已分! return chess == 1 ? user1.getUserId() : getUser2().getUserId(); } } catch (ArrayIndexOutOfBoundsException e) { // 如果出现数组下标越界的情况,就在这里直接忽略这个异常 continue; } } // 2. 检查所有的列 // 先遍历五种情况 for (int r = row - 4; r <= row; r++) { try { if (board[r][col] == chess && board[r + 1][col] == chess && board[r + 2][col] == chess && board[r + 3][col] == chess && board[r + 4][col] == chess) { // 构成了五子连珠,胜负已分! return chess == 1 ? user1.getUserId() : user2.getUserId(); } } catch (ArrayIndexOutOfBoundsException e) { // 如果出现数组下标越界的情况,就在这里直接忽略这个异常 continue; } } // 3. 检查右对角线 for (int r = row - 4, c = col + 4; r <= row && c >= col ; r++, c--) { try { if(board[r][c] == chess && board[r + 1][c - 1] == chess && board[r + 2][c - 2] == chess && board[r + 3][c - 3] == chess && board[r + 4][c - 4] == chess) { //构成五子连珠,胜负已分! return chess == 1 ? user1.getUserId() : user2.getUserId(); } }catch (ArrayIndexOutOfBoundsException e) { // 如果出现数组下标越界的情况,就在这里直接忽略这个异常 continue; } } // 4. 检查左对角线 for (int r = row = 4, c = col - 4; r <= row && c <= col ; r++, c++) { try { if (board[r][c] == chess && board[r + 1][c + 1] == chess && board[r + 2][c + 2] == chess && board[r + 3][c + 3] == chess && board[r + 4][c + 4] == chess) { //构成五子连珠,胜负已分! return chess == 1 ? user1.getUserId() : user2.getUserId(); } }catch (ArrayIndexOutOfBoundsException e) { // 如果出现数组下标越界的情况,就在这里直接忽略这个异常 continue; } // 胜负未分,就直接返回 0 了 return 0; }
相关问题
1. 更新玩家分数和场次
当前玩家比赛完成之后,胜负场数和分数都没有发生改变
- 这里就需要修改数据库
修改 userMapper.java
import org.apache.ibatis.annotations.Mapper; /** * 接口里面创建一些典型的方法 */
@Mapper
public interface UserMapper { // 往数据库中插入一个用户,用于注册功能 void insert(User user); // 根据用户名,来查询用户的详细信息,用于登录功能 User selectByName(String userName); // 总比赛场数 +1,获胜场数 +1,天梯分数 +30 void userWin(int userId); // 总比赛场数 +1,获胜场数 不变,天梯分数 -30 void userLose(int userId);
}
- 加入两个更新数据的方法
修改 `userMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.java_gobang.model.UserMapper"> <insert id="insert"> insert into user values(null, #{username}, #{password}, 1000, 0, 0); </insert> <select id="selectByName" resultType="org.example.java_gobang.model.User"> select * from user where username = #{username}; </select> <update id="userWin"> update user set totalCount = totalCount + 1, winCount = winCount + 1, score = score + 30 where userId = #{userId} </update> <update id="userLose"> update user set totalCount = totalCount + 1, scroe = score - 30 where userId = #{userId} </update>
</mapper>
- 增加两个更新操作
最后找到 Room
类中的 putChess
方法,在销毁房间前,进行更新操作
// 5. 如果胜负已分,就把 room 从房间管理器中销毁
if (response.getWinner() != 0) { System.out.println("游戏结束!房间即将销毁! roomId=" + roomId + " 获胜方为:" + response.getWinner()); // 更新获胜方和失败方的信息 int winUserId = response.getWinner(); int loseUserId = response.getWinner() == user1.getUserId() ? user2.getUserId() : user1.getUserId(); userMapper.userWin(winUserId); userMapper.userLose(loseUserId); // 销毁房间 roomManager.remove(roomId, user1.getUserId(), user2.getUserId());
}
2. 玩家掉线
在当前玩家出现掉线的情况,就需要通知对手:你自动获胜
之前的代码中,对与掉线的情况,是进行过检测的
- 这个不完备,还需要做更及时,更完备的判定 (一方掉线,另一方立即获胜)
我们找到 gameAPI
中的 handleTransportError
和 afterConnectionClosed
方法
- 在这两个方法末尾,都调用一个新的方法
noticeThatUserWin
// 通知对手获胜了
private void noticeThatUserWin(User user) throws IOException { // 1. 根据当前玩家,找到玩家所在的房间 Room room = roomManager.getRoomByUserId(user.getUserId()); if (room == null) { // 这个情况,意味着房间已经被释放了,也就没有“对手”了 System.out.println("当前房间已经释放,无需通知对手!"); return; } // 2. 根据房间找到对手 User thatUser = (user == room.getUser1()) ? room.getUser2() : room.getUser1(); // 3. 找到对手的在线状态 WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thatUser.getUserId()); if (webSocketSession == null) { // 这就意味着对手也掉线了 System.out.println("对手也已经掉线了,无需通知! "); return; } // 4. 构造一个响应,来通知对手,你是获胜方 GameResponse resp = new GameResponse(); resp.setMessage("putChess"); resp.setUserId(thatUser.getUserId()); resp.setWinner(thatUser.getUserId()); webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp))); // 5. 更新玩家的分数信息 int winUserId = thatUser.getUserId(); int loseUserId = user.getUserId(); userMapper.userWin(winUserId); userMapper.userLose(loseUserId); // 6. 释放房间对象 roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());
}
将 userAPI
中的 login
部分代码进行修改
@GetMapping("/userInfo")
@ResponseBody
public Object getUserInfo(HttpServletRequest req) { try { HttpSession httpSession = req.getSession(false); User user = (User) httpSession.getAttribute("user"); // 拿着这个 user 对象,去数据库中找,找到最新的数据 User newUser = userMapper.selectByName(user.getUsername()); return newUser; }catch (NullPointerException e) { return new User(); }
}