Drawing a net of a cube - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: Game Development (https://python-forum.io/forum-11.html) +--- Thread: Drawing a net of a cube (/thread-39679.html) |
RE: Drawing a net of a cube - freethrownucleus - Apr-29-2023 And this is the code with the shape that I want to use in the code beyond with the exact idea I had for the 3d-cube (setting the winning combinations for one board): import tkinter as tk class Square(tk.Button): """Special button for playing tic-tac-toe.""" colors = {"O": "blue", "X": "red"} def __init__(self, parent, index): super().__init__(parent, width=3, font=('Comic Sans MS', 20, 'bold')) self.index = index self._mark = None @property def mark(self): """What marker appears in square.""" return self._mark @mark.setter def mark(self, mark): self._mark = mark self["fg"] = self.colors.get(mark, 'black') self["text"] = '' if mark is None else mark class TicTacToe(tk.Tk): """Play tic-tac-toe. Players take turn clicking on empty buttons to place their marker. The first player to get three markers in a row, up/down, sideways or diagonally, wins """ # All possible winning conbinations wins = ((0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8), (9, 10, 11), (12, 13, 14), (15, 16, 17), (9, 12, 15), (10, 13, 16), (11, 14, 17), (18, 19, 20), (21, 22, 23), (24, 25, 26), (18, 21, 24), (19, 22, 25), (20, 23, 26), (27, 28, 29), (30, 31, 32), (33, 34, 35), (27, 30, 33), (28, 31, 34), (29, 32, 35), (36, 37, 38), (39, 40, 41), (42, 43, 44), (36, 39, 42), (37, 40, 43), (38, 41, 44), (45, 46, 47), (48, 49, 50), (51, 52, 53), (45, 48, 51), (46, 49, 52), (47, 50, 53)) #last one example of a diagonal combo over 2 boards def __init__(self): super().__init__() self.title('Tic Tac Toe') # create a frame to hold all four boards self.boards_frame = tk.Frame(self) self.boards_frame.pack(side="top", padx=5, pady=5) # create the left board self.frame1 = tk.Frame(self.boards_frame) self.frame1.pack(side="left") self.squares1 = [] for i in range(9): self.squares1.append(Square(self.frame1, i)) for square in self.squares1: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares1)) self.frame1.pack(side="top") self.frame2 = tk.Frame(self.boards_frame) self.frame2.pack(side="left") for i in range(9, 18): self.squares1.append(Square(self.frame2, i)) for square in self.squares1: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares1)) self.frame3 = tk.Frame(self.boards_frame) self.frame3.pack(side="left") for i in range(18, 27): self.squares1.append(Square(self.frame3, i)) for square in self.squares1: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares1)) self.frame4 = tk.Frame(self.boards_frame) self.frame4.pack(side="left") for i in range(27, 36): self.squares1.append(Square(self.frame4, i)) for square in self.squares1: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares1)) self.frame5 = tk.Frame(self.boards_frame) self.frame5.pack(side="left") for i in range(36, 45): self.squares1.append(Square(self.frame5, i)) for square in self.squares1: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares1)) self.frame6 = tk.Frame(self.boards_frame) self.frame6.pack(side="left") for i in range(45, 54): self.squares1.append(Square(self.frame6, i)) for square in self.squares1: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares1)) self.frame2.pack(side="bottom") self.frame3.pack(side="bottom") ''' # create the middle-left board self.frame2 = tk.Frame(self.boards_frame) self.frame2.pack(side="left") self.squares2 = [Square(self.frame2, i) for i in range(9)] for square in self.squares2: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares2, self.squares1)) # create the middle-right board self.frame3 = tk.Frame(self.boards_frame) self.frame3.pack(side="left") self.squares3 = [Square(self.frame3, i) for i in range(9)] for square in self.squares3: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares3, self.squares1, self.squares2, self.squares4, self.squares5, self.squares6)) self.frame4 = tk.Frame(self.boards_frame) self.frame4.pack(side="left") self.squares4 = [Square(self.frame4, i) for i in range(9)] for square in self.squares4: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares4, self.squares1, self.squares2, self.squares3, self.squares5, self.squares6)) self.frame5 = tk.Frame(self.boards_frame) self.frame5.pack(side="left") self.squares5 = [Square(self.frame5, i) for i in range(9)] for square in self.squares5: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares5, self.squares1, self.squares2, self.squares3, self.squares4, self.squares6)) self.frame1.pack(side="bottom") self.frame2.pack(side="top") self.frame6 = tk.Frame(self.boards_frame) self.frame6.pack(side="left") self.squares6 = [Square(self.frame6, i) for i in range(9)] for square in self.squares6: row = square.index // 3 column = square.index % 3 square.grid(row=row, column=column, padx=5, pady=5) square.configure(command=lambda arg=square: self.play(arg, self.squares6, self.squares1, self.squares2, self.squares3, self.squares4, self.squares5)) self.frame3.pack(side="bottom") ''' self.message = tk.Label(self, text=' ', width=30) self.message.pack(side="bottom") self.new_game = False self.player = "X" self.reset(self.squares1) def reset(self, squares1): """Reset board to empty""" self.open_squares = 54 self.new_game = False for square in squares1: square.mark = None ''' for square in squares2: square.mark = None for square in squares3: square.mark = None for square in squares4: square.mark = None for square in squares5: square.mark = None for square in squares6: square.mark = None ''' self.message['text'] = f"{self.player}'s turn" def play(self, square, squares1): """Put player marker in slot if open. Check for win or tie""" if self.new_game: return if square.mark is not None: self.message['text'] = "Invalid move" return square.mark = self.player self.open_squares -= 1 # check for a win for win in self.wins: if all(squares1[i].mark == self.player for i in win): self.message['text'] = f"{self.player} wins!" self.new_game = True return ''' if all(squares2[i].mark == self.player for i in win): self.message['text'] = f"{self.player} wins!" self.new_game = True return if all(squares3[i].mark == self.player for i in win): self.message['text'] = f"{self.player} wins!" self.new_game = True return if all(squares4[i].mark == self.player for i in win): self.message['text'] = f"{self.player} wins!" self.new_game = True return if all(squares5[i].mark == self.player for i in win): self.message['text'] = f"{self.player} wins!" self.new_game = True return if all(squares6[i].mark == self.player for i in win): self.message['text'] = f"{self.player} wins!" self.new_game = True return ''' # check for a tie if self.open_squares == 0: self.message['text'] = "Tie game" self.new_game = True return # switch players self.player = "O" if self.player == "X" else "X" self.message['text'] = f"{self.player}'s turn" def game_over(self, squares1): """Check for winner or tie""" # Check all winning combinations for win in self.wins: if all(squares1[i].mark == self.player for i in win): for i in win: squares1[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" return True ''' if all(squares2[i].mark == self.player for i in win): for i in win: squares2[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" return True if all(squares3[i].mark == self.player for i in win): for i in win: squares3[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" return True if all(squares4[i].mark == self.player for i in win): for i in win: squares4[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" return True if all(squares5[i].mark == self.player for i in win): for i in win: squares5[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" return True if all(squares6[i].mark == self.player for i in win): for i in win: squares6[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" return True ''' # Check for a tie if self.open_squares <= 0: self.message["text"] = "Game ends in a tie" return True return False TicTacToe().mainloop()Yes I know it's a bit long, sorry for that. RE: Drawing a net of a cube - freethrownucleus - May-03-2023 How can I set a counter for a number of wins? (Probably two different counters.) If I win, my number of wins increases. If computer wins, its number of wins increases. Also, following the counters of wins, if I win, there should be the message "You win!", if computer wins, there should be the message "You lose!". Here's the current code: """Tic-tac-toe with a computer opponent. Three boards played simultaneously""" import pygame import random PLAYER = 'X' ROBOT = 'O' EMPTY = ' ' background = "aliceblue" msg_color = "dodgerblue" colors = {EMPTY: 'dodger blue', PLAYER: 'green', ROBOT: 'red'} class Robot: 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""" 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), ) def __init__(self, side): dx = side / 6 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 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((-side/2, 0, 0), (0, 0, 0)), make_board((0, 0, 0), (0, 0, 0)), make_board((0, side/2, 0), (0, 0, 0)), make_board((0, -side/2, 0), (0, 0, 0)), make_board((side/2, 0, 0), (0, 0, 0)), make_board((0, side, 0), (0, 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 blit_text(surface, msg, pos, font, color = pygame.Color('dodgerblue')): x, y = pos m = msg.split('\n') for line in m: text = pygame.font.Font(None, 48).render(line, True, msg_color) width, height = text.get_size() surface.blit(text, (x - width/2, y - height * 2)) y += height + 2. def refresh_screen(surface, msg = None): """Draw the cube and an optional message""" surface.fill(background) for square in cube.squares: square.draw(surface) text_player1 = pygame.font.Font(None, 48).render("You", True, 'green') text_vs = pygame.font.Font(None, 48).render("vs", True, 'dodgerblue') text_player2 = pygame.font.Font(None, 48).render("Computer", True, 'red') surface.blit(text_player1, (text_player1.get_width(), text_player1.get_height())) surface.blit(text_vs, (center.x - text_vs.get_width() / 2, text_vs.get_height())) surface.blit(text_player2, (center.x + text_player2.get_width(), text_player2.get_height())) if msg: text = pygame.font.Font(None, 48).render(msg, True, msg_color) pos = (350, 850) blit_text(surface, msg, pos, None, color = pygame.Color('dodgerblue')) pygame.display.flip() pygame.display.set_caption("3D-Tic-Tac-Toe") surface = pygame.display.set_mode((700, 900)) center = pygame.Vector3(350, 350, 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((30, 0, 0)).move(center) robot = Robot(cube) refresh_screen(surface, None) br = 0 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() br += 1 print("BR: ", br) if cube.mark is ROBOT: cube.play(robot.play(), ROBOT) refresh_screen(surface, None) elif cube.click(pygame.Vector2(event.pos)): # Player selected square. Robot's turn, if not cube.done(): refresh_screen(surface, None) cube.play(robot.play(), ROBOT) # Is the game over? if cube.done(): refresh_screen(surface, "Ready for another play?\nJust click on the screen!") else: refresh_screen(surface, None) pygame.event.clear() if __name__ == "__main__": pygame.init() main() pygame.quit() RE: Drawing a net of a cube - deanhystad - May-03-2023 In your tkinter version you have a lot of code that gets repeated over and over. That is always a bad thing. Examining the code you'll notice that you are making a 3x3 tic-tac-toe board 6 times. You should write the code once and reuse 6 times. import tkinter as tk from functools import partial class Square(tk.Button): """Special button for playing tic-tac-toe.""" colors = {"O": "blue", "X": "red"} squares = [] def __init__(self, parent, index): super().__init__(parent, width=3, font=("Comic Sans MS", 20, "bold")) self.index = index self._mark = None self.squares.append(self) @property def mark(self): """What marker appears in square.""" return self._mark @mark.setter def mark(self, mark): self._mark = mark self["fg"] = self.colors.get(mark, "black") self["text"] = "" if mark is None else mark class Board(tk.Frame): """A frame that contains a 3x3 grid of squares.""" def __init__(self, parent, start_index=0): super().__init__(parent, bg="black") squares = [Square(self, i + start_index) for i in range(9)] for i, square in enumerate(squares): square.grid(row=i // 3, column=i % 3, padx=2, pady=2) class TicTacToe(tk.Tk): """Play tic-tac-toe.""" wins = ((0, 1, 2),) # shrunk for brevity. def __init__(self): super().__init__() self.title("Tic Tac Toe") frame = tk.Frame(self, bg="black") frame.pack(side=tk.TOP) # Make 6 boards and arrange in cross shape. Board(frame, 0).grid(row=0, column=1) Board(frame, 9).grid(row=1, column=0) Board(frame, 18).grid(row=1, column=1) Board(frame, 27).grid(row=1, column=2) Board(frame, 36).grid(row=2, column=1) Board(frame, 48).grid(row=3, column=1) self.squares = Square.squares[] for square in self.squares: square.configure(command=partial(self.play, square)) self.message = tk.Label(self, text=" ", width=30) self.message.pack(side=tk.TOP) self.new_game = False self.player = "X" self.reset() def reset(self): """Reset board to empty""" self.open_squares = 54 self.new_game = False for square in self.squares: square.mark = None self.message["text"] = f"{self.player}'s turn" def play(self, square): """Put player marker in slot if open. Check for win or tie""" if self.new_game: return if square.mark is not None: self.message["text"] = "Invalid move" return square.mark = self.player self.open_squares -= 1 # check for a win for win in self.wins: if all(self.squares[i].mark == self.player for i in win): for i in win: self.squares[i]["fg"] = "green" self.message["text"] = f"{self.player} wins!" self.new_game = True return # check for a tie if self.open_squares == 0: self.message["text"] = "Tie game" self.new_game = True return # switch players self.player = "O" if self.player == "X" else "X" self.message["text"] = f"{self.player}'s turn" TicTacToe().mainloop()This groups the squares into 3x3 board shapes, and you can arrange the frames in a cross shaped pattern. I'm still partial to using a map to place the squares. So flexible. """tkinter tic-tac-toe game""" import tkinter as tk from functools import partial class Square(tk.Button): """Special button for playing tic-tac-toe.""" colors = {"O": "blue", "X": "red"} squares = [] def __init__(self, parent): super().__init__(parent, width=3, font=("Comic Sans MS", 20, "bold")) self.index = len(self.squares) self._mark = None self.squares.append(self) @property def mark(self): """What mark appears in square.""" return self._mark @mark.setter def mark(self, mark): self._mark = mark self["fg"] = self.colors.get(mark, "black") self["text"] = "" if mark is None else mark class Board(tk.Frame): """A grid of squares who's shape is defined by a map.""" def __init__(self, map_, *args, **kwargs): super().__init__(*args, **kwargs) for r, row in enumerate(map_): for c, key in enumerate(row): if key == "S": Square(self).grid(row=r, column=c) class Score(tk.Label): """A label for displaying a score.""" def __init__(self, parent, mark, **kwargs): super().__init__(parent, **kwargs) self.mark = mark self._value = 0 self.value = 0 @property def value(self): return self._value @value.setter def value(self, new_value): self._value = new_value self["text"] = f"{self.mark}: {self._value}" class TicTacToe(tk.Tk): """A funky TicTacToe game. Use map to make any shape board you want.""" wins = ((0, 1, 2),) def __init__(self, map_): super().__init__() self.title("Tic Tac Toe") self.player = "X" self.open_squares = 0 Board(map_, self, bg="black").pack(side=tk.TOP) self.squares = Square.squares for square in self.squares: square.configure(command=partial(self.play, square)) font = ("Comic Sans MS", 20) frame = tk.Frame(self) frame.pack(side=tk.TOP, expand=True, fill=tk.X) self.scores = { mark: Score(frame, mark, fg=fg, font=font) for mark, fg in Square.colors.items() } self.message = tk.Label(frame, font=font, justify=tk.CENTER) self.scores["X"].pack(side=tk.LEFT, padx=5, pady=5) self.message.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5, pady=5) self.scores["O"].pack(side=tk.LEFT, padx=5, pady=5) self.reset() def play(self, square): """Put player mark in square if open""" if self.new_game: self.reset() elif square.mark is None: square.mark = self.player self.open_squares -= 1 player = self.player self.player = "X" if self.player == "O" else "O" win, draw = self.game_over(square) if win: for i in win: self.squares[i]["fg"] = "green" self.message["text"] = f"{player} wins!" self.scores[player].value += 1 self.new_game = True elif draw: self.message["text"] = "Game ends in draw" self.new_game = True else: self.message["text"] = f"{self.player}'s turn" def game_over(self, square): """Return Winning combination (or None), Draw status.""" for win in self.wins: if all(self.squares[i].mark == square.mark for i in win): return win, False return None, self.open_squares <= 0 def reset(self): """Reset all the boards""" for square in self.squares: square.mark = None self.new_game = False self.open_squares = len(self.squares) self.message["text"] = f"{self.player}'s turn" a = "SSSSSSBBB" b = "BBBSSSSSS" c = "BBBSSSBBB" TicTacToe((c, c, c, a, a, a, b, b, b, c, c, c)).mainloop() RE: Drawing a net of a cube - freethrownucleus - May-03-2023 Yes I agree with you. But if you saw my last reply, I'd rather stick to pygame than tkinter. I managed to make a cross shaped pattern by using pygame if you saw last reply. I gave up trying to combine more sides of a cube for a win because of your proof (how exactly minimax with alpha-beta pruning works). Also, thanks for help once more! :) RE: Drawing a net of a cube - deanhystad - May-04-2023 I don't think that is the way o draw a tic-tac-toe board in pygame. You should not be drawing vectors. I did it that way because you wanted something that looked like an orthogonal view of a Rubic's cube. The "Squares" were not square, nor were they squares. It was a painting of a cube that did nothing to simplify programming. For a tic-tac-toe board made up of square shaped Squares, I would write something that looks a lot like the programs above written in tkinter. I think I mentioned that to you a while ago. I would write a special Square sprite that can display an X or an O. You could place these sprites anywhere you wanted on the board. You could use the map idea which is very popular in pygame circles. You could manually position each square or you could make a class like the Board in my previous example that really just positions a group of squares. Pygame is written around the concept of sprits, and the more you use sprites the easier it is to write pygame programs. If you make everthing a sprite, you can refresh your screen with one command. If you want to find out what square is clicked, you c an have pygame do all the math. I would use sprites for everything. The squares, player instructions, the score sheet, everything. This is a very rough pass at writing tic-tac-toe in pygame. Lucily I has some image files that I use a long while ago to write a similar game in tkinter, drawing on a canvas. To play, you will have to proivide your own image files, """Pygame tic-tac-toe game""" import pygame import pathlib IMAGE_DIR = pathlib.Path(__file__).parent class BaseSprite(pygame.sprite.Sprite): """Base class for game sprites""" def __init__(self, image, x=0, y=0): super().__init__() self.image = image self.rect = self.image.get_rect() self.rect.x, self.rect.y = x, y self.width = self.rect.width self.height = self.rect.height def at(self, x, y): """Set upper left corner at x, y.""" self.rect.x, self.rect.y = x, y def contains(self, x, y): """Return True if point is inside rect.""" return self.rect.collidepoint(x, y) class Square(BaseSprite): """Sprite for tic-tac-toe squares.""" images = { "O": pygame.image.load(IMAGE_DIR/'o.png'), "X": pygame.image.load(IMAGE_DIR/'x.png'), None: pygame.image.load(IMAGE_DIR/'empty.png') } def __init__(self, x=0, y=0): super().__init__(self.images[None], x, y) self._mark = None def grid(self, row, column, x=20, y=20, gap=35): """Place squares in a grid""" self.at( x + column * (self.width + gap), y + row * (self.height + gap) ) @property def mark(self): return self._mark @mark.setter def mark(self, new_value): self._mark = new_value self.image = self.images[self._mark] class Label(BaseSprite): """A text display.""" def __init__(self, width, height, x=0, y=0, fg="black", bg="white", font=None): super().__init__(pygame.Surface((width, height)), x, y) self.fg = fg self.bg = bg self.font = pygame.font.Font(None, 32) if font is None else font self._text = "" self.text = "" @property def text(self): return self._text @text.setter def text(self, value): self._text = value text = self.font.render(value, 1, self.fg) self.image.fill(self.bg) self.image.blit( text, ( (self.width - text.get_width()) / 2, (self.height - text.get_height()) / 2 ) ) class Score(Label): """A label for displaying a score""" def __init__(self, mark, *args, **kwargs): super().__init__(*args, **kwargs) self.mark = mark self._value = 0 self.value = 0 @property def value(self): return self._value @value.setter def value(self, new_value): self._value = new_value self.text = f"{self.mark}: {self._value}" class Game(): """Tic-tac-toe game""" wins = ( (0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 4, 8), (0, 3, 6), (1, 4, 7), (2, 5, 8), (2, 4, 6) ) def __init__(self): bg = BaseSprite(pygame.image.load(IMAGE_DIR/'board.png')) y = bg.height self.message = Label(180, 25, 90, y) x_score = Score("X", 90, 25, 0, y, "red") o_score = Score("O", 90, 25, 270, y, "blue") self.scores = {"X": x_score, "O": o_score} self.screen = pygame.display.set_mode( (bg.width, y + self.message.height) ) self.sprites = pygame.sprite.Group() self.sprites.add(bg, self.message, x_score, o_score) self.squares = [] for i in range(9): square = Square() square.grid(i // 3, i % 3) self.squares.append(square) self.sprites.add(square) self.player = "X" self.new_game = False self.reset() self.draw() def click(self, x, y): """Find clicked square.""" for square in self.squares: if square.contains(x, y): self.play(square) break def play(self, square): """Put player mark in square if open""" if self.new_game: self.reset() elif square.mark is None: square.mark = self.player self.open_squares -= 1 player = self.player self.player = "X" if self.player == "O" else "O" win, draw = self.game_over(square) if win: self.message.text = f"{player} wins!" self.scores[player].value += 1 self.new_game = True elif draw: self.message.text = "Game ends in draw" self.new_game = True else: self.message.text = f"{self.player}'s turn" def game_over(self, square): """Return Winning combination (or None), Draw status.""" for win in self.wins: if all(self.squares[i].mark == square.mark for i in win): return win, False return None, self.open_squares <= 0 def reset(self): """Reset all the boards""" for square in self.squares: square.mark = None self.new_game = False self.open_squares = len(self.squares) self.message.text = f"{self.player}'s turn" def draw(self): self.sprites.draw(self.screen) pygame.display.flip() def main(): """Play game until out of lives or out of bricks""" pygame.init() pygame.display.set_caption("Tic-tac-toe") game = Game() running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN: game.click(*event.pos) game.draw() if __name__ == "__main__": main() RE: Drawing a net of a cube - freethrownucleus - May-05-2023 Alright thanks! RE: Drawing a net of a cube - freethrownucleus - May-05-2023 How can I easily to change the code so the game doesn't stop after making the winning combination. But what I want to make is next: -> the winner is a player who scores the winning combinations on two neighboring boards (sides of the cube) So there could be a case when one player won on one board and the other player on the other board and then a game continues until someone win on one board and it should be neighboring to the first board. I had an idea where I used boards' indexes but I didn't know what to do next. """Tic-tac-toe with a computer opponent. Three boards played simultaneously""" import pygame import random br1 = 0 br2 = 0 br1_copy = 0 br2_copy = 0 PLAYER = 'X' ROBOT = 'O' EMPTY = ' ' background = "blanchedalmond" msg_color = "dodgerblue" colors = {EMPTY: 'lightgrey', PLAYER: 'green4', ROBOT: 'red3'} difficulty = int(input("Press level (0 or 1): ")) class Robot: def __init__(self, cube): self.boards = cube.boards def get_scores(self, board): """Get scores for all open squares on a board""" global difficulty if difficulty == 0: """Easier level""" def minimax(mark, square, alpha=-1, beta=1, 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 else: 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""" 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), ) def __init__(self, side): dx = side / 6 self.squares = [] for index in range(9): x = (index % 3 - 1) * dx y = (index // 3 - 1) * dx self.squares.append(Square(self, index, dx - 2, (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, self.squares return None class Cube: """Three tic-tac-toe boards plastered on the faces of a cube""" def __init__(self, surface, 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((-side / 2, 0, 0), (0, 0, 0)), make_board((0, 0, 0), (0, 0, 0)), make_board((0, side / 2, 0), (0, 0, 0)), make_board((0, -side / 2, 0), (0, 0, 0)), make_board((side / 2, 0, 0), (0, 0, 0)), make_board((0, side, 0), (0, 0, 0)), ] self.surface = surface 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""" global br1, br2 square.mark = mark self.mark_count += 1 if winner := square.check_win(): self.winner = winner if mark == "X": br1 += 1 else: br2 += 1 for square in self.squares: if square not in winner: square.mark = EMPTY for square in winner[1]: square.mark = ROBOT if mark is ROBOT else PLAYER 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 >= 54 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 blit_text(surface, msg, pos, font, color=pygame.Color('dodgerblue')): x, y = pos m = msg.split('\n') for line in m: text = pygame.font.Font(None, 48).render(line, True, msg_color) width, height = text.get_size() surface.blit(text, (x - width / 2, y - height * 2)) y += height + 2. def refresh_screen(surface, msg=None): """Draw the cube and an optional message""" global br1_copy, br2_copy surface.fill(background) for square in cube.squares: square.draw(surface) text_player1 = pygame.font.Font(None, 48).render("You", True, 'green4') text_vs = pygame.font.Font(None, 48).render("vs", True, 'dodgerblue') text_player2 = pygame.font.Font(None, 48).render("Computer", True, 'red3') player1_wins = pygame.font.Font(None, 48).render(str(br1), True, 'green4') player2_wins = pygame.font.Font(None, 48).render(str(br2), True, 'red3') if br1_copy != br1: player1_winner = pygame.font.Font(None, 72).render("You win!", True, 'green4') width, height = player1_winner.get_size() surface.blit(player1_winner, (362.5 - width / 2, 875 - height * 2)) elif br2_copy != br2: player2_winner = pygame.font.Font(None, 72).render("You lose!", True, 'red3') width, height = player2_winner.get_size() surface.blit(player2_winner, (362.5 - width / 2, 875 - height * 2)) br1_copy = br1 br2_copy = br2 surface.blit(text_player1, (text_player1.get_width(), text_player1.get_height())) surface.blit(text_vs, (center.x - text_vs.get_width() / 2, text_vs.get_height())) surface.blit(text_player2, (center.x + text_player2.get_width(), text_player2.get_height())) surface.blit(player1_wins, ( text_player1.get_width() * 2 - text_player1.get_width() / 2 - 10, player1_wins.get_height() * 2 + 5)) surface.blit(player2_wins, ( (center.x + text_player2.get_width()) * 2 - (center.x + text_player2.get_width() / 2) - 10, player2_wins.get_height() * 2 + 5)) if msg: text = pygame.font.Font(None, 48).render(msg, True, msg_color) pos = (362.5, 925) blit_text(surface, msg, pos, None, color=pygame.Color('dodgerblue')) pygame.display.flip() pygame.display.set_caption("3D-Tic-Tac-Toe") surface = pygame.display.set_mode((725, 975)) center = pygame.Vector3(362.5, 362.5, 0) # Move cube for pretty orthogonal view at center of screen cube = Cube(surface, 300) for square in cube.squares: square.rotate((0, 45, 0)).rotate((30, 0, 0)).move(center) robot = Robot(cube) refresh_screen(surface, None) tmp = 0 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: tmp = cube.play(robot.play(), ROBOT) refresh_screen(surface, None) elif cube.click(pygame.Vector2(event.pos)): # Player selected square. Robot's turn, if not cube.done(): refresh_screen(surface, None) tmp = cube.play(robot.play(), ROBOT) # Is the game over? if cube.done(): player_wins = pygame.font.Font(None, 48).render(str(tmp), True, 'green4') surface.blit(player_wins, (player_wins.get_width(), player_wins.get_height())) refresh_screen(surface, "Ready for another play?\nJust click on the screen!") else: refresh_screen(surface, None) pygame.event.clear() if __name__ == "__main__": pygame.init() main() pygame.quit()Please ignore the wrong logic for making the surface, I just want to solve the problem described beyond for now. :) |