Batalla RPG
Los objetivos principales de esta práctica son acostumbrarte a leer código JavaScript y que practiques la implementación de modelos y algoritmos.
El objetivo secundario es que practiques la metodología TDD, del inglés Test Driven Development o desarrollo dirigido por tests.
Instalación y tests
Descarga el esqueleto inicial del proyecto y descomprímelo. En una consola, ve a la raíz del directorio del proyecto (donde se encuentra el archivo package.json
) y ejecuta:
npm install
Ahora puedes pasar los tests con:
npm test
La práctica no estará finalizada hasta que no pases los tests con 0 errores y 0 especificaciones pendientes:
$ npm test
> pvli2017-rpg-battle@1.0.0 test /Users/salva/workspace/pvli2017-rpg-battle
> node ./node_modules/gulp/bin/gulp.js test
[09:43:57] Using gulpfile ~/workspace/pvli2017-rpg-battle/gulpfile.js
[09:43:57] Starting 'lint'...
[09:43:57] Finished 'lint' after 71 ms
[09:43:57] Starting 'test'...
[09:43:57] Finished 'test' after 41 ms
........................................................................................
88 specs, 0 failures
Finished in 0.4 seconds
Calidad del código
Este proyecto exige que tu código cumpla con ciertos estándares de calidad. El conjunto de restricciones responde a la especificación recomendada por ESLint con dos alteraciones:
- Se debe usar el estilo camelCase para identificadores.
- Se deben usar comillas simples para las cadenas.
- Se deben poner espacios entre operandos y operadores.
- No se permiten líneas de más de 100 caracteres.
- No se permiten funciones de más de 40 statements
En definitiva esas reglas están ahí para que los programas sean fáciles de leer. La mejor recomendación que puedes seguir es hacer lo que veas que ya está hecho. Cuando contribuyas a código ajeno, mira y copia.
Las dos últimas reglas están pensadas para que no hagas funciones muy largas, tanto en horizontal como en vertical. Ten en cuenta que lo que hemos limitado es el número de statements, no de líneas así que el siguiente ejemplo son realmente 4 statements:
function f() {
var x = 1;
var y = 2;
return x + g(y);
function g(n) {
return n * n;
}
}
Al encargado de comprobar el estilo del código se lo llama linter y éste se pasa automáticamente junto con los tests. Un error se muestra así:
/Users/salva/workspace/pvli2017-rpg-battle/src/TurnList.js
15:8 error Identifier 'snake_case' is not in camel case camelcase
Ahí se indica dónde está el error mediante la ruta del fichero, la línea y la
columna en formato fila:columna
.
Metodología y guía de la práctica
Antes de comenzar lee el documento TDD.md y la guía de la práctica GUIDE.md. El primero presenta la metodología que seguirás para desarrollar la práctica y el segundo te muestra un orden de activación de tests sensato de cara a completar la práctica con éxito.
Descripción de la batalla
El objetivo de la práctica es crear una biblioteca que modele el funcionamiento de una batalla RPG.
Los combatientes en la batalla son personajes en distintos bandos que lucharán entre sí. Cuando sólo queden miembros de un bando este será declarado como vencedor.
Al comienzo de la batalla, los personajes son ordenados por su iniciativa, de mayor a menor y los turnos se suceden en este orden hasta que sólo quedan personajes de un bando. Si un personaje ha muerto, es decir, sus puntos de vida son 0, su turno se salta.
Cuando le llega el turno a un personaje este puede elegir entre realizar tres acciones: defender, atacar o lanzar un hechizo.
Si defiende, su defensa aumentará en un 10%. La defensa es importante porque afecta en el modo en que un personaje bloquea el efecto de las armas y los hechizos.
Si ataca, lo hará sobre un personaje objetivo que realizará una tirada de defensa. Si el objetivo supera esta tirada no recibirá ningún efecto pero si la falla, recibirá todo el daño del arma.
Si finalmente decide utilizar un hechizo, habrá de seleccionar el hechizo y luego un objetivo. Los hechizos se encuentran en un grimorio común a todo el bando y consumen puntos de maná. Si el personaje no tiene suficientes puntos de maná para seleccionar un hechizo, no puede utilizarlo.
Los bandos
Los bandos tienen un identificador, miembros y hechizos. Al conjunto de hechizos lo llamamos el grimorio del bando. Por ejemplo, la estructura:
var setup = {
heroes: {
members: [heroTank, heroWizard],
grimoire: [fire, health]
},
monsters: {
members: [fastEnemy],
grimoire: [fire, health]
}
};
Contiene dos bandos con identificadores heroes
y monsters
. El bando
heroes
tiene dos miembros. heroTank
y heroWizard
, mientras que el bando
monsters
sólo uno, fastEnemy
. Los dos bandos tienen grimorios con los
hechizos fire
y health
.
Los personajes
Los personajes tienen un nombre, un bando opcional y una serie de características que determinan su valor en la batalla. Estas características son iniciativa, defensa, puntos de vida y de maná, y máximos de vida y de maná.
La iniciativa determina el orden en el que los personajes aparecerán en la lista de turnos. Cuanto más alto, antes aparecen en esta lista.
La defensa establece la probabilidad (de 0 a 100) de que un efecto actúe sobre el personaje.
Los puntos de maná sirven para pagar los costes mágicos de los hechizos y los puntos de vida indican cuánto daño es capaz de resistir el personaje antes de ser morir (puntos de vida a 0). Ambas características están ligadas a unos valores máximos puntos de maná máximos y puntos de vida máximos respectivamente que no pueden sobrepasar.
La siguiente tabla resume las características numéricas y sus límites.
Característica | Mínimo | Máximo | Limitado por |
---|---|---|---|
Iniciativa | 0 | - | - |
Defensa | 0 | 100 | - |
Puntos de vida | 0 | - | Puntos de vida máximos |
Puntos de maná | 0 | - | Puntos de maná máximos |
Las acciones
Si durante su turno, un personaje elije defender, entrará en estado de defensa mejorada aumentando un 10% su defensa (redondeando arriba). El personaje puede defender tantas veces como quiera, aumentando su defensa en cada turno que defienda pero tan pronto otro personaje lo ataque, perderá su mejora.
Si elige atacar, habrá de elegir un personaje objetivo de entre todos los personajes vivos que queden en la batalla. El personaje causará el efecto de su arma al objetivo siguiendo las reglas de aplicación de efectos.
Por último, un personaje puede lanzar un hechizo, en tal caso se debe elegir un hechizo que se pueda pagar del grimorio del bando y un objetivo. De nuevo se utilizarán las reglas de aplicación de efectos con el efecto del hechizo.
Efectos
Armas y hechizos contienen efectos. Un arma contendrá un efecto que siempre reducirá los puntos de vida, los hechizos pueden tener efectos variados. Exceptuando el nombre y el bando, todas las características son susceptibles de verse alteradas por efectos.
Reglas de efecto
Las reglas de efecto esperan un efecto cualquiera, un objetivo y un indicador de alianza que indica si el efecto lo aplica un aliado del objetivo o no.
El procedimiento es el siguiente:
- Si el indicador de alianza indica que son aliados, continúa con el paso 2.
Si no:
- Genera un número al azar del 1 al 100.
- Si el resultado se encuentra por debajo o es igual a la defensa del objetivo, suspende estos pasos. Si no, continúa con el paso 2.
- Por cada característica afectada por el efecto
- Suma el valor del efecto al valor de la característica del objetivo.
- Corrige el valor para que se encuentre entre 0 y el máximo para esa característica, si tiene.
Armas y hechizos
Armas y hechizos son elementos con efectos. La diferencia sustancial entre ellos es que los hechizos tienen un coste en puntos de maná para el lanzador del hechizo. Este coste se aplica tenga o no tenga éxito la defensa del objetivo.
El efecto de los hechizos puede ser variado así como el de las armas pero en el caso de las armas el efecto debe reducir los puntos de vida de alguna forma.
La API de batalla
La batalla tendrá los siguientes métodos:
setup
para establecer la configuración inicial como la que hemos mostrados antes.start
para comenzar la batalla.stop
para detener la batalla.
Además la batalla expone los siguientes atributos:
characters
para inspeccionar el estado de los personajes.scrolls
para inspeccionar el estado de los personajes.options
para controlar la batalla.
La batalla emitirá los siguientes eventos:
start
junto con los identificadores de los combatientes por bando, al comienzo de la batalla.turn
junto con la información del turno, cada vez que le toque a un personaje.info
con el resultado de una acción.end
cuando sólo quede un bando.
En cualquier caso, el objeto de contexto será la Batalla
.
La interfaz de control
Controlar el curso de la batalla consiste en decidir qué ocurre a cada turno.
Para ello tendrás que suscribirte al evento turn
y elegir entre las
alternativas que se nos ofrecen en options
:
var battle = new Battle(setup);
battle.on('turn', function (turn) {
// Los héroes dañan al primer enemigo disponible.
if (turn.party === 'heroes') {
this.options.select('attack');
var candidates = this.options.list();
var monsters = candidates.filter(isMonster.bind(this));
this.options.select(monsters[0]);
}
// Los monstruos sólo defienden.
if (turn.party === 'monsters') {
this.options.select('defend');
}
function isMonster(charId) {
return this.characters.get(charId).party === 'monsters';
}
});
battle.start();
Las opciones
A cada turno, inicialmente las opciones serán: defend
, attack
y cast
.
Al elegir defend
se aplican las reglas para la acción defender y pasamos
al siguiente turno.
Si se elige attack
, las siguientes opciones han de mostrar los identificadores
de los personajes para seleccionar un objetivo. Cuando se elige el objetivo, se
ejecuta la acción atacar y se pasa al siguiente turno.
Si elige cast
, la siguientes opciones serán los hechizos disponibles (que
pueden ser ninguno) y luego la lista de objetivos. De nuevo al elegir el
objetivo, se efectúa la acción de lanzar hechizo y se pasa al siguiente turno.
Reglas de formación de identificadores
Los identificadores de los personajes no son los nombres puesto que estos
podrían repetirse. Si un bando tuviera dos personajes murciélago con el nombre
Bat
, sus identificadores sería Bat
y Bat 2
. Las reglas de formación de los
identificadores son:
- Recorre todos los bandos. Por cada bando:
- Recorre todos los miembros. Por cada miembros:
- Recupera el nombre del personaje y comprueba si está en el histograma de nombres.
- Si no está, añádelo con valor cero.
- Recupera el valor para nombre en el histograma de nombres.
- Si es 0, el identificador es el nombre.
- Si es mayor que 0, el identificador es el nombre seguido de un espacio y el valor del histograma más uno.
- Incrementa el valor del histograma en 1.
- Asigna al personaje ese identificador.
- Recorre todos los miembros. Por cada miembros: