Ir al contenido
wannacode

Cómo pensar un juego

En resumen: Un juego es estado, reglas, entrada, un bucle y un final; el azar con semilla lo hace comprobable, y la lógica vive en funciones que devuelven un valor y se pueden probar.

Problema

¿Por dónde empieza un juego?

Quieres escribir un juego. Por ejemplo, una carrera de dados: una ficha sale de la casilla 0, tiras un dado y avanza hasta llegar a la casilla 20.

Abres el editor y... ¿qué escribes primero? ¿El menú? ¿El dado? ¿El tablero? Si empiezas por todo a la vez, una hora después tienes mucho código que todavía no funciona.

Ayuda hacerle al juego cinco preguntas antes de escribir código.

La idea

Cinco preguntas para cada juego

  1. Estado. ¿Qué números describen el mundo ahora mismo? En la carrera son la casilla de la ficha y el número del turno. En el ajedrez es dónde está cada pieza y a quién le toca.
  2. Reglas. ¿Qué está permitido y qué pasa después? «La ficha avanza tantas casillas como marca el dado.»
  3. Entrada. ¿Qué llega de fuera? La elección del jugador o el número del dado.
  4. Bucle. Turno tras turno: entrada, actualizar el estado, mostrarlo. Y vuelta a empezar.
  5. Final. ¿Cuándo termina el juego y quién gana? «La ficha está en la casilla 20.»

Las respuestas son casi el código terminado: el estado son variables, las reglas son funciones, la entrada es input() o Random, el bucle es while o for, y el final es la condición del bucle.

Míralo

Turno tras turno

random = Random(42)
field = 0
turn = 1
while field < 20:
roll = random.randint(1, 6)
field = min(field + roll, 20)
print(f"Turno {turn}: dado {roll}, casilla {field}")
turn += 1
print("¡Meta! La ficha llegó a la casilla 20.")
Paso a paso, en palabras
  1. Un generador con semilla 42 y una ficha en la casilla 0. La cuenta de turnos empieza en 1.
  2. Turno 1: la entrada es una tirada de 6, el estado pasa a ser la casilla 6, la pantalla muestra la línea.
  3. Turno 2: la entrada es una tirada de 1, el estado pasa a ser la casilla 7, la pantalla muestra la línea.
  4. Turno 3: la entrada es una tirada de 1, el estado pasa a ser la casilla 8, la pantalla muestra la línea.
  5. Turno 4: la entrada es una tirada de 6, el estado pasa a ser la casilla 14, la pantalla muestra la línea.
  6. Turno 5: la entrada es una tirada de 3, el estado pasa a ser la casilla 17, la pantalla muestra la línea.
  7. Turno 6: la entrada es una tirada de 2, el estado pasa a ser la casilla 19, la pantalla muestra la línea.
  8. Turno 7: la entrada es una tirada de 2, el estado pasa a ser la casilla 20, la pantalla muestra la línea. Casilla 20: la condición while field < 20 ahora es falsa.
  9. El bucle termina porque la casilla llegó a 20. Ese es el final del juego.

Todo el juego es un solo bucle. En cada turno entra una entrada (el dado), se actualiza el estado (la casilla nueva) y se muestra (una línea en la pantalla). La condición del bucle es el final: mientras la casilla sea menor que 20, hay otro turno.

min mantiene la ficha dentro del tablero: si el dado la manda más allá de 20, se queda en 20.

Python no tiene un for de tres partes como Java. Por eso el turno se cuenta con turn, y el bucle es while: mientras la casilla sea menor que 20.

Más precisamente, para curiosos
  • Este juego es por turnos: no pasa nada hasta que llega la siguiente entrada. El ajedrez, los juegos de cartas y la mayoría de los juegos de esta sección funcionan así.
  • Los juegos en tiempo real (disparos, carreras de coches) también tienen un bucle, pero no espera al jugador. Se ejecuta 60 veces por segundo: lee las teclas que están pulsadas en ese momento, mueve todo según el tiempo que ha pasado y dibuja el fotograma. Aunque el jugador no pulse nada, el mundo se sigue moviendo.

La idea

Misma semilla, mismo juego

Si las tiradas son realmente aleatorias, cada ejecución es un juego distinto. Entonces, ¿cómo compruebas que el juego funciona? No sabes qué esperar.

Por eso en estas lecciones el generador es Random del módulo random, y recibe un número inicial, una semilla. La misma semilla da la misma secuencia, siempre. randint(1, 6) da un número entero del 1 al 6, ambos incluidos:

Random first = new Random(42);
Random second = new Random(42);
for (int i = 0; i < 5; i++) {
int a = first.nextInt(6) + 1;
int b = second.nextInt(6) + 1;
System.out.println(a + " y " + b);
}
Salida
3 y 3
4 y 4
1 y 1
3 y 3
1 y 1

Los dos generadores con 42 dan los mismos números. Ejecuta el programa otra vez y volverán a ser iguales. Por eso los ejemplos y los ejercicios de aquí tienen una salida exacta que se puede comprobar.

Mira también la salida en las otras pestañas. Cada lenguaje tiene su propio generador: con la misma semilla dan números distintos. Por eso los ejemplos con números aleatorios tienen una salida para cada lenguaje.

Más precisamente, para curiosos
  • Cuando el juego está listo para jugadores de verdad, quitas la semilla: Random(). Entonces el generador elige una semilla por sí mismo, y cada partida es distinta.
  • Es habitual imprimir la semilla al empezar la partida. Si un jugador encuentra un error, puedes jugar exactamente la misma partida con su semilla.

La idea

Primero la versión más pequeña que funciona

El programa de la escena es un juego completo en unas diez líneas. No tiene segundo jugador, ni menú, ni más reglas que «avanzar». Pero funciona de principio a fin y demuestra que el bucle y el final están bien.

A partir de ahí el juego crece una regla cada vez. La segunda regla: la ficha tiene que caer justo en la casilla 20. Si el dado la manda más allá, rebota hacia atrás tantas casillas como se pasó. Desde la 17 con un 5 llega a 20 y retrocede hasta la 18.

Solo añades una regla nueva cuando la anterior funciona. Si algo se rompe, sabes que está en lo último que escribiste.

La idea

La lógica devuelve, la pantalla imprime

La regla «justo 20» vive en una función que devuelve la casilla nueva. El final es una función que devuelve True o False:

static int move(int field, int roll) {
int next = field + roll;
if (next > 20) {
return 20 - (next - 20);
}
return next;
}
static boolean isFinished(int field) {
return field == 20;
}
Random random = new Random(7);
int field = 0;
int turn = 0;
while (!isFinished(field)) {
turn++;
int roll = random.nextInt(6) + 1;
field = move(field, roll);
System.out.println("Turno " + turn + ": dado " + roll + ", casilla " + field);
}
System.out.println("Justo 20 tras " + turn + " turnos");
Salida
Turno 1: dado 5, casilla 5
Turno 2: dado 3, casilla 8
Turno 3: dado 4, casilla 12
Turno 4: dado 5, casilla 17
Turno 5: dado 5, casilla 18
Turno 6: dado 5, casilla 17
Turno 7: dado 5, casilla 18
Turno 8: dado 6, casilla 16
Turno 9: dado 1, casilla 17
Turno 10: dado 1, casilla 18
Turno 11: dado 1, casilla 19
Turno 12: dado 1, casilla 20
Justo 20 tras 12 turnos

Ni move ni is_finished imprimen nada. Solo calculan. La impresión está en el código que hay debajo de las funciones.

¿Por qué así? Una función que devuelve un número se puede llamar con los valores que quieras y comprobar el resultado: move(17, 5) tiene que dar 18. Una función que imprime solo se comprueba a ojo. Y si mañana el juego pasa de la consola a una ventana o al navegador, las reglas siguen siendo las mismas; solo cambia la pantalla.

La casilla nueva en move se llama new_field y no next: en Python next es el nombre de una función incorporada, y es mejor no taparla.

Pruébalo

¿Dónde caerá la ficha?

¿Qué imprimirá el programa?

def move(field, roll):
new_field = field + roll
if new_field > 20:
return 20 - (new_field - 20)
return new_field
def is_finished(field):
return field == 20
print(move(10, 4))
print(move(17, 3))
print(move(18, 5))
Respuesta

Pruébalo

Encuentra el error

La ficha debería avanzar, pero el programa imprime la casilla 0 en cada turno:

Salida
Turno 1: dado 3, casilla 0
Turno 2: dado 2, casilla 0
Turno 3: dado 4, casilla 0

¿En qué línea está el error?

Elige la línea con el error.

Pruébalo

¿Cuándo termina el juego?

¿Qué falta para que el final sea justo en la casilla 20?

def is_finished(field):
    return field  20

Ejercicio

Tres juegos pequeños

Resuelve estos ejercicios en tu editor. La lógica va en funciones que devuelven un valor, y la impresión va en el código que hay debajo.

1. Una carrera para dos. Ana y Luis tiran por turnos, con Random(3). Vale la regla «justo 20», con move de la lección. Sus casillas están en una lista [0, 0]. Gana quien llegue primero a 20.

Salida
Ana: dado 2, casilla 2
Luis: dado 5, casilla 5
Ana: dado 5, casilla 7
Luis: dado 2, casilla 7
Ana: dado 3, casilla 10
Luis: dado 5, casilla 12
Ana: dado 4, casilla 14
Luis: dado 6, casilla 18
Ana: dado 5, casilla 19
Luis: dado 1, casilla 19
Ana: dado 5, casilla 16
Luis: dado 1, casilla 20
Luis gana

2. Adivina el número. El programa elige en secreto un número del 1 al 100 con Random(2026). El jugador escribe sus intentos, uno por línea. Después de cada uno, el programa dice «Más alto», «Más bajo» o «Correcto». Al final dice cuántos intentos hicieron falta. La pista sale de una función hint(guess, secret) que devuelve el texto.

Entrada
50
25
12
18
15
16
Salida
50: Más bajo
25: Más bajo
12: Más alto
18: Más bajo
15: Más alto
16: Correcto
Adivinado en 6 intentos

La entrada es distinta en cada lenguaje, porque sus generadores eligen un número secreto distinto a partir de la misma semilla.

3. ¿Quién ganó al tres en raya? La primera línea es el número de tableros. Cada tablero son tres líneas de tres caracteres: X, O o . para una casilla vacía. Escribe una función winner(board) que devuelva X, O o . si no hay tres en línea en una fila, una columna o una diagonal.

El tablero puede ser una lista con las tres líneas que leas: cada línea es un texto, y board[r][c] es el carácter de la fila r, columna c. Python no tiene un tipo aparte para un solo carácter, como char en Java: un carácter es un texto de longitud 1.

Entrada
3
XOX
OXO
OOX
XXO
OOO
X.X
XOX
XOO
OXX
Salida
X gana
O gana
Sin ganador

Más ejercicios

  • Trampas. La casilla 13 manda la ficha de vuelta a la casilla 5. Añade la regla a move y vuelve a ejecutar la carrera.
  • Imprime la semilla. Convierte la semilla en una variable, imprímela al empezar y ejecuta el juego con tres semillas distintas.
  • Tres en raya para dos. Los jugadores escriben la fila y la columna de su jugada. Después de cada jugada, muestra el tablero y usa winner para comprobar si el juego terminó.

Test

Ponte a prueba

1. ¿Cuál es el estado de la carrera de dados?
2. ¿Por qué el juego dice Random(42) y no Random()?
3. ¿Por qué la regla del movimiento es una función que devuelve la casilla nueva y no una función que imprime?
4. ¿Cuál es una buena primera versión de un juego?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·