class TicTacToeWindow(wx.Window): def __init__(self, parent, size=(300, 300), ai=None): wx.Window.__init__(self, parent, size=size) self._mouseover = None self._linewidth = 4 self._agent_thread = None self.ai = ai self._init_game() self._init_buffer() self._event_handlers() def reset_game(self, ai=None): if self._agent_thread: self._agent_thread.stop() self._agent_thread.join() self.ai = ai self._mouseover = None self._init_game() self._reinit_buffer = True def _init_game(self): self.game = TicTacToeState() def _init_buffer(self): self._buffer = wx.EmptyBitmap(*self.GetClientSize()) dc = wx.BufferedDC(None, self._buffer) dc.SetBackground(wx.Brush('White')) dc.Clear() self._draw_scene(dc) self._reinit_buffer = False def _piece_size(self): window_w, window_h = self.GetClientSize() piece_w = (window_w - 2 * self._linewidth) / 3 piece_h = (window_h - 2 * self._linewidth) / 3 return (piece_w, piece_h) def _piece_position(self, row, column): piece_w, piece_h = self._piece_size() x = row * (piece_w + self._linewidth) y = column * (piece_h + self._linewidth) return (x, y) def _draw_scene(self, dc): self._draw_board(dc) for index, piece in enumerate(self.game.board): row = index // 3 col = index % 3 if piece == 'X': self._draw_x(dc, row, col) elif piece == 'O': self._draw_o(dc, row, col) if self._mouseover: row, col = self._mouseover if self.game.turn == 'X': self._draw_x(dc, row, col, True) else: self._draw_o(dc, row, col, True) if self.game.is_finished(): self._draw_end_message(dc) def _draw_board(self, dc): win_w, win_h = self.GetClientSize() piece_w, piece_h = self._piece_size() if self._linewidth % 2: offset = self._linewidth / 2 else: offset = self._linewidth / 2 - 1 lines = [ (piece_w + self._linewidth - offset, 0, piece_w + self._linewidth - offset, win_h), (2 * (piece_w + self._linewidth) - offset, 0, 2 * (piece_w + self._linewidth) - offset, win_h), (0, piece_h + self._linewidth - offset, win_w, piece_h + self._linewidth - offset), (0, 2 * (piece_h + self._linewidth) - offset, win_w, 2 * (piece_h + self._linewidth) - offset) ] dc.SetPen(wx.Pen('Black', self._linewidth)) dc.DrawLineList(lines) def _draw_x(self, dc, row, col, faded=False): tr_x, tr_y = self._piece_position(row, col) piece_w, piece_h = self._piece_size() pad = 10 tr = (tr_x + pad, tr_y + pad) tl = (tr_x + piece_w - pad, tr_y + pad) br = (tr_x + pad, tr_y + piece_h - pad) bl = (tr_x + piece_w - pad, tr_y + piece_h - pad) if faded: color = '#FF7777' else: color = '#FF0000' dc.SetPen(wx.Pen(color, 10)) dc.DrawLine(*(tr + bl)) dc.DrawLine(*(tl + br)) def _draw_o(self, dc, row, col, faded=False): tr_x, tr_y = self._piece_position(row, col) piece_w, piece_h = self._piece_size() if faded: color = '#7777FF' else: color = '#0000FF' dc.SetPen(wx.Pen(color, 10)) dc.DrawEllipse(tr_x + 10, tr_y + 10, piece_w - 20, piece_h - 20) def _end_message_box(self): win_w, win_h = self.GetClientSize() w, h = int(win_w / 1.5), int(win_h / 2.5) x = (win_w - w) / 2 y = (win_h - h) / 2 return (x, y, w, h) def _draw_end_message(self, dc): if self.game.winner == 'X' or self.game.winner == 'O': message = 'Congrats! %s wins!\nClick to play again.' \ % (self.game.winner) else: message = "Cat's game.\nClick to play again." dc.SetPen(wx.Pen('Black', 5)) dc.SetBrush(wx.Brush('#99FFFF')) dc.DrawRectangle(*self._end_message_box()) dc.DrawLabel(message, self._end_message_box(), wx.ALIGN_CENTER) def _event_handlers(self): self.Bind(wx.EVT_SIZE, self._on_size) self.Bind(wx.EVT_PAINT, self._on_paint) self.Bind(wx.EVT_IDLE, self._on_idle) self.Bind(wx.EVT_LEFT_DOWN, self._on_click) self.Bind(wx.EVT_MOTION, self._on_motion) self.Bind(wx.EVT_WINDOW_DESTROY, self._on_destroy) def _on_size(self, event): self._reinit_buffer = True def _on_paint(self, event): wx.BufferedPaintDC(self, self._buffer) def _on_idle(self, event): if self._reinit_buffer == True: self._init_buffer() self.Refresh(False) if not self.game.is_finished(): self._agent_move() def _on_destroy(self, event): if self._agent_thread: self._agent_thread.stop(True) event.Skip() def _pos_to_row_col(self, x, y): piece_w, piece_h = self._piece_size() row = int(x // (piece_w + self._linewidth)) col = int(y // (piece_h + self._linewidth)) return (row, col) def _on_click(self, event): if not self.game.is_finished(): row, col = self._pos_to_row_col(*event.GetPositionTuple()) if (row, col) in self.game.valid_moves(): self.game.do_action(row, col) if self._mouseover == (row, col): self._mouseover = None self._reinit_buffer = True else: x, y, w, h = self._end_message_box() mouse_x, mouse_y = event.GetPositionTuple() if x <= mouse_x < x + w and y <= mouse_y < y + h: self._init_game() self._reinit_buffer = True def _on_motion(self, event): if not self.game.is_finished(): row, col = self._pos_to_row_col(*event.GetPositionTuple()) if self._mouseover != (row, col): if (row, col) in self.game.valid_moves(): self._mouseover = (row, col) else: self._mouseover = None self._reinit_buffer = True def _agent_move(self): if self.ai == self.game.turn and self._agent_thread is None: self._agent_thread = ComputerDecision(self, self.game.copy(), self.ai) self._agent_thread.start() def _agent_started(self): self.Unbind(wx.EVT_LEFT_DOWN) self.Unbind(wx.EVT_MOTION) def _agent_finished(self): self.Bind(wx.EVT_LEFT_DOWN, self._on_click) self.Bind(wx.EVT_MOTION, self._on_motion) self._agent_thread = None self._reinit_buffer = True
def _init_game(self): self.game = TicTacToeState()