Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Drawing a net of a cube
#21
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.
Reply
#22
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()
Reply
#23
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()
Reply
#24
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! :)
Reply
#25
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()
Reply
#26
Alright thanks! Cool
Reply
#27
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. :)
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Cube drawing freethrownucleus 51 9,929 Apr-12-2023, 08:04 AM
Last Post: ClaytonMorrison
  2D-Cube-Tic-Tac-Toe freethrownucleus 0 1,159 Mar-10-2023, 07:07 PM
Last Post: freethrownucleus
  PyGlet Trouble Drawing Cube. Windspar 3 5,744 Jan-02-2018, 06:37 PM
Last Post: Windspar

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020