目的:
-
后端:提供更新棋盘状态的接口(控制器方法和映射路径),用于处理落子请求。具体:接收玩家的落子坐标(如行列值),返回更新的棋盘状态:
-
前端:添加点击事件与异步请求。具体:1)在每个棋盘格子上绑定点击事件;2)使用fetch发送POST请求到后端接口,传递落子信息;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-row
和data-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 下棋。 - 如果没有选中,切换当前玩家,更新游戏状态显示。
- 使用