Skip to content

Commit

Permalink
- Refactored simple tttoe board:
Browse files Browse the repository at this point in the history
  * Doesn't return result value WIN/LOSS/DRAW but instead returns who is the winner. This is used for the main (ultimate) board to determine winner there.
  * Removed keeping track of whose side to move it is (also no playerJustMoved). This is now kept in the main board.
  * Removed history from individual boards. Now this is kept in main board
  * Added method to return a list of tttoe rows (string representation) which are used to build a console output for the full main board
- Added gui file to introduce pygame GUI elements to the game
- Implemented Ultimate board functionality:
  * Getting all moves & getting available moves
  * Take move/Make move
  * Board copy
  * Board string representation
  * Get result (Single board that is a draw is considered unavailable for both players)
- Added .png from pygame example to help me build understanding for pygame
  • Loading branch information
AngelVI13 committed Apr 25, 2019
1 parent 62ceee3 commit aeebea3
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 146 deletions.
233 changes: 111 additions & 122 deletions board.py
Original file line number Diff line number Diff line change
@@ -1,122 +1,111 @@
from collections import deque


PLAYER_X = 1
PLAYER_O = -1
NO_PLAYER = 0

STR_MATRIX = {
PLAYER_X: 'X',
PLAYER_O: 'O',
NO_PLAYER: '-'
}

ROWS = 3
BOARD_SIZE = ROWS*ROWS

LOSS = 0.0
DRAW = 0.5
WIN = 1.0


class BaseBoard:
"""Defines the general structure which a board implementation must implement"""
def __init__(self):
raise NotImplementedError

def __str__(self):
raise NotImplementedError

def __copy__(self):
raise NotImplementedError

def make_move(self, move):
raise NotImplementedError

def take_move(self):
raise NotImplementedError

def get_moves(self):
raise NotImplementedError

def get_result(self, player_jm):
raise NotImplementedError


class Board:
def __init__(self):
self.pos = [0] * BOARD_SIZE
self.side = PLAYER_X
self.playerJustMoved = PLAYER_O
self.history = deque()

def __str__(self):
lines = []
for combo in zip(*[self.pos[i::ROWS] for i in range(ROWS)]):
lines.extend(['{:<5}'.format(STR_MATRIX[elem]) for elem in combo])
lines.append('\n')
return ''.join(lines)

def __copy__(self):
_b = Board()
_b.pos = self.pos[:] # copy list
_b.side = self.side # todo remove this, not needed since player just moved
_b.playerJustMoved = self.playerJustMoved
_b.history = self.history.copy() # todo copying deque is too slow
return _b

def make_move(self, move):
assert move in self.get_moves(), 'Position is already occupied'

self.pos[move] = self.side
self.side = -self.side # change side to move
self.playerJustMoved = -self.playerJustMoved
self.history.append(move)

def take_move(self):
move = self.history.pop()
self.pos[move] = NO_PLAYER
self.side = -self.side # change side to move
self.playerJustMoved = -self.playerJustMoved

def get_moves(self):
return [idx for idx, value in enumerate(self.pos) if value == NO_PLAYER]

def get_result(self, player_jm):
cols_combo = [self.pos[i::ROWS] for i in range(ROWS)]
rows_combo = list(zip(*cols_combo))
# print(cols_combo)
# print(row s_combo)

for i in range(ROWS):
# Sum a row and a column
row_result, col_result = sum(rows_combo[i]), sum(cols_combo[i])

# Check if sum of values of a row is not equal to number of rows i.e. all 1s or all -1s
if abs(row_result) == ROWS:
return WIN if int(row_result / ROWS) == player_jm else LOSS

if abs(col_result) == ROWS:
return WIN if int(col_result / ROWS) == player_jm else LOSS

# Sum values on Right diagonal
# Look at right Diagonal
# exclude last element since it is not part of the diagonal
# i.e. if you have [1, 2, 3,
# 4, 5, 6,
# 7 ,8 ,9] then right diagonal is [3, 5, 7]
# i.e. starting from the right corner the diagonal is formed by every second number
# (3, 5, 7), however this will also result in 9 being included which it should not be
# therefore we remove it
result = sum(self.pos[ROWS - 1::ROWS - 1][:-1])
if abs(result) == ROWS:
return WIN if int(result / ROWS) == player_jm else LOSS

# Left diagonal
result = sum(self.pos[::ROWS + 1])
if abs(result) == ROWS:
return WIN if int(result / ROWS) == player_jm else LOSS

# Lastly check if no available squares are on the board => TIE
if sum([abs(elem) for elem in self.pos]) == BOARD_SIZE:
return DRAW
PLAYER_X = 1
PLAYER_O = -1
NO_PLAYER = 0

STR_MATRIX = {
PLAYER_X: 'X',
PLAYER_O: 'O',
NO_PLAYER: '-'
}

ROWS = 3
BOARD_SIZE = ROWS*ROWS

LOSS = 0.0
DRAW = 0.5
WIN = 1.0


class BaseBoard:
"""Defines the general structure which a board implementation
must implement
"""

def __str__(self):
raise NotImplementedError

def __copy__(self):
raise NotImplementedError

def make_move(self, move):
raise NotImplementedError

def take_move(self):
raise NotImplementedError

def get_moves(self):
raise NotImplementedError

def get_result(self, player_jm):
raise NotImplementedError


class Board:
def __init__(self):
self.pos = [0] * BOARD_SIZE

def get_row_strings(self):
lines = []
for combo in zip(*[self.pos[i::ROWS] for i in range(ROWS)]):
lines.append(''.join(['{:<5}'.format(STR_MATRIX[elem]) for elem in combo]))
return lines

def __copy__(self):
_b = Board()
_b.pos = self.pos[:] # copy list
return _b

def make_move(self, move, side):
assert move in self.get_moves(), 'Position is already occupied'

self.pos[move] = side

def take_move(self, move):
self.pos[move] = NO_PLAYER

def get_moves(self):
return [idx for idx, value in enumerate(self.pos) if value == NO_PLAYER]

def get_result(self, board=None):
if board is None:
board = self.pos

cols_combo = [board[i::ROWS] for i in range(ROWS)]
rows_combo = list(zip(*cols_combo))

for i in range(ROWS):
# Sum a row and a column
row_result, col_result = sum(rows_combo[i]), sum(cols_combo[i])

# Check if sum of values of a row is not equal to number of rows
# i.e. all 1s or all -1s
if abs(row_result) == ROWS:
return int(row_result / ROWS)

if abs(col_result) == ROWS:
return int(col_result / ROWS)

# Sum values on Right diagonal
# Look at right Diagonal
# exclude last element since it is not part of the diagonal
# i.e. if you have [1, 2, 3,
# 4, 5, 6,
# 7 ,8 ,9] then right diagonal is [3, 5, 7]
# i.e. starting from the right corner the diagonal is formed by
# every second number (3, 5, 7), however this will also result
# in 9 being included which it should not be therefore we remove it
result = sum(board[ROWS - 1::ROWS - 1][:-1])
if abs(result) == ROWS:
return int(result / ROWS)

# Left diagonal
result = sum(board[::ROWS + 1])
if abs(result) == ROWS:
return int(result / ROWS)

# Lastly check if no available squares are on the board => TIE
if sum([abs(elem) for elem in board]) == BOARD_SIZE:
# here 0.5 indicates a DRAW and for ultimate tttoe
# this means that a drawn board is not taken into account for
# any player
return DRAW
89 changes: 89 additions & 0 deletions gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# tutorial from https://pythonprogramming.net/drawing-objects-pygame-tutorial/?completed=/displaying-text-pygame-screen/

import pygame
import time

pygame.init()

display_width = 800
display_height = 600

black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)

car_width = 73

gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption('A bit Racey')
clock = pygame.time.Clock()

carImg = pygame.image.load('racecar.png')


def car(x, y):
gameDisplay.blit(carImg, (x, y))


def text_objects(text, font: pygame.font.Font):
textSurface = font.render(text, True, black)
return textSurface, textSurface.get_rect()


def message_display(text):
largeText = pygame.font.Font('freesansbold.ttf', 115)
TextSurf, TextRect = text_objects(text, largeText)
TextRect.center = ((display_width / 2), (display_height / 2))
gameDisplay.blit(TextSurf, TextRect)

pygame.display.update()


def crash():
message_display('You Crashed')
time.sleep(2)

game_loop()


def game_loop():
x = (display_width * 0.45)
y = (display_height * 0.8)

x_change = 0

gameExit = False

while not gameExit:

for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()

if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
x_change = -5
if event.key == pygame.K_RIGHT:
x_change = 5

if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
x_change = 0

x += x_change

gameDisplay.fill(white)
car(x, y)

if x > display_width - car_width or x < 0:
crash()

pygame.display.update()
clock.tick(60)


game_loop()
pygame.quit()
quit()

Binary file added racecar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit aeebea3

Please sign in to comment.