讲师中心 微信公众号
AI工具推荐 视频效率加速

Java Stream辅助实现井字棋胜利判断:策略与实践

风涛小哥_9570

风涛小哥_9570

发布时间:2025-11-21 16:50:02

|

961人浏览过

|

来源于php中文网

原创

Java Stream辅助实现井字棋胜利判断:策略与实践

本文探讨了在井字棋游戏中,如何利用java stream辅助判断胜利条件。虽然纯粹的stream解决方案难以应对复杂的空间逻辑,但通过结合命令式编程和stream的`anymatch`方法,可以高效地检查特定位置的横向、纵向和对角线胜利组合,从而实现一个实用且部分功能化的胜利判断机制。

Java Stream在井字棋胜利判断中的挑战

在井字棋这类需要检查特定空间排列(如横、竖、对角线)的游戏中,仅凭Java Stream进行纯粹的胜利条件判断具有固有挑战。最初的尝试,例如简单地检查集合中是否有某个元素出现3次,并不能准确反映胜利条件。井字棋的胜利并非基于元素数量,而是基于它们在棋盘上的特定线性排列。例如,即使棋盘上有三个相同的标记,如果它们不构成一条直线,则不构成胜利。

纯粹的Stream操作擅长于对数据集合进行过滤、映射和归约等转换,但对于需要感知元素“位置”和“相邻关系”的复杂二维或多维空间逻辑,Stream的表达能力会受到限制。直接使用Stream来遍历所有可能的胜利线(如所有行、列和对角线),并检查它们是否被同一玩家占据,会导致代码复杂且难以维护,甚至可能效率低下。因此,对于此类问题,结合命令式编程的结构化控制流与Stream的函数式特性,往往能达到更好的平衡。

混合式方法:结合命令式与函数式编程

鉴于纯粹的Stream解决方案的局限性,一种更为实用和高效的方法是采用混合式编程风格。这意味着使用命令式代码来管理棋盘状态和进行基本的坐标计算,同时利用Java Stream的anyMatch等操作来优雅地检查局部条件。

核心思想是:当玩家下了一步棋后,我们只需要检查围绕这个最新落子点的四种可能胜利方向(水平、垂直、主对角线、副对角线)。如果其中任何一个方向形成了三连子,则当前玩家获胜。

立即学习“Java免费学习笔记(深入)”;

为了实现这一逻辑,我们可以定义一个静态数组来表示相对于中心点的“邻居”偏移量,这些偏移量共同构成了一条潜在的胜利线。

OpenJDK 24
OpenJDK 24

OpenJDK 24 Windows x64 官方参考实现(24+36),适合 Windows 环境进行 Java 开发、编译、运行和版本兼容性验证。

下载
public class TicTacToe {
    // 定义表示四种胜利方向的邻居偏移量
    // 每个内部数组代表一个方向,包含两个偏移量,分别指向中心点的左右/上下/对角线两端
    public static final int[][][] NEIGHBOURS = {
            {{0, -1}, {0, 1}},   // 水平方向:左 (-1) 和 右 (+1)
            {{-1, 0}, {1, 0}},   // 垂直方向:上 (-1) 和 下 (+1)
            {{-1, -1}, {1, 1}},  // 主对角线:左上 (-1,-1) 和 右下 (+1,+1)
            {{1, -1}, {-1, 1}}   // 副对角线:右上 (+1,-1) 和 左下 (-1,+1)
    };

    // 棋盘表示,使用List<List<String>>,内部列表通过Arrays.asList创建以支持元素修改
    private List<List<String>> board = List.of(
        Arrays.asList("1", "4", "7"), // 示例棋盘初始值
        Arrays.asList("2", "5", "8"),
        Arrays.asList("3", "6", "9")
    );

    /**
     * 获取棋盘上指定坐标的值,并进行边界检查。
     *
     * @param row 行索引
     * @param col 列索引
     * @return 对应坐标的值,如果索引无效则返回 "INVALID INDEX"
     */
    public String getBoardValue(int row, int col) {
        if (row < 0 || row >= board.size() || col < 0 || col >= board.get(row).size()) {
            return "INVALID INDEX"; // 返回一个非法值,确保后续比较不会误判
        }
        return board.get(row).get(col);
    }

    /**
     * 判断在给定行和列的最新落子是否形成胜利组合。
     *
     * @param row 最新落子的行
     * @param col 最新落子的列
     * @return 如果形成胜利组合则返回 true,否则返回 false
     */
    public boolean isWinningMove(int row, int col) {
        // 使用Stream.anyMatch() 检查NEIGHBOURS中是否存在任何一个胜利组合
        return Arrays.stream(NEIGHBOURS)
                     .anyMatch(neighbour -> isWinningCombination(row, col, neighbour));
    }

    /**
     * 辅助方法:检查一个特定方向(由neighbour数组定义)是否形成胜利组合。
     *
     * @param row         最新落子的行
     * @param col         最新落子的列
     * @param neighbour   表示一个方向的两个偏移量(如水平方向的左右两端)
     * @return 如果该方向形成胜利组合则返回 true,否则返回 false
     */
    private boolean isWinningCombination(int row, int col, int[][] neighbour) {
        int[] leftShift = neighbour[0];  // 第一个偏移量
        int[] rightShift = neighbour[1]; // 第二个偏移量
        String currentPlayer = getBoardValue(row, col); // 获取当前落子玩家的标记

        // 检查中心点、左侧邻居和右侧邻居是否都与当前玩家标记相同
        return currentPlayer.equals(getBoardValue(row + leftShift[0], col + leftShift[1])) &&
               currentPlayer.equals(getBoardValue(row + rightShift[0], col + rightShift[1]));
    }

    // 示例:更新棋盘值(模拟玩家落子)
    public void setBoardValue(int row, int col, String playerMark) {
        if (row >= 0 && row < board.size() && col >= 0 && col < board.get(row).size()) {
            board.get(row).set(col, playerMark);
        }
    }

    // 示例:打印棋盘
    public void printBoard() {
        for (List<String> row : board) {
            System.out.println(String.join(" | ", row));
            if (row != board.get(board.size() - 1)) {
                System.out.println("---------");
            }
        }
        System.out.println("\n");
    }

    public static void main(String[] args) {
        TicTacToe game = new TicTacToe();
        System.out.println("初始棋盘:");
        game.printBoard();

        // 模拟玩家X在中心位置落子 (1, 1)
        game.setBoardValue(1, 1, "X");
        System.out.println("玩家X在(1,1)落子后:");
        game.printBoard();
        System.out.println("玩家X是否获胜? " + game.isWinningMove(1, 1)); // 此时不获胜

        // 模拟玩家X在(0,0)落子
        game.setBoardValue(0, 0, "X");
        System.out.println("玩家X在(0,0)落子后:");
        game.printBoard();
        System.out.println("玩家X是否获胜? " + game.isWinningMove(0, 0)); // 此时不获胜

        // 模拟玩家X在(2,2)落子,形成主对角线胜利
        game.setBoardValue(2, 2, "X");
        System.out.println("玩家X在(2,2)落子后:");
        game.printBoard();
        System.out.println("玩家X是否获胜? " + game.isWinningMove(2, 2)); // 此时获胜 (X, X, X) 对角线
    }
}

代码解析:

  1. NEIGHBOURS 常量:

    • 这是一个三维数组,巧妙地定义了四种可能的胜利方向。
    • 每个二维子数组(例如 {{0, -1}, {0, 1}})代表一个方向。
    • 内部的两个一维数组(例如 [0, -1] 和 [0, 1])是相对于中心点 (row, col) 的偏移量,它们分别指向该方向上的两个相邻格子。
    • 这种结构使得我们可以通过一个中心点和两个偏移量来检查一条线上是否有三个相同的标记。
  2. getBoardValue(int row, int col) 方法:

    • 负责安全地获取棋盘上指定坐标的值。
    • 包含边界检查,防止 IndexOutOfBoundsException。
    • 当坐标无效时,返回一个特殊字符串 "INVALID INDEX"。这很重要,因为它确保了 isWinningCombination 方法在比较时不会因为越界而抛出异常,并且 "INVALID INDEX" 永远不会与任何玩家的标记相等,从而避免误判。
  3. isWinningMove(int row, int col) 方法:

    • 这是入口点,用于检查最新落子 (row, col) 是否导致胜利。
    • 它利用 Arrays.stream(NEIGHBOURS) 将所有预定义的胜利方向转换为一个Stream。
    • 然后,使用 anyMatch 方法,如果Stream中的任何一个方向(由 neighbour 数组表示)满足胜利条件,则 anyMatch 会立即返回 true,停止进一步的检查。
  4. isWinningCombination(int row, int col, int[][] neighbour) 方法:

    • 这是一个私有辅助方法,实现了具体的胜利条件检查逻辑。
    • 它接收最新落子的坐标 (row, col) 和一个表示特定方向的 neighbour 数组。
    • 它首先获取当前落子玩家的标记 (currentPlayer)。
    • 然后,根据 leftShift 和 rightShift 计算出该方向上两个邻居的坐标,并获取它们的值。
    • 最后,通过 equals 方法比较 currentPlayer 是否与这两个邻居的值都相同。

注意事项与总结

  • 混合式编程的必要性: 此方案并非纯粹的函数式编程,而是结合了命令式逻辑(如棋盘状态管理、坐标计算)和函数式特性(如 Stream.anyMatch)。这在处理复杂游戏逻辑时是一种常见的、务实的做法。
  • 局部性检查: 优化在于只检查与最新落子相关的潜在胜利线,而不是遍历整个棋盘的所有可能胜利组合。这大大提高了效率。
  • 边界条件处理: getBoardValue 方法中的边界检查至关重要,它保证了程序的健壮性,避免了因访问越界而导致的运行时错误。
  • 棋盘表示: 示例中 List<List<String>> 的棋盘表示,使用 Arrays.asList() 创建内部列表是为了允许通过 set() 方法修改元素,但外部 List.of() 保持了外层列表结构的不可变性。在实际应用中,可能需要一个完全可变的二维数组或自定义的棋盘类。

总之,尽管Java Stream在处理井字棋这类具有强空间逻辑的游戏胜利条件时存在局限,但通过巧妙地结合命令式编程来管理状态和坐标,并利用Stream的anyMatch等功能进行条件判断,我们仍然可以构建出清晰、高效且部分功能化的解决方案。这种混合式方法充分发挥了两种编程范式的优势,是解决复杂问题的有效策略。

热门AI工具

更多
立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

切问学术

切问学术是一款AI论文写作工具,复旦大学NLP团队推出的AI学术智能体。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

6159

2023.08.02

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6024

2023.10.24

php中三维数组怎样求和
php中三维数组怎样求和

php中三维数组求和的方法:1、创建一个php示例文件;2、定义一个名为“$total”的变量,用于记录累加的结果。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2499

2024.02.23

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

1698

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

2604

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

6024

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

5529

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

7214

2024.03.22

OpenJDK JVM参数怎么调优
OpenJDK JVM参数怎么调优

Java应用运行缓慢,如何调整JVM参数?本专题介绍OpenJDK JVM性能调优的基本流程,涵盖性能指标确认、运行数据采集、内存配置和垃圾回收分析,帮助你先定位瓶颈,再通过对照测试验证效果,避免直接套用参数而影响应用稳定性。

0

2026.10.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
dev.java 官方:Learn Java
dev.java 官方:Learn Java

共0课时 | 0人学习

Java JDBC数据库连接官方教程
Java JDBC数据库连接官方教程

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn