基于 Spring Boot 的井字棋游戏开发与实现

目录

引言

项目概述

项目搭建

1. 环境准备

2. 创建 Spring Boot 项目

3. 项目结构

代码实现

1. DemoApplication.java

2. TicTacToeController.java

3. pom.xml

电脑落子策略 - Minimax 算法

findBestMove 方法

minimax 方法

运行游戏

总结


引言

        在软件开发领域,通过构建简单的游戏项目来学习和实践新的技术框架是一种高效且有趣的方式。本文将详细介绍如何使用 Spring Boot 和 Maven 框架开发一个简单的井字棋(Tic - Tac - Toe)游戏。这个项目不仅能帮助你熟悉 Spring Boot 的基本使用,还能让你了解如何构建一个基于 RESTful 接口的交互式应用。

项目概述

        我们要开发的井字棋游戏是一个基于 Web 的应用,玩家可以通过浏览器或工具(如 Postman)发送请求来与电脑进行游戏。游戏规则遵循传统的井字棋规则,玩家和电脑轮流在 3x3 的棋盘上落子,先在横、竖、斜方向连成一线的一方获胜,如果棋盘填满且没有一方获胜,则为平局。

项目搭建

1. 环境准备

确保你已经安装了 Java 和 Maven。Java 是运行 Spring Boot 应用的基础,而 Maven 则用于管理项目的依赖和构建。

2. 创建 Spring Boot 项目

可以使用 Spring Initializr(https://start.spring.io/)来快速创建一个 Spring Boot 项目,选择以下配置:

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.7.5
  • Dependencies: Spring Web

3. 项目结构

项目的主要结构如下:

src
├── main
│   ├── java
│   │   └── com
│   │       └── example
│   │           └── demo
│   │               ├── DemoApplication.java
│   │               └── controller
│   │                   └── TicTacToeController.java
│   └── resources
│       └── application.properties
└── pom.xml

代码实现

1. DemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

这是 Spring Boot 的主应用类,包含 main 方法,用于启动 Spring Boot 应用。@SpringBootApplication 注解是一个组合注解,它包含了 @Configuration@EnableAutoConfiguration 和 @ComponentScan 注解,用于自动配置 Spring Boot 应用。

2. TicTacToeController.java

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@RestController
public class TicTacToeController {
    private static final int BOARD_SIZE = 3;
    private char[][] board = new char[BOARD_SIZE][BOARD_SIZE];
    private boolean isPlayerTurn = true;
    private boolean gameOver = false;
    private char winner = ' ';

    public TicTacToeController() {
        initializeBoard();
    }

    private void initializeBoard() {
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                board[i][j] = ' ';
            }
        }
        isPlayerTurn = true;
        gameOver = false;
        winner = ' ';
    }

    @GetMapping("/start")
    public String startGame() {
        initializeBoard();
        return "新游戏开始!你先手,使用 /move?row=x&col=y 落子(x 和 y 范围 0 - 2)。";
    }

    @GetMapping("/move")
    public String makeMove(@RequestParam int row, @RequestParam int col) {
        if (gameOver) {
            return "游戏已结束,请使用 /start 开始新游戏。";
        }
        if (!isPlayerTurn) {
            return "现在是电脑回合,请等待。";
        }
        if (!isValidMove(row, col)) {
            return "无效的落子位置,请重新选择。";
        }

        board[row][col] = 'X';
        checkGameStatus();
        if (!gameOver) {
            computerMove();
            checkGameStatus();
        }

        return getBoardStatus();
    }

    private boolean isValidMove(int row, int col) {
        return row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] == ' ';
    }

    private void computerMove() {
        List<int[]> availableMoves = getAvailableMoves();
        if (availableMoves.isEmpty()) {
            return;
        }

        int[] bestMove = findBestMove();
        board[bestMove[0]][bestMove[1]] = 'O';
        isPlayerTurn = true;
    }

    private List<int[]> getAvailableMoves() {
        List<int[]> moves = new ArrayList<>();
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                if (board[i][j] == ' ') {
                    moves.add(new int[]{i, j});
                }
            }
        }
        return moves;
    }

    private int[] findBestMove() {
        int bestScore = Integer.MIN_VALUE;
        int[] bestMove = null;

        for (int[] move : getAvailableMoves()) {
            board[move[0]][move[1]] = 'O';
            int score = minimax(board, 0, false);
            board[move[0]][move[1]] = ' ';

            if (score > bestScore) {
                bestScore = score;
                bestMove = move;
            }
        }
        return bestMove;
    }

    private int minimax(char[][] board, int depth, boolean isMaximizing) {
        if (hasWon('O')) {
            return 1;
        } else if (hasWon('X')) {
            return -1;
        } else if (getAvailableMoves().isEmpty()) {
            return 0;
        }

        if (isMaximizing) {
            int bestScore = Integer.MIN_VALUE;
            for (int[] move : getAvailableMoves()) {
                board[move[0]][move[1]] = 'O';
                int score = minimax(board, depth + 1, false);
                board[move[0]][move[1]] = ' ';
                bestScore = Math.max(score, bestScore);
            }
            return bestScore;
        } else {
            int bestScore = Integer.MAX_VALUE;
            for (int[] move : getAvailableMoves()) {
                board[move[0]][move[1]] = 'X';
                int score = minimax(board, depth + 1, true);
                board[move[0]][move[1]] = ' ';
                bestScore = Math.min(score, bestScore);
            }
            return bestScore;
        }
    }

    private boolean hasWon(char player) {
        // 检查行
        for (int i = 0; i < BOARD_SIZE; i++) {
            if (board[i][0] == player && board[i][0] == board[i][1] && board[i][1] == board[i][2]) {
                return true;
            }
        }
        // 检查列
        for (int j = 0; j < BOARD_SIZE; j++) {
            if (board[0][j] == player && board[0][j] == board[1][j] && board[1][j] == board[2][j]) {
                return true;
            }
        }
        // 检查对角线
        if (board[0][0] == player && board[0][0] == board[1][1] && board[1][1] == board[2][2]) {
            return true;
        }
        if (board[0][2] == player && board[0][2] == board[1][1] && board[1][1] == board[2][0]) {
            return true;
        }
        return false;
    }

    private void checkGameStatus() {
        if (hasWon('X')) {
            winner = 'X';
            gameOver = true;
            return;
        }
        if (hasWon('O')) {
            winner = 'O';
            gameOver = true;
            return;
        }
        // 检查平局
        if (getAvailableMoves().isEmpty()) {
            gameOver = true;
            winner = ' ';
        }
    }

    private String getBoardStatus() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < BOARD_SIZE; i++) {
            for (int j = 0; j < BOARD_SIZE; j++) {
                sb.append(board[i][j]);
                if (j < BOARD_SIZE - 1) {
                    sb.append(" | ");
                }
            }
            sb.append("\n");
            if (i < BOARD_SIZE - 1) {
                sb.append("---------\n");
            }
        }
        if (gameOver) {
            if (winner == 'X') {
                sb.append("你赢了!使用 /start 开始新游戏。");
            } else if (winner == 'O') {
                sb.append("电脑赢了!使用 /start 开始新游戏。");
            } else {
                sb.append("平局!使用 /start 开始新游戏。");
            }
        } else {
            sb.append("轮到你落子,使用 /move?row=x&col=y。");
        }
        return sb.toString();
    }
}

这是一个 RESTful 控制器,包含两个主要的端点:

  • /start:用于开始新游戏,调用 initializeBoard 方法初始化棋盘,并返回提示信息。
  • /move:用于玩家落子,接收玩家的落子位置,验证其有效性,更新棋盘状态,检查游戏是否结束,如果未结束则调用 computerMove 方法让电脑落子,最后返回当前棋盘状态。

3. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

        这是 Maven 项目的配置文件,包含了项目的依赖和构建配置。spring-boot-starter-web 依赖用于支持 Spring Web 开发,spring-boot-starter-test 依赖用于测试。

电脑落子策略 - Minimax 算法

        电脑落子使用了 Minimax 算法来寻找最优落子位置。Minimax 算法是一种递归算法,用于在零和博弈中寻找最优策略。在井字棋游戏中,电脑会模拟所有可能的落子情况,通过递归调用 minimax 方法计算每个落子的得分,然后选择得分最高的落子位置。

findBestMove 方法

        该方法遍历所有可用的落子位置,对每个位置调用 minimax 方法计算得分,最终选择得分最高的位置作为最佳落子位置。

minimax 方法

  • 如果电脑获胜,返回 1。
  • 如果玩家获胜,返回 -1。
  • 如果平局,返回 0。
  • 如果是电脑回合(isMaximizing 为 true),选择得分最高的落子。
  • 如果是玩家回合(isMaximizing 为 false),选择得分最低的落子。

运行游戏

  1. 打开终端,进入项目根目录,运行以下命令启动应用:
mvn spring-boot:run
  1. 打开浏览器或使用工具(如 Postman)访问以下 URL 来玩游戏:
  2. 开始游戏:http://localhost:8080/start
  3. 落子:http://localhost:8080/move?row=0&col=0(将 0 替换为你想要的落子位置)

总结

通过这个项目,我们学习了如何使用 Spring Boot 和 Maven 框架开发一个简单的井字棋游戏。同时,我们还了解了 Minimax 算法在游戏开发中的应用,通过该算法可以让电脑做出更智能的决策。这个项目不仅可以作为学习 Spring Boot 的入门示例,还可以进一步扩展,例如添加用户界面、支持多人游戏等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

禹曦a

你的鼓励就是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值