Posts: 51
Threads: 6
Joined: Dec 2022
Hi, I need some help.
I need to draw a cube (like this) ![[Image: 8HGHVN]](https://gofile.io/d/8HGHVN) .
If anyone could help, I would really appreciate it! :)
A further idea is to play the game parallel on those 3 fields (cube sides).
Attached Files
Thumbnail(s)
Posts: 6,794
Threads: 20
Joined: Feb 2020
Drawing something like a rubic's cube does you no good. A 3D tic-tac-toe has 27 spots for x's and o's. An orthogonal view of a Rubic's cube only displays 19 cubes. Even if you spin it around you can't see the center.
Cube slides is no good either. There are 27 squares, but 8 of those are redundant, so you are only displaying 19 places where you can enter an x or o.
What you need are slices displaying the front face, the rear face, and the "center face".
Posts: 51
Threads: 6
Joined: Dec 2022
Dec-07-2022, 10:08 PM
(This post was last modified: Dec-08-2022, 12:16 AM by Yoriz.
Edit Reason: removed unnecessary quote of previous post
)
Hi, ty for replying!
Tbh, I didn't understand you well, except it's not a good idea.
But it's because I didn't explain what I need to do as understandable as I should.
To explain it the easiest way, my job is to do next:
there is a cube model that I've already posted and you should be playing just on those 3 sides (the exact cube and the way it's positioned) of the cube (classic Tic-Tac-Toe rules) without any rotation.
An example is here: https://gofile.io/d/La5dye (the cube shouldn't be positioned that way, it was recorded before I decided the cube's position).
Ok sorry, cannot post the example video because it's too large. :/ (EDIT: ignore this, an example's been posted!)
Posts: 6,794
Threads: 20
Joined: Feb 2020
So no 3D tic-tac-toe, just 3 regular tic-tac-toe games painted on the sides of a cube.
Posts: 51
Threads: 6
Joined: Dec 2022
Dec-07-2022, 11:47 PM
(This post was last modified: Dec-08-2022, 12:17 AM by Yoriz.
Edit Reason: removed unnecessary quote of previous post
)
Yeah ... sorry for a bad explanation / obviously a wrong title.
But, despite that, still need help.
Posts: 6,794
Threads: 20
Joined: Feb 2020
Jan-15-2023, 04:59 AM
(This post was last modified: Jan-15-2023, 04:59 AM by deanhystad.)
I wrote a 3D tic-tac-toe game with a very simple robot player. The code will look very familiar. I have not tested it much, so I wouldn't be surprised if some of the winning combinations are messed up.
"""3D tic-tac-toe game with robot player"""
import pygame
import random
from collections import Counter
background = "white"
EMPTY, PLAYER, ROBOT = ' ', 'X', 'O' # board markers
colors = {EMPTY: 'dodger blue', PLAYER: 'red', ROBOT: 'black'}
winning_combos = (
(0, 1, 2), (9, 10, 11), (18, 19, 20), (3, 4, 5), (12, 13, 14),
(21, 22, 23), (6, 7, 8), (15, 16, 17), (24, 25, 26), (0, 3, 6),
(9, 12, 15), (18, 21, 24), (1, 4, 7), (10, 13, 16), (19, 22, 25),
(2, 5, 8), (11, 14, 17), (20, 23, 26), (0, 9, 18), (1, 10, 19),
(2, 11, 20), (3, 12, 21), (4, 13, 22), (5, 14, 23), (6, 15, 24),
(7, 16, 25), (8, 17, 26), (0, 4, 8), (9, 13, 17), (18, 22, 26),
(2, 4, 6), (11, 13, 15), (20, 22, 24), (0, 12, 24), (1, 13, 25),
(2, 14, 26), (6, 12, 18), (7, 13, 19), (8, 14, 20), (0, 10, 20),
(3, 13, 23), (6, 16, 26), (2, 10, 18), (5, 13, 21), (8, 16, 24),
(0, 13, 26), (2, 13, 24), (6, 13, 20), (8, 13, 20)
)
frequency = Counter(i for combo in winning_combos for i in combo)
class Robot:
"""Computer tic-tac-toe player. Searches for for win or loss in one
move, else selects random square
"""
def __init__(self, board):
self.board = board
def play(self):
"""Return best square according to algorithm"""
empty_squares = self.board.empty_squares()
for square in empty_squares:
if square.check_win(ROBOT):
return square
for square in empty_squares:
if square.check_win(PLAYER):
return square
weights = [frequency[square.index] for square in empty_squares]
square = random.choices(empty_squares, weights=weights, k=1)[0]
return square
class Square:
"""Square in a 3D tic-tac-toe board"""
def __init__(self, index, radius, center):
self.radius = radius
self.index = index
self.mark = EMPTY
self.center = center
self.wins = []
def rotate(self, rotation):
"""Rotate square (yaw, pitch, roll) degrees"""
for deg, axis in zip(rotation, ((0, 0, 1), (0, 1, 0), (1, 0, 0))):
if deg:
self.center = self.center.rotate(deg, axis)
return self
def move(self, offset):
"""Move square offset (x, y, z) pixels"""
self.center = self.center + offset
return self
def draw(self, surface):
"""Draw projection of square on xy plane"""
pygame.draw.circle(
surface, colors[self.mark], self.projection(), self.radius
)
def contains(self, point):
"""Return True if point inside square"""
return self.projection().distance_to(point) <= self.radius
def projection(self):
"""Return xy projection"""
return pygame.Vector2(self.center.x, self.center.y)
def check_win(self, mark=None):
"""Return winning combination involving square"""
self.mark = self.mark if mark is None else mark
for combo in self.wins:
if all(self.mark == s.mark for s in combo):
break
else:
combo = None
self.mark = self.mark if mark is None else EMPTY
return combo
def add_win(self, squares):
if self in squares:
self.wins.append(squares)
class Board:
"""A 3D tic-tac-toe board"""
def __init__(self, size, surface):
spacing = size / 2
radius = spacing / 8
self.surface = surface
self.squares = []
for z in (-spacing, 0, spacing):
for y in (-spacing, 0, spacing):
for x in (-spacing, 0, spacing):
index = len(self.squares)
center = pygame.Vector3(x, y, z)
self.squares.append(Square(index, radius, center))
for combo in winning_combos:
squares = set(self.squares[i] for i in combo)
for square in self.squares:
square.add_win(squares)
self.mark = PLAYER
self.mark_count = 0
self.winner = None
def draw(self):
"""Draw board"""
self.surface.fill(background)
for i in range(9):
a = self.squares[i].projection()
b = self.squares[i+18].projection()
pygame.draw.line(self.surface, colors[EMPTY], a, b)
for i in range(0, 27, 3):
a = self.squares[i].projection()
b = self.squares[i+2].projection()
pygame.draw.line(self.surface, colors[EMPTY], a, b)
for square in self.squares:
square.draw(self.surface)
pygame.display.flip()
def reset(self):
"""Ready board to play new game"""
for square in self.squares:
square.mark = EMPTY
self.mark_count = 0
self.winner = None
def done(self):
"""Return winning squares if winner, True if draw, False if not done"""
return self.winner or self.mark_count >= 27
def empty_squares(self):
"""Return list of empty squares"""
return [s for s in self.squares if s.mark is EMPTY]
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
self.winner = square.check_win()
if self.winner:
for square in self.squares:
if square not in self.winner:
square.mark = EMPTY
self.mark = PLAYER if mark is ROBOT else ROBOT
def main():
"""Play tic-tac-toe"""
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
board = Board(300, surface)
robot = Robot(board)
for square in board.squares:
square.rotate((0, 35, 12)).move(center)
board.draw()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if board.done():
board.reset()
else:
board.click(pygame.Vector2(event.pos))
if board.mark == ROBOT and not board.done():
board.draw()
board.play(robot.play(), ROBOT)
board.draw()
if __name__ == "__main__":
pygame.init()
main()
pygame.quit()
Posts: 51
Threads: 6
Joined: Dec 2022
Jan-16-2023, 05:08 PM
(This post was last modified: Jan-18-2023, 08:51 PM by Yoriz.
Edit Reason: removed unnecessary quote of previous post
)
Hi, thanks!
We've already talked to each other and I was wondering if I can somehow change the minimax algorithm so that the computer tries to score 3 in a row (in a line) when trying to win and then tries to score the other 2 combos. You can see the winning combos from the 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: 'dodgerblue', PLAYER: 'green', ROBOT: 'red'}
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),
)
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 - 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((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 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, 700)
blit_text(surface, msg, pos, None, color = pygame.Color('dodgerblue'))
pygame.display.flip()
pygame.display.set_caption("Tic-Tac-Toe")
surface = pygame.display.set_mode((700, 750))
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)
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(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()
Posts: 6,794
Threads: 20
Joined: Feb 2020
Jan-18-2023, 06:19 PM
(This post was last modified: Jan-18-2023, 06:19 PM by deanhystad.)
Stop replaying to posts each time you ask a question. It makes the threads really long. Your last post had nothing to do with my last post. There is no reason why my post should be included. Imagine what a mess it would be if I replied to your post, then you replied to mine, and I replied back and so on an so on. Break the chain!
Minimax makes all decisions based on scores. If you prefer than minimax tries to win using rows and columns, modify the game code so that check_win returns a score. If the score is higher for wins that are rows and columns, minimax will give preference to plays that complete rows or columns.
Not sure how you would implement the scoring. Maybe multiple lists of winning combinations?
# Not real code
]rows = [(0, 1, 2), (3, 4, 5), (6, 7, 8)]
columns = [(0, 3, 6), (1, 4, 7), (2, 5, 8)]
diagonals = [(0, 4, 8), (2, 4, 6)]
if win_in_combos(columns):
score = 4
elif win_in_combos(rows):
score = 3
elif win_in_combos(diagonals):
score = 2 Or maybe encode the score in each of the combinations. Maybe the first value
# Not real code
winning_combos = [(4, (0, 1, 2)), (3, (0, 3, 6)), (2, (0, 4, 8))]
for combo in winning_combos:
score, squares = combo:
if combo_wins(squares):
return score
|