【问题标题】:A simpler way to determine the winner in tic-tac-toe?确定井字游戏获胜者的更简单方法?
【发布时间】:2018-04-09 21:52:22
【问题描述】:

我以这种方式模拟井字游戏板:

one sig gameBoard {
    cells: Row -> Col -> Mark -> Time
}

标记是 X 或 O:

enum Mark { X, O }

Row 和 Col 是简单的集合:

sig Row {}{ #Row = 3}
sig Col {}{ #Col = 3} 

在以下情况下获胜:

  • 有一行全是 X 或全是 O,或者
  • 有一个全是 X 或全是 O 的 col,或者
  • 存在一条从左到右的对角线,其中所有 X 或所有 O,或
  • 有一条从右到左的对角线,全是 X 或全是 O。

我用下面的复杂谓词来表达这一点。有没有更简单的方式来表达获胜者?

 pred winner [t: Time] {
    some m: Mark |
        some r: Row | all c: Col | board[r, c, t] = m
        or
        some c: Col| all r: Row | board[r, c, t]  = m
        or
        board[first, first, t] = m and 
        board[first.next, first.next, t] = m and 
        board[first.next.next, first.next.next, t] = m
        or
        board[last,last, t] = m and 
        board[last.prev, last.prev, t] = m and 
        board[last.prev.prev,last.prev.prev, t] = m
}

这是我完整的井字游戏模型:

open util/ordering[Time]
open util/ordering[Row]
open util/ordering[Col]

/*
Structure:
1. The game is played on a 3x3 board.
2. There are two players, Player1 and Player2.
3. Players mark the game board with either X or O.
4. The game is played over a series of time steps.
*/

// 4. The game is played over a series of time steps.
sig Time {}

// 3. Players mark the game board with either X or O.
enum Mark { X, O }

// 2. There are two players, Player1 and Player2.
enum Player { Player1, Player2 }

// 1. The game is played on a ... board.
one sig gameBoard {
    cells: Row -> Col -> Mark -> Time
} 

// 1. ... on a 3x3 board.
sig Row {}{ #Row = 3}
sig Col {}{ #Col = 3}

/*
Constraints:
1. Each cell has at most one mark (X or O) at each time.
2. A win stops further marking.
3. When all cells are marked, there can be no additional marking.
4. Players alternate moves.
5. There is no interrupt in the play: If cells are empty at time t-1,
     and there is no winner at time t-1, then there will be one
     fewer empty cells at time t. If there is a winner at time t-1,
     then there will be no change to the number of empty cells at
     time t (per invariant 2). 
6. Player1 marks cells O and Player2 marks cells X.
7. When there is a winner or when all cells are marked,
     then the recording of "last player to move" is blank.
*/

// 1. Each cell has at most one mark (X or O) at each time.
pred Each_cell_has_at_most_one_mark {
    no r: Row, c: Col, t: Time, disj m, m': Mark |
        ((r -> c -> m) in gameBoard.cells.t) and
       ((r -> c -> m') in gameBoard.cells.t) 
}

// 2. A win stops further marking.
pred gameBoard_remains_unchanged_after_win {
    all t: Time - first | 
        winner[t.prev] => gameBoard.cells.t = gameBoard.cells.(t.prev)
}

// 3. When all cells are marked, there can be no additional marking.
pred gameBoard_remains_unchanged_after_every_cell_is_marked {
    all t: Time - first | 
        every_cell_is_marked[t.prev] => gameBoard.cells.t = gameBoard.cells.(t.prev)
}

// 4. Players alternate moves.
pred Players_alternately_move {
    no t: Time - last, t': t.next | 
        (some LastPlayerToMove.person.t)  and
        (some LastPlayerToMove.person.t') and
        (LastPlayerToMove.person.t = LastPlayerToMove.person.t')
}

// 5. There is no interrupt in the play: If cells are empty at time t-1,
//     and there is no winner at time t-1, then there will be one
//     fewer empty cells at time t. If there is a winner at time t-1,
//     then there will be no change to the number of empty cells at
//     time t (per invariant 2). 
pred Progressively_fewer_empty_cells {
    all t: Time - first | 
        not every_cell_is_marked[t.prev] and not winner[t.prev] =>
            #empty_cells[t] < #empty_cells[t.prev]
}

// 6. Player1 marks cells O and Player2 marks cells X.
pred Players_mark_cells_appropriately {
    all t: Time - first |
        not every_cell_is_marked[t.prev] and not winner[t.prev] =>
            let c = gameBoard.cells.t - gameBoard.cells.(t.prev) |
                c[Row][Col] = X =>
                   (LastPlayerToMove.person.t = Player2)
                else
                   (LastPlayerToMove.person.t = Player1)
}

// 7. When there is a winner or when all cells are marked,
//      then the recording of "last player to move" is blank.
pred LastPlayerToMove_remains_unchanged_after_win_or_all_cells_marked {
    all t: Time - first |
        ((every_cell_is_marked[t.prev]) or (winner[t.prev])) => 
            no LastPlayerToMove.person.t 
}

// This provides one place that you can call to 
// have all the constraints enforced.
pred game_is_constrained_by_these_constraints {
    Each_cell_has_at_most_one_mark
    gameBoard_remains_unchanged_after_win
    gameBoard_remains_unchanged_after_every_cell_is_marked
    Players_alternately_move
    Progressively_fewer_empty_cells
    Players_mark_cells_appropriately
    LastPlayerToMove_remains_unchanged_after_win_or_all_cells_marked
}

// Return the set of empty cells at time t.
// This is implemented using set subtraction.
// (Row -> Col) is the set of all possible combinations
// of row and col. Subtract from that the set
// of (row, col) pairs containing a mark at time t.
fun empty_cells[t: Time]: Row -> Col {
    (Row -> Col) - gameBoard.cells.t.Mark
}

// Once the game board is completely marked,
// there won't be a "last player." Ditto for when
// there is a winner. That's why there "may" be
// a last player at time t. That is, there isn’t 
// necessarily a player involved at every time step, 
// i.e., there isn’t necessarily a (Player, Time) pair 
// for every value of Time.
one sig LastPlayerToMove {
    person: Player lone -> Time
}

// Return the mark (X or O) on board[r][c] at time t,
// or none if there is no mark.
fun board [r: Row, c: Col, t: Time]: lone Mark {
    gameBoard.cells[r][c].t
}

// There is a winner when (a) there is a row
// with all X's or all O's, or (b) there is a col
// with all X's or all O's, or (c) there is a left-to-right
// diagonal with all X's or all O's, or (d) there is a 
// right-to-left diagonal with all X's or all O's.
pred winner [t: Time] {
    some m: Mark |
        some r: Row | all c: Col | board[r, c, t] = m
        or
        some c: Col| all r: Row | board[r, c, t]  = m
        or
        board[first, first, t] = m and 
        board[first.next, first.next, t] = m and 
        board[first.next.next, first.next.next, t] = m
        or
        board[last,last, t] = m and 
        board[last.prev, last.prev, t] = m and 
        board[last.prev.prev,last.prev.prev, t] = m
}

// Every call of the game board is marked when
// the set of cells with marks equals all combinations
// of (row, col)
pred every_cell_is_marked[t: Time] {
    gameBoard.cells.t.Mark = (Row -> Col)
}

// Initially the game board has no cells.
// One of the players is first to play.
// The game is constrained by the invariants.
pred init [t: Time] {
    no gameBoard.cells.t 
    one p: Player | LastPlayerToMove.person.t = p
    game_is_constrained_by_these_constraints
}

pred doNothing [t: Time] {
    gameBoard.cells.t = gameBoard.cells.(t.prev)
}

pred Play {
    init[first]
    all t: Time - first |
        X.marked_on_gameboard_at_time[t] 
        or O.marked_on_gameboard_at_time[t]
        or doNothing[t]
}

pred marked_on_gameboard_at_time [m: Mark, t: Time] {
    some r: Row, c: Col {
        gameBoard.cells.t = gameBoard.cells.(t.prev) + 
              {r': Row, c': Col, m': Mark | r' = r and c' = c and m' = m}
    }
}

run Play for 3 but 12 Time

【问题讨论】:

    标签: alloy


    【解决方案1】:

    评论:您可以在 Alloy 5 Beta 中运行整个降价文本

    井字棋

    我们围绕棋盘设计这款游戏。 Board 是状态,我们将使用在谓词中编码的游戏规则 将转换约束到下一个板。

    设置

    通过定义棋盘大小、棋盘和玩家来设置游戏。董事会人数较多 字段,因为这使跟踪输出易于阅读。

    ```合金

    open util/ordering[Board]
    
    let N = 0+1+2
    let highest     =   max[N]
    
    
    
    sig Board {
        cells : N -> N -> Cell,
        move: Cell,
        to  : N->N,
        won : Cell
    }
    
    enum Cell { _, X, O }
    

    ```

    获胜

    如果有一行、一列或一条对角线包含同一玩家,则游戏获胜。我们可以这样编码:

    ```合金

    let rightdiag   =   { r,c : N | r = c }
    let leftdiag    =   { r,c : N | c = highest.minus[r] }
    
    pred won[b,b':Board ] {
        some token : X + O {
            let positions = b.cells.token {
    
                    some row    : N | N = row.positions 
                or  some column : N | N = positions.column 
                or  rightdiag in positions
                or  leftdiag in positions
    
                b'.won = token
            }
        }
    }
    

    ```

    完成

    当玩家获胜或没有更多空位时,游戏结束。

    ```合金

    pred finished[ b,b' : Board ] {
        not won[b,b'] implies {
            b'.won = _
            _ not in b'.cells[N][N]
        }
        b'.cells = b.cells
        b'.move = _
        b'.to = none -> none
    }
    

    ```

    播放

    比赛应该在球员之间交替进行。这就是为什么我们将玩家保留在前一个棋盘的move 字段中 并检查它不是我们。然后我们限制棋盘上的空位替换为玩家的 令牌。

    ```合金

    pred play[b, b' : Board ] {
        b'.won=_
        some token : X+O {
            b.move != token
            b'.move = token
            some row,col : N {
                b.cells[row][col] = _
                b'.cells = b.cells - row->col->_ + row->col->token
                b'.to = row->col
            }
        }
    }
    

    ```

    跟踪

    那么剩下的唯一事情就是设置第一个棋盘并确保游戏(Boards的痕迹)是 按规则玩。

    ```合金

    fact trace {
    
        first.move = _
        first.won = _
        first.cells = N->N->_
    
        all b' : Board - first, b  : b'.prev {
            not finished[b,b'] => play[b,b']
        }
    }
    

    ```

    运行

    通过run,我们可以寻找特定类型的解决方案。在这个例子中,我们试图找到一个游戏,其中 O 以右对角线获胜...

    ```合金

    run { some b : Board | rightdiag in b.cells.(O) } for 11 but 3 int
    

    ```

    这在 Alloy 5 表格视图中提供了以下输出(表格从 beta 5 重新排序):

    ┌──────────┬──────┬────┬───┬───┐
    │this/Board│cells │move│to │won│
    ├──────────┼─┬─┬──┼────┼───┼───┤
    │Board⁰    │0│0│_⁰│_⁰  │   │_⁰ │
    │          │ ├─┼──┼────┤   ├───┤
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board¹    │0│0│_⁰│X⁰  │2│1│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board²    │0│0│_⁰│O⁰  │1│2│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board³    │0│0│_⁰│X⁰  │0│1│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board⁴    │0│0│O⁰│O⁰  │0│0│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board⁵    │0│0│O⁰│X⁰  │2│0│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board⁶    │0│0│O⁰│O⁰  │1│1│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│_⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│O⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board⁷    │0│0│O⁰│X⁰  │1│0│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│O⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼─┬─┼───┤
    │Board⁸    │0│0│O⁰│O⁰  │2│2│_⁰ │
    │          │ ├─┼──┼────┼─┴─┼───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│O⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼───┼───┤
    │Board⁹    │0│0│O⁰│_⁰  │   │O⁰ │
    │          │ ├─┼──┼────┤   ├───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│O⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    ├──────────┼─┼─┼──┼────┼───┼───┤
    │Board¹⁰   │0│0│O⁰│_⁰  │   │O⁰ │
    │          │ ├─┼──┼────┤   ├───┤
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│_⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │1│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│O⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    │          ├─┼─┼──┤    │   │   │
    │          │2│0│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │1│X⁰│    │   │   │
    │          │ ├─┼──┤    │   │   │
    │          │ │2│O⁰│    │   │   │
    └──────────┴─┴─┴──┴────┴───┴───┘
    

    【讨论】:

    • 太棒了!谢谢@Peter Kriens
    【解决方案2】:

    我在 Lightning 中实现这个示例很有趣。你可能会觉得我的结果很有趣:

    Lightning () 是一个基于 Alloy 的语言工作台,允许您为任何 Alloy 规范定义具体语法。具体语法是通过使用称为 的专用模型转换语言(一种具有可解释性而非可分析性的 Alloy 变体)定义的,因此您可能需要一些时间来适应它。

    该工具可用作 Eclipse 插件 (update site)。

    这里an archive file包含上图所示井字游戏项目的源码,大家可以自己玩转例子。

    【讨论】:

    • 哇!这看起来很棒@Loïc Gammaitoni。是否有 Lightning-workbench 工具的独立版本?注意:您引用的“更新站点”表示“浏览器无法加载文档。”
    • 不,不幸的是,Lightning 是作为 Eclipse 插件发布的,我们最初的目标是将 Alloy island 连接到基于 Ecore 的大量现有建模工具。要安装该工具,您首先需要download Eclipse,然后是install the Lightning plugin
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-02-11
    • 1970-01-01
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多