[网页五子棋][匹配模块]实现胜负判定,处理玩家掉线

异常处理

手动注入对象

当前发现此处有一个空指针异常问题,我们怀疑 onlineUserManager 是空的

  • 这个 OnlineUserManager 是我们之前通过 Autowired 注入进来的对象
  • 并且不能给 Room 注释 @Component,如果这么写,这就成了单例,但很明显 Room 不是单例,是多例(有多个实例,有很多房间,每一个对局都是一个房间)

当前 Room 不能设为 Spring 组件,但是我们有需要拿到 OnlineUserManagerRoomManager,我们就需要通过手动处理的方式来获取到实例

在入口类中记录

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 来手动获取到 RoomManagerOnlineUserManager

java 复制代码
public Room() {  
      
    onlineUserManager = JavaGobangApplication.context.getBean(OnlineUserManager.class);  
    roomManager = JavaGobangApplication.context.getBean(RoomManager.class);  
}

实现胜负判定

实现胜负判定:判定棋面上是否出现物资连珠

  • 一行、一列、一个对角线...

如果棋盘上出现了五子连珠,那就一定是和这个新落子的位置是相关的

  • 进行判定的时候,不需判定整个棋盘
  • 只需要以 rowcol 这个位置为中心,判定周围若干个格子即可

先以一行为例来,考虑判定过程

  • 一共有有五种可能性
  • 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
    • 第一种情况:r = rowc = col - 4
    • 第二种情况:r = rowc = col - 3
    • 第三种情况:r = rowc = col - 2
    • 第四种情况:r = rowc = col - 1
    • 第五种情况:r = rowc = col

  • 一共有有五种可能性
  • 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
    • 第一种情况:r = row - 4c
    • 第二种情况:r = row - 3c
    • 第三种情况:r = row - 2c
    • 第四种情况:r = row - 1c
    • 第五种情况:r = rowc

右对角线

  • 一共有有五种可能性
  • 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
    • 第一种情况:r = row - 4c = c + 4
    • 第二种情况:r = row - 3c = c + 3
    • 第三种情况:r = row - 2c = c + 2
    • 第四种情况:r = row - 1c = c + 1
    • 第五种情况:r = rowc

左对角线

  • 一共有有五种可能性
  • 我们观察最左边第一个点,它的运动范围为:(row, col) 这个位置是玩家这次落子的位置
    • 第一种情况:r = rowc = col
    • 第二种情况:r = row + 1c = col + 1
    • 第三种情况:r = row + 2c = col + 2
    • 第四种情况:r = row + 3c = col + 3
    • 第五种情况:r = row + 4c = 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 中的 handleTransportErrorafterConnectionClosed 方法

  • 在这两个方法末尾,都调用一个新的方法 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();  
    }  
}
相关推荐
硅的褶皱1 小时前
对比分析LinkedBlockingQueue和SynchronousQueue
java·并发编程
MoFe11 小时前
【.net core】天地图坐标转换为高德地图坐标(WGS84 坐标转 GCJ02 坐标)
java·前端·.netcore
季鸢2 小时前
Java设计模式之观察者模式详解
java·观察者模式·设计模式
Fanxt_Ja2 小时前
【JVM】三色标记法原理
java·开发语言·jvm·算法
萌新小码农‍2 小时前
Spring框架学习day7--SpringWeb学习(概念与搭建配置)
学习·spring·状态模式
蓝婷儿2 小时前
6个月Python学习计划 Day 15 - 函数式编程、高阶函数、生成器/迭代器
开发语言·python·学习
love530love2 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
Mr Aokey2 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
slandarer3 小时前
MATLAB | 绘图复刻(十九)| 轻松拿捏 Nature Communications 绘图
开发语言·matlab
狐凄3 小时前
Python实例题:Python计算二元二次方程组
开发语言·python