Initial release: added Game class and tests for it
This commit is contained in:
commit
8a14b0033a
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
dist/
|
||||
|
||||
.venv/
|
||||
.vscode/
|
||||
__pycache__/
|
||||
.coverage
|
||||
.pytest_cache/
|
19
LICENCE
Normal file
19
LICENCE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018 The Python Packaging Authority
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
15
README.md
Normal file
15
README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Tic Tac Toe
|
||||
|
||||
A simple tic tac toe game implementation
|
||||
|
||||
## Usage
|
||||
|
||||
tic-tac-toe module exports main game class `Game` and `Pl` and `Tr` enums to simplify typing.
|
||||
|
||||
### `Game` class
|
||||
|
||||
TODO: Add Game class description
|
||||
|
||||
### `Pl` and `Tr` Enums
|
||||
|
||||
`Pl` has two members: `X` and `O` meaning X and O players. `Tr` enum has all members of `Pl` plus `E` entry meaning empty cell.
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
26
setup.py
Normal file
26
setup.py
Normal file
@ -0,0 +1,26 @@
|
||||
import setuptools
|
||||
|
||||
with open("README.md", "r", encoding="utf-8") as fh:
|
||||
long_description = fh.read()
|
||||
|
||||
setuptools.setup(
|
||||
name="tic-tac-toe-dm1sh",
|
||||
version="0.0.1",
|
||||
author="dm1sh",
|
||||
author_email="me@dmitriy.icu",
|
||||
description="A simple tic tac toe game implementation",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://github.com/dm1sh/tic-tac-toe",
|
||||
project_urls={
|
||||
"Bug Tracker": "https://github.com/dm1sh/tic-tac-toe/issues",
|
||||
},
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
package_dir={"": "src"},
|
||||
packages=setuptools.find_packages(where="src"),
|
||||
python_requires=">=3.6",
|
||||
)
|
3
src/tic_tac_toe/__init__.py
Normal file
3
src/tic_tac_toe/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .game import Game, Tr, Pl
|
||||
|
||||
__all__ = [Game, Tr, Pl]
|
92
src/tic_tac_toe/game.py
Normal file
92
src/tic_tac_toe/game.py
Normal file
@ -0,0 +1,92 @@
|
||||
from typing import List, Tuple
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Pl(Enum):
|
||||
X = 3
|
||||
O = 2
|
||||
|
||||
|
||||
class Tr(Enum):
|
||||
E = 0
|
||||
X = Pl.X
|
||||
O = Pl.O
|
||||
|
||||
|
||||
class Game:
|
||||
"""
|
||||
Board indexes preview:
|
||||
|
||||
[[0, 1, 2],
|
||||
|
||||
[3, 4, 5],
|
||||
|
||||
[6, 7, 8]]
|
||||
"""
|
||||
_board: List[Tr]
|
||||
|
||||
def __init__(self):
|
||||
self._board = [Tr.E] * 9
|
||||
|
||||
def get_board(self) -> List[Tr]:
|
||||
"""
|
||||
Returns copy of game board. Use it to display board in UI.
|
||||
"""
|
||||
return self._board.copy()
|
||||
|
||||
def check_move(self, pos: int) -> bool:
|
||||
"""
|
||||
Checks if board cell empty
|
||||
"""
|
||||
return self._board[pos] == Tr.E
|
||||
|
||||
def check_filled(self) -> bool:
|
||||
"""
|
||||
Checks if game board is filled
|
||||
"""
|
||||
return self._board.count(Tr.E) == 0
|
||||
|
||||
def check_win(self, pos: int) -> bool:
|
||||
"""
|
||||
Checks if this move will make player a winner.
|
||||
"""
|
||||
b = self._board
|
||||
|
||||
row_n = pos - pos % 3
|
||||
row = b[row_n] == b[row_n + 1] == b[row_n + 2] != Tr.E
|
||||
|
||||
col_n = pos % 3
|
||||
col = b[col_n] == b[col_n + 3] == b[col_n + 6] != Tr.E
|
||||
|
||||
diag = False
|
||||
alt_diag = False
|
||||
|
||||
if (pos % 4 == 0):
|
||||
diag = b[0] == b[4] == b[8] != Tr.E
|
||||
if (pos in (2, 4, 6)):
|
||||
alt_diag = b[2] == b[4] == b[6] != Tr.E
|
||||
|
||||
return row or col or diag or alt_diag
|
||||
|
||||
def insert(self, pos: int, who: Tr) -> None:
|
||||
"""
|
||||
Sets game board's cell to specified value. Better use `move` method when possible
|
||||
"""
|
||||
self._board[pos] = who
|
||||
|
||||
def move(self, pos: int, who: Pl) -> bool:
|
||||
"""
|
||||
Sets game board cell to specified value when possible. It also returns true if player has won.
|
||||
"""
|
||||
if (self.check_move(pos)):
|
||||
self.insert(pos, who)
|
||||
return self.check_win(pos)
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_free(self) -> Tuple[int]:
|
||||
"""
|
||||
Returns
|
||||
"""
|
||||
|
||||
return tuple(filter(lambda i: self._board[i] == Tr.E, range(9)))
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
151
tests/test_game.py
Normal file
151
tests/test_game.py
Normal file
@ -0,0 +1,151 @@
|
||||
from src.tic_tac_toe import Game, Tr
|
||||
|
||||
|
||||
def test_init():
|
||||
g = Game()
|
||||
|
||||
assert len(g._board) == 9
|
||||
|
||||
for i in range(9):
|
||||
assert g._board[i] == Tr.E
|
||||
|
||||
|
||||
def setup_insert_1X() -> Game:
|
||||
g = Game()
|
||||
g.insert(1, Tr.X)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def setup_insert_13X5O() -> Game:
|
||||
g = Game()
|
||||
g.insert(1, Tr.X)
|
||||
g.insert(5, Tr.O)
|
||||
g.insert(3, Tr.X)
|
||||
|
||||
return g
|
||||
|
||||
|
||||
def test_insert_1X():
|
||||
g = setup_insert_1X()
|
||||
|
||||
b = g._board
|
||||
assert b.count(Tr.X) == 1
|
||||
assert b.count(Tr.O) == 0
|
||||
assert b.count(Tr.E) == 8
|
||||
|
||||
|
||||
def test_insert_1X5O():
|
||||
g = setup_insert_13X5O()
|
||||
|
||||
b = g._board
|
||||
assert b.count(Tr.X) == 2
|
||||
assert b.count(Tr.O) == 1
|
||||
assert b.count(Tr.E) == 6
|
||||
|
||||
assert b[1] == Tr.X
|
||||
assert b[3] == Tr.X
|
||||
assert b[5] == Tr.O
|
||||
|
||||
|
||||
def test_get_board():
|
||||
g1 = setup_insert_1X()
|
||||
g2 = setup_insert_13X5O()
|
||||
|
||||
for g in (g1, g2):
|
||||
b = g._board
|
||||
test_b = g.get_board()
|
||||
|
||||
for el in Tr:
|
||||
assert b.count(el) == test_b.count(el)
|
||||
|
||||
|
||||
def test_move():
|
||||
g = Game()
|
||||
|
||||
res = g.move(0, Tr.X)
|
||||
assert res == False
|
||||
res_board = g.get_board()
|
||||
assert res_board[0] == Tr.X
|
||||
assert res_board.count(Tr.X) == 1
|
||||
assert res_board.count(Tr.O) == 0
|
||||
assert res_board.count(Tr.E) == 8
|
||||
|
||||
res = g.move(3, Tr.O)
|
||||
assert res == False
|
||||
res_board = g.get_board()
|
||||
assert res_board[3] == Tr.O
|
||||
assert res_board.count(Tr.X) == 1
|
||||
assert res_board.count(Tr.O) == 1
|
||||
assert res_board.count(Tr.E) == 7
|
||||
|
||||
res = g.move(1, Tr.X)
|
||||
assert res == False
|
||||
res_board = g.get_board()
|
||||
assert res_board[1] == Tr.X
|
||||
assert res_board.count(Tr.X) == 2
|
||||
assert res_board.count(Tr.O) == 1
|
||||
assert res_board.count(Tr.E) == 6
|
||||
|
||||
res = g.move(2, Tr.X)
|
||||
assert res == True
|
||||
res_board = g.get_board()
|
||||
assert res_board[2] == Tr.X
|
||||
assert res_board.count(Tr.X) == 3
|
||||
assert res_board.count(Tr.O) == 1
|
||||
assert res_board.count(Tr.E) == 5
|
||||
|
||||
res = g.move(2, Tr.X)
|
||||
assert res == False
|
||||
|
||||
|
||||
def test_get_free():
|
||||
g1 = setup_insert_1X()
|
||||
g2 = setup_insert_13X5O()
|
||||
|
||||
for g in (g1, g2):
|
||||
b = g._board
|
||||
free = g.get_free()
|
||||
|
||||
assert len(free) == b.count(Tr.E)
|
||||
|
||||
for i in free:
|
||||
assert b[i] == Tr.E
|
||||
|
||||
|
||||
def test_check_move():
|
||||
g = setup_insert_13X5O()
|
||||
|
||||
assert g.check_move(3) == False
|
||||
assert g.check_move(0) == True
|
||||
|
||||
|
||||
def test_check_filled():
|
||||
g = Game()
|
||||
|
||||
assert g.check_filled() == False
|
||||
|
||||
for i in range(8):
|
||||
g.insert(i, (Tr.X, Tr.O)[i % 2])
|
||||
assert g.check_filled() == False
|
||||
|
||||
g.insert(8, Tr.X)
|
||||
assert g.check_filled() == True
|
||||
|
||||
|
||||
def test_check_win():
|
||||
for steps in ((0, 1, 2), (0, 3, 6), (0, 4, 8), (2, 4, 6)):
|
||||
g = Game()
|
||||
|
||||
for i in range(9):
|
||||
assert g.check_win(i) == False
|
||||
|
||||
g.insert(5, Tr.O)
|
||||
|
||||
for i in range(9):
|
||||
assert g.check_win(i) == False
|
||||
|
||||
for i in steps:
|
||||
g.insert(i, Tr.X)
|
||||
|
||||
assert g.check_win(i) == True
|
Loading…
x
Reference in New Issue
Block a user