Jan-04-2023, 08:29 PM
Sorry about that. I was in mid-edit, cleaning up the code.
If you just want a tic-tac-toe game, here it is. I cleaned up the screen refresh some more, making it an enclosed function of main() so it knows about cube and surface. I moved constants to the top of the file where they are easier to find and modify. I made sure ROBOT and PLAYER were used consistently instead of sometimes use "X" and "O". I removed unnecessary or limited value class methods, wrote better comments, improved formatting, etc... Basically just cleaned out the gunk that accumulates in programs that grow organically.
if msg: text = pygame.font.Font(None, 100).render(msg, True, 'white')Are you learning anything from this, or do you just want a tic-tac-toe game? This last error was very obvious. That you didn't catch it immediately makes me think that you are so overwhelmed by other parts of the code that your brain switches off. If you have questions, ask. I'll answer questions if it is obvious that you put some effort into formulating good questions.
If you just want a tic-tac-toe game, here it is. I cleaned up the screen refresh some more, making it an enclosed function of main() so it knows about cube and surface. I moved constants to the top of the file where they are easier to find and modify. I made sure ROBOT and PLAYER were used consistently instead of sometimes use "X" and "O". I removed unnecessary or limited value class methods, wrote better comments, improved formatting, etc... Basically just cleaned out the gunk that accumulates in programs that grow organically.
"""Tic-tac-toe with a computer opponent. Three boards played simultaneously""" import pygame import random PLAYER = 'X' ROBOT = 'O' EMPTY = ' ' background = "white" msg_color = "white" colors = {EMPTY: 'dodger blue', PLAYER: 'red', ROBOT: 'black'} class Robot: """Computer tic-tac-toe player. Uses minimax algorithm with alpha/beta pruning and some additional heuristics to provide a challenging, but beatable opponent. """ 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): """minimax algorithm with alpha/beta pruning""" # Place the mark and check for a win square.mark = mark if square.check_win(): # Give extra weight to earlier wins/losses score = 10 - depth if mark is ROBOT else depth - 10 board.depth = min(board.depth, depth) elif len(empty_squares := board.empty_squares()) == 0: # No plays left. Draw. score = 0 elif mark is 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 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 # Remove mark and return score for the square square.mark = EMPTY 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 depth = 10 for board in self.boards: self.get_scores(board) depth = min(depth, board.depth) # Select board with minimum depth. This is the board 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. 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: """A 3D square in a tic-tac-toe board""" def __init__(self, board, index, side, center=(0, 0, 0)): self.board = board self.index = index self.mark = EMPTY 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) return self 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 return self 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 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), (0, 4, 8), (2, 4, 6) ) 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-6, (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 EMPTY] def check_win(self, square): """Return winning squares 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 faces of a cube""" def __init__(self, side): def make_board(center, rotation): board = Board(side) for square in board.squares: square.rotate(rotation).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 player's mark in clicked square.""" if self.mark is PLAYER: for square in self.squares: if square.contains(point): if square.mark is EMPTY: 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 = EMPTY self.mark = PLAYER if mark is 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 = EMPTY def main(): """Play tic-tac-toe""" def refresh_screen(msg=None): """Draw the cube and an optional message""" surface.fill(background) for square in cube.squares: square.draw(surface) if msg: text = pygame.font.Font(None, 100).render(msg, True, msg_color) surface.blit(text, (center.x - text.get_width() / 2, center.y)) pygame.display.flip() pygame.display.set_caption("Tic-Tac-Toe") surface = pygame.display.set_mode((500, 500)) center = pygame.Vector3(250, 250, 0) # Move cube for pretty orthogonal view at center of screen cube = Cube(300) for square in cube.squares: square.rotate((0, 45, 0)).rotate((22.5, 0, 0)).move(center) robot = Robot(cube) refresh_screen() 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(): # Start a new game cube.reset() if cube.mark is ROBOT: cube.play(robot.play(), ROBOT) refresh_screen() elif cube.click(pygame.Vector2(event.pos)): # Player selected square. Robot's turn, if not cube.done(): refresh_screen() cube.play(robot.play(), ROBOT) # Is the game over? refresh_screen("Game Over" if cube.done() else None) pygame.event.clear() if __name__ == "__main__": pygame.init() main() pygame.quit()