tictactoe in python
This commit is contained in:
1 parent
2180697c56
commit
29edc46339
11 files changed
+422
No files matched your search
@@ -0,0 +1,35 @@
|
||||
name: Python
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "python/**"
|
||||
- "**/python.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
PYTHONPATH=src pytest
|
||||
@@ -0,0 +1,4 @@
|
||||
__pycache__
|
||||
.pytest_cache
|
||||
.venv
|
||||
.vscode
|
||||
@@ -0,0 +1,29 @@
|
||||
# TicTacToe in Python 🐍
|
||||
|
||||
## Time spent
|
||||
- Total : 9 hrs 45 mins
|
||||
- `tictactoe.py` : 53 mins (transformed in Grid, Case and const later)
|
||||
- `test_tictactoe.py` : 45 mins
|
||||
- `CLI.py` : 24 mins
|
||||
- `GLI.py` : 16 mins
|
||||
- `python.yml` : 12 mins
|
||||
- `requirements.txt` : 2 min
|
||||
|
||||
## Dependencies
|
||||
|
||||
```bash
|
||||
sudo apt install python3 python3-pip
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
src/CLI.py
|
||||
```
|
||||
|
||||
## Graphical Interface
|
||||
|
||||
```bash
|
||||
src/GUI.py
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
pygame
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from tictactoe import Grid
|
||||
|
||||
|
||||
def main():
|
||||
game = Grid(3)
|
||||
outcome = game.outCome()
|
||||
while outcome == "playing":
|
||||
print(str(game))
|
||||
playerInput = input(game.whoIsPlaying() + " to play: ")
|
||||
if playerInput == "exit":
|
||||
break
|
||||
try:
|
||||
x = int(playerInput[0])
|
||||
y = int(playerInput[2])
|
||||
if not game.play(x, y):
|
||||
print("Invalid move")
|
||||
except:
|
||||
print("Invalid move")
|
||||
outcome = game.outCome()
|
||||
print(str(game))
|
||||
if outcome == "draw":
|
||||
print("Draw 😞\n")
|
||||
else:
|
||||
print("Circle wins 🎉\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import pygame
|
||||
from tictactoe import Grid
|
||||
|
||||
# Define some colors
|
||||
BLACK = (0, 0, 0)
|
||||
WHITE = (255, 255, 255)
|
||||
RED = (255, 0, 0)
|
||||
GREEN = (0, 255, 0)
|
||||
BLUE = (0, 0, 255)
|
||||
|
||||
SIZE = 3
|
||||
|
||||
game = Grid(SIZE)
|
||||
|
||||
# pygame setup
|
||||
pygame.init()
|
||||
screen = pygame.display.set_mode((SIZE * 200, SIZE * 200))
|
||||
pygame.display.set_caption("Tic Tac Toe in Python")
|
||||
clock = pygame.time.Clock()
|
||||
running = True
|
||||
|
||||
while running:
|
||||
outcome = game.outCome()
|
||||
|
||||
# poll for events
|
||||
# pygame.QUIT event means the user clicked X to close your window
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
if event.type == pygame.MOUSEBUTTONDOWN and outcome == "playing":
|
||||
x, y = pygame.mouse.get_pos()
|
||||
game.play(x // 200, y // 200)
|
||||
|
||||
# RENDER YOUR GAME HERE
|
||||
screen.fill(WHITE)
|
||||
for cList in game.grid:
|
||||
for c in cList:
|
||||
pygame.draw.rect(screen, BLACK, (c.x * 200, c.y * 200, 200, 200), 1)
|
||||
if c.what() == "circle":
|
||||
pygame.draw.circle(screen, RED, (c.x * 200 + 100, c.y * 200 + 100), 80)
|
||||
elif c.what() == "cross":
|
||||
pygame.draw.line(screen, RED, (c.x * 200 + 20, c.y * 200 + 20), (c.x * 200 + 180, c.y * 200 + 180), 10)
|
||||
pygame.draw.line(screen, RED, (c.x * 200 + 180, c.y * 200 + 20), (c.x * 200 + 20, c.y * 200 + 180), 10)
|
||||
if c.isOnWinLine:
|
||||
pygame.draw.rect(screen, GREEN, (c.x * 200, c.y * 200, 200, 200), 10)
|
||||
|
||||
# flip() the display to put your work on screen
|
||||
pygame.display.flip()
|
||||
|
||||
clock.tick(60) # limits FPS to 60
|
||||
|
||||
pygame.quit()
|
||||
@@ -0,0 +1,20 @@
|
||||
from .const import CASECONTENT
|
||||
|
||||
|
||||
class Case:
|
||||
|
||||
def __init__(self, char, x, y):
|
||||
self.char = char
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.isOnWinLine = False
|
||||
|
||||
def what(self):
|
||||
for key, value in CASECONTENT.items():
|
||||
if self.char == value:
|
||||
return key
|
||||
|
||||
def __str__(self):
|
||||
if self.isOnWinLine:
|
||||
return "-" + self.char + "-"
|
||||
return " " + self.char + " "
|
||||
@@ -0,0 +1,71 @@
|
||||
from .Case import Case
|
||||
from .const import CASECONTENT, DIRECTIONS
|
||||
|
||||
|
||||
class Grid:
|
||||
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
self.grid = [[Case(CASECONTENT["empty"], x, y) for x in range(size)] for y in range(size)]
|
||||
self.turn = 1
|
||||
|
||||
def whoIsPlaying(self):
|
||||
return list(CASECONTENT.keys())[self.turn % 2]
|
||||
|
||||
def __str__(self):
|
||||
ret = ""
|
||||
for _ in range(self.size):
|
||||
ret += "-------"
|
||||
ret += "-\n"
|
||||
for y in range(self.size):
|
||||
ret += "|"
|
||||
for x in range(self.size):
|
||||
ret += str(x) + str(self.grid[x][y]) + str(y)
|
||||
if x != self.size - 1:
|
||||
ret += "|"
|
||||
ret += "|\n"
|
||||
for _ in range(self.size):
|
||||
ret += "-------"
|
||||
ret += "-\n"
|
||||
return ret
|
||||
|
||||
def getCaseDir(self, case: Case, dir):
|
||||
retX = case.x + dir[0]
|
||||
retY = case.y + dir[1]
|
||||
if retX < 0 or retX >= self.size or retY < 0 or retY >= self.size:
|
||||
return None
|
||||
return self.grid[retX][retY]
|
||||
|
||||
def nbCaseEmpty(self):
|
||||
ret = 0
|
||||
for cList in self.grid:
|
||||
for c in cList:
|
||||
if c.what() == "empty":
|
||||
ret += 1
|
||||
return ret
|
||||
|
||||
def outCome(self):
|
||||
if self.nbCaseEmpty() == 0:
|
||||
return "draw"
|
||||
for cList in self.grid:
|
||||
for c in cList:
|
||||
for dir in DIRECTIONS:
|
||||
case1 = self.getCaseDir(c, DIRECTIONS[dir])
|
||||
if case1 is None:
|
||||
continue
|
||||
case2 = self.getCaseDir(case1, DIRECTIONS[dir])
|
||||
if case2 is None:
|
||||
continue
|
||||
if (c.what() == case1.what()) and (c.what() == case2.what()) and (c.what() != "empty"):
|
||||
c.isOnWinLine = True
|
||||
case1.isOnWinLine = True
|
||||
case2.isOnWinLine = True
|
||||
return c.what()
|
||||
return "playing"
|
||||
|
||||
def play(self, x, y):
|
||||
if self.grid[x][y].what() == "empty":
|
||||
self.grid[x][y] = Case(list(CASECONTENT.values())[self.turn % 2], x, y)
|
||||
self.turn += 1
|
||||
return True
|
||||
return False
|
||||
@@ -0,0 +1,3 @@
|
||||
from .Grid import Grid
|
||||
from .Case import Case
|
||||
from .const import *
|
||||
@@ -0,0 +1,2 @@
|
||||
CASECONTENT = {"cross": "❌", "circle": "⭕️", "empty": " "}
|
||||
DIRECTIONS = {"up": [0, 1], "down": [0, -1], "left": [-1, 0], "right": [1, 0], "upLeft": [-1, 1], "upRight": [1, 1], "downLeft": [-1, -1], "downRight": [1, -1]}
|
||||
@@ -0,0 +1,173 @@
|
||||
from tictactoe import *
|
||||
|
||||
|
||||
def testCase():
|
||||
case = Case(CASECONTENT["empty"], 0, 0)
|
||||
assert case.what() == "empty"
|
||||
assert case.x == 0
|
||||
assert case.y == 0
|
||||
assert case.isOnWinLine == False
|
||||
case = Case(CASECONTENT["circle"], 1, 2)
|
||||
assert case.what() == "circle"
|
||||
assert case.x == 1
|
||||
assert case.y == 2
|
||||
assert case.isOnWinLine == False
|
||||
case = Case(CASECONTENT["cross"], 2, 1)
|
||||
assert case.what() == "cross"
|
||||
assert case.x == 2
|
||||
assert case.y == 1
|
||||
assert case.isOnWinLine == False
|
||||
print("testCase passed")
|
||||
|
||||
|
||||
def testGrid():
|
||||
game = Grid(3)
|
||||
assert game.size == 3
|
||||
assert game.turn == 1
|
||||
assert game.grid[0][0].what() == "empty"
|
||||
assert game.grid[0][0].x == 0
|
||||
assert game.grid[0][0].y == 0
|
||||
assert game.grid[0][0].isOnWinLine == False
|
||||
assert game.grid[1][1].what() == "empty"
|
||||
assert game.grid[1][1].x == 1
|
||||
assert game.grid[1][1].y == 1
|
||||
assert game.grid[1][1].isOnWinLine == False
|
||||
assert game.grid[2][2].what() == "empty"
|
||||
assert game.grid[2][2].x == 2
|
||||
assert game.grid[2][2].y == 2
|
||||
assert game.grid[2][2].isOnWinLine == False
|
||||
print("testGrid passed")
|
||||
|
||||
|
||||
def testGridStr():
|
||||
game = Grid(3)
|
||||
assert str(
|
||||
game
|
||||
) == "----------------------\n|0 0|1 0|2 0|\n----------------------\n|0 1|1 1|2 1|\n----------------------\n|0 2|1 2|2 2|\n----------------------\n"
|
||||
print("testGridStr passed")
|
||||
|
||||
|
||||
def testGridBehavour():
|
||||
# testing vertical line win
|
||||
game = Grid(3)
|
||||
assert game.grid[0][0].what() == "empty"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(0, 0) == True
|
||||
assert game.play(0, 0) == False
|
||||
assert game.grid[0][0].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(1, 1) == True
|
||||
assert game.play(1, 1) == False
|
||||
assert game.grid[1][1].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(2, 2) == True
|
||||
assert game.play(2, 2) == False
|
||||
assert game.grid[2][2].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(1, 0) == True
|
||||
assert game.play(1, 0) == False
|
||||
assert game.grid[1][0].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(0, 2) == True
|
||||
assert game.play(0, 2) == False
|
||||
assert game.grid[0][2].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(1, 2) == True
|
||||
assert game.play(1, 2) == False
|
||||
assert game.grid[1][2].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.outCome() == "cross"
|
||||
print("vertical line win passed")
|
||||
# testing digonal win
|
||||
game = Grid(3)
|
||||
assert game.grid[0][0].what() == "empty"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(0, 0) == True
|
||||
assert game.play(0, 0) == False
|
||||
assert game.grid[0][0].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(0, 1) == True
|
||||
assert game.play(0, 1) == False
|
||||
assert game.grid[0][1].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(1, 1) == True
|
||||
assert game.play(1, 1) == False
|
||||
assert game.grid[1][1].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(0, 2) == True
|
||||
assert game.play(0, 2) == False
|
||||
assert game.grid[0][2].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(2, 2) == True
|
||||
assert game.play(2, 2) == False
|
||||
assert game.grid[2][2].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.outCome() == "circle"
|
||||
print("diagonal win passed")
|
||||
# testing horizontal line win
|
||||
game = Grid(3)
|
||||
assert game.grid[0][0].what() == "empty"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(0, 0) == True
|
||||
assert game.play(0, 0) == False
|
||||
assert game.grid[0][0].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(0, 1) == True
|
||||
assert game.play(0, 1) == False
|
||||
assert game.grid[0][1].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(1, 0) == True
|
||||
assert game.play(1, 0) == False
|
||||
assert game.grid[1][0].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(1, 1) == True
|
||||
assert game.play(1, 1) == False
|
||||
assert game.grid[1][1].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(2, 0) == True
|
||||
assert game.play(2, 0) == False
|
||||
assert game.grid[2][0].what() == "circle"
|
||||
assert game.outCome() == "circle"
|
||||
print("horizontal line win passed")
|
||||
# testing draw
|
||||
game = Grid(3)
|
||||
assert game.grid[0][0].what() == "empty"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(0, 0) == True
|
||||
assert game.play(0, 0) == False
|
||||
assert game.grid[0][0].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(0, 1) == True
|
||||
assert game.play(0, 1) == False
|
||||
assert game.grid[0][1].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(0, 2) == True
|
||||
assert game.play(0, 2) == False
|
||||
assert game.grid[0][2].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(1, 0) == True
|
||||
assert game.play(1, 0) == False
|
||||
assert game.grid[1][0].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(1, 1) == True
|
||||
assert game.play(1, 1) == False
|
||||
assert game.grid[1][1].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(1, 2) == True
|
||||
assert game.play(1, 2) == False
|
||||
assert game.grid[1][2].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(2, 0) == True
|
||||
assert game.play(2, 0) == False
|
||||
assert game.grid[2][0].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.play(2, 1) == True
|
||||
assert game.play(2, 1) == False
|
||||
assert game.grid[2][1].what() == "cross"
|
||||
assert game.whoIsPlaying() == "circle"
|
||||
assert game.play(2, 2) == True
|
||||
assert game.play(2, 2) == False
|
||||
assert game.grid[2][2].what() == "circle"
|
||||
assert game.whoIsPlaying() == "cross"
|
||||
assert game.outCome() == "draw"
|
||||
print("draw passed")
|
||||
Reference in new issue
Block a user