Búsqueda de sitios web

Crea un juego de tres en raya con Python y Tkinter


Jugar juegos de computadora es una excelente manera de relajarse o desafiarse a sí mismo. Algunas personas incluso lo hacen de forma profesional. También es divertido y educativo crear tus propios juegos de computadora. En este tutorial, crearás un juego clásico de tres en raya usando Python y Tkinter.

Con este proyecto, recorrerás los procesos de pensamiento necesarios para crear tu propio juego. También aprenderá cómo integrar sus diversas habilidades y conocimientos de programación para desarrollar un juego de computadora funcional y divertido.

En este tutorial, aprenderá cómo:

  • Programa la lógica del juego clásico de tres en raya usando Python
  • Crea la interfaz gráfica de usuario (GUI) del juego usando el kit de herramientas Tkinter.
  • Integre la lógica y la GUI del juego en un juego de computadora completamente funcional

Como se mencionó, utilizará el marco GUI de Tkinter de la biblioteca estándar de Python para crear la interfaz de su juego. También utilizará el patrón modelo-vista-controlador y un enfoque orientado a objetos para organizar su código. Para obtener más información sobre estos conceptos, consulte los enlaces en los requisitos previos.

Para descargar el código fuente completo de este proyecto, haga clic en el enlace en el cuadro a continuación:

Demostración: un juego de tres en raya en Python

En este proyecto paso a paso, crearás un juego de tres en raya en Python. Utilizarás el kit de herramientas Tkinter de la biblioteca estándar de Python para crear la GUI del juego. En el siguiente vídeo de demostración, tendrás una idea de cómo funcionará tu juego una vez que hayas completado este tutorial:

Tu juego de tres en raya tendrá una interfaz que reproduce el clásico tablero de juego de tres por tres. Los jugadores se turnarán para realizar sus movimientos en un dispositivo compartido. La pantalla del juego en la parte superior de la ventana mostrará el nombre del jugador que será el siguiente.

Si un jugador gana, la pantalla del juego mostrará un mensaje ganador con el nombre o la marca del jugador (X o O). Al mismo tiempo, la combinación ganadora de celdas se resaltará en el tablero.

Finalmente, el menú Archivo del juego tendrá opciones para restablecer el juego si quieres volver a jugar o para salir del juego cuando hayas terminado de jugar.

Si esto le parece un proyecto divertido, ¡siga leyendo para comenzar!

Descripción del proyecto

Su objetivo con este proyecto es crear un juego de tres en raya en Python. Para la interfaz del juego, utilizarás el kit de herramientas GUI de Tkinter, que viene en la instalación estándar de Python como una batería incluida.

El juego de tres en raya es para dos jugadores. Un jugador juega X y el otro juega O. Los jugadores se turnan para colocar sus marcas en una cuadrícula de celdas de tres por tres. Si un jugador determinado obtiene tres marcas seguidas en horizontal, vertical o diagonal, ese jugador gana el juego. El juego estará empatado si nadie consigue tres seguidos cuando se marquen todas las celdas.

Con estas reglas en mente, necesitarás reunir los siguientes componentes del juego:

  • El tablero del juego, que construirás con una clase llamada TicTacToeBoard
  • La lógica del juego, que administrarás usando una clase llamada TicTacToeGame

El tablero de juego funcionará como una combinación entre vista y controlador en un diseño modelo-vista-controlador. Para construir el tablero, usará una ventana de Tkinter, que puede crear creando una instancia de la clase tkinter.Tk. Esta ventana tendrá dos componentes principales:

  1. Pantalla superior: muestra información sobre el estado del juego.
  2. Cuadrícula de celdas: Representa movimientos anteriores y espacios o celdas disponibles.

Crearás la visualización del juego usando un widget tkinter.Label, que te permite mostrar texto e imágenes.

Para la cuadrícula de celdas, utilizará una serie de widgets tkinter.Button organizados en una cuadrícula. Cuando un jugador hace clic en uno de estos botones, la lógica del juego se ejecutará para procesar el movimiento del jugador y determinar si hay un ganador. En este caso, la lógica del juego funcionará como el modelo, que gestionará los datos, la lógica y las reglas de tu juego.

Ahora que tienes una idea general de cómo crear tu juego de tres en raya, debes consultar algunos requisitos previos de conocimiento que te permitirán aprovechar al máximo este tutorial.

Requisitos previos

Para completar este proyecto de juego de tres en raya, debes sentirte cómodo o al menos familiarizado con los conceptos y temas tratados en los siguientes recursos:

  • Programación de la GUI de Python con Tkinter
  • Programación orientada a objetos (OOP) en Python 3
  • Bucles "for" de Python (iteración definitiva)
  • Cuándo utilizar una lista por comprensión en Python
  • Modelo-Vista-Controlador (MVC) explicado - Con Legos
  • Diccionarios en Python
  • Cómo iterar a través de un diccionario en Python
  • Funciones principales en Python
  • Escriba código Pythonic y limpio con Namedtuple

Si no tiene todos los conocimientos sugeridos antes de comenzar este tutorial, está bien. Aprenderás haciendo, ¡así que adelante y pruébalo! Siempre puedes detenerte y revisar los recursos vinculados aquí si te quedas atascado.

Paso 1: Configura el tablero de juego Tic-Tac-Toe con Tkinter

Para comenzar, comenzarás creando el tablero de juego. Antes de hacer esto, debes decidir cómo organizar el código para tu juego de tres en raya. Debido a que este proyecto será bastante pequeño, inicialmente puedes mantener todo el código en un único archivo .py. De esta manera, ejecutar el código y ejecutar tu juego será más sencillo.

Continúe y inicie su editor de código o IDE favorito. Luego cree un archivo tic_tac_toe.py en su directorio de trabajo actual:

# tic_tac_toe.py

"""A tic-tac-toe game built with Python and Tkinter."""

A lo largo de este tutorial, agregará código de forma incremental a este archivo, así que manténgalo abierto y cerca. Si desea obtener el código completo para este proyecto de juego de tres en raya, puede hacer clic en la siguiente sección plegable y copiar el código:

"""A tic-tac-toe game built with Python and Tkinter."""

import tkinter as tk
from itertools import cycle
from tkinter import font
from typing import NamedTuple

class Player(NamedTuple):
    label: str
    color: str

class Move(NamedTuple):
    row: int
    col: int
    label: str = ""

BOARD_SIZE = 3
DEFAULT_PLAYERS = (
    Player(label="X", color="blue"),
    Player(label="O", color="green"),
)

class TicTacToeGame:
    def __init__(self, players=DEFAULT_PLAYERS, board_size=BOARD_SIZE):
        self._players = cycle(players)
        self.board_size = board_size
        self.current_player = next(self._players)
        self.winner_combo = []
        self._current_moves = []
        self._has_winner = False
        self._winning_combos = []
        self._setup_board()

    def _setup_board(self):
        self._current_moves = [
            [Move(row, col) for col in range(self.board_size)]
            for row in range(self.board_size)
        ]
        self._winning_combos = self._get_winning_combos()

    def _get_winning_combos(self):
        rows = [
            [(move.row, move.col) for move in row]
            for row in self._current_moves
        ]
        columns = [list(col) for col in zip(*rows)]
        first_diagonal = [row[i] for i, row in enumerate(rows)]
        second_diagonal = [col[j] for j, col in enumerate(reversed(columns))]
        return rows + columns + [first_diagonal, second_diagonal]

    def toggle_player(self):
        """Return a toggled player."""
        self.current_player = next(self._players)

    def is_valid_move(self, move):
        """Return True if move is valid, and False otherwise."""
        row, col = move.row, move.col
        move_was_not_played = self._current_moves[row][col].label == ""
        no_winner = not self._has_winner
        return no_winner and move_was_not_played

    def process_move(self, move):
        """Process the current move and check if it's a win."""
        row, col = move.row, move.col
        self._current_moves[row][col] = move
        for combo in self._winning_combos:
            results = set(self._current_moves[n][m].label for n, m in combo)
            is_win = (len(results) == 1) and ("" not in results)
            if is_win:
                self._has_winner = True
                self.winner_combo = combo
                break

    def has_winner(self):
        """Return True if the game has a winner, and False otherwise."""
        return self._has_winner

    def is_tied(self):
        """Return True if the game is tied, and False otherwise."""
        no_winner = not self._has_winner
        played_moves = (
            move.label for row in self._current_moves for move in row
        )
        return no_winner and all(played_moves)

    def reset_game(self):
        """Reset the game state to play again."""
        for row, row_content in enumerate(self._current_moves):
            for col, _ in enumerate(row_content):
                row_content[col] = Move(row, col)
        self._has_winner = False
        self.winner_combo = []

class TicTacToeBoard(tk.Tk):
    def __init__(self, game):
        super().__init__()
        self.title("Tic-Tac-Toe Game")
        self._cells = {}
        self._game = game
        self._create_menu()
        self._create_board_display()
        self._create_board_grid()

    def _create_menu(self):
        menu_bar = tk.Menu(master=self)
        self.config(menu=menu_bar)
        file_menu = tk.Menu(master=menu_bar)
        file_menu.add_command(label="Play Again", command=self.reset_board)
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=quit)
        menu_bar.add_cascade(label="File", menu=file_menu)

    def _create_board_display(self):
        display_frame = tk.Frame(master=self)
        display_frame.pack(fill=tk.X)
        self.display = tk.Label(
            master=display_frame,
            text="Ready?",
            font=font.Font(size=28, weight="bold"),
        )
        self.display.pack()

    def _create_board_grid(self):
        grid_frame = tk.Frame(master=self)
        grid_frame.pack()
        for row in range(self._game.board_size):
            self.rowconfigure(row, weight=1, minsize=50)
            self.columnconfigure(row, weight=1, minsize=75)
            for col in range(self._game.board_size):
                button = tk.Button(
                    master=grid_frame,
                    text="",
                    font=font.Font(size=36, weight="bold"),
                    fg="black",
                    width=3,
                    height=2,
                    highlightbackground="lightblue",
                )
                self._cells[button] = (row, col)
                button.bind("<ButtonPress-1>", self.play)
                button.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")

    def play(self, event):
        """Handle a player's move."""
        clicked_btn = event.widget
        row, col = self._cells[clicked_btn]
        move = Move(row, col, self._game.current_player.label)
        if self._game.is_valid_move(move):
            self._update_button(clicked_btn)
            self._game.process_move(move)
            if self._game.is_tied():
                self._update_display(msg="Tied game!", color="red")
            elif self._game.has_winner():
                self._highlight_cells()
                msg = f'Player "{self._game.current_player.label}" won!'
                color = self._game.current_player.color
                self._update_display(msg, color)
            else:
                self._game.toggle_player()
                msg = f"{self._game.current_player.label}'s turn"
                self._update_display(msg)

    def _update_button(self, clicked_btn):
        clicked_btn.config(text=self._game.current_player.label)
        clicked_btn.config(fg=self._game.current_player.color)

    def _update_display(self, msg, color="black"):
        self.display["text"] = msg
        self.display["fg"] = color

    def _highlight_cells(self):
        for button, coordinates in self._cells.items():
            if coordinates in self._game.winner_combo:
                button.config(highlightbackground="red")

    def reset_board(self):
        """Reset the game's board to play again."""
        self._game.reset_game()
        self._update_display(msg="Ready?")
        for button in self._cells.keys():
            button.config(highlightbackground="lightblue")
            button.config(text="")
            button.config(fg="black")

def main():
    """Create the game's board and run its main loop."""
    game = TicTacToeGame()
    board = TicTacToeBoard(game)
    board.mainloop()

if __name__ == "__main__":
    main()

Tener el código fuente completo de antemano le permitirá verificar su progreso mientras realiza el tutorial.

Alternativamente, también puedes descargar el código fuente del juego desde GitHub haciendo clic en el enlace en el cuadro a continuación:

Ahora que sabes cómo será el código final del juego, es hora de asegurarte de tener la versión de Tkinter correcta para este proyecto. Luego, seguirás adelante y crearás tu tablero de juego.

Asegúrese de tener la versión correcta de Tkinter

Para completar este proyecto, utilizará una instalación estándar de Python. No es necesario crear un entorno virtual porque no se requiere dependencia externa. El único paquete que necesitará es Tkinter, que viene con la biblioteca estándar de Python.

Sin embargo, debe asegurarse de tener instalada la versión correcta de Tkinter. Deberías tener Tkinter mayor o igual a 8,6. De lo contrario, tu juego no funcionará.

Puede verificar su versión actual de Tkinter iniciando una sesión interactiva de Python y ejecutando el siguiente código:

>>> import tkinter
>>> tkinter.TkVersion
8.6

Si este código no muestra una versión mayor o igual a 8.6 para su instalación de Tkinter, deberá solucionarlo.

En Ubuntu Linux, es posible que necesites instalar el paquete python3-tk usando el administrador de paquetes del sistema, apt. Esto se debe a que normalmente Ubuntu no incluye Tkinter en su instalación predeterminada de Python.

Una vez que haya instalado Tkinter correctamente, deberá verificar su versión actual. Si la versión de Tkinter es inferior a 8.6, deberá instalar una versión más reciente de Python descargándola de la página de descarga oficial o utilizando una herramienta como pyenv o Docker.

En macOS y Windows, una opción sencilla es instalar una versión de Python mayor o igual a 3.9.8 desde la página de descarga.

Una vez que esté seguro de tener la versión correcta de Tkinter, puede volver a su editor de código y comenzar a escribir código. Comenzarás con la clase de Python que representará el tablero del juego de tres en raya.

Crea una clase para representar el tablero de juego

Para construir el tablero de tu juego de tres en raya, usarás la clase Tk, que te permite crear la ventana principal de tu aplicación Tkinter. Luego agregará una pantalla en un marco superior y una cuadrícula de celdas que cubrirán el resto de la ventana principal.

Continúe e importe los objetos requeridos y defina la clase de tablero:

# tic_tac_toe.py

import tkinter as tk
from tkinter import font

class TicTacToeBoard(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Tic-Tac-Toe Game")
        self._cells = {}

En este fragmento de código, primero importa tkinter como tk para llevar el nombre del módulo a su espacio de nombres actual. Usar la abreviatura tk es una práctica común cuando se trata de usar Tkinter en tu código.

Luego importa el módulo font directamente desde tkinter. Utilizarás este módulo más adelante en este tutorial para modificar la fuente de la pantalla del juego.

La clase TicTacToeBoard hereda de Tk, lo que la convierte en una ventana GUI completa. Esta ventana representará el tablero de juego. Dentro de .__init__(), primero llamas al método .__init__() de la superclase para inicializar correctamente la clase principal. Para hacer esto, utiliza la función super() incorporada.

El atributo .title de Tk define el texto que se mostrará en la barra de título de la ventana. En este ejemplo, establece el título en la cadena "Tic-Tac-Toe Game".

El atributo no público ._cells contiene un diccionario inicialmente vacío. Este diccionario asignará los botones o celdas del tablero de juego a sus coordenadas correspondientes (fila y columna) en la cuadrícula. Estas coordenadas serán números enteros que reflejarán la fila y columna donde aparecerá un botón determinado.

Para continuar con el tablero de juego, ahora necesitas crear una pantalla donde puedas proporcionar información sobre el estado y el resultado del juego. Para esta visualización, utilizará un widget Frame como panel de visualización y un widget Etiqueta para mostrar la información requerida.

Ahora continúa y agrega el siguiente método a tu clase TicTacToeBoard:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def _create_board_display(self):
        display_frame = tk.Frame(master=self)
        display_frame.pack(fill=tk.X)
        self.display = tk.Label(
            master=display_frame,
            text="Ready?",
            font=font.Font(size=28, weight="bold"),
        )
        self.display.pack()

Aquí hay un desglose de lo que hace este método línea por línea:

  • La Línea 8 crea un objeto Frame para contener la visualización del juego. Tenga en cuenta que el argumento master está establecido en self, lo que significa que la ventana principal del juego será la ventana principal del marco.

  • La Línea 9 usa el .pack() administrador de geometría para colocar el objeto de marco en el borde superior de la ventana principal. Al establecer el argumento fill en tk.X, se asegura de que cuando el usuario cambie el tamaño de la ventana, el marco ocupará todo su ancho.

  • Las líneas 10 a 14 crean un objeto Label. Esta etiqueta debe vivir dentro del objeto de marco, por lo que establece su argumento master en el marco real. La etiqueta mostrará inicialmente el texto "Ready?", lo que indica que el juego está listo y los jugadores pueden comenzar una nueva partida. Finalmente, cambia el tamaño de fuente de la etiqueta a 28 píxeles y la pone en negrita.

  • La Línea 15 empaqueta la etiqueta de visualización dentro del marco usando el administrador de geometría .pack().

¡Fresco! Ya tienes la pantalla del juego. Ahora puedes crear la cuadrícula de celdas. Un clásico juego de tres en raya tiene una cuadrícula de celdas de tres por tres.

Aquí hay un método que crea la cuadrícula de celdas usando objetos Button:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def _create_board_grid(self):
        grid_frame = tk.Frame(master=self)
        grid_frame.pack()
        for row in range(3):
            self.rowconfigure(row, weight=1, minsize=50)
            self.columnconfigure(row, weight=1, minsize=75)
            for col in range(3):
                button = tk.Button(
                    master=grid_frame,
                    text="",
                    font=font.Font(size=36, weight="bold"),
                    fg="black",
                    width=3,
                    height=2,
                    highlightbackground="lightblue",
                )
                self._cells[button] = (row, col)
                button.grid(
                    row=row,
                    column=col,
                    padx=5,
                    pady=5,
                    sticky="nsew"
                )

¡Guau! ¡Este método hace mucho! Aquí hay una explicación de lo que hace cada línea:

  • La Línea 8 crea un objeto Frame para contener la cuadrícula de celdas del juego. Estableces el argumento master en self, lo que nuevamente significa que la ventana principal del juego será la principal de este objeto de marco.

  • La Línea 9 utiliza el administrador de geometría .pack() para colocar el objeto de marco en la ventana principal. Este marco ocupará el área debajo de la pantalla del juego, hasta la parte inferior de la ventana.

  • La Línea 10 inicia un bucle que se itera desde 0 a 2. Estos números representan las coordenadas de fila de cada celda de la cuadrícula. Por ahora, tendrás 3 filas en la cuadrícula. Sin embargo, cambiará este número mágico más adelante y brindará la opción de usar un tamaño de cuadrícula diferente, como cuatro por cuatro.

  • Las líneas 11 y 12 configuran el ancho y el tamaño mínimo de cada celda de la cuadrícula.

  • La línea 13 recorre las tres coordenadas de la columna. Nuevamente utiliza tres columnas, pero cambiará este número más adelante para brindar más flexibilidad y deshacerse de los números mágicos.

  • Las líneas 14 a 22 crean un objeto Button para cada celda de la cuadrícula. Tenga en cuenta que establece varios atributos, incluidos master, text, font, etc.

  • La línea 23 agrega cada botón nuevo al diccionario ._cells. Los botones funcionan como teclas y sus coordenadas, expresadas como (fila, columna), funcionan como valores.

  • Las líneas 24 a 30 finalmente agregan cada botón a la ventana principal usando el administrador de geometría .grid().

Ahora que ha implementado ._create_board_display() y ._create_board_grid(), puede llamarlos desde el inicializador de clase. Continúe y agregue las siguientes dos líneas a .__init__() en su clase TicTacToeBoard:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Tic-Tac-Toe Game")
        self._cells = {}
        self._create_board_display()
        self._create_board_grid()

Estas dos líneas arman el tablero de juego agregando la pantalla y la cuadrícula de celdas. ¿No es genial?

Con estas actualizaciones, casi puedes ejecutar tu aplicación y ver cómo se verá tu juego de tres en raya. Sólo necesita escribir algunas líneas más de código repetitivo. Necesita crear una instancia de TicTacToeBoard y llamar a su método .mainloop() para iniciar su aplicación Tkinter.

Continúe y agregue el siguiente fragmento de código al final de su archivo tic_tac_toe.py:

# tic_tac_toe.py
# ...

def main():
    """Create the game's board and run its main loop."""
    board = TicTacToeBoard()
    board.mainloop()

if __name__ == "__main__":
    main()

Este fragmento de código define una función main() para tu juego. Dentro de esta función, primero crea una instancia de TicTacToeBoard y luego ejecuta su bucle principal llamando a .mainloop().

La construcción if __name__ == "__main__": es un patrón común en las aplicaciones Python. Le permite controlar la ejecución de su código. En este caso, la llamada a main() solo ocurrirá si ejecuta el archivo .py como un programa ejecutable, en lugar de un módulo importable.

¡Eso es todo! Ahora estás listo para ejecutar tu juego por primera vez. Por supuesto, el juego aún no se puede jugar, pero el tablero está listo. Para ejecutar el juego, continúa y ejecuta el siguiente comando en tu línea de comando:

$ python tic_tac_toe.py

Una vez que haya ejecutado este comando, aparecerá la siguiente ventana en su pantalla:

¡Fresco! Tu juego de tres en raya está empezando a parecerse a algo real. Ahora necesitas hacer que esta ventana responda a las acciones de los jugadores en el tablero.

Paso 2: configurar la lógica del juego Tic-Tac-Toe en Python

Hasta este punto, has creado un tablero de juego de tres en raya adecuado usando Tkinter. Ahora debes pensar en cómo lidiar con la lógica del juego. Esta lógica consistirá en un código que procesa el movimiento de un jugador y determina si este jugador ganó el juego o no.

Varias ideas son claves a la hora de implementar la lógica de un juego de tres en raya. Primero, necesitas una representación válida de los jugadores y sus movimientos. También necesitas una clase de nivel superior para representar el juego en sí. En esta sección, definirá clases para estos tres conceptos lógicos.

Puede descargar el código fuente para este paso haciendo clic en el enlace siguiente y navegando a la carpeta source_code_step_2/:

Definir clases para los jugadores y sus movimientos.

En la primera ronda, definirás clases para representar a los jugadores y sus movimientos en el tablero de juego. Estas clases serán bastante básicas. Todo lo que necesitan son algunos atributos cada uno. Ni siquiera necesitan tener ningún método.

Puede utilizar algunas herramientas para crear clases que cumplan con estos requisitos. Por ejemplo, puede utilizar una clase de datos o una tupla con nombre. En este tutorial, usará una tupla con nombre para ambas clases porque este tipo de clase proporciona todo lo que necesita.

En lugar de usar la clásica namedtuple del módulo collections, usarás la clase NamedTuple de typing como forma de proporcionar información de sugerencia de tipo inicial en sus clases.

Regrese a su editor de código y agregue el siguiente código al comienzo de su archivo tic_tac_toe.py:

# tic_tac_toe.py

import tkinter as tk
from tkinter import font
from typing import NamedTuple

class Player(NamedTuple):
    label: str
    color: str

class Move(NamedTuple):
    row: int
    col: int
    label: str = ""

# ...

Aquí hay un desglose de este fragmento de código:

  • La línea 5 importa NamedTuple de typing.

  • Las líneas 7 a 9 definen la clase Player. El atributo .label almacenará los signos clásicos del jugador, X y O. El atributo .color contendrá una cadena con un color Tkinter. Usarás este color para identificar al jugador objetivo en el tablero de juego.

  • Las líneas 11 a 14 definen la clase Move. Los atributos .row y .col contendrán las coordenadas que identifican la celda objetivo del movimiento. El atributo .label contendrá el signo que identifica al jugador, X u O. Tenga en cuenta que .label por defecto es la cadena vacía, "", lo que significa que este movimiento específico aún no se ha realizado.

Con estas dos clases implementadas, ahora puedes definir la clase que usarás para representar la lógica del juego.

Crea una clase para representar la lógica del juego

En esta sección, definirás una clase para administrar la lógica del juego. Esta clase se encargará de procesar los movimientos, encontrar un ganador, alternar jugadores y realizar algunas otras tareas. Vuelva a su archivo tic_tac_toe.py y agregue la siguiente clase justo antes de su clase TicTacToeBoard:

# tic_tac_toe.py
import tkinter as tk
from itertools import cycle
from tkinter import font
# ...

class TicTacToeGame:
    def __init__(self, players=DEFAULT_PLAYERS, board_size=BOARD_SIZE):
        self._players = cycle(players)
        self.board_size = board_size
        self.current_player = next(self._players)
        self.winner_combo = []
        self._current_moves = []
        self._has_winner = False
        self._winning_combos = []
        self._setup_board()

class TicTacToeBoard(tk.Tk):
    # ...

Aquí, primero importa cycle() desde el módulo itertools. Luego defines TicTacToeGame, cuyo inicializador toma dos argumentos, jugadores y board_size. El argumento players contendrá una tupla de dos objetos Player, que representan a los jugadores X y O. Este argumento por defecto es DEFAULT_PLAYERS, una constante que definirás en un momento.

El argumento board_size contendrá un número que representa el tamaño del tablero de juego. En un juego clásico de tres en raya, este tamaño sería 3. En su clase, el argumento predeterminado es BOARD_SIZE, otra constante que definirá pronto.

Dentro de .__init__(), define los siguientes atributos de instancia:

._players

Un iterador cíclico sobre la tupla de entrada de jugadores

.board_size

El tamaño del tablero

.current_player

El jugador actual

.winner_combo

La combinación de celdas que define a un ganador

._current_moves

La lista de movimientos de los jugadores en un juego determinado.

._has_winner

Una variable booleana para determinar si el juego tiene ganador o no.

._winning_combos

Una lista que contiene las combinaciones de celdas que definen una victoria.

El atributo ._players llama a cycle() desde el módulo itertools. Esta función toma un iterable como argumento y devuelve un iterador que produce cíclicamente elementos del iterable de entrada. En este caso, el argumento de cycle() es la tupla de reproductores predeterminados pasados a través del argumento players. A medida que avance en este tutorial, aprenderá más sobre todos los atributos de la tabla anterior.

Con respecto a la última línea en .__init__(), llama a ._setup_board(), que es un método que también definirás en un momento.

Ahora continúa y define las siguientes constantes justo debajo de la clase Move:

# tic_tac_toe.py
# ...

class Move(NamedTuple):
    # ...

BOARD_SIZE = 3
DEFAULT_PLAYERS = (
    Player(label="X", color="blue"),
    Player(label="O", color="green"),
)

class TicTacToeGame:
    # ...

Como ya aprendiste, BOARD_SIZE tiene el tamaño del tablero de tres en raya. Normalmente, este tamaño es 3. Entonces, tendrás una cuadrícula de celdas de tres por tres en el tablero.

Por otro lado, DEFAULT_PLAYERS define una tupla de dos elementos. Cada elemento representa a un jugador en el juego. Los atributos .label y .color de cada reproductor se establecen en valores adecuados. El jugador X será azul y el jugador O será verde.

Configurar el tablero de juego abstracto

Gestionar el estado del juego en cada momento es un paso fundamental en la lógica del juego. Debes realizar un seguimiento de cada movimiento en el tablero. Para hacer esto, usarás el atributo ._current_moves, que actualizarás cada vez que un jugador haga un movimiento.

También debes determinar qué combinaciones de celdas en el tablero determinan una ganancia. Almacenarás estas combinaciones en el atributo ._taining_combos.

Aquí está el método ._setup_board(), que calcula los valores iniciales para ._current_moves y ._wining_combos:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def _setup_board(self):
        self._current_moves = [
            [Move(row, col) for col in range(self.board_size)]
            for row in range(self.board_size)
        ]
        self._winning_combos = self._get_winning_combos()

En ._setup_board(), utiliza una lista por comprensión para proporcionar una lista inicial de valores para ._current_moves. La comprensión crea una lista de listas. Cada lista interna contendrá objetos Mover vacíos. Un movimiento vacío almacena las coordenadas de la celda que lo contiene y una cadena vacía como etiqueta del jugador inicial.

La última línea de este método llama a ._get_wining_combos() y asigna su valor de retorno a ._taining_combos. Implementarás este nuevo método en la siguiente sección.

Descubra las combinaciones ganadoras

En un tablero clásico de tres en raya, tendrás ocho combinaciones ganadoras posibles. Son esencialmente las filas, columnas y diagonales del tablero. La siguiente ilustración muestra estas combinaciones ganadoras:

¿Cómo obtendrías las coordenadas de todas estas combinaciones usando código Python? Hay varias formas de hacerlo. En el siguiente código, utilizará cuatro listas por comprensión para obtener todas las combinaciones ganadoras posibles:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def _get_winning_combos(self):
        rows = [
            [(move.row, move.col) for move in row]
            for row in self._current_moves
        ]
        columns = [list(col) for col in zip(*rows)]
        first_diagonal = [row[i] for i, row in enumerate(rows)]
        second_diagonal = [col[j] for j, col in enumerate(reversed(columns))]
        return rows + columns + [first_diagonal, second_diagonal]

La entrada principal para este método es el atributo ._current_moves. De forma predeterminada, este atributo contendrá una lista que contiene tres sublistas. Cada sublista representará una fila en la cuadrícula y tendrá tres objetos Mover en ella.

La primera comprensión itera sobre las filas de la cuadrícula, obtiene las coordenadas de cada celda y crea una sublista de coordenadas. Cada sublista de coordenadas representa una combinación ganadora. La segunda comprensión crea sublistas que contienen las coordenadas de cada celda en las columnas de la cuadrícula.

Las comprensiones tercera y cuarta utilizan un enfoque similar para obtener las coordenadas de cada celda en las diagonales del tablero. Finalmente, el método devuelve una lista de listas que contienen todas las combinaciones ganadoras posibles en el tablero de tres en raya.

Con esta configuración inicial de tu tablero de juego, estás listo para empezar a pensar en procesar los movimientos de los jugadores.

Paso 3: procesar los movimientos de los jugadores según la lógica del juego

En este juego de tres en raya, manejarás principalmente un tipo de evento: movimientos de los jugadores. Traducido a términos de Tkinter, el movimiento de un jugador es solo un clic en la celda seleccionada, que está representada por un widget de botón.

El movimiento de cada jugador activará un montón de operaciones en la clase TicTacToeGame. Estas operaciones incluyen:

  • Validando el movimiento
  • Buscando un ganador
  • Comprobando un juego empatado
  • Alternar el jugador para el siguiente movimiento

En las siguientes secciones, escribirá el código para manejar todas estas operaciones en su clase TicTacToeGame.

Para descargar el código fuente de este paso desde GitHub, haga clic en el enlace siguiente y navegue hasta la carpeta source_code_step_3/:

Validar los movimientos de los jugadores

Debes validar el movimiento cada vez que un jugador hace clic en una celda determinada en el tablero de tres en raya. La pregunta es: ¿Qué define una jugada válida? Bueno, al menos dos condiciones hacen que un movimiento sea válido. Los jugadores sólo pueden jugar si:

  1. El juego no tiene ganador.
  2. La jugada seleccionada aún no se ha realizado.

Continúe y agregue el siguiente método al final de TicTacToeGame:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def is_valid_move(self, move):
        """Return True if move is valid, and False otherwise."""
        row, col = move.row, move.col
        move_was_not_played = self._current_moves[row][col].label == ""
        no_winner = not self._has_winner
        return no_winner and move_was_not_played

El método .is_valid_move() toma un objeto Move como argumento. Esto es lo que hace el resto del método:

  • La Línea 9 obtiene las coordenadas .row y .col de la entrada move.

  • La Línea 10 comprueba si el movimiento en las coordenadas actuales, [row][col], todavía contiene una cadena vacía como etiqueta. Esta condición será Verdadera si ningún jugador ha realizado el movimiento ingresado antes.

  • La línea 11 comprueba si el juego aún no tiene un ganador.

Este método devuelve un valor booleano que resulta de comprobar si el juego no tiene ganador y la jugada actual aún no se ha realizado.

Procesar los movimientos de los jugadores para encontrar un ganador

Ahora es el momento de determinar si un jugador ganó el juego después de su último movimiento. Esta puede ser tu principal preocupación como diseñador del juego. Por supuesto, tendrás muchas formas diferentes de saber si un jugador determinado ganó el juego.

En este proyecto, utilizará las siguientes ideas para determinar el ganador:

  • Cada celda del tablero de juego tiene un objeto Mover asociado.
  • Cada objeto Move tiene un atributo .label que identifica al jugador que ha realizado el movimiento.

Para saber si el último jugador ganó el juego, comprobarás si la etiqueta del jugador está presente en todos los movimientos posibles contenidos en una combinación ganadora determinada.

Continúe y agregue el siguiente método a su clase TicTacToeGame:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def process_move(self, move):
        """Process the current move and check if it's a win."""
        row, col = move.row, move.col
        self._current_moves[row][col] = move
        for combo in self._winning_combos:
            results = set(
                self._current_moves[n][m].label
                for n, m in combo
            )
            is_win = (len(results) == 1) and ("" not in results)
            if is_win:
                self._has_winner = True
                self.winner_combo = combo
                break

Aquí hay un desglose de lo que hace este nuevo método línea por línea:

  • La Línea 7 define process_move(), que toma un objeto Move como argumento.

  • La Línea 9 obtiene las coordenadas .row y .col de la entrada move.

  • La Línea 10 asigna la entrada move al elemento en [row][col] en la lista de movimientos actuales.

  • La línea 11 inicia un bucle sobre las combinaciones ganadoras.

  • Las líneas 12 a 15 ejecutan una expresión generadora que recupera todas las etiquetas de los movimientos en la combinación ganadora actual. Luego, el resultado se convierte en un objeto set.

  • La Línea 16 define una expresión booleana que comprueba si el movimiento actual determinó una ganancia o no. El resultado se almacena en is_win.

  • La línea 17 verifica el contenido de is_win. Si la variable contiene True, entonces ._has_winner se establece en True y .winner_combo se establece en la combinación actual . Luego el bucle se rompe y la función termina.

En las líneas 12 a 16, es necesario aclarar algunos puntos e ideas. Para comprender mejor las líneas 12 a 15, digamos que todas las etiquetas en los movimientos asociados con las celdas de la combinación ganadora actual tienen una X. En ese caso, la expresión generadora producirá tres etiquetas X.

Cuando alimenta la función incorporada set() con varias instancias de X, obtiene un conjunto con una única instancia de X. Los conjuntos no permiten valores repetidos.

Entonces, si el conjunto en results contiene un único valor diferente de la cadena vacía, entonces tienes un ganador. En este ejemplo, el ganador sería el jugador con la etiqueta X. La línea 16 verifica ambas condiciones.

Para concluir el tema de encontrar el ganador de tu juego de tres en raya, continúa y agrega el siguiente método al final de tu clase TicTacToeGame:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def has_winner(self):
        """Return True if the game has a winner, and False otherwise."""
        return self._has_winner

Este método devuelve el valor booleano almacenado en ._has_winner siempre que necesites comprobar si el partido actual tiene un ganador o no. Utilizarás este método más adelante en este tutorial.

Verificar juegos empatados

En el juego de tres en raya, si los jugadores juegan en todas las casillas y no hay un ganador, entonces el juego está empatado. Así pues, tienes dos condiciones que comprobar antes de declarar el partido empatado:

  1. Se han realizado todos los movimientos posibles.
  2. El juego no tiene ganador.

Continúe y agregue el siguiente método al final de TicTacToeGame para verificar estas condiciones:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def is_tied(self):
        """Return True if the game is tied, and False otherwise."""
        no_winner = not self._has_winner
        played_moves = (
            move.label for row in self._current_moves for move in row
        )
        return no_winner and all(played_moves)

Dentro de .is_tied(), primero verificas si el juego aún no tiene un ganador. Luego usa la función incorporada all() para verificar si todos los movimientos en ._current_moves tienen una etiqueta diferente de la cadena vacía. Si este es el caso, entonces ya se han jugado todas las celdas posibles. Si se cumplen ambas condiciones, entonces el juego está empatado.

Alternar jugadores entre turnos

Cada vez que un jugador realiza un movimiento válido, debes alternar el jugador actual para que el otro jugador pueda realizar el siguiente movimiento. Para proporcionar esta funcionalidad, continúe y agregue el siguiente método a su clase TicTacToeGame:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

    def toggle_player(self):
        """Return a toggled player."""
        self.current_player = next(self._players)

Debido a que ._players contiene un iterador que recorre cíclicamente los dos reproductores predeterminados, puede llamar a next() en este iterador para obtener el siguiente reproductor cuando lo necesite. Este mecanismo de alternancia permitirá que el siguiente jugador tome su turno y continúe el juego.

Paso 4: procesar los movimientos de los jugadores en el tablero de juego

En este punto, eres capaz de manejar los movimientos de los jugadores según la lógica del juego. Ahora tienes que conectar esta lógica al propio tablero de juego. También debes escribir el código para que el tablero responda a los movimientos de los jugadores.

Primero, continúa e inyecta la lógica del juego en el tablero. Para hacer esto, actualice la clase TicTacToeBoard como se muestra en el siguiente código:

# tic_tac_toe.py
# ...

class TicTacToeGame:
    # ...

class TicTacToeBoard(tk.Tk):
    def __init__(self, game):
        super().__init__()
        self.title("Tic-Tac-Toe Game")
        self._cells = {}
        self._game = game
        self._create_board_display()
        self._create_board_grid()

    # ...

    def _create_board_grid(self):
        grid_frame = tk.Frame(master=self)
        grid_frame.pack()
        for row in range(self._game.board_size):
            self.rowconfigure(row, weight=1, minsize=50)
            self.columnconfigure(row, weight=1, minsize=75)
            for col in range(self._game.board_size):
                # ...

En este fragmento de código, primero agrega un argumento game al inicializador TicTacToeBoard. Luego asignas este argumento a un atributo de instancia, ._game, que te dará acceso completo a la lógica del juego desde el tablero.

La segunda actualización es usar ._game.board_size para establecer el tamaño del tablero en lugar de usar un número mágico. Esta actualización también le permite utilizar diferentes tamaños de tablero. Por ejemplo, puedes crear una cuadrícula de tablero de cuatro por cuatro, lo que puede ser una experiencia emocionante.

Con estas actualizaciones, estás listo para sumergirte en el manejo de los movimientos de los jugadores en la clase TicTacToeBoard.

Como de costumbre, puede descargar el código fuente para este paso haciendo clic en el enlace siguiente y navegando a la carpeta source_code_step_4/:

Manejar el evento de movimiento de un jugador

El método .mainloop() de la clase Tk ejecuta lo que se conoce como bucle principal o bucle de eventos de la aplicación. Este es un bucle infinito en el que suceden todos los eventos de la GUI. Dentro de este bucle, puedes manejar los eventos en la interfaz de usuario de la aplicación. Un evento es una acción del usuario en la GUI, como presionar una tecla, mover el mouse o hacer clic con el mouse.

Cuando un jugador en tu juego de tres en raya hace clic en una celda, se produce un evento de clic dentro del bucle de eventos del juego. Puede procesar este evento proporcionando un método de controlador apropiado en su clase TicTacToeBoard. Para hacer esto, regrese a su editor de código y agregue el siguiente método .play() al final de la clase:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def play(self, event):
        """Handle a player's move."""
        clicked_btn = event.widget
        row, col = self._cells[clicked_btn]
        move = Move(row, col, self._game.current_player.label)
        if self._game.is_valid_move(move):
            self._update_button(clicked_btn)
            self._game.process_move(move)
            if self._game.is_tied():
                self._update_display(msg="Tied game!", color="red")
            elif self._game.has_winner():
                self._highlight_cells()
                msg = f'Player "{self._game.current_player.label}" won!'
                color = self._game.current_player.color
                self._update_display(msg, color)
            else:
                self._game.toggle_player()
                msg = f"{self._game.current_player.label}'s turn"
                self._update_display(msg)

Este método es fundamental en tu juego de tres en raya porque reúne casi toda la lógica del juego y el comportamiento de la GUI. Aquí hay un resumen de lo que hace este método:

  • La Línea 7 define play(), que toma un objeto de evento de Tkinter como argumento.

  • La Línea 9 recupera el widget que desencadenó el evento actual. Este widget será uno de los botones en la cuadrícula del tablero.

  • La Línea 10 descomprime las coordenadas del botón en dos variables locales, row y col.

  • La Línea 11 crea un nuevo objeto Move usando row, col y el .label< del reproductor actual. /código> atributo como argumentos.

  • La línea 12 comienza una declaración condicional que verifica si el movimiento del jugador es válido o no. Si el movimiento es válido, entonces se ejecuta el bloque de código if. De lo contrario, no se lleva a cabo ninguna acción adicional.

  • La Línea 13 actualiza el botón en el que se hizo clic llamando al método ._update_button(). Escribirás este método en la siguiente sección. En resumen, el método actualiza el texto del botón para reflejar la etiqueta y el color del reproductor actual.

  • La Línea 14 llama a .process_move() en el objeto ._game usando el movimiento actual como argumento.

  • La línea 15 comprueba si el juego está empatado. Si ese es el caso, la pantalla del juego se actualiza en consecuencia.

  • La línea 17 comprueba si el jugador actual ha ganado el juego. Luego, la línea 18 resalta las celdas ganadoras y las líneas 19 a 21 actualizan la pantalla del juego para reconocer al ganador.

  • La línea 22 se ejecuta si el juego no está empatado y no hay un ganador. En este caso, las líneas 23 a 25 alternan al jugador para el siguiente movimiento y actualizan la pantalla para señalar el jugador que jugará a continuación.

¡Ya casi estás ahí! Con algunas actualizaciones y adiciones más, tu juego de tres en raya estará listo para su primera partida.

El siguiente paso es conectar cada botón del tablero de juego al método .play(). Para hacer esto, regrese al método ._create_board_grid() y actualícelo como en el siguiente código:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def _create_board_grid(self):
        grid_frame = tk.Frame(master=self)
        grid_frame.pack()
        for row in range(self._game.board_size):
            self.rowconfigure(row, weight=1, minsize=50)
            self.columnconfigure(row, weight=1, minsize=75)
            for col in range(self._game.board_size):
                # ...
                self._cells[button] = (row, col)
                button.bind("<ButtonPress-1>", self.play)
                # ...

La línea resaltada vincula el evento de clic de cada botón en el tablero de juego con el método .play(). De esta manera, cada vez que un jugador hace clic en un botón determinado, el método se ejecutará para procesar el movimiento y actualizar el estado del juego.

Actualiza el tablero de juego para reflejar el estado del juego

Para completar el código para procesar los movimientos de los jugadores en el tablero de juego, debes escribir tres métodos auxiliares. Estos métodos completarán las siguientes acciones:

  • Actualizar el botón en el que se hizo clic
  • Actualizar la pantalla del juego
  • Resalte las celdas ganadoras

Para comenzar, continúa y agrega ._update_button() a TicTacToeBoard:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def _update_button(self, clicked_btn):
        clicked_btn.config(text=self._game.current_player.label)
        clicked_btn.config(fg=self._game.current_player.color)

En este fragmento de código, ._update_button() llama a .config() en el botón en el que se hizo clic para establecer su atributo .text en la etiqueta del reproductor actual. . El método también establece el color de primer plano del texto, fg, al color del reproductor actual.

El siguiente método auxiliar a agregar es ._update_display():

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def _update_display(self, msg, color="black"):
        self.display["text"] = msg
        self.display["fg"] = color

En este método, en lugar de usar .config() para modificar el texto y el color de la pantalla del juego, usas una notación de subíndice estilo diccionario. Usar este tipo de notación es otra opción que ofrece Tkinter para acceder a los atributos de un widget.

Finalmente, necesitas un método auxiliar para resaltar las celdas ganadoras una vez que un jugador determinado realiza un movimiento ganador:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    # ...

    def _highlight_cells(self):
        for button, coordinates in self._cells.items():
            if coordinates in self._game.winner_combo:
                button.config(highlightbackground="red")

El bucle dentro de ._highlight_cells() itera sobre los elementos en el diccionario ._cells. Este diccionario asigna botones a sus coordenadas de fila y columna en la cuadrícula del tablero. Si las coordenadas actuales están en una combinación de celdas ganadora, entonces el color de fondo del botón se establece en "red", resaltando la combinación de celdas en el tablero.

Con este último método de ayuda, ¡tu juego de tres en raya está listo para el primer partido!

Ejecute su juego Tic-Tac-Toe por primera vez

Para terminar de armar la lógica y la interfaz de usuario de tu juego de tres en raya, debes actualizar la función main() del juego. Hasta este punto, sólo tienes un objeto TicTacToeBoard. Debes crear un objeto TicTacToeGame y pasarlo al constructor actualizado TicTacToeBoard.

Vuelva a main() y actualícelo como en el siguiente código:

# tic_tac_toe.py
# ...

def main():
    """Create the game's board and run its main loop."""
    game = TicTacToeGame()
    board = TicTacToeBoard(game)
    board.mainloop()

En este código, la primera línea resaltada crea una instancia de TicTacToeGame, que usarás para manejar la lógica del juego. La segunda línea resaltada pasa la nueva instancia al constructor de la clase TicTacToeBoard, que inyecta la lógica del juego en el tablero.

Con estas actualizaciones implementadas, ahora puedes ejecutar tu juego. Para hacer esto, abra una ventana de terminal y navegue hasta el directorio que contiene su archivo tic_tac_toe.py. Luego ejecute el siguiente comando:

$ python tic_tac_toe.py

Una vez que se haya ejecutado este comando, la ventana principal de tu juego aparecerá en tu pantalla. ¡Adelante y pruebalo! Se comportará algo como esto:

¡Guau! ¡Tu proyecto de juego se ve increíble hasta ahora! Permite que dos jugadores compartan su mouse y jueguen una clásica partida de tres en raya. La GUI del juego se ve bien y, en general, el juego funciona como se esperaba.

En la siguiente sección, escribirás código para permitir a los jugadores reiniciar el juego y jugar nuevamente. También ofrecerás la opción de salir del juego.

Paso 5: proporcione opciones para volver a jugar y salir del juego

En esta sección, le proporcionarás a tu juego de tres en raya un menú principal. Este menú tendrá una opción para reiniciar el juego para que los jugadores puedan comenzar otra partida. También tendrá la opción de salir del juego una vez que los jugadores hayan terminado de jugar.

Los menús principales suelen ser un componente esencial de muchas aplicaciones GUI. Entonces, aprender a crearlos en Tkinter es un buen ejercicio para mejorar tus habilidades relacionadas con la GUI más allá del desarrollo del juego en sí.

Este es un ejemplo de cómo crear tus propios juegos es una poderosa experiencia de aprendizaje porque te permite integrar conocimientos y habilidades que luego puedes usar en otros proyectos que no son juegos.

El código fuente completo para este paso está disponible para descargar. Simplemente haga clic en el enlace a continuación y navegue hasta la carpeta source_code_step_5/:

Crea el menú principal del juego

Para agregar un menú principal a una aplicación Tkinter, puede usar la clase tkinter.Menu. Esta clase le permite crear una barra de menú en la parte superior de su ventana de Tkinter. También le permite agregar menús desplegables a la barra de menú.

Aquí está el código que crea y agrega un menú principal a tu juego de tres en raya:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    def __init__(self, game):
        # ...

    def _create_menu(self):
        menu_bar = tk.Menu(master=self)
        self.config(menu=menu_bar)
        file_menu = tk.Menu(master=menu_bar)
        file_menu.add_command(
            label="Play Again",
            command=self.reset_board
        )
        file_menu.add_separator()
        file_menu.add_command(label="Exit", command=quit)
        menu_bar.add_cascade(label="File", menu=file_menu)

Esto es lo que hace este código línea por línea:

  • La Línea 8 define un método auxiliar llamado ._create_menu() para manejar la creación del menú en un solo lugar.

  • La Línea 9 crea una instancia de Menú, que funcionará como barra de menú.

  • La Línea 10 establece el objeto de la barra de menú como el menú principal de su ventana actual de Tkinter.

  • La Línea 11 crea otra instancia de Menú para proporcionar un menú Archivo. Tenga en cuenta que el argumento master en el constructor de la clase se establece en el objeto de la barra de menú.

  • Las líneas 12 a 15 agregan una nueva opción de menú al menú Archivo usando el método .add_command(). Esta nueva opción tendrá la etiqueta "Play Again". Cuando un usuario hace clic en esta opción, la aplicación ejecutará el método .reset_board(), que usted proporcionó a través del argumento command. Escribirás este método en la siguiente sección.

  • La Línea 16 agrega un separador de menú usando el método .add_separator(). Los separadores son útiles cuando necesita separar grupos de comandos relacionados en un menú desplegable determinado.

  • La Línea 17 agrega un comando Salir al menú Archivo. Este comando hará que el juego salga llamando a la función quit().

  • La Línea 18 finalmente agrega el menú Archivo a la barra de menú llamando a .add_cascade() con los argumentos apropiados.

Para agregar realmente el menú principal a la ventana principal de tu juego, debes llamar a ._create_menu() desde el inicializador de TicTacToeBoard. Entonces, continúa y agrega la siguiente línea al método .__init__() de la clase:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
    def __init__(self, game):
        super().__init__()
        self.title("Tic-Tac-Toe Game")
        self._cells = {}
        self._game = game
        self._create_menu()
        self._create_board_display()
        self._create_board_grid()

    # ...

Con esta actualización final, el menú principal de tu juego está casi listo para usar. Sin embargo, antes de usarlo, debes implementar el método .reset_board(). Eso es lo que harás en la siguiente sección para permitir que los jugadores vuelvan a jugar.

Implementar la opción Jugar de nuevo

Para restablecer el tablero de juego y permitir que los jugadores vuelvan a jugar, debes agregar código a ambas clases, TicTacToeGame y TicTacToeBoard.

En la clase de lógica del juego, debes restablecer el atributo ._current_moves para contener una lista de objetos Move inicialmente vacíos. También debe restablecer ._has_winner y .winner_combo a su estado inicial. Por otro lado, en la clase de tablero de juego, debes restablecer la visualización del tablero y las celdas a su estado inicial.

Vuelve a TicTacToeGame en tu editor de código y agrega el siguiente método justo al final de la clase:

# tic_tac_toe.py
# ...

class TicTacToeGame(tk.Tk):
        # ...

    def reset_game(self):
        """Reset the game state to play again."""
        for row, row_content in enumerate(self._current_moves):
            for col, _ in enumerate(row_content):
                row_content[col] = Move(row, col)
        self._has_winner = False
        self.winner_combo = []

El bucle for en .reset_game() establece todos los movimientos actuales en un objeto Move vacío. La característica principal de un movimiento vacío es que su atributo .label contiene la cadena vacía, "".

Después de actualizar los movimientos actuales, los métodos configuran ._has_winner en False y .winner_combo en una lista vacía. Estos tres reinicios garantizan que la representación abstracta del juego esté lista para comenzar una nueva partida.

Tenga en cuenta que reset_game() no restablece el jugador a X cuando prepara el juego para una nueva partida. Normalmente, el ganador del partido anterior pasa primero al siguiente. Por lo tanto, no es necesario reiniciar el reproductor aquí.

Una vez que haya proporcionado la nueva funcionalidad requerida en la lógica del juego, estará listo para actualizar la funcionalidad del tablero de juego. Continúe y agregue el siguiente método al final de TicTacToeBoard:

# tic_tac_toe.py
# ...

class TicTacToeBoard(tk.Tk):
        # ...

    def reset_board(self):
        """Reset the game's board to play again."""
        self._game.reset_game()
        self._update_display(msg="Ready?")
        for button in self._cells.keys():
            button.config(highlightbackground="lightblue")
            button.config(text="")
            button.config(fg="black")

Este método .reset_board() funciona de la siguiente manera:

  • La Línea 9 llama a .reset_game() para restablecer la representación abstracta del tablero.

  • La línea 10 actualiza la visualización del tablero para contener el texto inicial, "Ready?".

  • La línea 11 inicia un bucle sobre los botones en la cuadrícula del tablero.

  • Las líneas 12 a 14 restauran las propiedades highlightbackground, text y fg de cada botón a su estado inicial.

¡Eso es todo! Con esta última característica, tu proyecto de juego de tres en raya está completo. ¡Adelante y pruebalo!

Conclusión

A lo largo de este proyecto paso a paso, has creado un clásico juego de computadora de tres en raya usando Python y Tkinter. Este marco GUI está disponible en la biblioteca estándar de Python. Se ejecuta en Windows, Linux y macOS, por lo que tu juego funcionará muy bien en las tres plataformas. ¡Eso es muy bonito!

En este tutorial, aprendiste cómo:

  • Implementa la lógica del clásico juego de tres en raya usando Python
  • Construye el tablero o GUI del juego usando Tkinter de la biblioteca estándar de Python
  • Conecta la lógica y la GUI del juego para que el juego funcione correctamente

Este conocimiento le proporciona la base para crear juegos y proyectos GUI nuevos y más complejos, lo que le ayudará a llevar sus habilidades de Python al siguiente nivel.

Próximos pasos

El juego de tres en raya que implementaste en este tutorial es bastante básico. Sin embargo, puedes ampliarlo de varias formas diferentes y divertidas. Algunas ideas para llevar este proyecto de juego al siguiente nivel incluyen permitir a sus usuarios:

  • Usa un tamaño de tablero de juego diferente
  • Juega contra la computadora

¿Qué otras ideas se te ocurren para ampliar este proyecto? ¡Sé creativo y diviértete!

Ahora que ha completado este proyecto, aquí hay algunos proyectos excelentes adicionales para continuar creando sus propios juegos con Python:

  • Crea tu primer juego de Python: ¡piedra, papel, tijera!: En este tutorial, aprenderás a programar piedra, papel o tijera en Python desde cero. Aprenderá cómo recibir información del usuario, hacer que la computadora elija una acción aleatoria, determinar un ganador y dividir su código en funciones.

  • PyGame: Introducción a la programación de juegos en Python: en este tutorial paso a paso, aprenderá a usar PyGame. Esta biblioteca le permite crear juegos y programas multimedia enriquecidos en Python. ¡Aprenderá cómo dibujar elementos en su pantalla, implementar la detección de colisiones, manejar la entrada del usuario y mucho más!

  • Crea un juego de asteroides con Python y Pygame: ¡este artículo te mostrará cómo crear un juego en PyGame!

  • Arcade: Introducción al marco de juego de Python: este artículo es un excelente seguimiento porque muestra otro marco de juego de Python que te permite crear juegos más complejos.

  • Crea un juego de plataformas en Python con Arcade: en este tutorial paso a paso, crearás un juego de plataformas en Python usando la biblioteca arcade. Cubrirá técnicas para diseñar niveles, obtener activos e implementar funciones avanzadas.

  • Construya un motor de juego Tic-Tac-Toe con un reproductor de IA en Python: en este tutorial paso a paso, creará un motor de juego universal en Python con reglas de tres en raya y dos jugadores de computadora, incluido un inmejorable. Reproductor de IA que utiliza el algoritmo minimax.

Artículos relacionados