前后端交互+实现ai对战

目的:

  1. 后端:提供更新棋盘状态的接口(控制器方法和映射路径),用于处理落子请求。具体:接收玩家的落子坐标(如行列值),返回更新的棋盘状态:

  2. 前端:添加点击事件与异步请求。具体:1)在每个棋盘格子上绑定点击事件;2)使用fetch发送POST请求到后端接口,传递落子信息;3)更新页面上的棋盘状态。

  3. 加分:利用异步交互添加更多功能,例如实现胜负判定、添加游戏重置功能、实现简单 AI对战

在这里,首先我先尝试前后端分离实验2中所写的代码

分离之后的前端页面是静态页面,于是我先将前端代码放入了static文件夹下,命名为FiveChess.html,在controller控制器中重新写了一个后端代码,实现对前端落子请求的处理,返回页面更新的网盘状态。

分离之后的前端页面是静态页面,于是我先将前端代码放入了static文件夹下,命名为FiveChess.html,在controller控制器中重新写了一个后端代码,实现对前端落子请求的处理,返回页面更新的网盘状态。

前端代码设计如下所示:

  • 定义变量

    	const boardElement = document.getElementById('board');
        const newGameButton = document.getElementById('newGame');
        const statusText = document.getElementById('status');
    
  • 绘制棋盘

    • 清空棋盘容器的内容。
    • 使用嵌套的 for 循环创建 19x19 个单元格元素。
    • 为每个单元格添加 cell 类名,并设置 data-rowdata-col 属性,用于记录单元格的行和列信息。
    • 为每个单元格添加点击事件监听器,点击时调用 handleCellClick 函数。
    • 将单元格元素添加到棋盘容器中。
    function drawBoard() {
         
            boardElement.innerHTML = '';
            for (let i = 0; i < BOARD_SIZE; i++) {
         
                for (let j = 0; j < BOARD_SIZE; j++) {
         
                    const cell = document.createElement('div');
                    cell.classList.add('cell');
                    cell.dataset.row = i;
                    cell.dataset.col = j;
                    cell.addEventListener('click', handleCellClick);
                    boardElement.appendChild(cell);
                }
            }
        }
    
  • 处理单元格点击事件

    async function handleCellClick(event) {
         
            const row = parseInt(event.target.dataset.row);
            const col = parseInt(event.target.dataset.col);
    
            if (gameBoard[row][col] === 0) {
         
                try {
         
                    const response = await fetch('/api/wuziqi/move', {
         
                        method: 'POST',
                        headers: {
         
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({
          row, col })
                    });
                    const data = await response.json();
                    gameBoard[row][col] = currentPlayer;
    
                    const stone = document.createElement('div');
                    stone.classList.add('stone', currentPlayer === 1 ? 'black' : 'white');
                    event.target.appendChild(stone);
    
                    if (data.win) {
         
                        statusText.textContent = `${
           currentPlayer === 1 ? '黑棋' : '白棋'} 获胜!`;
                        disableBoard();
                    } else if (data.draw) {
         
                        statusText.textContent = '平局!';
                    } else {
         
                        currentPlayer = currentPlayer === 1 ? 2 : 1;
                        statusText.textContent = `当前玩家: ${
           currentPlayer === 1 ? '黑棋' : '白棋'}`;
                    }
                } catch (error) {
         
                    console.error('Error:', error);
                }
            }
        }
    
  • 开始新游戏,重置棋盘

    • 使用 fetch API 向服务器发送 POST 请求,请求开始新游戏。
    • 将当前玩家重置为黑棋,更新游戏状态显示。
    • 调用 drawBoard 函数重新绘制棋盘。
    async function resetGame() {
         
            try {
         
                await fetch('/api/wuziqi/new-game', {
          method: 'POST' });
                gameBoard = Array.from({
          length: BOARD_SIZE }, () => Array(BOARD_SIZE).fill(0));
                currentPlayer = 1;
                statusText.textContent = '当前玩家: 黑棋';
                drawBoard();
            } catch (error) {
         
                console.error('Error:', error);
            }
        }
    
    // 监听新游戏按钮点击事件
        newGameButton.addEventListener('click', resetGame);
        drawBoard();
    
  • 禁用棋盘

    • 获取所有棋盘单元格元素。
    • 移除每个单元格的点击事件监听器,禁用棋盘交互。
    function disableBoard() {
         
            const cells = document.querySelectorAll('.cell');
            cells.forEach(cell => cell.removeEventListener('click', handleCellClick));
        }
    

后端代码如下所示:

package org.example.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/wuziqi")
public class WuziqiApiController {
   

    private static final int BOARD_SIZE = 19;
    private int[][] gameBoard = new int[BOARD_SIZE][BOARD_SIZE];
    private int currentPlayer = 1; // 1 表示黑棋,2 表示白棋

    @PostMapping("/move")
    public ResponseEntity<MoveResponse> move(@RequestBody MoveRequest request) {
   
        int row = request.getRow();
        int col = request.getCol();

        if (gameBoard[row][col] == 0) {
   
            gameBoard[row][col] = currentPlayer;
            boolean win = checkWin(row, col, currentPlayer);
            boolean draw = isBoardFull();
            currentPlayer = currentPlayer == 1 ? 2 : 1;
            return ResponseEntity.ok(new MoveResponse(win, draw));
        } else {
   
            return ResponseEntity.badRequest().body(null);
        }
    }

    @PostMapping("/new-game")
    public ResponseEntity<Void> newGame() {
   
        gameBoard = new int[BOARD_SIZE][BOARD_SIZE];
        currentPlayer = 1;
        return ResponseEntity.ok().build();
    }

    private boolean checkWin(int row, int col, int player) {
   
        return checkLine(row, col, player, 1, 0) || // 水平
                checkLine(row, col, player, 0, 1) || // 垂直
                checkLine(row, col, player, 1, 1) || // 主对角线
                checkLine(row, col, player, 1, -1); // 副对角线
    }

    private boolean checkLine(int row, int col, int player, int dRow, int dCol) {
   
        int count = 1;
        for (int i = 1; i < 5; i++) {
   
            if (getCellValue(row + i * dRow, col + i * dCol) != player) break;
            count++;
        }
        for (int i = 1; i < 5; i++) {
   
            if (getCellValue(row - i * dRow, col - i * dCol) != player) break;
            count++;
        }
        return count >= 5;
    }

    private int getCellValue(int row, int col) {
   
        if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) return 0; // 超出边界视为空
        return gameBoard[row][col];
    }

    private boolean isBoardFull() {
   
        for (int[] row : gameBoard) {
   
            for (int cell : row) {
   
                if (cell == 0) return false;
            }
        }
        return true;
    }

    static class MoveRequest {
   
        private int row;
        private int col;

        public int getRow() {
   
            return row;
        }

        public void setRow(int row) {
   
            this.row = row;
        }

        public int getCol() {
   
            return col;
        }

        public void setCol(int col) {
   
            this.col = col;
        }
    }

    static class MoveResponse {
   
        private boolean win;
        private boolean draw;

        public MoveResponse(boolean win, boolean draw) {
   
            this.win = win;
            this.draw = draw;
        }

        public boolean isWin() {
   
            return win;
        }

        public void setWin(boolean win) {
   
            this.win = win;
        }

        public boolean isDraw() {
   
            return draw;
        }

        public void setDraw(boolean draw) {
   
            this.draw = draw;
        }
    }
}

项目结构如图所示:

在这里插入图片描述

由于是静态页面,所以访问时直接访问locaohost:8001/FiveChess3.html,访问页面如下:

在这里插入图片描述

这里前后端进行了简单的交互。为了方便后续操作,现在将前端代码移入templates文件夹下。新建一个WuziaiPageController.java类进行重定向,代码如下所示:

package org.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class WuziqiPageController {
   

    @GetMapping("/fivechess3")
    public String showGame(Model model) {
   
        model.addAttribute("title", "五子棋");
        return "FiveChess3"; // 返回视图名,对应 templates 目录下的 FiveChess3.html
    }
}

项目结构如图所示:

在这里插入图片描述

现在运行项目,访问地址localhost:8001/fivechess3,可以访问到五子棋页面,后端也能接收到前端的POST请求,实时更新棋盘状态,并能实现获胜判定和对游戏的重置。

在这里插入图片描述

接下来尝试实现ai对战功能:

前端部分代码解释:

  • 处理单元格点击事件

    • 获取点击单元格的行和列信息。
    • 检查该单元格是否为空(即是否没有棋子),如果为空,则调用 makeMove 函数进行落子操作。
    // 处理单元格点击事件
        async function handleCellClick(event) {
         
            const row = parseInt(event.target.dataset.row);
            const col = parseInt(event.target.dataset.col);
    
            if (!event.target.querySelector('.stone')) {
          // 只允许在空位落子
                try {
         
                    await makeMove(row, col);
                } catch (error) {
         
                    console.error('Error:', error);
                }
            }
        }
    
  • 发送请求到服务器

    • 使用 fetch API 向服务器发送 POST 请求,包含落子的行和列信息。
    • 解析服务器返回的 JSON 数据。
    • 如果数据存在,调用 placeStone 函数在指定位置放置棋子。
    • 根据服务器返回的结果判断是否有玩家获胜或平局,更新游戏状态显示。
    • 如果对战 AI 复选框被选中,切换到 AI 玩家,调用 makeAIMove 函数让 AI 下棋。
    • 如果没有选中,切换当前玩家,更新游戏状态显示。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值