Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Cube drawing
#11
Ok, ty again a lot!
Can I feel free to ask you something again if I have some trouble?
Reply
#12
You are free to ask questions on this forum as long as they follow forum rules. This link has suggestions on how to write posts and ask questions. I suggest you read about how to ask a smart question. The better you follow these suggestions the better, and quicker the responses will be.

https://python-forum.io/misc.php?action=help
Reply
#13
Could you please explain me the code you sent for the game (functions etc.)?
I didn't understand everything very well ...
Reply
#14
That is an example of a bad question. It is vague and it makes you look lazy. What didn't you understand? If you aren't willing to take the time to ask a good question, why would I take the time to answer?

A good question would ask questions about specific details of the program. The post would not only ask the question but discuss what you've done to try to find answers. This information would make it easier for me to provide a good answer.

Have you written a flat 3x3 tic-tac-toe game? You should start with that. This game is essentially just 3 tic-tac-toe games. If you can have the computer pick moves for a 3x3 game, all you have to do is call that code 3 times, one for each board, and then pick the best board solution.
Reply
#15
Alright, ty!

I tried to change a multi-player game to a one playing against computer, but I don't know how to block multiple clicks (for example, I start a game and make a move (a click), then computer does, then my turn again etc. but if I click multiple times before computer plays no matter I click on the same field or a new one, computer will also play that many times (as many as I click) and I don't know how to solve it).

import asyncio
from random import *
from time import sleep

import pygame


class Square:
    """I am a square in a tic-tac-toe board in 3D"""
    colors = {None: "white", 0: "green", 1: "red"}

    def __init__(self, side, center=(0, 0, 0)):
        self.marker = None
        self.points = [
            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 empty(self):
        """Return True if square not filled"""
        return self.marker is None

    def rotate(self, rotation):
        """Rotate square (rx, ry, rz) degrees"""
        for degrees, vector in zip(rotation, ((1, 0, 0), (0, 1, 0), (0, 0, 1))):
            if degrees:
                for point in self.points:
                    point.rotate_ip(degrees, vector)

    def move(self, offset):
        """Move square offset (dx, dy, dz) pixels"""
        for point in self.points:
            dx, dy, dz = offset
            point.x += dx
            point.y += dy
            point.z += dz

    def projection(self):
        """Return points projected on xy plane"""
        return [pygame.Vector2(point.x, point.y) for point in self.points]

    def draw(self, surface):
        """Draw self"""
        pygame.draw.polygon(surface, self.colors[self.marker], self.projection())

    def contains(self, point):
        """Return True if my xy projection contains point."""

        def t_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 t_contains(a, b, c, p):
            """Return True if triangle ABC contains point p."""
            # Create 3 triangles that have p as a vertex.  If the sum of the area of these
            # triangles = area of the triangle ABC, then p is inside the triangle
            area = t_area(a, b, c)
            a1 = t_area(a, p, b)
            a2 = t_area(b, p, c)
            a3 = t_area(c, p, a)
            return area >= int(a1 + a2 + a3)  # Compensate for numerical errors

        # Split projected polygon into two triangles.  Test if point is inside either triangle.
        a, b, c, d = self.projection()
        return t_contains(a, b, c, point) or t_contains(a, c, d, point)


class Cube:
    # Now supports X and diamond patterns
    winning_combos = (
        (0, 2, 4, 6, 8), (9, 11, 13, 15, 17), (18, 20, 22, 24, 26),
        (1, 3, 5, 7), (10, 12, 14, 16), (19, 21, 23, 25),
        (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),
    )

    """A tic-tac-toe board plastered on the side of a cube"""

    def __init__(self, side):
        def make_squares(move, rotation):
            """Make all the squares on a face.  Rotate and the
            move the squares so they are on the "face" of the cube.
            """
            dx = side / 3
            gap = 6
            squares = [
                Square(dx - gap, (-dx, -dx, 0)),
                Square(dx - gap, (0, -dx, 0)),
                Square(dx - gap, (dx, -dx, 0)),
                Square(dx - gap, (-dx, 0, 0)),
                Square(dx - gap, (0, 0, 0)),
                Square(dx - gap, (dx, 0, 0)),
                Square(dx - gap, (-dx, dx, 0)),
                Square(dx - gap, (0, dx, 0)),
                Square(dx - gap, (dx, dx, 0))
            ]
            for square in squares:
                square.rotate(rotation)
                square.move(move)
            return squares

        self.squares = [
            *make_squares((0, 0, -side / 2), (0, 0, 0)),
            *make_squares((side / 2, 0, 0), (0, 270, 0)),
            *make_squares((0, -side / 2, 0), (90, 0, 0))]
        for index, square in enumerate(self.squares):
            square.index = index
        self.marker = 0
        self.reset()

    def find_square(self, point):
        """Return square that was clicked, or None"""
        for square in self.squares:
            if square.contains(point):
                return square if square.empty() else None
        return None

    def click(self, point):
        self.marker = 0
        """Place marker on clicked square"""
        if self.done:
            # Start now game
            self.reset()
        elif square := self.find_square(point):
            # Place marker and check for a win
            square.marker = self.marker
            self.marker_count += 1
            self.done = self.check_win(square) or self.marker_count >= 27

    def bot_move(self):
        self.marker = 1
        if self.done:
            # Start now game
            self.reset()

        # get random market positon that is unchecked
        square: Square = choice([square for square in self.squares if square.empty()])
        square.marker = self.marker
        self.marker_count += 1
        self.done = self.check_win(square) or self.marker_count >= 27

    def check_win(self, square):
        """Check if most recent move results in a win"""

        def all_match(marker, combo):
            """Return True if all squares in combo match marker"""
            return all((self.squares[i].marker == marker for i in combo))

        marker = square.marker
        for combo in self.winning_combos:
            if square.index in combo and all_match(marker, combo):
                self.reset()
                self.done = True
                for i in combo:
                    self.squares[i].marker = marker
                return True
        return False

    def reset(self):
        """Reset all squares to empty"""
        for square in self.squares:
            square.marker = None
        self.done = False
        self.marker_count = 0

    def rotate(self, rotation):
        """Rotate cube (rx, ry, rz) degrees """
        for square in self.squares:
            square.rotate(rotation)

    def move(self, move):
        """Move cube offset (dx, dy, dz) pixels"""
        for square in self.squares:
            square.move(move)

    def draw(self, surface):
        """Draw all the squares."""
        for square in self.squares:
            square.draw(surface)


def main():
    pygame.display.set_caption("Tic-Tac-Toe")
    surface = pygame.display.set_mode((500, 500))

    # Make 3 sides of a cube, and rotate so you can see all three sides
    cube = Cube(300)
    cube.rotate((0, 45, 0))
    cube.rotate((22.5, 0, 0))
    center = surface.get_rect()
    cube.move(pygame.Vector3(center.centerx, center.centery, 0))
    cube.draw(surface)
    pygame.display.flip()

    turn = 0

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                cube.click(pygame.Vector2(event.pos))
                surface.fill("black")
                cube.draw(surface)
                pygame.display.flip()
                turn += 1
            if turn % 2 == 1:
                pygame.time.wait(1000)
                cube.bot_move()
                surface.fill("black")
                cube.draw(surface)
                pygame.display.flip()
                turn += 1

if __name__ == "__main__":
    pygame.init()
    main()
    pygame.quit()
Reply
#16
I took a shot at using the minimax algorithm with alpha-beta pruning. This works great in a regular tic-tac-toe game, but not very well in this case.

My first attempt to solve for the tic-tac-toe cube was to treat each face like a separate board and choose the highest score from the three boards. The robot player is fairly responsive in this mode but is easy to beat. The robot has a bias towards the first board, and you can make it waste turns filling empty, but unproductive squares while you set up a win on the 2nd or 3rd board.

My second attempt was to simultaneously solve a tic-tac-toe board with 27 squares. Even if with alpha-beta pruning this is painfully slow. After waiting an hour for the computer to make its first move, I gave up waiting.

In response to your questions.

This is the logic I use to control the robot player:
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if cube.done():
                    # No plays left to make.  Reset for new game
                    cube.reset()
                else:
                    # Check if player clicked on an open square
                    if cube.click(pygame.Vector2(event.pos)):
                        # Player selected a square.  If moves available,
                        # have robot make a play.
                        if not cube.done():
                            cube.draw(surface)
                            cube.play(robot.play(), "O")
                        # Throw away clicks that happened while waiting
                        # for robot player
                        pygame.event.clear()
                    cube.draw(surface)
I changed the Cube.draw() method to make it easier to update the window.
    def draw(self, surface):
        """Draw all the tic-tac-toe boards."""
        surface.fill("white")
        for square in self.squares:
            square.draw(surface)
        pygame.display.flip()
Reply
#17
Yeah, I've tried to implement those parts, but can't make it work (a part with the robot player).
I completely agree with the speed part, but yes, I don't know how to make it better.

And also, I'd like to add an option that I can choose whether I want to start first or the computer. Right now, with this what I've already implemented, I always start first and when someone wins (me or the computer), the other player always starts first next.
Reply
#18
I think I have something. I went back to playing the boards individually and noticed that my minimax algorithm awarded a zero score to every square in an empty tic-tac-toe board. This meant that if you were in a losing position on a board, and your best hope was a draw, any square on an empty board appeared to be as good a choice.

To fix the problem I adjust a board's scores to have a floor of zero. Now losing on a board is weighted the same as not finishing (because you lost) on a different board. The computer is now a good opponent. Not unbeatable, but tough.

Realizing that all squares on an empty board have a score of zero also allows not using minimax on empty boards. Now I only have to compute scores for boards that contain marks. This speeds play considerably.
"""A tic-tac-toe game"""
import pygame
import random

PLAYER = 'X'
ROBOT = 'O'
colors = {None: 'black', ROBOT: 'red', PLAYER: 'green'}


class Robot:
    """Computer tic-tac-toe player"""
    def __init__(self, cube):
        self.boards = cube.boards

    def get_scores(self, board):
        """Get scores for all open squares on a board"""

        def minimax(mark, square, alpha=-1000, beta=1000, depth=0):
            """Compute a score for the square.  Uses using minimax
            algorithm with alpha/beta pruning.
            """
            # Place the marker and compute a score for the square.
            # Score is a win, a loss, a draw, or the score from
            # the next play.
            square.mark = mark
            if square.check_win():
                # Give extra weight to earlier wins/losses
                score = 10 - depth if mark == ROBOT else depth - 10
            else:
                empty_squares = board.empty_squares()
                if not empty_squares:
                    score = 0  # Draw
                elif mark == PLAYER:
                    # Pick best move for robot
                    score = -1000
                    for s in empty_squares:
                        score = max(score, minimax(ROBOT, s, alpha, beta, depth+1))
                        alpha = max(alpha, score)
                        if alpha > beta:
                            break
                else:
                    # Guess what move we think player will make
                    score = 1000
                    for s in empty_squares:
                        score = min(score, minimax(PLAYER, s, alpha, beta, depth+1))
                        beta = min(beta, score)
                        if alpha > beta:
                            break
            # Undo play and return score
            square.mark = None
            return score

        # Collect scores for empty squares.  If board is empty,
        # minimax will return 0 for all squares
        empty_squares = board.empty_squares()
        if len(empty_squares) == 9:
            scores = [[0, s] for s in empty_squares]
        else:
            scores = [[minimax("O", s), s] for s in empty_squares]

        # Adjust score so floor is zero to prevent ignoring
        # potential losing boards in favor of empty boards.
        if scores:
            min_score = min(score[0] for score in scores)
            for score in scores:
                score[0] -= min_score
        return scores

    def play(self):
        """Select square for next robot play"""
        scores = [
            *self.get_scores(self.boards[0]),
            *self.get_scores(self.boards[1]),
            *self.get_scores(self.boards[2])
        ]
        # Randomly select from best scores.
        max_score = max(score[0] for score in scores)
        squares = [score[1] for score in scores if score[0] >= max_score]
        return random.choice(squares)


class Square:
    """I am a square in a tic-tac-toe board in 3D"""
    def __init__(self, board, index, side, center=(0, 0, 0)):
        self.board = board
        self.index = index
        self.mark = None
        self.corners = [
            pygame.Vector3(-1, -1, 0) * side / 2,
            pygame.Vector3(1, -1, 0) * side / 2,
            pygame.Vector3(1, 1, 0) * side / 2,
            pygame.Vector3(-1, 1, 0) * side / 2
        ]
        self.move(center)

    def rotate(self, rotation):
        """Rotate square (rx, ry, rz) degrees"""
        for deg, axis in zip(rotation, ((1, 0, 0), (0, 1, 0), (0, 0, 1))):
            if deg:
                for corner in self.corners:
                    corner.rotate_ip(deg, axis)

    def move(self, offset):
        """Move square offset (x, y, z) pixels"""
        x, y, z = offset
        for corner in self.corners:
            corner.x += x
            corner.y += y
            corner.z += z

    def projection(self):
        """Return corners projected on xy plane"""
        return [pygame.Vector2(p.x, p.y) for p in self.corners]

    def draw(self, surface):
        """Draw projection of square on xy plane"""
        pygame.draw.polygon(surface, colors[self.mark], self.projection())

    def contains(self, point):
        """Return True if projection contains point."""
        def area(a, b, c):
            """Compute area of triangle"""
            return abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0)

        def has_point(a, b, c, p):
            """Return True if triange ABC contains point p."""
            return area(a, b, c) >= int(area(a, p, b) + area(b, p, c) + area(c, p, a))

        a, b, c, d = self.projection()
        return has_point(a, b, c, point) or has_point(a, c, d, point)

    def check_win(self):
        return self.board.check_win(self)


class Board:
    """A 3x3 tic-tac-toe boaard"""
    winning_combos = (
        (0, 2, 4, 6, 8), (1, 3, 5, 7), (0, 1, 2), (3, 4, 5), (6, 7, 8),
        (0, 3, 6), (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6)
    )

    def __init__(self, side):
        dx = side / 3
        self.squares = []
        for index in range(9):
            x = (index % 3 - 1) * dx
            y = (index // 3 - 1) * dx
            self.squares.append(Square(self, index, dx-6, (x, y, 0)))

    def empty_squares(self):
        """Return list of empty squares"""
        return [s for s in self.squares if s.mark is None]

    def check_win(self, square):
        """Return winning combination from last move, else None"""
        for combo in self.winning_combos:
            if square.index in combo:
                squares = [self.squares[i] for i in combo]
                if all(square.mark == s.mark for s in squares):
                    return squares
        return None


class Cube:
    """Three tic-tac-toe boards plastered on the sides of a cube"""
    def __init__(self, side):
        def make_board(center, rotation):
            board = Board(side)
            for square in board.squares:
                square.rotate(rotation)
                square.move(center)
            return board

        self.boards = [
            make_board((0, 0, -side/2), (0, 0, 0)),
            make_board((side/2, 0, 0), (0, 270, 0)),
            make_board((0, -side/2, 0), (90, 0, 0))
        ]
        self.squares = [s for b in self.boards for s in b.squares]
        self.winner = None
        self.mark = PLAYER
        self.reset()

    def click(self, point):
        """Try to place mark in clicked square."""
        if self.mark == PLAYER:
            for square in self.squares:
                if square.contains(point):
                    if square.mark is None:
                        self.play(square, self.mark)
                        return square
                    break
        return None

    def play(self, square, mark):
        """Place mark in square.  Test if is a winning move"""
        square.mark = mark
        self.mark_count += 1
        if winner := square.check_win():
            self.winner = winner
            for square in self.squares:
                if square not in winner:
                    square.mark = None
        self.mark = PLAYER if mark == ROBOT else ROBOT

    def done(self):
        """Return True if there is a winner or draw"""
        return self.winner or self.mark_count >= 27

    def reset(self):
        """Reset all boards to empty"""
        self.mark_count = 0
        self.winner = None
        for square in self.squares:
            square.mark = None

    def draw(self, surface):
        """Draw all the tic-tac-toe boards."""
        surface.fill("white")
        for square in self.squares:
            square.draw(surface)
        pygame.display.flip()


def main():
    """Play tic-tac-toe"""
    pygame.display.set_caption("Tic-Tac-Toe")
    surface = pygame.display.set_mode((500, 500))
    cube = Cube(300)
    center = surface.get_rect()
    for square in cube.squares:
        square.rotate((0, 45, 0))
        square.rotate((22.5, 0, 0))
        square.move((center.centerx, center.centery, 0))
    robot = Robot(cube)
    cube.draw(surface)

    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():
                    cube.reset()
                else:
                    cube.click(pygame.Vector2(event.pos))
                    pygame.event.clear()
                if cube.mark == ROBOT and not cube.done():
                    cube.play(robot.play(), "O")
                cube.draw(surface)


if __name__ == "__main__":
    pygame.init()
    main()
    pygame.quit()
Reply
#19
This seems like a great solution and, yes, the computer is now a good opponent, but when you start first, you pretty much win in 7 moves every time (first time when you have 2 in a row (3 moves in total) and in fourth move (the computer's second move) the computer blocks you and in fifth move (your third) you make 2 in a row again, but then the computer doesn't follow the game anymore and it's an easy win for you, not much different if the computer starts first (when the computer has a chance to win it etc.).
Reply
#20
As I said, not unbeatable. To be unbeatable the robot player has to look at all three boards simultaneously. That takes a really long time using minimax. I even wrote the code in C to see if that would be fast enough to use as a support module. No luck.

Here's another hack. The easiest way to beat the computer was something like this:
X O X
X O 6
O X 9

If player selects 6, computer selects 9 and the board ends in a draw, score = 0. But the computer can also draw selecting a square in an empty board. Because we are solving each board individually, there is no way for minmax to know that selecting a square on a different side will result in a loss on the next move.

An attempt to prevent this is to solve the board twice, once playing "O" (the computer mark) and once playing "X" (the player mark). When the computer plays "X" it will see playing square 9 is an immediate win (score = 10). No other square will have this high a score.
"""A tic-tac-toe game"""
import pygame
import random

PLAYER = 'X'
ROBOT = 'O'
colors = {None: 'black', ROBOT: 'red', PLAYER: 'green'}


class Robot:
    """Computer tic-tac-toe player"""
    def __init__(self, cube):
        self.boards = cube.boards

    def get_scores(self, board):
        """Get scores for all open squares on a board"""

        def minimax(mark, square, alpha=-1000, beta=1000, depth=0):
            """Compute a score for the square.  Uses using minimax
            algorithm with alpha/beta pruning.
            """
            # Place the marker and compute a score for the square.
            # Score is a win, a loss, a draw, or the score from
            # the next play.
            square.mark = mark
            if square.check_win():
                # Give extra weight to earlier wins/losses
                score = 10 - depth if mark == ROBOT else depth - 10
                board.depth = min(board.depth, depth)
            else:
                empty_squares = board.empty_squares()
                if not empty_squares:
                    score = 0  # Draw
                elif mark == PLAYER:
                    # Pick best move for robot
                    score = -1000
                    for s in empty_squares:
                        score = max(score, minimax(ROBOT, s, alpha, beta, depth+1))
                        alpha = max(alpha, score)
                        if alpha > beta:
                            break
                else:
                    # Guess what move we think player will make
                    score = 1000
                    for s in empty_squares:
                        score = min(score, minimax(PLAYER, s, alpha, beta, depth+1))
                        beta = min(beta, score)
                        if alpha > beta:
                            break
            # Undo play and return score
            square.mark = None
            return score

        # Collect scores for empty squares.  If board is empty,
        # minimax will return 0 for all squares
        board.depth = 10
        empty_squares = board.empty_squares()
        if len(empty_squares) == 9:
            board.scores = [[0, s] for s in empty_squares]
        else:
            # Calling minimax twice.  The first is to find the square
            # giving us best chance to win board.  Second is to prevent
            # abandoning board early and giving player an easy win.
            board.scores = [[minimax("O", s), s] for s in empty_squares]
            [minimax("X", s, depth=1) for s in empty_squares]

    def play(self):
        """Place robot mark."""
        # Get scores for all empty squares
        depth = 10
        for iboard in self.boards:
            self.get_scores(board)
            depth = min(depth, board.depth)

        # Select board(s) with minimum depth.  This is the board(s) that
        # will win/lose in the least number of moves.
        scores = []
        for board in self.boards:
            if board.depth <= depth:
                scores.extend(board.scores)

        # Randomly select from best scores on the selected board(s).
        max_score = max(score[0] for score in scores)
        squares = [score[1] for score in scores if score[0] >= max_score]
        return random.choice(squares)


class Square:
    """I am a square in a tic-tac-toe board in 3D"""
    def __init__(self, board, index, side, center=(0, 0, 0)):
        self.board = board
        self.index = index
        self.mark = None
        self.corners = [
            pygame.Vector3(-1, -1, 0) * side / 2,
            pygame.Vector3(1, -1, 0) * side / 2,
            pygame.Vector3(1, 1, 0) * side / 2,
            pygame.Vector3(-1, 1, 0) * side / 2
        ]
        self.move(center)

    def rotate(self, rotation):
        """Rotate square (rx, ry, rz) degrees"""
        for deg, axis in zip(rotation, ((1, 0, 0), (0, 1, 0), (0, 0, 1))):
            if deg:
                for corner in self.corners:
                    corner.rotate_ip(deg, axis)

    def move(self, offset):
        """Move square offset (x, y, z) pixels"""
        x, y, z = offset
        for corner in self.corners:
            corner.x += x
            corner.y += y
            corner.z += z

    def projection(self):
        """Return corners projected on xy plane"""
        return [pygame.Vector2(p.x, p.y) for p in self.corners]

    def draw(self, surface):
        """Draw projection of square on xy plane"""
        pygame.draw.polygon(surface, colors[self.mark], self.projection())

    def contains(self, point):
        """Return True if projection contains point."""
        def area(a, b, c):
            """Compute area of triangle"""
            return abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - b.y)) / 2.0)

        def has_point(a, b, c, p):
            """Return True if triange ABC contains point p."""
            return area(a, b, c) >= int(area(a, p, b) + area(b, p, c) + area(c, p, a))

        a, b, c, d = self.projection()
        return has_point(a, b, c, point) or has_point(a, c, d, point)

    def check_win(self):
        return self.board.check_win(self)


class Board:
    """A 3x3 tic-tac-toe boaard"""
    winning_combos = (
        (0, 2, 4, 6, 8), (1, 3, 5, 7), (0, 1, 2), (3, 4, 5), (6, 7, 8),
        (0, 3, 6), (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6)
    )

    def __init__(self, side):
        dx = side / 3
        self.squares = []
        for index in range(9):
            x = (index % 3 - 1) * dx
            y = (index // 3 - 1) * dx
            self.squares.append(Square(self, index, dx-6, (x, y, 0)))
        self.scores = None
        self.depth = 10

    def empty_squares(self):
        """Return list of empty squares"""
        return [s for s in self.squares if s.mark is None]

    def check_win(self, square):
        """Return winning combination from last move, else None"""
        for combo in self.winning_combos:
            if square.index in combo:
                squares = [self.squares[i] for i in combo]
                if all(square.mark == s.mark for s in squares):
                    return squares
        return None


class Cube:
    """Three tic-tac-toe boards plastered on the sides of a cube"""
    def __init__(self, side):
        def make_board(center, rotation):
            board = Board(side)
            for square in board.squares:
                square.rotate(rotation)
                square.move(center)
            return board

        self.boards = [
            make_board((0, 0, -side/2), (0, 0, 0)),
            make_board((side/2, 0, 0), (0, 270, 0)),
            make_board((0, -side/2, 0), (90, 0, 0))
        ]
        self.squares = [s for b in self.boards for s in b.squares]
        self.winner = None
        self.mark = PLAYER
        self.reset()

    def click(self, point):
        """Try to place mark in clicked square."""
        if self.mark == PLAYER:
            for square in self.squares:
                if square.contains(point):
                    if square.mark is None:
                        self.play(square, self.mark)
                        return square
                    break
        return None

    def play(self, square, mark):
        """Place mark in square.  Test if is a winning move"""
        square.mark = mark
        self.mark_count += 1
        if winner := square.check_win():
            self.winner = winner
            for square in self.squares:
                if square not in winner:
                    square.mark = None
        self.mark = PLAYER if mark == ROBOT else ROBOT

    def done(self):
        """Return True if there is a winner or draw"""
        return self.winner or self.mark_count >= 27

    def reset(self):
        """Reset all boards to empty"""
        self.mark_count = 0
        self.winner = None
        for square in self.squares:
            square.mark = None

    def draw(self, surface):
        """Draw all the tic-tac-toe boards."""
        surface.fill("white")
        for square in self.squares:
            square.draw(surface)
        pygame.display.flip()


def main():
    """Play tic-tac-toe"""
    pygame.display.set_caption("Tic-Tac-Toe")
    surface = pygame.display.set_mode((500, 500))
    cube = Cube(300)
    center = surface.get_rect()
    for square in cube.squares:
        square.rotate((0, 45, 0))
        square.rotate((22.5, 0, 0))
        square.move((center.centerx, center.centery, 0))
    robot = Robot(cube)
    cube.draw(surface)

    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():
                    cube.reset()
                else:
                    cube.click(pygame.Vector2(event.pos))
                    pygame.event.clear()
                if cube.mark == ROBOT and not cube.done():
                    cube.play(robot.play(), "O")
                cube.draw(surface)


if __name__ == "__main__":
    pygame.init()
    main()
    pygame.quit()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Drawing a net of a cube freethrownucleus 26 5,110 May-05-2023, 10:23 PM
Last Post: freethrownucleus
  2D-Cube-Tic-Tac-Toe freethrownucleus 0 1,157 Mar-10-2023, 07:07 PM
Last Post: freethrownucleus
  PyGlet Trouble Drawing Cube. Windspar 3 5,739 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