Jan-04-2023, 08:28 PM
(This post was last modified: Jan-06-2023, 12:46 AM by Yoriz.
Edit Reason: removed unnecessary quote of previous post
)
Could you please implement that in the whole code? I tried it now, made text a global variable in the refresh() function because of "UnboundLocalError: local variable 'text' referenced before assignment" but still can't make it work. I don't get all the logic ...
"""A tic-tac-toe game""" import pygame import random PLAYER = 'X' ROBOT = 'O' colors = {None: 'gray30', ROBOT: 'red', PLAYER: 'green'} class Robot: """Computer tic-tac-toe player""" def __init__(self, cube): self.boards = cube.boards def get_scores(self, board): """Get scores for all open squares on a board""" def minimax(mark, square, alpha=-1000, beta=1000, depth=0): """Compute a score for the square. Uses using minimax algorithm with alpha/beta pruning. """ # Place the marker and compute a score for the square. # Score is a win, a loss, a draw, or the score from # the next play. square.mark = mark if square.check_win(): # Give extra weight to earlier wins/losses score = 10 - depth if mark == ROBOT else depth - 10 board.depth = min(board.depth, depth) else: empty_squares = board.empty_squares() if not empty_squares: score = 0 # Draw elif mark == PLAYER: # Pick best move for robot score = -1000 for s in empty_squares: score = max(score, minimax(ROBOT, s, alpha, beta, depth+1)) alpha = max(alpha, score) if alpha > beta: break else: # Guess what move we think player will make score = 1000 for s in empty_squares: score = min(score, minimax(PLAYER, s, alpha, beta, depth+1)) beta = min(beta, score) if alpha > beta: break # Undo play and return score square.mark = None return score # Collect scores for empty squares. If board is empty, # minimax will return 0 for all squares board.depth = 10 empty_squares = board.empty_squares() if len(empty_squares) == 9: board.scores = [[0, s] for s in empty_squares] else: # Calling minimax twice. The first is to find the square # giving us best chance to win board. Second is to prevent # abandoning board early and giving player an easy win. board.scores = [[minimax("O", s), s] for s in empty_squares] [minimax("X", s, depth=1) for s in empty_squares] def play(self): """Place robot mark.""" # Get scores for all empty squares pygame.time.wait(1000) depth = 10 for board in self.boards: self.get_scores(board) depth = min(depth, board.depth) # Select board(s) with minimum depth. This is the board(s) that # will win/lose in the least number of moves. scores = [] for board in self.boards: if board.depth <= depth: scores.extend(board.scores) # Randomly select from best scores on the selected board(s). max_score = max(score[0] for score in scores) squares = [score[1] for score in scores if score[0] >= max_score] return random.choice(squares) class Square: """I am a square in a tic-tac-toe board in 3D""" def __init__(self, board, index, side, center=(0, 0, 0)): self.board = board self.index = index self.mark = None self.corners = [ pygame.Vector3(-1, -1, 0) * side / 2, pygame.Vector3(1, -1, 0) * side / 2, pygame.Vector3(1, 1, 0) * side / 2, pygame.Vector3(-1, 1, 0) * side / 2 ] self.move(center) def rotate(self, rotation): """Rotate square (rx, ry, rz) degrees""" for deg, axis in zip(rotation, ((1, 0, 0), (0, 1, 0), (0, 0, 1))): if deg: for corner in self.corners: corner.rotate_ip(deg, axis) def move(self, offset): """Move square offset (x, y, z) pixels""" x, y, z = offset for corner in self.corners: corner.x += x corner.y += y corner.z += z def projection(self): """Return corners projected on xy plane""" return [pygame.Vector2(p.x, p.y) for p in self.corners] def draw(self, surface): """Draw projection of square on xy plane""" pygame.draw.polygon(surface, colors[self.mark], self.projection()) def contains(self, point): """Return True if projection contains point.""" def area(a, b, c): """Compute area of triangle""" return abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0) def has_point(a, b, c, p): """Return True if triange ABC contains point p.""" return area(a, b, c) >= int(area(a, p, b) + area(b, p, c) + area(c, p, a)) a, b, c, d = self.projection() return has_point(a, b, c, point) or has_point(a, c, d, point) def check_win(self): return self.board.check_win(self) class Board: """A 3x3 tic-tac-toe board""" winning_combos = ( (0, 2, 4, 6, 8), (1, 3, 5, 7), (0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8) ) def __init__(self, side): dx = side / 3 self.squares = [] for index in range(9): x = (index % 3 - 1) * dx y = (index // 3 - 1) * dx self.squares.append(Square(self, index, dx - 3, (x, y, 0))) self.scores = None self.depth = 10 def empty_squares(self): """Return list of empty squares""" return [s for s in self.squares if s.mark is None] def check_win(self, square): """Return winning combination from last move, else None""" for combo in self.winning_combos: if square.index in combo: squares = [self.squares[i] for i in combo] if all(square.mark == s.mark for s in squares): return squares return None class Cube: """Three tic-tac-toe boards plastered on the sides of a cube""" def __init__(self, side): def make_board(center, rotation): board = Board(side) for square in board.squares: square.rotate(rotation) square.move(center) return board self.boards = [ make_board((0, 0, -side/2), (0, 0, 0)), make_board((side/2, 0, 0), (0, 270, 0)), make_board((0, -side/2, 0), (90, 0, 0)) ] self.squares = [s for b in self.boards for s in b.squares] self.winner = None self.mark = PLAYER self.reset() def click(self, point): """Try to place mark in clicked square.""" if self.mark == PLAYER: for square in self.squares: if square.contains(point): if square.mark is None: self.play(square, self.mark) return square break return None def play(self, square, mark): """Place mark in square. Test if is a winning move""" square.mark = mark self.mark_count += 1 if winner := square.check_win(): self.winner = winner for square in self.squares: if square not in winner: square.mark = None self.mark = PLAYER if mark == ROBOT else ROBOT def done(self): """Return True if there is a winner or draw""" return self.winner or self.mark_count >= 27 def reset(self): """Reset all boards to empty""" self.mark_count = 0 self.winner = None for square in self.squares: square.mark = None def draw(self, surface): """Draw all the tic-tac-toe boards.""" surface.fill("gray95") for square in self.squares: square.draw(surface) pygame.display.flip() def refresh(surface, cube, msg=None): """Draw all the tic-tac-toe boards.""" global text surface.fill("white") for square in cube.squares: square.draw(surface) if msg: text = pygame.font.Font(None, 100).render(text, True, 'white') rect = text.get_rect() pos = ((surface.get_width() - rect.width) / 2, (surface.get_height() - rect.height) / 2) surface.blit(text, pos) pygame.display.flip() def main(): """Play tic-tac-toe""" pygame.display.set_caption("Tic-Tac-Toe") surface = pygame.display.set_mode((750, 750)) cube = Cube(300) center = surface.get_rect() font = pygame.font.SysFont('Times New Roman', 40) for square in cube.squares: square.rotate((0, 45, 0)) square.rotate((30, 0, 0)) square.move((center.centerx, center.centery, 0)) robot = Robot(cube) cube.draw(surface) refresh(surface, cube) running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN: if cube.done(): # Game over. Start a new game cube.reset() if cube.mark == ROBOT: cube.play(robot.play(), ROBOT) refresh(surface, cube) else: if cube.click(pygame.Vector2(event.pos)): # Player selected square. Robot's turn, if not cube.done(): refresh(surface, cube) cube.play(robot.play(), ROBOT) # Is the game over? refresh(surface, cube, "Game Over" if cube.done() else None) pygame.event.clear() if __name__ == "__main__": pygame.init() main() pygame.quit()Also, is there any similar logic by which, during the game, I can display for example "your turn" and "computer's turn" so that I don't have to make a new function?