【问题标题】:Representing game states in Tic Tac Toe在井字游戏中表示游戏状态
【发布时间】:2009-11-18 22:40:51
【问题描述】:

我目前正在为我的数据结构课程做的作业的目标是创建一个带有人工智能的量子井字游戏,以赢得胜利。

目前,我很难找到最有效的方式来表示状态。

当前结构概述:

抽象游戏

  • 拥有并管理 AbstractPlayer(game.nextPlayer() 通过 int ID 返回下一个玩家)
  • 在游戏开始时拥有并初始化 AbstractBoard
  • 有一个 GameTree(如果在初始化中调用完成,否则不完整)

抽象板

  • 具有状态、维度和父游戏
  • 是 Player 和 State 之间的中介,(将 State 从行集合转换为 Point 表示
  • 是 StateConsumer

抽象播放器

  • 是国家生产者
  • 有一个具体的评估策略来评估当前的董事会

StateTransveralPool

  • 预计算“三态”的可能横向。
  • 将它们存储在 HashMap 中,其中 Set 包含给定“3-state”的 nextStates

状态

  • 包含 3 组 - 一组 X-Move、O-Move 和棋盘
  • 集合中的每个整数都是一个行。这些整数值可用于从 StateTransversalPool 中获取下一个行状态

SO,原理是 每行可以用二进制数 000-111 表示,其中 0 表示开放空间,1 表示封闭空间。

所以,对于一个不完整的 TTT 板:

From the Set<Integer> board perspective:
X_X  R1 might be: 101
OO_  R2 might be: 110
X_X  R3 might be: 101, where 1 is an open space, and 0 is a closed space

From the Set<Integer> xMoves perspective:
X_X  R1 might be: 101
OO_  R2 might be: 000
X_X  R3 might be: 101, where 1 is an X and 0 is not

From the Set<Integer> oMoves perspective:
X_X  R1 might be: 000
OO_  R2 might be: 110
X_X  R3 might be: 000, where 1 is an O and 0 is not

然后我们看到 x{R1,R2,R3} & o{R1,R2,R3} => board{R1,R2,R3}

问题是快速为 GameTree 生成下一个状态。如果我有棋盘{R1,R2,R3} 的玩家 Max (x),那么获取 R1、R2 和 R3 的下一个行状态很简单..

Set<Integer> R1nextStates = StateTransversalPool.get(R1); 

问题是我必须将这些状态中的每一个与 R1 和 R2 结合起来。

除了 Set 之外,还有更好的数据结构可以使用吗?一般来说,有没有更有效的方法?我还发现 PointState 调解很麻烦。我可以在那里尝试另一种方法吗?

谢谢!

这是我的 ConcretePlayer 类的代码。它可能有助于解释玩家如何使用 StateProducer(可能需要成为 StateFactory 或 StateBuilder)通过移动产生新状态。

public class ConcretePlayerGeneric extends AbstractPlayer {

 @Override
 public BinaryState makeMove() {
  // Given a move and the current state, produce a new state
  Point playerMove = super.strategy.evaluate(this);
  BinaryState currentState = super.getInGame().getBoard().getState();


  return StateProducer.getState(this, playerMove, currentState);
 }
}

编辑:我从普通 TTT 开始,然后转向 Quantum TTT。给定框架,它应该像创建几个新的 Concrete 类并调整一些东西一样简单。

【问题讨论】:

  • 您的示例板表示似乎错误。
  • 怎么样?对于 Set 板,0 表示没有玩家移动到那里。对于 Set xMoves,0 意味着 X 没有移动到那里。对于 Set oMoves,0 表示 O 没有移动到那里。
  • 好吧,除了O只有两步之外..哈哈
  • 编辑:修复了在同一个地方多次移动的问题(我认为)

标签: java design-patterns optimization


【解决方案1】:

我的建议:

  • 考虑表示单个方块而不是行,其中+1 == O-1 == X0 表示一个空方块。 这允许您通过检查水平、垂直或对角行的总和是否等于+3-3 来检测结束状态
  • 其次,将此 2D 3x3 矩阵“展平”为单个数组,其中元素 [0-2] 表示第一行,元素 [3-5] 表示第二行,元素 [6-8] 表示第三行。
  • 在给定棋盘当前状态的情况下,使用递归或迭代方法生成后续游戏状态。

编辑

我觉得无聊,所以决定编写一些“玩具代码”来实现游戏板,包括确定它是否处于终端状态以及在下一步移动后生成一组板状态的方法。尽管我没有尝试过,但它应该适用于任何尺寸的电路板。享受...

样本输出

$ java Board
Creating board:

---
---
---

Initialising board:

-OX
O--
XO-
Terminal state: false


Generating next move states:

XOX
O--
XO-

-OX
OX-
XO-

-OX
O-X
XO-

-OX
O--
XOX

代码

import java.util.List;
import java.util.LinkedList;
import java.util.Random;

public class Board {
    private final int[] squares;

    public Board() {
    this.squares = new int[9];
    }

    protected Board(int[] squares) {
    this.squares = squares;
    }

    public void init() {
    Random rnd = new Random();
    int turn = 1; // 'O' always goes first.

        for (int i=0; i<squares.length; ++i) {
        double d = rnd.nextDouble();

        if (d < 0.75) {
        squares[i] = turn;
        turn = turn == 1 ? -1 : 1; // Flip to other player's turn.
        } else {
        squares[i] = 0; // Empty square.
        }

        if (isTerminalState()) {
        break;
        }
    }
    }

    public boolean isTerminalState() {
    boolean ret = false;

    boolean foundEmpty = false;
    int hSum = 0;
    int[] vSum = new int[3];

    for (int i=0; i<squares.length; ++i) {
        hSum += squares[i];

        if (isWinningRow(hSum)) {
        ret = true;
        break;
        } else if (i == 2 || i == 5) {
        hSum = 0;
        }

        int col = i % 3;
        vSum[col] += squares[i];

        if (isWinningRow(vSum[col])) {
        ret = true;
        break;
        }

        if (squares[i] == 0) {
        foundEmpty = true;
        }
    }

    if (!ret) {
        if (!foundEmpty) {
        ret = true;
        } else {
        int diag1 = 0;
        int diag2 = 0;
        int rowSz = (int)Math.sqrt(squares.length);

        for (int i=0; i<squares.length; ++i) {
            if (i % (rowSz + 1) == 0) {
            diag1 += squares[i];

            if (isWinningRow(diag1)) {
                ret = true;
                break;
            }
            }

            if (i > 0 && i % (rowSz - 1) == 0) {
            diag2 += squares[i];

            if (isWinningRow(diag2)) {
                ret = true;
                break;
            }
            }
        }
        }
    }

    return ret;
    }

    private boolean isWinningRow(int rowSum) {
    return rowSum == 3 || rowSum == -3;
    }

    public List<Board> getNextStates() {
    List<Board> ret = new LinkedList<Board>();

    int tmp = 0;
    for (int i=0; i<squares.length; ++i) {
        tmp += squares[i];
    }

    // Next turn is 'O' (i.e. +1) if the board sums to 0.
    // Otherwise it's 'X's turn.
    int turn = tmp == 0 ? 1 : -1;

    if (!isTerminalState()) {
        for (int i=0; i<squares.length; ++i) {
        if (squares[i] == 0) { // Empty square          
            int[] squaresA = new int[squares.length];
            System.arraycopy(squares, 0, squaresA, 0, squares.length);
            squaresA[i] = turn;
            ret.add(new Board(squaresA));
        }
        }
    }

    return ret;
    }

    public String toString() {
    StringBuilder sb = new StringBuilder();

    for (int i=0; i<squares.length; ++i) {
        if (squares[i] == 1) {
        sb.append('O');
        } else if (squares[i] == -1) {
        sb.append('X');
        } else {
        assert squares[i] == 0;
        sb.append('-');
        }

        if (i == 2 || i == 5) {
        sb.append('\n');
        }
    }

    return sb.toString();
    }

    public static void main(String[] args) {
    System.err.println("Creating board:\n");
    Board bd = new Board();
    System.err.println(bd);

    System.err.println("\nInitialising board:\n");
    bd.init();
    System.err.println(bd);
    System.err.println("Terminal state: " + bd.isTerminalState() + '\n');

    System.err.println("\nGenerating next move states:\n");
    List<Board> nextStates = bd.getNextStates();

    for (Board bd1 : nextStates) {
        System.err.println(bd1.toString() + '\n');
    }
    }
}

【讨论】:

  • 为什么要压扁它?作为一个抽象,单个数组是弱的。在内部它可能是扁平的,但呈现的抽象应该提供行和列。
  • @tster:同意。我说的是内部表示,但您是正确的,因为您可以将其包装在提供 getSquareValue(int row, int column) 等方法的 Board 类中。但是,使用单个数组作为表示可以更轻松地使用递归生成未来的游戏状态,因为您只需遍历数组并插入“O”或“X”,而无需真正关心它是 3x3 板。
【解决方案2】:

每个方格不应该只有三种可能的状态(,X,O)吗?

要么存储一个由 3 状态方格组成的网格,要么存储 2 个移动列表。您不需要存储整个棋盘,因为它是由移动定义的。

还有,你的意思是:

为 游戏树

什么是游戏树? “下一个州”的一些例子是什么?

【讨论】:

  • 博弈树是 n 个游戏级别的树表示。我代表每一行,而不是每一方。每行有 2^3 个状态——二进制 000 到 111。给定行:000,它的下一个状态是:100,010,001,其中 1 表示移动。棋盘可能的下一个状态是以下集合:{row1 next states, row2, row3},{row1, row2 next states, row3},{row1, row2, row3 next states}
猜你喜欢
  • 2015-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
  • 2022-07-18
相关资源
最近更新 更多