Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Cube drawing
#30
Sorry about that. I was in mid-edit, cleaning up the code.
    if msg:
        text = pygame.font.Font(None, 100).render(msg, True, 'white')
Are you learning anything from this, or do you just want a tic-tac-toe game? This last error was very obvious. That you didn't catch it immediately makes me think that you are so overwhelmed by other parts of the code that your brain switches off. If you have questions, ask. I'll answer questions if it is obvious that you put some effort into formulating good questions.

If you just want a tic-tac-toe game, here it is. I cleaned up the screen refresh some more, making it an enclosed function of main() so it knows about cube and surface. I moved constants to the top of the file where they are easier to find and modify. I made sure ROBOT and PLAYER were used consistently instead of sometimes use "X" and "O". I removed unnecessary or limited value class methods, wrote better comments, improved formatting, etc... Basically just cleaned out the gunk that accumulates in programs that grow organically.
"""Tic-tac-toe with a computer opponent.  Three boards played simultaneously"""
import pygame
import random

PLAYER = 'X'
ROBOT = 'O'
EMPTY = ' '
background = "white"
msg_color = "white"
colors = {EMPTY: 'dodger blue', PLAYER: 'red', ROBOT: 'black'}


class Robot:
    """Computer tic-tac-toe player.  Uses minimax algorithm with
    alpha/beta pruning and some additional heuristics to provide
    a challenging, but beatable opponent.
    """
    def __init__(self, cube):
        self.boards = cube.boards

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

        def minimax(mark, square, alpha=-1000, beta=1000, depth=0):
            """minimax algorithm with alpha/beta pruning"""
            # Place the mark and check for a win
            square.mark = mark
            if square.check_win():
                # Give extra weight to earlier wins/losses
                score = 10 - depth if mark is ROBOT else depth - 10
                board.depth = min(board.depth, depth)
            elif len(empty_squares := board.empty_squares()) == 0:
                # No plays left.  Draw.
                score = 0
            elif mark is PLAYER:
                # Pick best move for robot
                score = -1000
                for s in empty_squares:
                    score = max(score, minimax(ROBOT, s, alpha, beta, depth+1))
                    alpha = max(alpha, score)
                    if alpha > beta:
                        break
            else:
                # Guess what move player will make
                score = 1000
                for s in empty_squares:
                    score = min(score, minimax(PLAYER, s, alpha, beta, depth+1))
                    beta = min(beta, score)
                    if alpha > beta:
                        break

            # Remove mark and return score for the square
            square.mark = EMPTY
            return score

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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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


def main():
    """Play tic-tac-toe"""

    def refresh_screen(msg=None):
        """Draw the cube and an optional message"""
        surface.fill(background)
        for square in cube.squares:
            square.draw(surface)
        if msg:
            text = pygame.font.Font(None, 100).render(msg, True, msg_color)
            surface.blit(text, (center.x - text.get_width() / 2, center.y))
        pygame.display.flip()

    pygame.display.set_caption("Tic-Tac-Toe")
    surface = pygame.display.set_mode((500, 500))
    center = pygame.Vector3(250, 250, 0)

    # Move cube for pretty orthogonal view at center of screen
    cube = Cube(300)
    for square in cube.squares:
        square.rotate((0, 45, 0)).rotate((22.5, 0, 0)).move(center)
    robot = Robot(cube)
    refresh_screen()

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if cube.done():
                    # Start a new game
                    cube.reset()
                    if cube.mark is ROBOT:
                        cube.play(robot.play(), ROBOT)
                    refresh_screen()
                elif cube.click(pygame.Vector2(event.pos)):
                    # Player selected square.  Robot's turn,
                    if not cube.done():
                        refresh_screen()
                        cube.play(robot.play(), ROBOT)
                    # Is the game over?
                    refresh_screen("Game Over" if cube.done() else None)
                    pygame.event.clear()


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


Messages In This Thread
Cube drawing - by freethrownucleus - Dec-11-2022, 11:49 PM
RE: Cube drawing - by deanhystad - Dec-13-2022, 04:22 AM
RE: Cube drawing - by freethrownucleus - Dec-13-2022, 12:51 PM
RE: Cube drawing - by ClaytonMorrison - Apr-12-2023, 08:04 AM
RE: Cube drawing - by deanhystad - Dec-13-2022, 10:29 PM
RE: Cube drawing - by freethrownucleus - Dec-14-2022, 12:01 AM
RE: Cube drawing - by deanhystad - Dec-14-2022, 05:13 AM
RE: Cube drawing - by freethrownucleus - Dec-14-2022, 11:06 AM
RE: Cube drawing - by deanhystad - Dec-14-2022, 01:14 PM
RE: Cube drawing - by freethrownucleus - Dec-14-2022, 02:09 PM
RE: Cube drawing - by deanhystad - Dec-14-2022, 08:05 PM
RE: Cube drawing - by freethrownucleus - Dec-14-2022, 10:10 PM
RE: Cube drawing - by deanhystad - Dec-14-2022, 11:44 PM
RE: Cube drawing - by freethrownucleus - Dec-16-2022, 07:49 PM
RE: Cube drawing - by deanhystad - Dec-19-2022, 04:54 AM
RE: Cube drawing - by freethrownucleus - Dec-20-2022, 01:18 PM
RE: Cube drawing - by deanhystad - Dec-21-2022, 12:01 AM
RE: Cube drawing - by freethrownucleus - Dec-21-2022, 12:43 PM
RE: Cube drawing - by deanhystad - Jan-01-2023, 04:36 AM
RE: Cube drawing - by freethrownucleus - Jan-01-2023, 10:25 PM
RE: Cube drawing - by deanhystad - Jan-02-2023, 02:17 PM
RE: Cube drawing - by freethrownucleus - Jan-02-2023, 09:39 PM
RE: Cube drawing - by deanhystad - Jan-02-2023, 10:07 PM
RE: Cube drawing - by freethrownucleus - Jan-02-2023, 10:41 PM
RE: Cube drawing - by deanhystad - Jan-03-2023, 04:31 AM
RE: Cube drawing - by freethrownucleus - Jan-03-2023, 04:01 PM
RE: Cube drawing - by deanhystad - Jan-03-2023, 07:21 PM
RE: Cube drawing - by freethrownucleus - Jan-03-2023, 09:18 PM
RE: Cube drawing - by deanhystad - Jan-03-2023, 10:19 PM
RE: Cube drawing - by freethrownucleus - Jan-04-2023, 08:28 PM
RE: Cube drawing - by deanhystad - Jan-04-2023, 08:29 PM
RE: Cube drawing - by freethrownucleus - Jan-04-2023, 08:53 PM
RE: Cube drawing - by deanhystad - Jan-04-2023, 09:15 PM
RE: Cube drawing - by freethrownucleus - Jan-04-2023, 09:20 PM
RE: Cube drawing - by deanhystad - Jan-04-2023, 09:34 PM
RE: Cube drawing - by freethrownucleus - Jan-05-2023, 01:59 PM
RE: Cube drawing - by deanhystad - Jan-05-2023, 06:06 PM
RE: Cube drawing - by freethrownucleus - Jan-05-2023, 06:26 PM
RE: Cube drawing - by deanhystad - Jan-05-2023, 07:35 PM
RE: Cube drawing - by freethrownucleus - Jan-05-2023, 08:32 PM
RE: Cube drawing - by deanhystad - Jan-05-2023, 09:16 PM
RE: Cube drawing - by freethrownucleus - Jan-05-2023, 09:32 PM
RE: Cube drawing - by deanhystad - Jan-05-2023, 10:03 PM
RE: Cube drawing - by freethrownucleus - Jan-05-2023, 10:18 PM
RE: Cube drawing - by deanhystad - Jan-05-2023, 10:23 PM
RE: Cube drawing - by freethrownucleus - Jan-05-2023, 11:24 PM
RE: Cube drawing - by deanhystad - Jan-06-2023, 07:15 PM
RE: Cube drawing - by freethrownucleus - Jan-07-2023, 08:44 PM
RE: Cube drawing - by deanhystad - Jan-07-2023, 11:19 PM
RE: Cube drawing - by freethrownucleus - Jan-08-2023, 12:25 AM
RE: Cube drawing - by deanhystad - Jan-08-2023, 05:11 PM
RE: Cube drawing - by freethrownucleus - Jan-08-2023, 06:29 PM

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