From 0f0fde3e5a1e33db438c0e4a21ba01124ca2fd70 Mon Sep 17 00:00:00 2001 From: AngelVI13 Date: Sat, 4 May 2019 18:13:58 +0300 Subject: [PATCH 1/3] - Fixed take back (needed to update playerJustMoved and to keep track of nextBoard instead of inferring it from move played) --- board/base_board.py | 2 +- board/ultimate_board.py | 14 ++++++++------ engine/uct.py | 13 +++++++------ gui/gui_app.py | 19 ++++++++++++++++--- gui/gui_board.py | 5 ++--- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/board/base_board.py b/board/base_board.py index 948c732..e3c3709 100644 --- a/board/base_board.py +++ b/board/base_board.py @@ -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): diff --git a/board/ultimate_board.py b/board/ultimate_board.py index efc8d81..02264fe 100644 --- a/board/ultimate_board.py +++ b/board/ultimate_board.py @@ -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): @@ -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 @@ -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 @@ -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') diff --git a/engine/uct.py b/engine/uct.py index c969184..6bff3f7 100644 --- a/engine/uct.py +++ b/engine/uct.py @@ -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) @@ -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(): @@ -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 diff --git a/gui/gui_app.py b/gui/gui_app.py index 4aa7880..2c48d18 100644 --- a/gui/gui_app.py +++ b/gui/gui_app.py @@ -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: @@ -92,7 +97,7 @@ 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 @@ -100,10 +105,18 @@ def do_nothing(self): def get_game_input(self, game_type, mouse_pos): if game_type == GameType.SINGLE_PLAYER: if self.board.playerJustMoved == PLAYER_O: # X's turn todo allow user to select side to play + # self.click_random_cell() todo used to testing if mouse_pos is not None: self.click_cell_under_mouse(mouse_pos) else: - self.click_random_cell() # todo replace with AI + # 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: diff --git a/gui/gui_board.py b/gui/gui_board.py index e3cd5b1..00da08f 100644 --- a/gui/gui_board.py +++ b/gui/gui_board.py @@ -20,9 +20,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)) From 806b8315f773408cd5b76f95f478617c10604cce Mon Sep 17 00:00:00 2001 From: AngelVI13 Date: Sun, 5 May 2019 09:04:33 +0300 Subject: [PATCH 2/3] - Changed main UI colors - Added menu background animation - Added media folder to put all UI related images. --- gui/colors.py | 23 ++++++++++++++++----- gui/constants.py | 11 +++++----- gui/gui_app.py | 16 +++++++------- gui/gui_board.py | 6 ++++++ gui/menu_background.py | 46 +++++++++++++++++++++++++++++++++++++++++ media/button.png | Bin 0 -> 1435 bytes media/button.xcf | Bin 0 -> 6522 bytes media/hashtag.xcf | Bin 0 -> 3750 bytes media/hashtag_1.png | Bin 0 -> 852 bytes 9 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 gui/menu_background.py create mode 100644 media/button.png create mode 100644 media/button.xcf create mode 100644 media/hashtag.xcf create mode 100644 media/hashtag_1.png diff --git a/gui/colors.py b/gui/colors.py index 5bc7f37..64dea3d 100644 --- a/gui/colors.py +++ b/gui/colors.py @@ -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 diff --git a/gui/constants.py b/gui/constants.py index 69ee5f6..e4b8dda 100644 --- a/gui/constants.py +++ b/gui/constants.py @@ -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}, } @@ -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 diff --git a/gui/gui_app.py b/gui/gui_app.py index 2c48d18..026855e 100644 --- a/gui/gui_app.py +++ b/gui/gui_app.py @@ -104,12 +104,11 @@ def do_nothing(self): 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 - # self.click_random_cell() todo used to testing 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: @@ -160,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: @@ -169,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__': diff --git a/gui/gui_board.py b/gui/gui_board.py index 00da08f..9a94f06 100644 --- a/gui/gui_board.py +++ b/gui/gui_board.py @@ -3,6 +3,7 @@ import pygame from gui.constants import * from board.ultimate_board import * +from gui.menu_background import Background @total_ordering @@ -48,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): @@ -165,3 +167,7 @@ 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() + diff --git a/gui/menu_background.py b/gui/menu_background.py new file mode 100644 index 0000000..2152d1a --- /dev/null +++ b/gui/menu_background.py @@ -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 diff --git a/media/button.png b/media/button.png new file mode 100644 index 0000000000000000000000000000000000000000..9845c8c8ebef85b698bfc4bb661c6c3f1334a45b GIT binary patch literal 1435 zcmV;M1!Ve(P)WFU8GbZ8()Nlj2>E@cM*00jR@L_t(|+U=a(Yg1<& z$3G`cnncsayi!HGHkJ6YmzHj(9h;lruB=4yYO6yAyBLEC`3Jo6TIkI#R`6zEL1bg3 zxG}0T;tMXsifqN&*_uQqeM_b3OOht>?4oU&lea1+YUuNUK%OVR=Y;&e&+q)6-;;BW zB+D{_w+EGQfe=I>1gRd>#Xgu1h2;Cay^Qwv6Pub6o+=Zq9uKV^4;Kdqal75v?RF{q zbE!~L4-O2-m)&mRy$aIh^>Vtek27b^;BvX7qJqqaLh?^dO_{a^umHwV;UsurBCD?? za4Hz2yQhcq=g(&%B`=5E@9oXJuLGQ`f(S9ngJspHby>dldcDPy`e=WDCNdg@|0+f! z@b85S0Qh{q;)1LtaEtI+#i^g324HS(ZsTRIQTVIk7+Pd&6wTJuP#@ny={J;Z?9i}q2v5`_%I84{Y(EYLoCL3SFcu&AW^NB%kAwv zwd8Nc#B@4hIvo=x6Mlz-_E3n=hli;V5mGrZ`VSstQKwt!kYx^xjnNtmvNe%lx~`6! z`}PsG*^pE!?lm_vr`K~l5I}m(jwm$C|J=Wydk#mY-TL@3UknddZw~pby?tE_;N#Iz zJ{uXq7>}ctWwuA7obdZ;c=jw~Gv3g^-R5SI)XPoCV&T86)08%m;J~9td3H6j%%>wG ziavi!3n@twf|Qd-4GoI6V|p4*k!!#jBN1wsmle6?smUY+DJK)#wkg_{Xtb!U1Xvdq zN{$zTydq1rwTiYrnJj6q%ZiXfGz zko7+?z)W3T$>^9d7!-ZBoSdV6{#+SB1y*6XeVUvxn_7_F6BEj<6Pt~65!)vg3(Hv% zQkzWj&diL$W@|jYVUAhSYDd191i~3xs#Yi!+dQmf3>vmFst^_ClY)R4p*HZ72V6KkH`7?=FOGOMY9=H zD(*O)+;KV!N7dvcT|-0E6eu7qi;El`8shi1Hc}c5X|B!8GtRZ7{>zvbg7gO zq>w0I-M+oP{cbX`pwqFe(Lh!b{^Y>}I>*LJ9?Lm7$qt{7zZ)9~S*^?(3?wufBw0q6 zOk#;fX`Y(my(dr5rPEcp%F4Ug`Ugpp0p6|f7pEnOpF27-xxf&M@#U>s7^^T5zY6D$ z9tB`vU_g4Kg<*{>;|>HcuP$#F^m;CJc5d1V{jI3fb*U65{C>8qM#Y@Lz@?53;_A(N z-Q*kp<;HlN@2^}z@D@afKnPNOg6Qpb08+v?6(?x3Z7hhhyL%zCKO`gE%}M z9@}h0L{2GRo#FBDVKB()zCMM|*C_bl0KeZapFMk4c<(i^;e!L#n3|fByJv;wdc_+E_^~0&RClAj%Uwc--w>SMFc#_Mm5? zNAyMG3+ToK3K$ZfOrJ`^15Lc>1p$4~7mU$_ULpcVOb8dN^m@DF_s#D1uJphbjfU85 zX6M&$=J(C_Z*F!ssDxwUurDA6l#oo|@@Nj;QcI!NFDAz+{2P|K13_Oj6pf2+-g)A|gI;mdbDQ{81+-g38-RMS3e_GL9}JK> zoQQdSN+c+@+iQU^RZVEgJjsZnib}*cBqxv`4FnRhI%6QR=ZZ|pY%0dVx`Z)E#ZCJ^ z=zE~8hlWwD+q-YzHRwqV-r#aV!`v`vm>_j(Cy@RCv&vkUf4SF8fp^8WBTk z9EA6mDdfJRyKB?t?zsRf?2TkXRRSq7BnMPf86Eb7qkdWRry}04;1ByA^J{{V#dGUu+EV3?j*(T{yFH~W5M`o?&fYs`VzRz-bo*ak^28xGgo)ps- zbkDZx5sA`#9V@T65Igx?haC}I3?u_)>S{1CR;0=abHEH;!fL34@-WAdz%|)|*%`Km z^o?BEOo{P)KZG_pC{ZZWoxKCd(UEcJ;!&J<64ASeK0}mz z0D01Ni?Px>&Gnlvz%>1m6XxA@Pcb(6`s%gAI%BsRt1_KLcU@%lOh3O8ecoGu9ijiy zS+OC?4mObDj#F3f7-&ZdPQ%G{Z-05ac<;uA7;^1$EYDv~rktNM_H!#dUQRV$W$ec_ zh+1zl_FW@MlWX`E=Z9v0*jd)+^&JYvqsa(L|FusBxE5a3J{j1bFJO4pNj|G=axKW< z8_3$oZ279M;48y7C`$|Q^HRv3);cu@GbXeyt(F-DP0&gwM#nRoGqr3wt7&I5TL6_# zYbP>W0SKBgYJ+hLbaVORbO(1#<4al1bf;9X9Go7RuzbFkr-kep9tw!h71~AXSSdfU zpLWtTIm&qWaxp9#7#<_=mduPC>BcT*c58U|B1rzrW|(H!Mwuq+Bc{d0Jk7ub z(ijh(ixhJhInqLkF;s$}3H1^e;bD4g@8DV!M#^-}0y*wlGnpFWAtURE9?LC;jJ5JK Q|Lu^$QH4kFT0FD<1f(5$!TAJbx^xQV9eW1^XKT^ ztC4KJY}Mu~=B@H=l%TQ&< zGW5!UZ9`S9Y#Ej<5AiK;YfFkea%hCFHegf?9U#-CHm1?I{O!v1;Mr#q- zYDWq#-!OGo*3EKFbzpB*tB&f%4#N6oDr7dBA`jw9y-gXt9QEjaklGPy$ZC2&{YYv2 z(A7x|8xv6L78tX71g4v`H-Wb$@b(1Wk-)tP+)o&~sUQgcqI*|jELjvN1dYbR-F4Nb zn>Un}Y7@n~TO6t@_Swd=oSP28%rqs?lpTFlJ$fcLF)=oE|C!v({^?=RGtg8c3k4{E=9?oGlOzN`UR1NHhV!FCw=!zq^cz+DV)EZQJa!xh#4HjHd z95r9jZME#`mMI&qO?Efd9&PB@)a1zFsha`H=JAH(>eZHPs8yG`OI9;qw<;<o!BTsdm`D07?ixlSja0eglSf|!;Z~L{m|N1@*ko?ycOyv!_Ii!q}y3I zIUC2#h<%&~S3u|s?06}3JPjSJeplpjHiU@Sn-!6_;*G+(94Gt(gmdAY!sL)1+z5%|jJ=9dpUjR}b0c{u6WW70 zh!i-qlhT@WX{(;1z1^y(C=*-twDo?%8|MG>e!3K-0{=NW@B9ydR>(Q8a~%-xGq3wk zz)8HSj}r3!-hsE=F~HwDz&?fXPhkHH&;^`-7l2svL!eb?W`6@@%^uc#0Ms>e!zY6vR{$89bw?H(n z37szx53hX@YrhNbe4L{-teyD<AKbf*2dH;uwMoG7@BVZ zy$tR>py#2P`T=kenweh#`47mUARYL3LXy?Jen{3a{0+>X0Qy{_qmu(0h(m0TD8x5l z@gi(RQpugG;7(!X^v{6vSma(pp1%uAj{(x=6RTnTOPF(9pCX#q!My~I<9Y$yci>J# zGW8=MpRD&FnB7nuCeFLw3&kpizlOC1^ck#Q19}DAn?UEleGBv$D}E1Hgz?=_qyt#; z4-)5H@8h$V{>~*o+O{88%4ap(YMA^j^U+eiOh}u7O&{2T^ literal 0 HcmV?d00001 diff --git a/media/hashtag_1.png b/media/hashtag_1.png new file mode 100644 index 0000000000000000000000000000000000000000..3adaaf3582c5f1507860455246d1373253cbc59d GIT binary patch literal 852 zcmV-a1FQUrP)WFU8GbZ8()Nlj2>E@cM*00Ov4L_t(&-tCyVZt74F zhUburP!J-aNEHOK06{S1QPLn%A{rW=fHD*mv^+oxpn}&(Ne7rkEI~n(fW*F-xgEJS zv9SgCa3h~?tdZr%|IHB25vT+@0S}4;P?9_|8Bir$L)5z zw@oG!xLhtm9pv8>0RV8j-Eez*E9qY@mj$-Z=L4hBNIxRt4?C77YW+0<*>1N`uh&)S zLyev^18KEdxLU0g>!Vhy3G`t!8o}f75b7W_^>J2^N~Jo)_rD>-CaTzJVMYtm5%FetmsW@1@?~-{Ilmf#}_@4x;fPr!F6g z#Uf_2*}cs$3}iAH$z0Cma#$!7_V)AhGo(@}LOmkl`f=LB7a{PFP-Y&IJ!(Bv0SzwUQ45x&*$IdE)Tue(m}4ZX-ucn`0??vw_jdfAQp>B-ayG@5=*7h-afVu z)+{1w3gJ)l{HgQ#SwQ;zK90v@abbIv^6zvy2lG73vV=N_xPCaDPOw_7MWy~OmrHiv zMGfTGK1%JrOEVBP`ar~kM)4{@`u#qRMkBF4B-5(f?c!`U`@SLxk==J`1|m)?yWI}= z_xG=A^kFue=|@CuzH6nAi-P>5k4w}K)gXhx0Efe&;=!ob>)~WF5$NL*_2Z-#MQ<8) e5FO Date: Sun, 5 May 2019 12:25:36 +0300 Subject: [PATCH 3/3] - Style changes (pep8 fixes) --- gui/gui_board.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gui/gui_board.py b/gui/gui_board.py index 9a94f06..c283c22 100644 --- a/gui/gui_board.py +++ b/gui/gui_board.py @@ -170,4 +170,3 @@ def draw_side_to_move(self, player_to_move): def draw_menu_animation(self): self.background.update() -