异常处理
手动注入对象

当前发现此处有一个空指针异常问题,我们怀疑 onlineUserManager
是空的
- 这个
OnlineUserManager
是我们之前通过Autowired
注入进来的对象 - 并且不能给
Room
注释@Component
,如果这么写,这就成了单例,但很明显Room
不是单例,是多例(有多个实例,有很多房间,每一个对局都是一个房间)
当前 Room
不能设为 Spring
组件,但是我们有需要拿到 OnlineUserManager
和 RoomManager
,我们就需要通过手动处理的方式来获取到实例
在入口类中记录
java
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
java
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 的落子
- 检查胜负的时候,以当前落子位置为中心,检查所有相关的行、列、对角线即可,不必遍历整个棋盘
java
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
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
<?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
方法,在销毁房间前,进行更新操作
java
// 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
java
// 通知对手获胜了
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
部分代码进行修改
java
@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();
}
}