-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
Showing
4 changed files
with
363 additions
and
146 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
|
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.