Posts: 51
Threads: 6
Joined: Dec 2022
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.
Posts: 51
Threads: 6
Joined: Dec 2022
May-03-2023, 04:40 PM
(This post was last modified: May-03-2023, 04:41 PM by freethrownucleus.)
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()
Posts: 6,798
Threads: 20
Joined: Feb 2020
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()
Posts: 51
Threads: 6
Joined: Dec 2022
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! :)
Posts: 6,798
Threads: 20
Joined: Feb 2020
May-04-2023, 04:27 PM
(This post was last modified: May-04-2023, 04:27 PM by deanhystad.)
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()
Posts: 51
Threads: 6
Joined: Dec 2022
Alright thanks!
Posts: 51
Threads: 6
Joined: Dec 2022
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. :)
Posts: 5
Threads: 0
Joined: Apr 2025
Apr-15-2025, 05:06 PM
(This post was last modified: Apr-15-2025, 05:07 PM by jassonadder.)
Hey! You're off to a solid start by trying to use Pygame for this. Let me try to explain in simple terms what’s going on and how you can make it work better for your idea.
You said you want to draw a “net of a cube” where each square shows a tic-tac-toe board. So think of the net like this: it’s a flat layout with 6 connected squares (just like how you can cut and fold paper to make a cube). And inside each of those squares, you want a tic-tac-toe board — that’s a 3x3 grid.
Right now, your code is loading the same image 6 times (cube_model.png) and putting each one in a square, but it doesn’t actually draw tic-tac-toe boards on those squares. Also, your code doesn’t do anything when you click or interact — it just shows images.
Let me give you a simple idea to get started:
First, don’t worry about using images yet.
Just draw 6 squares (faces of the cube) on the screen in a cube net shape.
Then, inside each square, draw a 3x3 grid with lines — that's your tic-tac-toe board.
Here’s a basic version of that:
import pygame
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
pygame.init()
screen = pygame.display.set_mode((500, 500))
pygame.display.set_caption("Cube Net with Tic-Tac-Toe Boards")
def draw_tic_tac_toe(x, y, size):
cell = size // 3
for i in range(1, 3):
# Vertical lines
pygame.draw.line(screen, BLACK, (x + i * cell, y), (x + i * cell, y + size), 2)
# Horizontal lines
pygame.draw.line(screen, BLACK, (x, y + i * cell), (x + size, y + i * cell), 2)
running = True
while running:
screen.fill(WHITE)
# Define square size
square_size = 100
# Draw the net (like a cross shape)
positions = [
(200, 50), # Top
(100, 150), # Left
(200, 150), # Front
(300, 150), # Right
(400, 150), # Extra face (optional)
(200, 250) # Bottom
]
for pos in positions:
pygame.draw.rect(screen, BLACK, (pos[0], pos[1], square_size, square_size), 2)
draw_tic_tac_toe(pos[0], pos[1], square_size)
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit() This will draw a tic-tac-toe board inside each of the 6 squares laid out in a cube-net shape. You can add labels or player interaction later, but this gives you the visual layout first.
Let me know if you want help making the game playable too!
|