Skip to main content

xo

Latest PyPI version Supported Python versions

A Tic-tac-toe CLI game and library written in Python.

The library is written in a modular way. Its overall design consists of 4 decoupled components:

  1. A Tic-tac-toe board data structure, xo.board.
  2. An arbiter for analyzing the state of a board, xo.arbiter.
  3. A game engine to implement and enforce the Tic-tac-toe game logic, xo.game.
  4. And finally, an AI for finding excellent moves, xo.ai.

The board

>>> from xo.board import Board
>>> from xo.token import isempty

>>> board = Board.fromstring('..x.o')
>>> print(board)
..x.o....

>>> print(board.toascii()) # doctest: +NORMALIZE_WHITESPACE
   |   | x
---+---+---
   | o |
---+---+---
   |   |

>>> board[1, 3]
'x'

>>> board[3, 3] = 'x'
>>> print(board)
..x.o...x

>>> for r, c, piece in board:
...   if isempty(piece):
...     print('{}, {}'.format(r, c))
...
1, 1
1, 2
2, 1
2, 3
3, 1
3, 2

The board isn't concerned with whether or not a given layout can be reached in an actual Tic-tac-toe game. Hence, the following is perfectly legal:

>>> board = Board.fromstring('xxxxxxxxo')
>>> print(board)
xxxxxxxxo

The arbiter is concerned about that though and can detect such invalid board layouts.

The arbiter

>>> from xo import arbiter
>>> from xo.board import Board

>>> arbiter.outcome(Board.fromstring(), 'x')
{'status': 'in-progress', 'piece_counts': {'xs': 0, 'os': 0, 'es': 9}}

>>> arbiter.outcome(Board.fromstring('xxxoo'), 'o')
{'status': 'gameover', 'reason': 'loser', 'details': [{'where': 'row', 'index': 1, 'positions': [(1, 1), (1, 2), (1, 3)]}], 'piece_counts': {'xs': 3, 'os': 2, 'es': 4}}

>>> arbiter.outcome(Board.fromstring('xxxxxxxxo'), 'x')
{'status': 'invalid', 'reason': 'too-many-moves-ahead', 'piece_counts': {'xs': 8, 'os': 1, 'es': 0}}

The game engine

Enforcer of the game rules.

>>> from xo.game import Game

>>> game = Game()
>>> game.start('x')
>>> game.moveto(1, 1)
{'name': 'next-turn', 'last_move': {'r': 1, 'c': 1, 'token': 'x'}}

>>> game.moveto(1, 1)
{'name': 'invalid-move', 'reason': 'occupied'}

>>> game.moveto(0, 0)
{'name': 'invalid-move', 'reason': 'out-of-bounds'}

>>> game.moveto(2, 2)
{'name': 'next-turn', 'last_move': {'r': 2, 'c': 2, 'token': 'o'}}

>>> game.moveto(3, 1)
{'name': 'next-turn', 'last_move': {'r': 3, 'c': 1, 'token': 'x'}}

>>> print(game.board.toascii()) # doctest: +NORMALIZE_WHITESPACE
 x |   |
---+---+---
   | o |
---+---+---
 x |   |

>>> game.moveto(3, 3)
{'name': 'next-turn', 'last_move': {'r': 3, 'c': 3, 'token': 'o'}}

>>> game.moveto(2, 1)
{'name': 'gameover', 'reason': 'winner', 'last_move': {'r': 2, 'c': 1, 'token': 'x'}, 'details': [{'where': 'column', 'index': 1, 'positions': [(1, 1), (2, 1), (3, 1)]}]}

>>> game.moveto(1, 3)
Traceback (most recent call last):
...
xo.error.IllegalStateError: gameover

>>> # start a new game
>>> game.restart()
>>> # since x won, it would be x's turn to play
>>> # if the game was squashed then it would have been o's turn to play
>>> game.moveto(1, 1)
{'name': 'next-turn', 'last_move': {'r': 1, 'c': 1, 'token': 'x'}}

>>> print(game.board.toascii()) # doctest: +NORMALIZE_WHITESPACE
 x |   |
---+---+---
   |   |
---+---+---
   |   |

The AI

No Tic-tac-toe library is complete without an AI that can play a perfect game of Tic-tac-toe.

>>> from xo import ai
>>> from xo.board import Board

>>> ai.evaluate(Board.fromstring('xo.xo.'), 'x')
MinimaxResult(score=26, depth=1, positions=[(3, 1)])

>>> ai.evaluate(Board.fromstring('xo.xo.'), 'o')
MinimaxResult(score=26, depth=1, positions=[(3, 2)])

>>> ai.evaluate(Board.fromstring('x.o'), 'x')
MinimaxResult(score=18, depth=5, positions=[(2, 1), (3, 1), (3, 3)])

Finally, xo.cli brings it all together in its implementation of the command-line Tic-tac-toe game. It's interesting to see how easy it becomes to implement the game so be sure to check it out.

Note: An extensive suite of tests is also available that can help you better understand how each component is supposed to work.

Installation

Install it using pip, or your favourite Python package manager:

pip install xo

You would now have access to an executable called xo. Type

xo

to start playing immediately.

Usage

For help, type

xo -h

By default xo is configured for a human player to play with x and a computer player to play with o. However, this can be easily changed to allow any of the other 3 possibilities:

# Computer vs Human
xo -x computer -o human

# Human vs Human
xo -x human -o human
xo -o human # since x defaults to human

# Computer vs Computer
xo -x computer -o computer
xo -x computer # since o defaults to computer

You can also change who plays first. By default it's the x player.

# Let o play first
xo -f o

Finally, when letting the computers battle it out you can specify the number of times you want them to play each other. By default they play 50 rounds.

xo -x computer -r 5
# .....
#
# Game statistics
# ---------------
# Total games played: 5 (2.438 secs)
# Number of times x won: 0
# Number of times o won: 0
# Number of squashed games: 5

Development

You'll need Nix with flakes enabled.

git clone git@github.com:dwayne/xo-python.git
cd xo-python
nix develop

You're now all set to begin development. Some common tasks are available through make:

Command What it does
make build Build the sdist and wheel into dist/
make check Build the package, run the tests and doctests, and check metadata
make check-all Run make check, then the tests on the other supported Python versions
make clean Remove build artifacts

Testing

Tests are written using the unittest unit testing framework.

Run everything with make check, or run tests directly:

python -m unittest

Run a specific test module.

python -m unittest tests.test_arbiter

Run a specific test case.

python -m unittest tests.test_arbiter.GameoverPositionsTestCase

Run a specific test method.

python -m unittest tests.test_arbiter.GameoverPositionsTestCase.test_when_x_wins

Credits

Thanks to Patrick Henry Winston for clarifying the Minimax algorithm. His video on the topic was a joy to watch.

Metadata

Release files for xo 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xo 1.1.0
File Size Uploaded
xo-1.1.0.tar.gz 10.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for xo 1.1.0
File Interpreter ABI Platform
xo-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 23.0 kB

Release files / xo-1.1.0.tar.gz

Download URL xo-1.1.0.tar.gz
Size 10.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f70893cce7b3945814cbb371decf56a62d315c45bf696b7246fa318e3cf9ce88
BLAKE2b-256 checksum
How to use checksums
2bbf6657426a811c4a2a94410a33f5af4523c1e6a78093a0e9cb4701727cb7b7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / xo-1.1.0-py3-none-any.whl

Download URL xo-1.1.0-py3-none-any.whl
Size 12.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
54d364099bc50f65d95bbf417bd909bc4bf82c1f2af5f3f2a18c0819adb3ceb7
BLAKE2b-256 checksum
How to use checksums
3051049d1fce6ded0e81fd36f85d40419873b5062b2ffa408c346282896ae664
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Pop!_OS","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page