El tres en raya, también conocido como tic-tac-toe o juego de la vieja, es un proyecto ideal para practicar lógica, listas, funciones, bucles y validación de entradas. Aunque sus reglas son sencillas, construir una versión completa obliga a representar el tablero, alternar jugadores, detectar victorias y controlar movimientos inválidos. El resultado es un programa pequeño, pero con una estructura muy parecida a la de proyectos mayores.
Antes de empezar, conviene repasar las listas en Python, las funciones, los bucles, la función input() y el manejo de errores.
La documentación oficial de estructuras de datos explica listas y recorridos. El tutorial oficial de control de flujo describe condiciones, bucles y funciones.
Representar el tablero
Usaremos una lista con nueve posiciones. Cada elemento corresponde a una casilla:
tablero = [" "] * 9
Los índices van de cero a ocho. Para el jugador resulta más natural elegir del 1 al 9, así que convertiremos su elección restando uno.
Mostrar el tablero
def mostrar_tablero(tablero):
print()
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]} ")
print()
Durante las primeras partidas puede ser útil mostrar un tablero de referencia:
def mostrar_referencia():
print(" 1 | 2 | 3 ")
print("---+---+---")
print(" 4 | 5 | 6 ")
print("---+---+---")
print(" 7 | 8 | 9 ")
Definir las combinaciones ganadoras
COMBINACIONES = (
(0, 1, 2),
(3, 4, 5),
(6, 7, 8),
(0, 3, 6),
(1, 4, 7),
(2, 5, 8),
(0, 4, 8),
(2, 4, 6),
)
Una tupla es adecuada porque estas combinaciones no cambian durante la partida.
Comprobar si alguien ganó
def hay_ganador(tablero, simbolo):
return any(
tablero[a] == tablero[b] == tablero[c] == simbolo
for a, b, c in COMBINACIONES
)
any() devuelve verdadero cuando al menos una combinación está completa. Esta función no imprime nada, por lo que resulta sencilla de probar.
Detectar el empate
def tablero_lleno(tablero):
return all(casilla != " " for casilla in tablero)
Solo existe empate si el tablero está lleno y ningún jugador ganó.
Leer una jugada válida
def pedir_jugada(tablero, jugador):
while True:
texto = input(f"Jugador {jugador}, elige una casilla (1-9): ").strip()
if not texto.isdigit():
print("Debes escribir un número.")
continue
posicion = int(texto)
if posicion < 1 or posicion > 9:
print("La posición debe estar entre 1 y 9.")
continue
indice = posicion - 1
if tablero[indice] != " ":
print("Esa casilla ya está ocupada.")
continue
return indice
La validación se hace paso a paso. Así el programa puede explicar exactamente qué entrada fue incorrecta.
Alternar jugadores
def siguiente_jugador(actual):
return "O" if actual == "X" else "X"
Esta función evita repetir condiciones dentro del bucle principal.
Crear la partida completa
def jugar():
tablero = [" "] * 9
jugador = "X"
mostrar_referencia()
while True:
mostrar_tablero(tablero)
indice = pedir_jugada(tablero, jugador)
tablero[indice] = jugador
if hay_ganador(tablero, jugador):
mostrar_tablero(tablero)
print(f"¡El jugador {jugador} ganó!")
return jugador
if tablero_lleno(tablero):
mostrar_tablero(tablero)
print("La partida terminó en empate.")
return None
jugador = siguiente_jugador(jugador)
El orden de las comprobaciones importa. Primero revisamos la victoria y después el empate. En la última jugada, el tablero puede quedar lleno y al mismo tiempo producir una victoria.
Permitir varias partidas
def preguntar_repeticion():
while True:
respuesta = input("¿Jugar otra vez? (s/n): ").strip().lower()
if respuesta in {"s", "si", "sí"}:
return True
if respuesta == "n":
return False
print("Respuesta no válida.")
def main():
victorias = {"X": 0, "O": 0, "empates": 0}
while True:
resultado = jugar()
if resultado is None:
victorias["empates"] += 1
else:
victorias[resultado] += 1
print("Marcador:", victorias)
if not preguntar_repeticion():
break
if __name__ == "__main__":
main()
Elegir símbolos
Puedes permitir que el primer jugador elija:
def elegir_simbolo():
while True:
simbolo = input("Elige X u O: ").strip().upper()
if simbolo in {"X", "O"}:
return simbolo
print("Elige únicamente X u O.")
El segundo recibe el símbolo restante. No permitas cadenas vacías ni símbolos que dificulten la lectura.
Crear un oponente básico
Una inteligencia artificial sencilla puede escoger una casilla libre al azar:
import random
def jugada_aleatoria(tablero):
libres = [
indice
for indice, casilla in enumerate(tablero)
if casilla == " "
]
return random.choice(libres)
Después puedes mejorarla con tres prioridades: ganar si existe una jugada ganadora, bloquear una victoria del usuario y, en caso contrario, elegir centro, esquina o una casilla aleatoria.
Simular una jugada sin modificar el tablero
def seria_ganadora(tablero, indice, simbolo):
copia = tablero.copy()
copia[indice] = simbolo
return hay_ganador(copia, simbolo)
Crear una copia evita alterar accidentalmente la partida mientras la IA evalúa opciones.
Pruebas unitarias
def test_fila_ganadora():
tablero = ["X", "X", "X", " ", "O", " ", "O", " ", " "]
assert hay_ganador(tablero, "X") is True
def test_tablero_no_lleno():
tablero = ["X", "O", " ", "X", "O", "X", "O", "X", "O"]
assert tablero_lleno(tablero) is False
Prueba las tres filas, columnas, diagonales, empate y ausencia de ganador. También conviene comprobar que una jugada simulada no modifica la lista original.
Separar lógica e interfaz
Funciones como hay_ganador y tablero_lleno no dependen del terminal. Por eso pueden reutilizarse en una interfaz gráfica con Tkinter, una página web o una API. Mantener input() y print() fuera de la lógica principal facilita esa evolución.
Mejoras posibles
Puedes añadir nombres de jugadores, historial de movimientos, opción para deshacer, colores del terminal, tablero gráfico, dificultad de IA y algoritmo minimax. Antes de implementar minimax, asegúrate de que las reglas básicas estén cubiertas por pruebas.
Errores frecuentes
Los fallos más comunes son confundir posiciones del 1 al 9 con índices del 0 al 8, permitir sobrescribir una casilla, comprobar empate antes de victoria y modificar el tablero real durante una simulación. También es habitual mezclar toda la lógica en un único bucle difícil de probar.
Conclusión
El tres en raya enseña a convertir reglas en funciones pequeñas y verificables. Representa el tablero con una lista, guarda las combinaciones ganadoras, valida cada jugada y separa la interfaz de la lógica. Con esta base puedes añadir marcador, partidas repetidas, un oponente automático o una interfaz gráfica sin reescribir el núcleo del juego.






