El juego de tres en raya, también llamado juego de la vieja o tic-tac-toe, es un proyecto excelente para practicar listas, funciones, validación, bucles y lógica de victoria. Aunque el tablero es pequeño, obliga a separar correctamente el estado, las reglas y la interacción con el usuario.
Para reforzar conceptos, consulta las guías sobre listas, funciones, entrada y salida, bucles y lógica de programación. Como fuentes externas, revisa el tutorial oficial de control de flujo y el tutorial oficial de estructuras de datos.
Representar el tablero
tablero = [" "] * 9Las posiciones de cero a ocho forman tres filas. Una lista plana simplifica las combinaciones ganadoras y evita estructuras anidadas innecesarias.
Mostrar posiciones
def mostrar(tablero):
print(f" {tablero[0]} | {tablero[1]} | {tablero[2]} ")
print("---+---+---")
print(f" {tablero[3]} | {tablero[4]} | {tablero[5]} ")
print("---+---+---")
print(f" {tablero[6]} | {tablero[7]} | {tablero[8]} ")Durante las instrucciones, muestra un tablero numerado del 1 al 9 para que el jugador entienda dónde escribir.
Combinaciones ganadoras
LINEAS = [
(0, 1, 2), (3, 4, 5), (6, 7, 8),
(0, 3, 6), (1, 4, 7), (2, 5, 8),
(0, 4, 8), (2, 4, 6),
]Mantener las reglas en una constante permite revisarlas fácilmente y reutilizarlas en pruebas.
Detectar victoria
def ganador(tablero, simbolo):
return any(
tablero[a] == tablero[b] == tablero[c] == simbolo
for a, b, c in LINEAS
)La función responde únicamente a una pregunta y no imprime mensajes. Así puede usarse desde terminal, interfaz gráfica o inteligencia artificial.
Detectar empate
def completo(tablero):
return all(celda != " " for celda in tablero)Comprueba la victoria antes del empate. La última jugada puede llenar el tablero y ganar simultáneamente.
Leer una jugada
def leer_jugada(tablero, simbolo):
while True:
entrada = input(f"Turno de {simbolo}, posición 1-9: ").strip()
try:
posicion = int(entrada) - 1
except ValueError:
print("Escribe un número.")
continue
if posicion not in range(9):
print("La posición debe estar entre 1 y 9.")
continue
if tablero[posicion] != " ":
print("Esa casilla ya está ocupada.")
continue
return posicionLa función no modifica el tablero; devuelve una posición válida. Esto reduce efectos secundarios.
Cambiar de jugador
jugador = "O" if jugador == "X" else "X"La expresión condicional es adecuada porque la decisión es simple y tiene solo dos resultados.
Bucle principal
def jugar():
tablero = [" "] * 9
jugador = "X"
while True:
mostrar(tablero)
posicion = leer_jugada(tablero, jugador)
tablero[posicion] = jugador
if ganador(tablero, jugador):
mostrar(tablero)
print(f"Ganó {jugador}")
return
if completo(tablero):
mostrar(tablero)
print("Empate")
return
jugador = "O" if jugador == "X" else "X"Reiniciar sin duplicar código
Una función externa puede preguntar si se desea otra partida y llamar nuevamente a jugar(). No reinicies modificando variables globales dispersas.
while True:
jugar()
if input("¿Jugar otra vez? (s/n): ").lower() != "s":
breakMarcador
Haz que jugar() devuelva "X", "O" o None. El bucle externo actualiza un diccionario de victorias y empates. Este diseño mantiene el marcador fuera de una partida individual.
Modo contra el ordenador
La versión más sencilla elige una casilla libre al azar. Una estrategia mejor intenta ganar, después bloquear, ocupar el centro y finalmente escoger esquinas. Cada estrategia debe recibir el tablero y devolver una posición, igual que un jugador humano.
No modificar el tablero durante la evaluación
Para simular movimientos, copia la lista con tablero.copy(). Si una función de análisis modifica el tablero original y no lo restaura, aparecerán jugadas fantasma difíciles de depurar.
Minimax
El algoritmo minimax explora estados futuros y asigna puntuaciones a victoria, derrota y empate. En un tablero de tres en raya el espacio es pequeño, pero conviene añadir profundidad a la puntuación para preferir victorias rápidas y derrotas tardías. Separa esta ampliación del juego básico.
Pruebas
def test_fila_ganadora():
tablero = ["X", "X", "X", " ", "O", " ", "O", " ", " "]
assert ganador(tablero, "X")
def test_empate():
tablero = list("XOXOOXXXO")
assert completo(tablero)Prueba filas, columnas, diagonales, tablero vacío, casillas ocupadas y victoria en la última jugada.
Interfaz gráfica
Con Tkinter, cada botón representa una posición. El controlador valida la jugada, actualiza el tablero y desactiva la interfaz al terminar. Mantén las reglas en funciones independientes para no duplicar lógica entre terminal y GUI.
Guardar y cargar partidas
Para practicar persistencia, guarda el tablero, el turno y el marcador en JSON. Valida al cargar que existan nueve casillas, que solo contengan X, O o espacios y que la cantidad de jugadas sea coherente. Nunca confíes en un archivo modificado manualmente sin comprobar sus reglas.
Errores frecuentes
Los errores habituales son mezclar posiciones 1-9 con índices 0-8, comprobar empate antes de victoria, usar variables globales, permitir sobrescribir casillas y repetir ocho condiciones manuales. También es común olvidar reiniciar el tablero entre partidas.
Conclusión
El juego de tres en raya enseña a modelar estado y reglas de manera limpia. Representa el tablero con una lista, define combinaciones ganadoras, valida entradas y separa cada responsabilidad. Después puedes añadir marcador, interfaz gráfica o minimax sin alterar el núcleo del juego.






