En este proyecto vamos a construir un juego RPG por turnos completo en PSeInt. La idea no es solamente hacer un juego largo: vamos a usarlo para practicar cómo dividir un problema grande en partes más pequeñas mediante SubProcesos y Funciones.
El jugador podrá elegir una clase, combatir enemigos aleatorios, usar pociones, defenderse, comprar mejoras, ganar experiencia, subir de nivel y enfrentarse finalmente a un jefe.
¿Qué vamos a practicar?
- variables y tipos de datos;
- condicionales y estructuras
Segun; - ciclos
Mientras; - números aleatorios;
- parámetros por valor y por referencia;
- SubProcesos y Funciones;
- manejo del estado de un juego;
- separación de responsabilidades.
1. La idea del juego
El juego comienza creando un héroe. Cada clase tiene estadísticas diferentes:
- Guerrero: mucha vida y defensa.
- Mago: menos vida, pero mucho ataque.
- Arquero: estadísticas más equilibradas.
Después de cada combate el jugador recibe oro y experiencia. Entre batallas puede visitar una tienda, comprar pociones o mejorar sus estadísticas. Al conseguir cinco victorias aparece el jefe final.
2. ¿Por qué modularizar?
Podríamos escribir todo dentro del proceso principal, pero el código rápidamente se volvería difícil de leer. Por eso separaremos tareas concretas como mostrar el estado del héroe, generar enemigos, calcular daño, administrar la tienda o subir de nivel.
Una Función resulta útil cuando necesitamos calcular y devolver un valor. Un SubProceso es conveniente cuando queremos ejecutar una tarea más amplia y, si hace falta, modificar variables recibidas Por Referencia.
3. Código completo
Copiá el siguiente código en PSeInt y ejecutalo. Después vamos a repasar las partes más importantes.
Funcion resultado <- CalcularDanio(ataque, defensa) Definir resultado Como Entero; resultado <- ataque + Aleatorio(1,6) - defensa; Si resultado < 1 Entonces resultado <- 1; FinSi FinFuncion SubProceso MostrarEstado(nombre, clase, nivel, vida, vidaMaxima, ataque, defensa, experiencia, experienciaNecesaria, oro, pociones, combatesGanados) Limpiar Pantalla; Escribir "===================================="; Escribir " ESTADO DEL HEROE"; Escribir "===================================="; Escribir "Nombre: ", nombre; Escribir "Clase: ", clase; Escribir "Nivel: ", nivel; Escribir "Vida: ", vida, "/", vidaMaxima; Escribir "Ataque: ", ataque; Escribir "Defensa: ", defensa; Escribir "Experiencia: ", experiencia, "/", experienciaNecesaria; Escribir "Oro: ", oro; Escribir "Pociones: ", pociones; Escribir "Combates ganados: ", combatesGanados; FinSubProceso SubProceso CrearPersonaje(opcion, clase Por Referencia, vidaMaxima Por Referencia, ataque Por Referencia, defensa Por Referencia) Segun opcion Hacer 1: clase <- "Guerrero"; vidaMaxima <- 130; ataque <- 18; defensa <- 8; 2: clase <- "Mago"; vidaMaxima <- 90; ataque <- 25; defensa <- 4; 3: clase <- "Arquero"; vidaMaxima <- 105; ataque <- 21; defensa <- 6; De Otro Modo: clase <- "Guerrero"; vidaMaxima <- 130; ataque <- 18; defensa <- 8; FinSegun FinSubProceso SubProceso CrearEnemigo(nivel, combatesGanados, nombreEnemigo Por Referencia, vidaEnemigo Por Referencia, ataqueEnemigo Por Referencia, defensaEnemigo Por Referencia) Definir enemigo Como Entero; Si combatesGanados >= 5 Entonces nombreEnemigo <- "Dragon Oscuro"; vidaEnemigo <- 180 + nivel * 20; ataqueEnemigo <- 22 + nivel * 3; defensaEnemigo <- 8 + nivel; SiNo enemigo <- Aleatorio(1,4); Segun enemigo Hacer 1: nombreEnemigo <- "Goblin"; vidaEnemigo <- 45 + nivel * 10; ataqueEnemigo <- 10 + nivel * 2; defensaEnemigo <- 2 + nivel; 2: nombreEnemigo <- "Esqueleto"; vidaEnemigo <- 55 + nivel * 10; ataqueEnemigo <- 12 + nivel * 2; defensaEnemigo <- 3 + nivel; 3: nombreEnemigo <- "Orco"; vidaEnemigo <- 75 + nivel * 12; ataqueEnemigo <- 15 + nivel * 2; defensaEnemigo <- 5 + nivel; 4: nombreEnemigo <- "Hechicero oscuro"; vidaEnemigo <- 60 + nivel * 10; ataqueEnemigo <- 18 + nivel * 2; defensaEnemigo <- 3 + nivel; FinSegun FinSi FinSubProceso SubProceso SubirNivel(experiencia Por Referencia, experienciaNecesaria Por Referencia, nivel Por Referencia, vida Por Referencia, vidaMaxima Por Referencia, ataque Por Referencia, defensa Por Referencia) Mientras experiencia >= experienciaNecesaria Hacer experiencia <- experiencia - experienciaNecesaria; nivel <- nivel + 1; experienciaNecesaria <- experienciaNecesaria + 50; vidaMaxima <- vidaMaxima + 20; ataque <- ataque + 4; defensa <- defensa + 2; vida <- vidaMaxima; Escribir ""; Escribir "===================================="; Escribir " ¡SUBISTE DE NIVEL!"; Escribir "===================================="; Escribir "Ahora sos nivel ", nivel; Escribir "Tu vida, ataque y defensa aumentaron."; FinMientras FinSubProceso SubProceso Tienda(oro Por Referencia, pociones Por Referencia, ataque Por Referencia, defensa Por Referencia) Definir opcion Como Entero; Limpiar Pantalla; Escribir "===================================="; Escribir " TIENDA"; Escribir "===================================="; Escribir "Oro disponible: ", oro; Escribir ""; Escribir "1. Poción - 20 oro"; Escribir "2. Mejorar ataque - 50 oro"; Escribir "3. Mejorar defensa - 50 oro"; Escribir "4. Volver"; Leer opcion; Segun opcion Hacer 1: Si oro >= 20 Entonces oro <- oro - 20; pociones <- pociones + 1; Escribir "Compraste una poción."; SiNo Escribir "No tenés suficiente oro."; FinSi; 2: Si oro >= 50 Entonces oro <- oro - 50; ataque <- ataque + 3; Escribir "Tu ataque aumentó."; SiNo Escribir "No tenés suficiente oro."; FinSi; 3: Si oro >= 50 Entonces oro <- oro - 50; defensa <- defensa + 2; Escribir "Tu defensa aumentó."; SiNo Escribir "No tenés suficiente oro."; FinSi; FinSegun Esperar Tecla; FinSubProceso SubProceso Combatir(nombre, clase, vida Por Referencia, vidaMaxima, ataque, defensa, pociones Por Referencia, nombreEnemigo, vidaEnemigo Por Referencia, ataqueEnemigo, defensaEnemigo) Definir opcion, danio, danioEnemigo, probabilidad Como Entero; Mientras vida > 0 Y vidaEnemigo > 0 Hacer Limpiar Pantalla; Escribir "===================================="; Escribir nombre, " VS ", nombreEnemigo; Escribir "===================================="; Escribir nombre, ": ", vida, "/", vidaMaxima, " HP"; Escribir nombreEnemigo, ": ", vidaEnemigo, " HP"; Escribir ""; Escribir "1. Atacar"; Escribir "2. Ataque especial"; Escribir "3. Defender"; Escribir "4. Usar poción"; Leer opcion; Segun opcion Hacer 1: danio <- CalcularDanio(ataque, defensaEnemigo); probabilidad <- Aleatorio(1,100); Si probabilidad <= 15 Entonces danio <- danio * 2; Escribir "¡GOLPE CRITICO!"; FinSi vidaEnemigo <- vidaEnemigo - danio; Escribir "Causás ", danio, " puntos de daño."; 2: Segun clase Hacer "Guerrero": danio <- ataque * 2 - defensaEnemigo; Escribir "¡GOLPE DEVASTADOR!"; "Mago": danio <- ataque * 2 + Aleatorio(5,15); Escribir "¡BOLA DE FUEGO!"; "Arquero": danio <- ataque + Aleatorio(10,20); Escribir "¡FLECHA PRECISA!"; FinSegun Si danio < 1 Entonces danio <- 1; FinSi vidaEnemigo <- vidaEnemigo - danio; Escribir "Causás ", danio, " puntos de daño."; 3: Escribir "Te preparás para recibir el ataque."; 4: Si pociones > 0 Entonces vida <- vida + 40; Si vida > vidaMaxima Entonces vida <- vidaMaxima; FinSi pociones <- pociones - 1; Escribir "Usaste una poción. Vida actual: ", vida; SiNo Escribir "No te quedan pociones."; FinSi; FinSegun Si vidaEnemigo > 0 Entonces danioEnemigo <- CalcularDanio(ataqueEnemigo, defensa); Si opcion = 3 Entonces danioEnemigo <- Trunc(danioEnemigo / 2); FinSi vida <- vida - danioEnemigo; Escribir nombreEnemigo, " te causa ", danioEnemigo, " de daño."; FinSi Esperar Tecla; FinMientras FinSubProceso Proceso RPG_ClubProgramador Definir nombre, clase, nombreEnemigo Como Cadena; Definir vida, vidaMaxima, ataque, defensa Como Entero; Definir nivel, experiencia, experienciaNecesaria Como Entero; Definir oro, pociones, combatesGanados Como Entero; Definir vidaEnemigo, ataqueEnemigo, defensaEnemigo Como Entero; Definir opcion Como Entero; Definir jugando Como Logico; Limpiar Pantalla; Escribir "===================================="; Escribir " REINOS DE PSEINT"; Escribir "===================================="; Escribir "¿Cómo se llama tu héroe?"; Leer nombre; Escribir ""; Escribir "Elegí una clase:"; Escribir "1. Guerrero"; Escribir "2. Mago"; Escribir "3. Arquero"; Leer opcion; CrearPersonaje(opcion, clase, vidaMaxima, ataque, defensa); vida <- vidaMaxima; nivel <- 1; experiencia <- 0; experienciaNecesaria <- 100; oro <- 30; pociones <- 3; combatesGanados <- 0; jugando <- Verdadero; Mientras jugando Hacer MostrarEstado(nombre, clase, nivel, vida, vidaMaxima, ataque, defensa, experiencia, experienciaNecesaria, oro, pociones, combatesGanados); Escribir ""; Escribir "1. Buscar enemigo"; Escribir "2. Ir a la tienda"; Escribir "3. Descansar"; Escribir "4. Salir"; Leer opcion; Segun opcion Hacer 1: CrearEnemigo(nivel, combatesGanados, nombreEnemigo, vidaEnemigo, ataqueEnemigo, defensaEnemigo); Escribir ""; Escribir "¡APARECE ", nombreEnemigo, "!"; Esperar Tecla; Combatir(nombre, clase, vida, vidaMaxima, ataque, defensa, pociones, nombreEnemigo, vidaEnemigo, ataqueEnemigo, defensaEnemigo); Si vida <= 0 Entonces Limpiar Pantalla; Escribir "===================================="; Escribir " GAME OVER"; Escribir "===================================="; Escribir nombre, " ha sido derrotado."; Escribir "Nivel alcanzado: ", nivel; Escribir "Combates ganados: ", combatesGanados; jugando <- Falso; SiNo Si nombreEnemigo = "Dragon Oscuro" Entonces Limpiar Pantalla; Escribir "===================================="; Escribir " ¡VICTORIA FINAL!"; Escribir "===================================="; Escribir nombre, " derrotó al Dragon Oscuro."; Escribir "El reino vuelve a estar a salvo."; jugando <- Falso; SiNo combatesGanados <- combatesGanados + 1; oro <- oro + Aleatorio(15,35); experiencia <- experiencia + Aleatorio(35,60); Escribir "¡Victoria! Ganaste oro y experiencia."; SubirNivel(experiencia, experienciaNecesaria, nivel, vida, vidaMaxima, ataque, defensa); Esperar Tecla; FinSi FinSi; 2: Tienda(oro, pociones, ataque, defensa); 3: vida <- vidaMaxima; Escribir "Descansaste y recuperaste toda tu vida."; Esperar Tecla; 4: jugando <- Falso; De Otro Modo: Escribir "Opción inválida."; Esperar Tecla; FinSegun FinMientras Escribir ""; Escribir "Gracias por jugar."; FinProceso
4. La función CalcularDanio
CalcularDanio() recibe el ataque de quien golpea y la defensa de quien recibe el golpe. Agregamos un pequeño valor aleatorio para que los ataques no causen exactamente el mismo daño en cada turno.
La función devuelve un número entero y garantiza un mínimo de 1 punto de daño. Es un buen ejemplo de una tarea pequeña que conviene aislar porque se utiliza tanto para el jugador como para los enemigos.
5. Parámetros Por Referencia
En varios SubProcesos usamos Por Referencia. Esto permite que una modificación realizada dentro del SubProceso afecte directamente a la variable original.
Por ejemplo, la tienda puede descontar oro y aumentar el ataque:
Tienda(oro, pociones, ataque, defensa);
Como esas variables se reciben Por Referencia, los cambios siguen existiendo cuando volvemos al proceso principal.
6. Generación de enemigos
CrearEnemigo() usa Aleatorio(1,4) para elegir uno de cuatro enemigos. Sus estadísticas además dependen del nivel actual del jugador, por lo que los combates se vuelven progresivamente más difíciles.
Cuando el jugador alcanza cinco victorias dejamos de generar enemigos normales y aparece el Dragón Oscuro.
7. El sistema de combate
Mientras tanto el jugador como el enemigo tengan vida, el combate continúa. Cada turno permite:
- realizar un ataque normal;
- usar una habilidad especial propia de la clase;
- defenderse para reducir el siguiente daño;
- usar una poción.
Los ataques normales tienen además un 15 % de probabilidad de convertirse en golpes críticos y causar el doble de daño.
8. Experiencia y niveles
Después de cada victoria se obtiene experiencia. Cuando alcanza el valor necesario se ejecuta SubirNivel().
Al subir de nivel aumentan la vida máxima, el ataque y la defensa. También restauramos completamente la vida del personaje.
Usamos un ciclo Mientras dentro del SubProceso para contemplar incluso el caso en que una recompensa grande permita subir más de un nivel.
9. La tienda
El oro ganado puede invertirse en:
- pociones;
- mejoras de ataque;
- mejoras de defensa.
Esta mecánica introduce una pequeña decisión estratégica: guardar oro para mejorar estadísticas o gastar antes para tener más recursos durante los combates.
10. Ideas para seguir mejorándolo
El juego todavía puede crecer mucho. Algunos desafíos interesantes son:
- agregar maná y hacer que los ataques especiales tengan un costo;
- incorporar enfriamiento para las habilidades especiales;
- crear más clases;
- agregar armaduras y armas;
- incorporar distintos jefes;
- crear probabilidades de esquivar ataques;
- agregar estados como veneno o quemadura;
- crear un inventario con vectores;
- guardar puntuaciones de diferentes jugadores;
- incorporar un mapa o mazmorra usando matrices.
Desafío
Intentá agregar una cuarta clase llamada Paladín. Debería tener mucha defensa y una habilidad especial que, además de causar daño, recupere una pequeña cantidad de vida.
Después intentá agregar un enemigo nuevo con una habilidad especial propia.
Seguí aprendiendo PSeInt
Si todavía no trabajaste con SubProcesos y Funciones, te conviene revisar primero nuestra guía:
Funciones y SubProcesos en PSeInt →
También podés encontrar todos los ejercicios y proyectos en la guía principal:
Aprender PSeInt: guía completa y ejercicios resueltos →
Descubre más desde Club Programador
Suscríbete y recibe las últimas entradas en tu correo electrónico.