Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
AngelVI13 committed May 6, 2019
2 parents e88879d + 3b9f24f commit 4d71d19
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 35 deletions.
2 changes: 1 addition & 1 deletion board/base_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __str__(self):
def __copy__(self):
raise NotImplementedError

def make_move(self, move):
def make_move(self, *args, **kwargs):
raise NotImplementedError

def take_move(self):
Expand Down
14 changes: 8 additions & 6 deletions board/ultimate_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from board.base_board import *


Move = namedtuple('Move', ['board_idx', 'move_idx'])
Move = namedtuple('Move', ['board_idx', 'move_idx', 'forced_board'])


class UltimateBoard(BaseBoard):
Expand Down Expand Up @@ -80,13 +80,13 @@ def _is_board_valid(self, board: int):
# if board is not None but nextBoard is None (i.e. start of game)
return True, board

def make_move(self, move: int, board: int = None):
def make_move(self, board: int, move: int):
"""Make a move to the specified board, if no board is specified
the move is done on the board indicated by nextBoard i.e forced
by the last played move.
@param move: move to play (i.e. index on the board)
@param board: index indicating which board to play the move on
@param move: move to play (i.e. index on the board)
"""
self.playerJustMoved = -self.playerJustMoved

Expand All @@ -98,14 +98,16 @@ def _make_move(self, move: int, board: int):
"""Actually perform move when the validity of the move has already been determined."""

self.pos[board].make_move(move, self.playerJustMoved)
self.history.append(Move(board_idx=board, move_idx=move))
self.history.append(Move(board_idx=board, move_idx=move, forced_board=self.nextBoard))
# the move on the board represents the next board
self.nextBoard = move if self.pos[move].get_result() is None else ANY_BOARD

def take_move(self):
self.playerJustMoved = -self.playerJustMoved

move = self.history.pop()
self.pos[move.board_idx].take_move(move.move_idx)
self.nextBoard = move.board_idx # update nextBoard to be the one forced
self.nextBoard = move.forced_board # update nextBoard to be the one forced

def get_all_moves(self) -> List[Tuple[int, int]]:
"""Get all possible moves in the form of a list of tuples
Expand Down Expand Up @@ -177,7 +179,7 @@ def get_result(self, player_jm):
# user_input.strip()
# user_input = user_input.split(' ')
# target_board, move_idx = int(user_input[0]), int(user_input[1])
ub.make_move(move_idx, target_board)
ub.make_move(target_board, move_idx)
print(ub)
print('\n\n')

Expand Down
13 changes: 7 additions & 6 deletions engine/uct.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def uct_multi(rootstate_: Board, itermax, verbose):
processes = []
for move in moves:
current_state = rootstate_.__copy__()
current_state.make_move(move)
current_state.make_move(*move)
p = Process(target=uct, args=(queue, move, current_state, avg_iters, verbose))
p.start()
processes.append(p)
Expand All @@ -28,8 +28,9 @@ def uct_multi(rootstate_: Board, itermax, verbose):
process.join()
# for move in moves:
# state = rootstate_.__copy__()
# state.make_move(move)
# state.make_move(*move)
# uct(queue, move, state, avg_iters, verbose)
# time.sleep(0.1)

results = []
while not queue.empty():
Expand Down Expand Up @@ -61,19 +62,19 @@ def uct(queue: Queue, move_origin, rootstate, itermax, verbose=False):
# Select
while not node.untriedMoves and node.childNodes: # node is fully expanded and non-terminal
node = node.uct_select_child()
state.make_move(node.move)
state.make_move(*node.move)
moves_to_root += 1

# Expand
if node.untriedMoves: # if we can expand (i.e. state/node is non-terminal)
m = rand_choice(node.untriedMoves)
state.make_move(m)
state.make_move(*m)
moves_to_root += 1
node = node.add_child(m, state) # add child and descend tree

# Rollout - this can often be made orders of magnitude quicker using a state.GetRandomMove() function
while state.get_result(state.side) is None: # while state is non-terminal
state.make_move(rand_choice(state.get_moves()))
while state.get_result(state.playerJustMoved) is None: # while state is non-terminal
state.make_move(*rand_choice(state.get_moves()))
moves_to_root += 1

# Backpropagate
Expand Down
23 changes: 18 additions & 5 deletions gui/colors.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
# colors.py
# Contains color definitions in the form of tuples (length 3 -> RGB representation)

# blue - 3b d6 ff
# blue highlight - 31 b0 d1
# red - ff 10 53
# red highlight - d1 0E 44
# grey - 7b 81 89

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 94, 88)
RED_HIGHLIGHT = (255, 219, 212)
# RED = (255, 94, 88)
RED = (0xff, 0x10, 0x53)
# RED_HIGHLIGHT = (255, 219, 212)
RED_HIGHLIGHT = (0xd1, 0x0e, 0x44)
GREEN = (0, 200, 0)
GREEN_HIGHLIGHT = (0, 255, 0)
BLUE = (59, 214, 255)
BLUE_HIGHLIGHT = (164, 217, 221)
GREY = (170, 191, 193)
PURPLE = (0x6c, 0x6e, 0xA0)
PURPLE_HIGHLIGHT = (0x59, 0x5b, 0x83)
# BLUE = (59, 214, 255)
BLUE = (0x3b, 0xd6, 0xff)
# BLUE_HIGHLIGHT = (164, 217, 221)
BLUE_HIGHLIGHT = (0x31, 0xb0, 0xd1)
# GREY = (170, 191, 193)
GREY = (0x7b, 0x81, 0x89)
YELLOW = (0, 250, 250)
HIGHLIGHT_LOW = 140
HIGHLIGHT_HIGH = 200
Expand Down
11 changes: 6 additions & 5 deletions gui/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ class GameType(Enum):
BUTTON_WIDTH, BUTTON_HEIGHT = 300, 50
BUTTON_Y_SPACING = 1.5

MENU_BUTTON_POSITIONS = {
MENU_BUTTON_PROPERTIES = {
0: {'x': (DISPLAY_WIDTH-BUTTON_WIDTH) / 2, 'y': (DISPLAY_HEIGHT / 3) + 1*BUTTON_HEIGHT*BUTTON_Y_SPACING,
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': GREEN, 'ac': GREEN_HIGHLIGHT},
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': PURPLE, 'ac': PURPLE_HIGHLIGHT},
1: {'x': (DISPLAY_WIDTH-BUTTON_WIDTH) / 2, 'y': (DISPLAY_HEIGHT / 3) + 2*BUTTON_HEIGHT*BUTTON_Y_SPACING,
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': GREEN, 'ac': GREEN_HIGHLIGHT},
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': PURPLE, 'ac': PURPLE_HIGHLIGHT},
2: {'x': (DISPLAY_WIDTH-BUTTON_WIDTH) / 2, 'y': (DISPLAY_HEIGHT / 3) + 3*BUTTON_HEIGHT*BUTTON_Y_SPACING,
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': GREEN, 'ac': GREEN_HIGHLIGHT},
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': PURPLE, 'ac': PURPLE_HIGHLIGHT},
3: {'x': (DISPLAY_WIDTH-BUTTON_WIDTH) / 2, 'y': (DISPLAY_HEIGHT / 3) + 4*BUTTON_HEIGHT*BUTTON_Y_SPACING,
'w': BUTTON_WIDTH, 'h': BUTTON_HEIGHT, 'ic': RED, 'ac': RED_HIGHLIGHT},
}
Expand Down Expand Up @@ -103,7 +103,8 @@ class GameType(Enum):
'w': MAIN_BOX_WIDTH, 'h': MAIN_BOX_HEIGHT},
]

FRAMES_PER_SECOND = 60
GAME_FRAMES_PER_SECOND = 60
MENU_FRAMES_PER_SECOND = 30
# after entering game loop pause for some time before allowing the user to click
# fixes issues with accidental clicks
PAUSE_BEFORE_USER_INPUT = 1
31 changes: 22 additions & 9 deletions gui/gui_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ def subcell_clicked(self, cell):
self.clicked_cells.add(cell)

move, board = cell.cell_idx, cell.board_idx
self.board.make_move(move, board)
self.board.make_move(board, move)
# print(board, move)
# print(self.board)
# print(self.board.playerJustMoved, self.board.nextBoard)
self.allowed_cells = self.find_allowed_cells()
# print(self.allowed_cells)
# print()

result = self.board.pos[board].get_result()
if result is not None:
Expand Down Expand Up @@ -92,18 +97,25 @@ def click_random_cell(self):
time.sleep(0.2) # sleep 1s so output can be checked

def get_best_engine_move(self):
print(uct_multi(self.board, itermax=10000, verbose=False))
return uct_multi(self.board, itermax=1000, verbose=False)

def do_nothing(self):
pass # todo remove this later

def get_game_input(self, game_type, mouse_pos):
if game_type == GameType.SINGLE_PLAYER:
# self.click_random_cell() used for debugginh
if self.board.playerJustMoved == PLAYER_O: # X's turn todo allow user to select side to play
if mouse_pos is not None:
self.click_cell_under_mouse(mouse_pos)
else:
self.click_random_cell() # todo replace with AI
board, move = self.get_best_engine_move()
for cell in self.allowed_cells:
if cell.board_idx == board and cell.cell_idx == move:
self.subcell_clicked(cell)
break
else:
raise Exception('Wrong engine move (move not in allowed moves) ({}{})'.format(board, move))

elif game_type == GameType.MULTI_PLAYER:
if mouse_pos is not None:
Expand Down Expand Up @@ -147,7 +159,7 @@ def game_loop(self, game_type):
self.draw_side_to_move(-self.board.playerJustMoved)

pygame.display.update()
self.clock.tick(FRAMES_PER_SECOND)
self.clock.tick(GAME_FRAMES_PER_SECOND)

def menu_loop(self):
while True:
Expand All @@ -156,18 +168,19 @@ def menu_loop(self):
self.quit_game()

self.gameDisplay.fill(WHITE)
self.draw_menu_animation()
self.message_display("Ultimate Tic Tac Toe", pos=(DISPLAY_WIDTH / 2, DISPLAY_HEIGHT / 3),
font='comicsansms', size=40, update=False)

self.button("Single Player", **MENU_BUTTON_POSITIONS[0],
self.button("Single Player", **MENU_BUTTON_PROPERTIES[0],
action=partial(self.game_loop, GameType.SINGLE_PLAYER))
self.button("Two Player", **MENU_BUTTON_POSITIONS[1],
self.button("Two Player", **MENU_BUTTON_PROPERTIES[1],
action=partial(self.game_loop, GameType.MULTI_PLAYER))
self.button("Settings", **MENU_BUTTON_POSITIONS[2], action=self.do_nothing)
self.button("Quit", **MENU_BUTTON_POSITIONS[3], action=self.quit_game)
self.button("Settings", **MENU_BUTTON_PROPERTIES[2], action=self.do_nothing)
self.button("Quit", **MENU_BUTTON_PROPERTIES[3], action=self.quit_game)

pygame.display.update()
self.clock.tick(15)
self.clock.tick(MENU_FRAMES_PER_SECOND)


if __name__ == '__main__':
Expand Down
10 changes: 7 additions & 3 deletions gui/gui_board.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pygame
from gui.constants import *
from board.ultimate_board import *
from gui.menu_background import Background


@total_ordering
Expand All @@ -20,9 +21,8 @@ def __init__(self, pos_x, pos_y, width, height, player, board_idx=None, cell_idx
self.board_idx = board_idx
self.cell_idx = cell_idx

def __repr__(self):
return '{name}(pos_x={x}, pos_y={y}, width={w}, height={h}, player={p})'.format(
name=self.__class__.__name__, x=self.pos_x, y=self.pos_y, w=self.width, h=self.height, p=self.player)
def __repr__(self): # todo only used for debugging
return '{}(board={}, cell={})'.format(self.__class__.__name__, self.board_idx, self.cell_idx)

def __hash__(self):
return hash((self.pos_x, self.pos_y))
Expand All @@ -49,6 +49,7 @@ def __init__(self):
self.gameDisplay = pygame.display.set_mode((DISPLAY_WIDTH, DISPLAY_HEIGHT))
pygame.display.set_caption('Ultimate Tic Tac Toe')
self.clock = pygame.time.Clock()
self.background = Background(self.gameDisplay)

@staticmethod
def get_text_objects(text, font):
Expand Down Expand Up @@ -166,3 +167,6 @@ def draw_side_to_move(self, player_to_move):
pygame.draw.rect(self.gameDisplay, BLACK, (OFFSET_X+8, OFFSET_Y-22, 24, 14))
pygame.draw.rect(self.gameDisplay, self.colors[player_to_move], (OFFSET_X+10, OFFSET_Y-20, 20, 10))
self.message_display(text=' to move', pos=(OFFSET_X+60, OFFSET_Y-15), font='comicsansms', size=14)

def draw_menu_animation(self):
self.background.update()
46 changes: 46 additions & 0 deletions gui/menu_background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import pygame
import random
from gui.constants import *


class Animation:
__slots__ = ['img', 'x', 'y', 'x_increment', 'y_increment']

def __init__(self, img, x, y, x_increment, y_increment):
self.img = img
self.x = x
self.y = y
self.x_increment = x_increment
self.y_increment = y_increment


class Background:
num_animations = 10

def __init__(self, game_display):
self.game_display = game_display
x_coords = (random.randrange(0, DISPLAY_WIDTH) for _ in range(self.num_animations))
y_coords = (random.randrange(0, DISPLAY_HEIGHT) for _ in range(self.num_animations))
increment_padding = [0] * (self.num_animations//2)
x_increment = [random.randrange(2, 5) for _ in range(self.num_animations // 2)]
x_increment.extend(increment_padding)
y_increment = increment_padding[:]
y_increment.extend([random.randrange(2, 5) for _ in range(self.num_animations // 2)])
self.animations = []
for x, y, increment_x, increment_y in zip(x_coords, y_coords, x_increment, y_increment):
hashtag_img = pygame.image.load('../media/hashtag_1.png')
self.animations.append(Animation(img=hashtag_img, x=x, y=y,
x_increment=increment_x, y_increment=increment_y))

def update(self):
for idx in range(len(self.animations)):
animation = self.animations[idx]
self.game_display.blit(animation.img, (animation.x, animation.y))

self.animations[idx].x += self.animations[idx].x_increment
if self.animations[idx].x > DISPLAY_WIDTH:
self.animations[idx].x = 0

self.animations[idx].y += self.animations[idx].y_increment
if self.animations[idx].y > DISPLAY_HEIGHT:
self.animations[idx].y = 0
Binary file added media/button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added media/button.xcf
Binary file not shown.
Binary file added media/hashtag.xcf
Binary file not shown.
Binary file added media/hashtag_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 4d71d19

Please sign in to comment.