Do you build things with AMP? Fill out the new AMP Developer Survey!
AMP
  • websites

Tic-Tac-Toe

Introduction

This is a sample showing how to implement Tic-Tac-Toe. This includes:

  • How to use amp-state to maintain the state of play.

  • How to use expressions to detect winning moves.

  • How to use actions and events to create interative gameplay.

Setup

amp-bind is required for interactive gameplay.

<script async custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script>

Initial game state

We use an amp-state called gameState to keep the state of play. Additionally, some state values are not initialized but are explained below:

  • currentPlayer: Can be 1 or -1, for ⚡ and O respectively.
  • board: Stores the state of the board. This object has 9 properties, from a to i, for the tiles from top-left to bottom-right.
  • tr,mr,br,lc,mc,rc,fd,bd: Read as Top Row, Middle Row, and so on, these 8 values hold the tallies towards each of the possible winning states.
<amp-state id="gameState">
  <script type="application/json">
    {
      "currentPlayer": 1,
      "displayValues": {
        "-1": "O",
        "1": "⚡"
      }
    }
  </script>
</amp-state>

Showing a win

To detect a win, we check whether any of the tallies has reached positive or negative 3.

Let's play!

⚡ wins!!!

O wins!!!

<div class="results-component">
  <h1 [class]="max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3 ? 'hide' : 'show'">Let's play!</h1>
  <h1 [class]="max(tr,br,lc,rc,fd,bd,mc,mr) == 3 ? 'show' : 'hide'"
    class="hide">⚡ wins!!!</h1>
  <h1 [class]="min(tr,br,lc,rc,fd,bd,mc,mr) == -3 ? 'show' : 'hide'"
    class="hide">O wins!!!</h1>
</div>

Handling a player's turn

Each tile of the playing board is a button. When a move is played, state is updated:

  • The appropriate tallies are incremented or decremented. For example, playing top-left alters the tally for 3 possible winning states: tr,lc and bd (top row, left column and backward diagonal).

  • The state of the board is updated, to record that this tile has been filled.

  • The game play is switched to the other play by multiplying the current player by -1.

Display attributes for each tile are updated based on this change in state:

  • [text]: Each tile either contains nothing, or the appropriate value from board.

  • [class]: If any of the winning states for this tile has been reached, the background is changed accordingly.

  • [disabled]: This button must be disabled if either a win has occurred, or the tile already played.

<div class="board-component">
  <table>
    <tbody>
      <tr>
        <td class="cell">
          <button on="tap:AMP.setState({
                        tr: tr + gameState.currentPlayer,
                        lc: lc + gameState.currentPlayer,
                        bd: bd + gameState.currentPlayer,
                        board: {
                          a: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.a ? board.a : ''"
            [class]="max(abs(tr),abs(lc),abs(bd)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.a || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
        <td class="cell cell-vert">
          <button on="tap:AMP.setState({
                        tr: tr + gameState.currentPlayer,
                        mc: mc + gameState.currentPlayer,
                        board: {
                          b: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.b ? board.b : ''"
            [class]="max(abs(tr),abs(mc)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.b || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
        <td class="cell">
          <button on="tap:AMP.setState({
                        tr: tr + gameState.currentPlayer,
                        rc: rc + gameState.currentPlayer,
                        fd: fd + gameState.currentPlayer,
                        board: {
                          c: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.c ? board.c : ''"
            [class]="max(abs(tr),abs(rc),abs(fd)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.c || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
      </tr>
      <tr>
        <td class="cell cell-horiz">
          <button on="tap:AMP.setState({
                        mr: mr + gameState.currentPlayer,
                        lc: lc + gameState.currentPlayer,
                        board: {
                          d: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.d ? board.d : ''"
            [class]="max(abs(mr),abs(lc)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.d || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
        <td class="cell cell-horiz cell-vert">
          <button on="tap:AMP.setState({
                        mr: mr + gameState.currentPlayer,
                        mc: mc + gameState.currentPlayer,
                        fd: fd + gameState.currentPlayer,bd: bd + gameState.currentPlayer,
                        board: {
                          e: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.e ? board.e : ''"
            [class]="max(abs(mr),abs(mc),abs(fd),abs(bd)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.e || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
        <td class="cell cell-horiz">
          <button on="tap:AMP.setState({
                        mr: mr + gameState.currentPlayer,
                        rc: rc + gameState.currentPlayer,
                        board: {
                          f: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.f ? board.f : ''"
            [class]="max(abs(mr),abs(rc)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.f || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
      </tr>
      <tr>
        <td class="cell">
          <button on="tap:AMP.setState({
                        br: br + gameState.currentPlayer,
                        lc: lc + gameState.currentPlayer,
                        fd: fd + gameState.currentPlayer,
                        board: {
                          g: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.g ? board.g : ''"
            [class]="max(abs(br),abs(lc),abs(fd)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.g || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
        <td class="cell cell-vert">
          <button on="tap:AMP.setState({
                        br: br + gameState.currentPlayer,
                        mc: mc + gameState.currentPlayer,
                        board: {
                          h: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.h ? board.h : ''"
            [class]="max(abs(br),abs(mc)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.h || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
        <td class="cell">
          <button on="tap:AMP.setState({
                        br: br + gameState.currentPlayer,
                        rc: rc + gameState.currentPlayer,
                        bd: bd + gameState.currentPlayer,
                        board: {
                          i: gameState.displayValues[gameState.currentPlayer]
                        },
                        gameState: {
                          currentPlayer: gameState.currentPlayer * -1
                        }
                      })"
            [text]="board.i ? board.i : ''"
            [class]="max(abs(br),abs(rc), abs(bd)) == 3 ? 'grid-button win' : 'grid-button in-play'"
            [disabled]="board.i || max(abs(tr),abs(br),abs(lc),abs(rc),abs(fd),abs(bd),abs(mc),abs(mr)) == 3"
            class="grid-button in-play">
          </button>
        </td>
      </tr>
    </tbody>
  </table>
</div>

Resetting the game state

To start a new game, the board and 8 tallies are reset to null and the currentPlayer reset to .

<div class="reset-component">
  <button class="reset-button"
    on="tap:AMP.setState({
                gameState: {
                  currentPlayer: 1,
                  displayValues: {
                    '-1': 'O',
                    '1': '⚡'
                  }
                },
                tr: null,
                mr: null,
                br: null,
                lc: null,
                mc: null,
                rc: null,
                fd: null,
                bd: null,
                board: null
              })">
    Restart game
  </button>
</div>

Top tips

Put ⚡ in the center square!

需要进一步说明?

如果此页面的说明未能涵盖您的所有问题,欢迎与其他 AMP 用户取得联系,讨论您的具体用例。

前往 Stack Overflow
一项无法解释的功能?

AMP 项目强烈鼓励您参与其中并作出贡献!我们希望您能成为我们开源社区的持续参与者,但我们也欢迎您对热衷问题做出单次贡献。

在 GitHub 上编辑实例