Skip to content

Instantly share code, notes, and snippets.

@skanev
Last active August 29, 2015 14:01
Show Gist options
  • Save skanev/1b39daf4b3288c6cf432 to your computer and use it in GitHub Desktop.
Save skanev/1b39daf4b3288c6cf432 to your computer and use it in GitHub Desktop.
Code from a live demo on the Python FMI course
import re
import os
from game import Game
BOARD = """
0 │ 1 │ 2
───┼───┼───
3 │ 4 │ 5
───┼───┼───
6 │ 7 │ 8
"""
class CLI:
def __init__(self):
self._game = Game()
def play(self):
while self._game.outcome() == Game.IN_PROGRESS:
self._clear()
self._draw_board()
move = None
while move is None:
move = self._get_move()
self._game.play(move)
self._clear()
self._draw_board()
outcome = self._game.outcome()
if outcome == Game.X_WINS:
print("Whoo! X wins!")
elif outcome == Game.O_WINS:
print("Ooooh wins!")
else:
print("It's a tie (⧓)!")
def _clear(self):
os.system('clear')
def _draw_board(self):
game = self._game
chars = {Game.X: 'x', Game.O: 'o', None: ' '}
state = [chars[game.at(i)] for i in range(9)]
print(self._fill_board(state))
def _get_move(self):
print("What is your move (1-9)?: ", end='')
try:
number = int(input()) - 1
if self._game.valid_move(number):
return number
except ValueError:
return None
def _fill_board(self, symbols):
# ['x' , ' ', ' ', 'o', ' ']
# "x o "
def replacement(x):
i = int(x.group())
return symbols[i]
result = re.sub(r"\d", replacement, BOARD)
return result
CLI().play()
class InvalidMoveError(Exception):
pass
class Game:
IN_PROGRESS = '<in-progress>'
O_WINS = '<o-wins>'
X_WINS = '<x-wins>'
TIE = '<tie>'
X = 'x'
O = 'o'
EMPTY = None
WINNING_LINES = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[6, 4, 2],
]
EMPTY_BOARD = [EMPTY] * 9
def __init__(self, board=None):
self._board = board or self.EMPTY_BOARD[:]
self._player = self.X
def outcome(self):
for line in self.WINNING_LINES:
player = self._player_on(line)
if not player:
continue
return {
self.O: self.O_WINS,
self.X: self.X_WINS,
}[player]
if all(s != self.EMPTY for s in self._board):
return self.TIE
return self.IN_PROGRESS
def current_player(self):
return self._player
def play(self, position):
if not self.valid_move(position):
raise InvalidMoveError("Whoops!")
self._board[position] = self._player
if self._player == self.X:
self._player = self.O
else:
self._player = self.X
def at(self, position):
return self._board[position]
def valid_move(self, position):
return position in range(9) and \
self._board[position] == self.EMPTY and \
self.outcome() == self.IN_PROGRESS
def _player_on(self, indices):
board = self._board
squares = [board[i] for i in indices]
if squares[0] == self.EMPTY:
return None
player = squares[0]
if all(s == player for s in squares):
return player
import pygame, sys
from game import Game
pygame.init()
SIZE = 100
BORDER = 10
PADDING = 20
DIMENSION = SIZE * 3 + BORDER * 4
THICKNESS = 10
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
OVERLAY_ALPHA = 180
screen = pygame.display.set_mode([DIMENSION, DIMENSION])
font = pygame.font.SysFont("monospace", 33, True)
game = Game()
def square_to_rectangle(square):
row = square // 3
column = square % 3
x = BORDER + (SIZE + BORDER) * row
y = BORDER + (SIZE + BORDER) * column
return [x, y, SIZE, SIZE]
def square_to_inner_rectangle(square):
x, y, w, h = square_to_rectangle(square)
x += PADDING
y += PADDING
w -= PADDING * 2
h -= PADDING * 2
return [x, y, w, h]
def draw_x(square):
x, y, w, h = square_to_inner_rectangle(square)
pygame.draw.line(screen, WHITE, [x, y], [x + w, y + h], THICKNESS)
pygame.draw.line(screen, WHITE, [x + w, y], [x, y + h], THICKNESS)
def draw_o(square):
x, y, w, h = square_to_inner_rectangle(square)
pygame.draw.ellipse(screen, WHITE, [x, y, w, h], THICKNESS)
def within(point, rectangle):
x, y = point
l, t, w, h = rectangle
return l <= x <= l + w and t <= y <= t + h
def square_for(position):
for i in range(9):
rectangle = square_to_rectangle(i)
if within(position, rectangle):
return i
return None
def draw_board():
screen.fill(WHITE)
for i in range(9):
rectangle = square_to_rectangle(i)
pygame.draw.rect(screen, BLACK, rectangle)
for i in range(9):
if game.at(i) == Game.X:
draw_x(i)
elif game.at(i) == Game.O:
draw_o(i)
def draw_message(msg):
label = font.render(msg, 1, WHITE)
rect = label.get_rect()
rect.centerx = screen.get_rect().centerx
rect.centery = screen.get_rect().centery
overlay = pygame.Surface((DIMENSION, DIMENSION))
overlay.set_alpha(OVERLAY_ALPHA)
overlay.fill(BLACK)
screen.blit(overlay, (0, 0))
screen.blit(label, rect)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP:
position = pygame.mouse.get_pos()
square = square_for(position)
if game.valid_move(square):
game.play(square)
draw_board()
outcome = game.outcome()
if outcome == Game.X_WINS:
draw_message("X wins")
elif outcome == Game.O_WINS:
draw_message("O wins")
elif outcome == Game.TIE:
draw_message("Tie ;(")
pygame.display.flip()
gui:
python2 gui.py
cli:
python3 cli.py
test:
python3 tests.py

ДИЗАЙН

  • Да напишем тестове с очаквано поведение
  • Ще извлечем отделни функционалности

Клас Game:

  • Outcome на игра (върви в момента, X, O, равен)
  • Кой е на ход?
  • Игране на ход
  • Проверка дали ход е валиден
  • Състояние на дъска

class Game: IN_PROGRESS O_WINS X_WINS TIE

X
O
EMPTY

def outcome(self):
def current_player(self):
def play(self, position):
def at(self, position):
def valid_move(self, position):
import unittest
from game import Game, InvalidMoveError
class GameTest(unittest.TestCase):
def createGame(self, board):
mapping = {
'O': Game.O,
'X': Game.X,
' ': Game.EMPTY,
}
state = [mapping[s] for s in board]
return Game(state)
def assertOutcome(self, expected_outcome, board):
game = self.createGame(board)
self.assertEqual(
game.outcome(),
expected_outcome)
def testDeterminingInProgress(self):
self.assertOutcome(Game.IN_PROGRESS, [
' ', ' ', ' ',
' ', ' ', ' ',
' ', ' ', ' ',
])
self.assertOutcome(Game.IN_PROGRESS, [
'X', 'O', ' ',
'X', 'X', ' ',
' ', 'O', ' ',
])
def testDeterminingWinOnHorizontals(self):
self.assertOutcome(Game.O_WINS, [
'O', 'O', 'O',
' ', ' ', ' ',
' ', ' ', ' ',
])
self.assertOutcome(Game.X_WINS, [
' ', ' ', ' ',
'X', 'X', 'X',
' ', ' ', ' ',
])
self.assertOutcome(Game.O_WINS, [
' ', ' ', ' ',
' ', ' ', ' ',
'O', 'O', 'O',
])
def testDeterminingWinOnVerticals(self):
self.assertOutcome(Game.O_WINS, [
'O', ' ', ' ',
'O', ' ', ' ',
'O', ' ', ' ',
])
self.assertOutcome(Game.X_WINS, [
' ', 'X', ' ',
' ', 'X', ' ',
' ', 'X', ' ',
])
self.assertOutcome(Game.O_WINS, [
' ', ' ', 'O',
' ', ' ', 'O',
' ', ' ', 'O',
])
def testDeterminingWinOnDiagonals(self):
self.assertOutcome(Game.O_WINS, [
'O', ' ', ' ',
' ', 'O', ' ',
' ', ' ', 'O',
])
self.assertOutcome(Game.X_WINS, [
' ', ' ', 'X',
' ', 'X', ' ',
'X', ' ', ' ',
])
def testDeterminingTie(self):
self.assertOutcome(Game.TIE, [
'O', 'X', 'X',
'X', 'O', 'O',
'O', 'X', 'X',
])
def testPlayingAMove(self):
game = Game()
self.assertEqual(
game.current_player(),
Game.X)
self.assertEqual(game.outcome(),
Game.IN_PROGRESS)
game.play(1)
self.assertEqual(
game.current_player(),
Game.O)
game.play(2)
self.assertEqual(
game.current_player(),
Game.X)
def testPlayingAFewMoves(self):
game = Game()
game.play(0)
game.play(1)
game.play(2)
self.assertEqual(
game.current_player(),
Game.O)
self.assertEqual(game.at(0), Game.X)
self.assertEqual(game.at(1), Game.O)
self.assertEqual(game.at(2), Game.X)
def testIsMoveValid(self):
game = Game()
self.assertTrue(game.valid_move(0))
self.assertTrue(game.valid_move(8))
def testMovesOutsideTheBoardAreInvalid(self):
game = Game()
self.assertFalse(game.valid_move(-1))
self.assertFalse(game.valid_move(9))
def testMoveOnOccupiedSquareIsInvalid(self):
game = self.createGame([
' ', 'X', ' ',
' ', ' ', ' ',
' ', ' ', ' ',
])
self.assertTrue(game.valid_move(0))
self.assertFalse(game.valid_move(1))
def testMoveIsInvalidIfGameIsComplete(self):
game = self.createGame([
'O', 'X', ' ',
'O', ' ', 'X',
'O', 'X', 'X',
])
self.assertFalse(game.valid_move(2))
def testMoveIsInvalidIfGameIsComplete(self):
game = self.createGame([
'O', 'X', ' ',
'O', ' ', 'X',
'O', 'X', 'X',
])
with self.assertRaises(InvalidMoveError):
game.play(2)
if __name__ == '__main__':
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment