Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add script for command intro #15

Merged
merged 8 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions sfcasts/command_coding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# Adding Actions to the Game - Chapter 3

The game development department asked for a new feature "make the game more interactive".
Instead of running battles automatically, they want the player to be able to choose
what action to perform at the start of the turn. Right now, our game only
supports the **attack** action, so to make it more real we'll **also** add a couple more actions.
**Heal** and **Surrender** action.

This is a great **opportunity** to put the command pattern in action!

## Applying the Command Pattern

The first step is to identify the code that needs to change and encapsulate it into its own command class.
Open up the `GameApplication` class and
go to the `play()` method. Inside the `while` loop there's a comment telling us where the
player's turn starts. Select the code right before checking if the player won and cut it,
and since we're already here let's write the code we know we need. We know that we want to
instantiate an `AttackCommand` object and call `execute()` on it, so let's do that. Create a
variable called `$playerAction` and set it to `new AttackCommand()`. We'll create this class soon.
then, say `$playerAction->execute()`.

```php
public function play(Character $player, Character $ai, FightResultSet $fightResultSet): void
{
while (true) {
...
$playerAction = new AttackCommand();
$playerAction->execute();
}
}
```

Yes, I know I'm missing some arguments, to be able to attack we need to know about
the player and AI objects, but we'll get to that in a moment.

I'll press `Alt+Enter`, then select `Create class`, and
I'll put it on the `App\ActionCommand` namespace because `Command` it's already taken by Symfony. I'll leave
the empty constructor for now, and I'll implement the `execute()` method, then paste!

```php
public function execute()
{
$damage = $player->attack();
if ($damage === 0) {
self::$printer->printFor($player)->exhaustedMessage();
$fightResultSet->of($player)->addExhaustedTurn();
}

$damageDealt = $ai->receiveAttack($damage);
$fightResultSet->of($player)->addDamageDealt($damageDealt);

self::$printer->printFor($player)->attackMessage($damageDealt);
self::$printer->writeln('');
usleep(300000);
}
```

Perfect! It's now obvious what arguments we're missing, the `$player`, `$ai`, and `$fightResult`.
So now, the next question is, should we pass them to the method or to the constructor? Usually,
the `execute()` method of the command pattern does not have any arguments because we want to
decouple the instantiation from the execution. By setting them to the constructor
we're decoupling **when** this command is going to be run from when it's created. Also,
it will make our life easier when implementing the undo operation. The arguments won't have
to be available at that specific moment.

Alright! Let's add these three arguments to the constructor.

```php
class AttackCommand
{
public function __construct(
private readonly Character $player,
private readonly Character $opponent,
private readonly FightResultSet $fightResultSet
) {
}
}
```

Next, in the `execute()` method we need to replace the local variables with `$this`.

```php
class AttackCommand
{
public function execute()
{
$damage = $this->player->attack();
if ($damage === 0) {
GameApplication::$printer->printFor($this->player)->exhaustedMessage();
$this->fightResultSet->of($this->player)->addExhaustedTurn();
}

$damageDealt = $this->opponent->receiveAttack($damage);
$this->fightResultSet->of($this->player)->addDamageDealt($damageDealt);

GameApplication::$printer->printFor($this->player)->attackMessage($damageDealt);
GameApplication::$printer->writeln('');
usleep(300000);
}
}
```

Perfect! Our `AttackCommand` is ready. Now, let's go back to the `GameApplication`.
Let's add the arguments to the `AttackCommand` object. `$player`, `$opponent`, and `$fightResultSet`.

```php
public function play(Character $player, Character $ai, FightResultSet $fightResultSet): void
{
while (true) {
$playerAction = new AttackCommand($player, $ai, $fightResultSet);
$playerAction->execute();
}
}
```

And, scroll down a bit, there's the AI's turn, let's use our command there too.

```php
public function play(Character $player, Character $ai, FightResultSet $fightResultSet): void
{
while (true) {
// AI's turn
$aiAttackCommand = new AttackCommand($ai, $player, $fightResultSet);
$aiAttackCommand->execute();
}
}
```

Great! We're ready to give it a try. Let's run the game and play a match, everything
should work exactly as before. Exiting, right?!

## Implementing More Actions

* Create other actions. Heal and Surrender
* First create an interface `ActionCommandInterface`
```php
interface ActionInterface
{
public function execute(): void;
}
```

* Make `AttackCommand` implement it
```php
class AttackCommand implements ActionCommandInterface
```

* Create HealAction class
* copy-paste implementation
```php
class HealCommand implements ActionCommandInterface
{
public function __construct(private readonly Character $player)
{
}

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
));
}
}
```
* Explain constructor:
- Benefits of putting the arguments on the constructor instead of on the interface
The constructor for this action has fewer arguments than the `AttackCommand`, it only cares about the player.
And this is another benefit of putting the arguments in the constructor instead of the interface method
because this help us keeping the interface simple and avoid having arguments that are not
used by all the commands.
* Explain `execute()` method:
- Rolling a dice to add some randomness
- Set the player's health to the new amount but not more than the max health

* Create SurrenderCommand + interface
```php
class SurrenderCommand implements ActionCommandInterface
```
* copy-paste implementation
```php
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!');
}
}
```

* Explanation: For this action I'll cheat a bit because there's no proper way to end the match, so I'll
set the player's health to 0

* ask player to choose an action
* add `match` to create command object based on selection
* try it

Now, let's go back to the `GameApplication` and ask the player to choose an action.

```php
public function play(Character $player, Character $ai, FightResultSet $fightResultSet): void
{
$actionChoice = GameApplication::$printer->choice('Your turn', [
'Attack',
'Heal',
'Surrender',
]);

$playerAction = match ($actionChoice) {
'Attack' => new AttackCommand($player, $ai, $fightResultSet),
'Heal' => new HealCommand($player),
'Surrender' => new SurrenderCommand($player),
};
}
```

Perfect! It's time of the truth! Let's play a few rounds.

`bin/console app:game:play`

Sweet! it's asking us what to do
30 changes: 30 additions & 0 deletions sfcasts/command_coding_outline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Adding Actions to the Game - Chapter 3

The game development department asked for a new feature "make the game more interactive".
Instead of running battles automatically, they want the player to be able to choose
what action to perform at the start of the turn. Right now, our game only
supports the **attack** action, so to make it more real we'll **also** add a couple more actions.
**Heal** and **Surrender** action.

This is a great **opportunity** to put the command pattern in action!

## Applying the Command Pattern
- first identify code that will change
- copy and replace it by the code we want to have
- Use IDE to create the command class and paste
- add the missing arguments. Where to put them?
- constructor vs method
- Fix GameApp code
- Run the game

# Implement other actions
- Create CommandInterface
- make AttackCommand implement it
- copy/paste Heal action
- add Surrender


# Ask player to choose an action
- go back to GameApp and paste code to ask the player what action to do
- replace dummy code with real
- run the game
100 changes: 100 additions & 0 deletions sfcasts/command_intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Command Pattern Definition

Are you ready for a new design patterns episode? Go grab a cup of coffee and sit tight
because we'll take a deep dive into the command pattern.

Let's start with the boring part first, the definition and theory, and later we'll
have some fun applying it into our game application.

The command pattern is a behavioral pattern. In case you already forgot what it means
here's a quick reminder. Behavioral patterns are those that help you design classes
with specific responsibilities that can then work together, instead of putting
all of that code into one giant class.

The official definition says that the command pattern **encapsulates** a request
as a stand-alone object, allowing parameterization of clients with different requests,
queueing or logging requests, and support undoable operations.

Uhm, whaat?. Ok, let's try it again with a more friendly definition.

The command pattern encapsulates a task into an object, **decoupling** what it does,
how it does it, and when it gets done. Also, it allows to undo its actions
by **remembering** enough information of what things need to be reverted.

Ha! I don't know if it got any better but don't worry, it will make more sense
when we see it in action.

## Pattern Anatomy

The pattern is composed by three main parts:
- It has a Command Interface with a single public method that's usually called `execute()`.
- There are concrete commands which implement the Command Interface and hold the task's logic.
- And third, there's an **Invoker** object which holds a command reference and calls `execute()` at some point.

If you look over the internet you'll discover that I didn't mention two other parts,
the **receiver** and **client**. The receiver is the object that contains the business logic,
and the client is the one that's on charge of **creating** command objects.

In my opinion, those parts increases the complexity of the design and are not totally required.
They might be useful in a heavy **object-oriented** application so the responsibilities are split better,
but in our case, we would be **over-engineering** our solution, so we'll
keep it simple and just ignore them.

## Imaginary Example

Ok, that's enough theory. Let's see an example. Suppose that we want to implement a remote control for a TV.
It has many buttons to interact with it, it can turn the volume up or down, power it on or off, and so on.

Without much thinking, we could easily implement it with a `switch-case` statement, where each `case`
would represent a button's action containing all the logic.

```php
public function pressButton(string $button)
{
switch ($button) {
case 'turnOn':
$this->powerOnDevice($this->tv->getAddress())
$this->tv->initializeSettings();
$this->tv->turnOnScreen();
$this->tv->openDefaultChannel();
break;
case 'turnOff':
$this->tv->closeApps();
$this->tv->turnOffScreen();
$this->powerOffDevice($this->tv->getAddress())
break;
case 'mute':
$this->tv->toggleMute();
break;
...
}
}
```

As we keep implementing more buttons this approach will become a mess pretty quickly. Also,
it would be a maintenance nightmare and impossible to reuse the code.

A better approach would be to... you guessed it! Implement the command pattern. We could encapsulate
the logic of each button into their own **command** object.

And, in our `pressButton` method we would just call `execute()` on
the **right** command instance.

Our code would look like this:

The `commands` property holds a list of command objects, and if the button name
is in the list, we call `execute()`.

Now, you might be wondering, how do I instantiate the commands?
Well, creating objects can be addressed in many ways but there are excellent patterns
that deal with instantiation in a fashion way, for example, my favorite, the **factory** pattern.
In our example, we could introduce a `CommandFactory` and delegate it all the instantiation logic,
but that's a topic for a future chapter.

From this very moment you can start notice the benefits of the command pattern. We can add or remove buttons
without touching the code of the `pressButton` method. Also, all the logic is encapsulated
into their own class, making it easier to maintain and reuse. And... as a nice side effect,
we've successfully applied the **Open-Close** principle. This method is now open for extension
but closed for modification.

Next, let's see the command pattern in action and implement it into our game application!
9 changes: 9 additions & 0 deletions sfcasts/command_undo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Undo feature - Chapter 4

implement undo feature

## Using Command for queuing actions

## Where do we see this in the real world?

## Conclusion
10 changes: 10 additions & 0 deletions sfcasts/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Design Patterns Episode 2 - Chapter 1

- Salute audience
- A quick reminder about what patterns are and what we've covered so far
- What patterns we'll cover in this tutorial and what kind of patterns they are
- Recap of the current state of the game
- Play a round, show GameCommand quickly and then GameApplication::play()
- show where we already introduced patterns

- Coming next: Command Pattern
4 changes: 3 additions & 1 deletion sfcasts/metadata.yml
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
chapters: []
chapters:
- intro
- command-intro
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MolloKhan Btw, the actual file name is sfcasts/command_intro.md, i.e. with the underscore instead of dash... I think we should match style in both cases, probably sfcasts/command-intro.md is the correct one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, I already fixed that. Thanks anyways