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 métodos 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 métodos, la entrada es Console.ReadLine() o Random, el bucle es while o for, y el final es la condición del bucle.

Míralo

Turno tras turno

Random random = new Random(42);
int field = 0;
for (int turn = 1; field < 20; turn++)
{
int roll = random.Next(1, 7);
field = Math.Min(field + roll, 20);
Console.WriteLine("Turno " + turn + ": dado " + roll + ", casilla " + field);
}
Console.WriteLine("¡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.
  2. Turno 1: la entrada es una tirada de 5, el estado pasa a ser la casilla 5, la pantalla muestra la línea.
  3. Turno 2: la entrada es una tirada de 1, el estado pasa a ser la casilla 6, la pantalla muestra la línea.
  4. Turno 3: la entrada es una tirada de 1, el estado pasa a ser la casilla 7, la pantalla muestra la línea.
  5. Turno 4: la entrada es una tirada de 4, el estado pasa a ser la casilla 11, la pantalla muestra la línea.
  6. Turno 5: la entrada es una tirada de 2, el estado pasa a ser la casilla 13, la pantalla muestra la línea.
  7. Turno 6: la entrada es una tirada de 2, el estado pasa a ser la casilla 15, la pantalla muestra la línea.
  8. Turno 7: la entrada es una tirada de 5, el estado pasa a ser la casilla 20, la pantalla muestra la línea. Casilla 20: la condición 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.

Math.Min mantiene la ficha dentro del tablero: si el dado la manda más allá de 20, se queda en 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 Random recibe un número inicial, una semilla. La misma semilla da la misma secuencia, siempre:

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: new Random(). Entonces el generador elige una semilla por sí mismo a partir del reloj, 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 un método que devuelve la casilla nueva. El final es un método 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 IsFinished imprimen nada. Solo calculan. La impresión está en Main.

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

Pruébalo

¿Dónde caerá la ficha?

¿Qué imprimirá el programa?

static int Move(int field, int roll)
{
int next = field + roll;
if (next > 20)
{
return 20 - (next - 20);
}
return next;
}
static bool IsFinished(int field)
{
return field == 20;
}
Console.WriteLine(Move(10, 4));
Console.WriteLine(Move(17, 3));
Console.WriteLine(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 6, 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?

static bool IsFinished(int field)
{
    return field  20;
}

Ejercicio

Tres juegos pequeños

Resuelve estos ejercicios en tu editor. La lógica va en métodos que devuelven un valor, y la impresión va en Main.

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

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

2. Adivina el número. El programa elige en secreto un número del 1 al 100 con new 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 un método string Hint(int guess, int secret) que devuelve el texto.

Entrada
50
25
12
18
16
Salida
50: Más bajo
25: Más bajo
12: Más alto
18: Más bajo
16: Correcto
Adivinado en 5 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 un método char Winner(char[,] board) que devuelva X, O o . si no hay tres en línea en una fila, una columna o una diagonal.

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 new Random(42) y no new Random()?
3. ¿Por qué la regla del movimiento es un método que devuelve la casilla nueva y no un método 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 ·