Skip to content

Commit 39d2dbb

Browse files
Add files via upload
0 parents  commit 39d2dbb

5 files changed

Lines changed: 241 additions & 0 deletions

File tree

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 AttackingOrDefending
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# python-tictactoe
2+
[![PyPI version](https://badge.fury.io/py/python-tictactoe.svg)](https://badge.fury.io/py/python-tictactoe) [![Tests](https://github.com/AttackingOrDefending/python-tictactoe/actions/workflows/tests.yml/badge.svg)](https://github.com/AttackingOrDefending/python-tictactoe/actions/workflows/tests.yml) [![Build](https://github.com/AttackingOrDefending/python-tictactoe/actions/workflows/build.yml/badge.svg)](https://github.com/AttackingOrDefending/python-tictactoe/actions/workflows/build.yml)
3+
4+
python-tictactoe is a tic-tac-toe library that supports different sizes and dimensions and how many marks in a row to win.
5+
6+
## Features
7+
8+
* Different board sizes
9+
```python
10+
from tictactoe import Board
11+
12+
board = Board(dimensions=(4, 5))
13+
```
14+
* More than 2 dimensions
15+
```python
16+
from tictactoe import Board
17+
18+
board = Board(dimensions=(6, 2, 5, 8))
19+
```
20+
* More than 3 in a row to win
21+
```python
22+
from tictactoe import Board
23+
24+
board = Board(dimensions=(10, 10, 10), x_in_a_row=8)
25+
```
26+
27+
## License
28+
python-tictactoe is licensed under the MIT License. Check out LICENSE for the full text.

setup.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import setuptools
2+
3+
with open("README.md", "r", encoding="utf-8") as fh:
4+
long_description = fh.read()
5+
6+
setuptools.setup(
7+
name="python-tictactoe",
8+
version="0.0.1",
9+
author="AttackingOrDefending",
10+
description="A tictactoe library for Python with varying board sizes and move generation",
11+
long_description=long_description,
12+
long_description_content_type="text/markdown",
13+
url="https://github.com/AttackingOrDefending/python-tictactoe",
14+
project_urls={
15+
"Bug Tracker": "https://github.com/AttackingOrDefending/python-tictactoe/issues",
16+
},
17+
classifiers=[
18+
"Programming Language :: Python :: 3",
19+
"License :: OSI Approved :: MIT License",
20+
"Operating System :: OS Independent",
21+
],
22+
packages=["tictactoe"],
23+
python_requires=">=3.6",
24+
install_requires=["numpy==1.22.1"],
25+
)

test.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from tictactoe import Board
2+
3+
4+
def draw_board():
5+
board = Board()
6+
board.push((0, 0))
7+
board.push((0, 1))
8+
board.push((0, 2))
9+
board.push((1, 1))
10+
board.push((1, 0))
11+
board.push((2, 0))
12+
board.push((1, 2))
13+
board.push((2, 2))
14+
board.push((2, 1))
15+
return board
16+
17+
18+
def x_win_board():
19+
board = Board()
20+
board.push((0, 0))
21+
board.push((0, 1))
22+
board.push((0, 2))
23+
board.push((1, 1))
24+
board.push((1, 0))
25+
board.push((1, 2))
26+
board.push((2, 0))
27+
return board
28+
29+
30+
def o_win_board():
31+
board = Board()
32+
board.push((0, 0))
33+
board.push((0, 1))
34+
board.push((0, 2))
35+
board.push((1, 1))
36+
board.push((1, 0))
37+
board.push((2, 0))
38+
board.push((1, 2))
39+
board.push((2, 1))
40+
return board
41+
42+
43+
def unfinished_board():
44+
board = Board()
45+
board.push((0, 0))
46+
board.push((0, 1))
47+
board.push((0, 2))
48+
board.push((1, 1))
49+
board.push((1, 0))
50+
board.push((2, 0))
51+
board.push((1, 2))
52+
board.push((2, 2))
53+
return board
54+
55+
56+
def test_result():
57+
assert x_win_board().result() == 1
58+
assert o_win_board().result() == 2
59+
assert draw_board().result() == 0
60+
assert unfinished_board().result() is None
61+
62+
63+
if __name__ == "__main__":
64+
test_result()

tictactoe/__init__.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import itertools
2+
import numpy
3+
4+
X = 1
5+
O = 2
6+
7+
8+
class Board:
9+
def __init__(self, dimensions=(3, 3), x_in_a_row=3):
10+
self.dimensions = dimensions
11+
self.x_in_a_row = x_in_a_row
12+
self.board = self.create_board()
13+
self._directions = self.find_directions()
14+
self.move_count = 0
15+
self.moves = []
16+
self.x = []
17+
self.o = []
18+
self.turn = X
19+
20+
def create_board(self):
21+
return numpy.zeros(self.dimensions)
22+
23+
def copy(self):
24+
board = Board(self.dimensions, self.x_in_a_row)
25+
board.turn = self.turn
26+
board.board = self.board.copy()
27+
return board
28+
29+
def get_mark_at_position(self, position):
30+
position = tuple(position)
31+
return self.board[position]
32+
33+
def set_mark(self, coordinates, player):
34+
self.board[coordinates] = player
35+
if player == X:
36+
self.x.append(Move(coordinates))
37+
else:
38+
self.o.append(Move(coordinates))
39+
40+
def is_empty(self, position):
41+
return self.get_mark_at_position(position) == 0
42+
43+
def push(self, coordinates):
44+
coordinates = tuple(coordinates)
45+
if not self.is_empty(coordinates):
46+
raise Exception("Position is not empty.")
47+
move = Move(coordinates)
48+
self.set_mark(coordinates, self.turn)
49+
self.turn = X if self.turn == O else O
50+
self.moves.append(move)
51+
self.move_count += 1
52+
53+
def find_directions(self):
54+
directions = list(itertools.product([1, 0, -1], repeat=len(self.dimensions)))
55+
correct_directions = []
56+
for direction in directions:
57+
for item in direction:
58+
if item > 0:
59+
correct_directions.append(direction)
60+
break
61+
elif item < 0:
62+
break
63+
return correct_directions
64+
65+
def possible_moves(self):
66+
return numpy.argwhere(self.board == 0)
67+
68+
def out_of_bounds(self, pos):
69+
return (pos < 0).any() or (pos >= self.dimensions).any()
70+
71+
def in_bounds(self, pos):
72+
return not self.out_of_bounds(pos)
73+
74+
def has_won(self, player):
75+
positions = numpy.argwhere(self.board == player)
76+
for position in positions:
77+
for direction in self._directions:
78+
for in_a_row in range(1, self.x_in_a_row):
79+
pos = position + numpy.multiply(direction, in_a_row)
80+
if self.out_of_bounds(pos) or self.board[tuple(pos)] != player:
81+
break
82+
else:
83+
return True
84+
return False
85+
86+
def result(self):
87+
if self.has_won(X):
88+
return X
89+
elif self.has_won(O):
90+
return O
91+
elif self.board.all():
92+
return 0
93+
94+
95+
class Move:
96+
def __init__(self, coordinate_move=None, str_move=None):
97+
assert coordinate_move or str_move
98+
self.coordinate_move = coordinate_move
99+
self.str_move = str_move
100+
if self.coordinate_move:
101+
self.str_move = "-".join(map(str, tuple(self.coordinate_move)))
102+
else:
103+
self.coordinate_move = tuple(map(int, self.str_move.split("-")))

0 commit comments

Comments
 (0)