From 1dc6d953ce9110ec932e893d779a9ba880e57c33 Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Mon, 8 Jul 2024 09:37:05 -0700 Subject: [PATCH] Add documentation for TicTacToe --- examples/tic_tac_toe/README.md | 52 +++++++++++++ examples/tic_tac_toe/ascii_board.py | 113 ---------------------------- examples/tic_tac_toe/enums.py | 16 ++++ examples/tic_tac_toe/tic_tac_toe.py | 17 +++-- 4 files changed, 77 insertions(+), 121 deletions(-) delete mode 100644 examples/tic_tac_toe/ascii_board.py diff --git a/examples/tic_tac_toe/README.md b/examples/tic_tac_toe/README.md index 29012645..ac3304c6 100644 --- a/examples/tic_tac_toe/README.md +++ b/examples/tic_tac_toe/README.md @@ -5,3 +5,55 @@ After cloning the Unitary library you can use command line flags to run the game ``` python -m examples.tic_tac_toe.tic_tac_toe ``` + +## Rules of the game + +There are nine positions on the 3x3 board, labeled a through h. +The object is to get three in a row. Players alternate turns, +placing either an X or an O on each square. + +In the quantum version, there is an additional move for placing +an X or an O on two squares in superposition, called the split move. + +Once the board is full (i.e. each of the squares has either X, +O, or a superposition of them), the board is measured and the +result is determined. + +## Quantum representation + +Each square is represented as a Qutrit. This square can either be: + +* |0> meaning that the square is empty +* |1> meaning that the square has an X +* |2> meaning that the square has an O + +Placing an X or O will do the qutrit version of an "X" gate +on that square. Since these are qutrits (not qubits), the +definition of this gate is that it is swapping the amplitude +of the zero state with either the one or two state +(depending on whether this is an X or O move). +For example, if someone places an O on a square, then placing +an X on that same square has no effect, since the zero state +has zero amplitude. + +Note that there are two variants of the rules with regards to +the split move. In one rule, the qutrit X gate is applied +followed by a square root of iSWAP gate (restricted to either the +|1> or |2> subspace depending on the move). + +In the other variant, the corresponding states |01> and |10> +(or |02> and |20>) are swapped. + +## Code organization + +The qutrit operations (such as the split move) are defined in +`tic_tac_split.py`. These classes can be used as examples of +how to create your own gates and effects using unitary. + +The game itself is defined in `tic_tac_toe.py`. The `GameInterface` +class defines the main game loop and input/output. + +The `TicTacToe` class keeps track of the game state and also +allows you to sample (measure) the board. + +Enums used by the example are stored in `enums.py`. diff --git a/examples/tic_tac_toe/ascii_board.py b/examples/tic_tac_toe/ascii_board.py deleted file mode 100644 index 0bb312ef..00000000 --- a/examples/tic_tac_toe/ascii_board.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2023 The Unitary Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from .enums import TicTacSquare, TicTacResult, TicTacRules -from .tic_tac_toe import TicTacToe -import argparse -import textwrap - -help_str = """\ -In classical TicTacToe, players alternate in putting their token (either an -X or an O) on the squares of a 3x3 board. Each of the 3x3 squares of the board -is labeled by its own letter as follows: - - | | - a | b | c - _____|_____|_____ - | | - d | e | f - _____|_____|_____ - | | - g | h | i - | | - -When it's your turn, to put your token on an empty square just input the -corresponding square's letter. That is, to put your token on the top-right -square, input 'c'. - -In Quantum TicTacToe, you get access to so-called split moves, in which your -token can be put on two squares simultaneously. To input a quantum move, -enter the two letters of the involved squares. For example, a quantum move -putting your token in the top left and bottom right squares has input 'ai'. - -Split moves can be made without restrictions using the default, fully quantum, -ruleset. If you'd like to allow split moves only on empty squares, choose the -minimal quantum ruleset. If you'd like no quantum moves at all, choose the -classical ruleset. Choose different rulesets can be done using the -r option, -see below. -""" - - -def _flip_turn(turn: TicTacSquare): - return TicTacSquare.O if turn == TicTacSquare.X else TicTacSquare.X - - -class AsciiBoard: - def __init__(self, rules: TicTacRules): - self.board = TicTacToe(rules) - - def play(self): - turn = TicTacSquare.X - result = TicTacResult.UNFINISHED - - while result == TicTacResult.UNFINISHED: - print(self.board.print()) - move = input(f"{turn.name} turn to move: ").lower() - if move == "q": - exit() - - try: - result = self.board.move(move, turn) - except Exception as e: - print(e) - continue - - turn = _flip_turn(turn) - - print(f"Result: {result.name}") - print(self.board.print()) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description=textwrap.dedent(help_str), - formatter_class=argparse.RawTextHelpFormatter, - ) - parser.add_argument( - "-r", - dest="rules", - default=3, - type=int, - choices=range(1, 4), - help=textwrap.dedent( - """ - Set the ruleset: - 1: Classical - 2: Minimal Quantum - Allow split moves, but only on empty squares - 3: Fully Quantum (Default) - Allow split moves, no restrictions""" - ), - ) - args = parser.parse_args() - - print( - "Starting a new game of Quantum TicTacToe with " - "ruleset '%s'" % TicTacRules(args.rules - 1) - ) - print("Change the ruleset using the -r option. Or use -h for more help.") - print("Input 'q' to exit.") - - # Start a new game with the chosen ruleset (defaults to FULLY_QUANTUM) - AsciiBoard(TicTacRules(args.rules - 1)).play() diff --git a/examples/tic_tac_toe/enums.py b/examples/tic_tac_toe/enums.py index 14ac512e..43c1a863 100644 --- a/examples/tic_tac_toe/enums.py +++ b/examples/tic_tac_toe/enums.py @@ -17,12 +17,20 @@ class GameMoves(enum.Enum): + """Possible inputs for the ASCII version of tic tac toe.""" + EXIT = "exit" MAP = "map" HELP = "help" class TicTacSquare(enum.Enum): + """Possible states of one tic tac toe square. + + For the quantum version of tic tac toe, these + are represented as qutrits (qubits with three states). + """ + EMPTY = 0 X = 1 O = 2 @@ -35,6 +43,14 @@ def from_result(cls, value: Union[enum.Enum, int]): class TicTacResult(enum.Enum): + """End results of a tic tac toe game. + + Either one side has won or it is a draw. + If the game has continued past the end state, + it is possible both sides have completed a three-in-a-row. + If the game is not complete, the result is UNFINISHED. + """ + UNFINISHED = 0 X_WINS = 1 O_WINS = 2 diff --git a/examples/tic_tac_toe/tic_tac_toe.py b/examples/tic_tac_toe/tic_tac_toe.py index ca51b00d..d483b057 100644 --- a/examples/tic_tac_toe/tic_tac_toe.py +++ b/examples/tic_tac_toe/tic_tac_toe.py @@ -79,6 +79,13 @@ def _result_to_str(result: List[TicTacSquare]) -> str: def eval_board(result: List[TicTacSquare]) -> TicTacResult: + """Determines who has won the tic tac toe board. + + This function checks all the possible three-in-a-row positions + (all cols, all rows, and the two diagonals. Depending on which + player(s) have a three in a row (X's, O's, both, or neither) + returns the result of the tic tac toe board. + """ x_wins = False o_wins = False still_empty = False @@ -90,18 +97,12 @@ def eval_board(result: List[TicTacSquare]) -> TicTacResult: if any(result[check[idx]] == TicTacSquare.EMPTY for idx in range(3)): still_empty = True if x_wins: - if o_wins: - return TicTacResult.BOTH_WIN - else: - return TicTacResult.X_WINS + return TicTacResult.BOTH_WIN if o_wins else TicTacResult.X_WINS else: if o_wins: return TicTacResult.O_WINS else: - if still_empty: - return TicTacResult.UNFINISHED - else: - return TicTacResult.DRAW + return TicTacResult.UNFINISHED if still_empty else TicTacResult.DRAW class TicTacToe: