Proyecto avanzado en PSeInt: crear un juego RPG por turnos completo


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.

Deja un comentario

Descubre más desde Club Programador

Suscríbete ahora para seguir leyendo y obtener acceso al archivo completo.

Seguir leyendo