Implementar más acciones
¡Muy bien! Estamos listos para añadir más acciones a nuestro juego y permitir que los jugadores elijan sus acciones.
Primero, necesitamos crear una interfaz para nuestros comandos. Para ello, dentro del directorio ActionCommand
, vamos a crear un nuevo archivo PHP y lo llamaremos ActionCommandInterface
. Dentro, añadiremos un único método llamado execute()
sin argumentos.
// ... lines 1 - 4 | |
interface ActionCommandInterface | |
{ | |
public function execute(): void; | |
} |
¡Interfaz terminada! A continuación, abramos AttackCommand
y hagamos que implemente ActionCommandInterface
. El método execute()
ya está implementado aquí, así que ya está listo. ¡Qué bien!
Si te has descargado el código del curso, podemos ahorrarnos algo de tiempo cogiendo el resto de acciones que necesitamos en nuestro directorio tutorial
en la raíz de nuestro proyecto. Copia los archivos HealCommand
y SurrenderCommand
en el directorio ActionCommand
.
Vamos a comprobarlos. Dentro de HealCommand
, podemos ver que tiene un constructor que sólo se preocupa del objeto jugador.
// ... lines 1 - 4 | |
use App\Character\Character; | |
// ... lines 6 - 8 | |
class HealCommand implements ActionCommandInterface | |
{ | |
public function __construct(private readonly Character $player) | |
{ | |
} | |
// ... lines 14 - 28 | |
} |
Y en el método execute()
, tenemos algo de código que calcula cuánto daño se curará el jugador, y luego ajusta la salud del jugador a la nueva cantidad (sin exceder su salud máxima). Por último, imprime un mensaje en la pantalla.
// ... lines 1 - 14 | |
public function execute(): void | |
{ | |
$healAmount = Dice::roll(20) + $this->player->getLevel() * 3; | |
$newAmount = $this->player->getCurrentHealth() + $healAmount; | |
$newAmount = min($newAmount, $this->player->getMaxHealth()); | |
$this->player->setHealth($newAmount); | |
$this->player->setStamina(Character::MAX_STAMINA); | |
GameApplication::$printer->writeln(sprintf( | |
'You healed %d damage', | |
$healAmount | |
)); | |
} |
Si echamos un vistazo a SurrenderCommand
, el constructor aquí es el mismo que el de HealCommand
- sólo se preocupa del objeto jugador. Y en el método execute()
, he hecho un poco de trampa porque no hay una forma adecuada de terminar una batalla, así que me he limitado a poner la salud del jugador en 0
. Genial, ¿verdad?
// ... lines 1 - 4 | |
use App\Character\Character; | |
use App\GameApplication; | |
class SurrenderCommand implements ActionCommandInterface | |
{ | |
public function __construct(private readonly Character $player) | |
{ | |
} | |
public function execute(): void | |
{ | |
$this->player->setHealth(0); | |
GameApplication::$printer->block('You\'ve surrendered! Better luck next time!'); | |
} | |
} |
Pedir al jugador que elija una acción
¡Muy bien! ¡Es hora de pedir al jugador que elija una acción! Antes cerraré algunos archivos. Bien, vuelve a GameApplication
... y justo antes de definir $playerAction
, escribe $actionChoice
y ponlo en GameApplication::$printer->choice()
, donde la pregunta es Your Turn
, y las opciones son Attack
, Heal
y Surrender
.
// ... lines 1 - 12 | |
class GameApplication | |
{ | |
// ... lines 15 - 26 | |
public function play(Character $player, Character $ai, FightResultSet $fightResultSet): void | |
{ | |
while (true) { | |
// ... lines 30 - 35 | |
// Player's turn | |
$actionChoice = GameApplication::$printer->choice('Your turn', [ | |
'Attack', | |
'Heal', | |
'Surrender', | |
]); | |
// ... lines 42 - 66 | |
} | |
} | |
// ... lines 69 - 198 | |
} |
A continuación, sustituiremos la instanciación AttackCommand
por una expresión match
, pero cópiala primero, porque la necesitaremos dentro de un momento. Ahora escribe match ($actionChoice)
. Dentro, el primer caso que queremos añadir es Attack
, y ahora... pega. Para el segundo caso, escribe Heal
y ponlo en new HealCommand($player)
. El tercer y último caso es Surrender
, y lo pondremos en new SurrenderCommand($player)
. ¡Perfecto!
// ... lines 1 - 26 | |
public function play(Character $player, Character $ai, FightResultSet $fightResultSet): void | |
{ | |
while (true) { | |
// ... lines 30 - 42 | |
$playerAction = match ($actionChoice) { | |
'Attack' => new AttackCommand($player, $ai, $fightResultSet), | |
'Heal' => new HealCommand($player), | |
'Surrender' => new SurrenderCommand($player), | |
}; | |
// ... lines 48 - 66 | |
} | |
} | |
// ... lines 69 - 200 |
Vamos a probarlo. Gira hasta tu terminal y ejecuta:
php bin/console app:game:play
Esta vez, seré un "mago arquero", y... ¡mira eso! ¡Nos pregunta qué hacer! Ataquemos primero, y... ¡genial! Hicimos 12
puntos de daño y recibimos 10
, así que nuestra salud actual es 40
de 50
. Intentemos curarnos a continuación. Y... ¡fíjate! Curamos 8
puntos, y la IA hizo 0
puntos de daño porque bloqueamos su ataque, así que nuestra salud actual es 48
. ¡La acción "curar" funciona como se esperaba! Por último, intentemos rendirnos. Elige la opción 2
y... ¡fantástico! Nos rendimos y perdemos la partida. Rendirse no es algo que normalmente celebraríamos, pero en este caso, significa que nuestra acción "rendirse" está funcionando como se supone que debe hacerlo.
¡Choca esos cinco con tu patito de goma porque hemos conseguido que nuestro juego sea más interactivo! Pero... ¿no sería genial poder deshacer nuestra última acción si, digamos... la IA tuviera un poco de suerte? ¡Eso a continuación!
Looks really great!
But there's something bugging me: What if we need services in these commands? Since we instanciate them in the (symfony) command, we can't use the usual autowiring. So do we have to autowire these services in the symfony command (not really a fan of this, because it means I must load all services of the commands inside my symfony command... not very clean imho), or is there anotherway to achieve it?