This post is part of a series of blog posts following the roguelike tutorial
to demonstrate the new class
feature in Perl 5.38.0.
In this post we’re gonna start where we left off in Part 0. If you
remember, the Engine
class looked like this:
class Engine {
field $height :param;
field $width :param;
field $app = Games::ROT->new(
screen_width => $width,
screen_height => $height,
);
ADJUST {
# start the app
$app->run( sub { $self->render() } );
}
method render() {
my $x = $width / 2;
my $y = $height / 2;
$app->draw($x, $y, 'Hello World', '#fff', '#000');
}
}
First, let’s give ourselves a way to quit that doesn’t involve killing the process. Games::ROT allows us to add an event handler, so we’ll go ahead and do that before we start the game loop.
ADJUST {
$app->add_event_handler(
'keydown' => sub ($event) { exit }
);
$app->run( sub { $self->render() } );
}
Now if we press a key the game will exit and leave us back in our shell. Next,
we need to replace Hello World
with an icon for our player.Traditionally the
player is @
, so let’s add some fields for the player location:
class Engine {
my $height :param;
my $width :param;
field $player_x = $width / 2;
field $player_y = $height / 2;
[...]
We also need to update the render method to draw the player icon.
method render() {
$app->draw($player_x, $player_y, '@', '#fff', '#000');
}
Now if we run things we’ll get a nice little @
in the middle of the terminal,
but that’s really not very exciting. We need to get it moving around. Right now
any button we push exits the game, and that simply won’t do. Let’s fix that by
adjusting our ADJUST block.
ADJUST {
$app->add_event_handler(
'keydown' => sub ($event) {
my %KEY_MAP = (
h => sub { $player_x -= 1 },
j => sub { $player_y += 1 },
k => sub { $player_y -= 1 },
l => sub { $player_x += 1 },
q => sub { exit }
);
$KEY_MAP{$event->key}->();
}
);
$app->run( sub { $self->render() } );
}
In our event handler we add a key map to, well, map keys to actions. I’m using
the vim movement pattern here, you’re entirely welcome to replace them with
WASD
or whatever you prefer. Each key is mapped to a little subroutine that,
because it’s defined within the scope of the class, has access to the field
variables in that class. We adjust the player’s position by one in each
direction. Notice we’ve also moved quit into its own mapping, rather than an if
statement. At the end we look up the correct key mapping and we just run the
subroutine that’s associated with it. Swapping in this code should allow us to
move around.
If you run this code you’ll see we’re leaving a trail behind us. We need to clear the screen between each draw.
method render() {
$app->clear();
$app->draw($player_x, $player_y, '@', '#fff', '#000');
}
I’d like to give a shout out to Jeremy for pointing out that I hadn’t actually
explained where the $app->clear()
came in and some leftover typos that had
crept in from earlier versions of this tutorial’s code. They’re cleaned up now.
That’s where we’ll leave off for now. Next time we’ll refactor things a little bit and add a non player character, because playing by yourself in an empty room can get boring.
Here’s the full code listing:
#!/usr/bin/env perl
use 5.38.0;
use lib qw(lib);
use experimental 'class';
use Games::ROT;
class Engine {
field $height :param;
field $width: param;
field $player_x = $width / 2;
field $player_y = $height / 2;
field $app = Games::ROT->new(
screen_width => $width,
screen_height => $height,
);
ADJUST {
$app->add_event_handler(
'keydown' => sub ($event) {
my %KEY_MAP = (
h => sub { $player_x -= 1 },
j => sub { $player_y += 1 },
k => sub { $player_y -= 1 },
l => sub { $player_x += 1 },
q => sub { exit }
);
# lets execute the action now
$KEY_MAP{$event->key}->();
}
);
$app->run( sub { $self->render() } );
}
method render() {
$app->clear();
$app->draw($player_x, $player_y, '@', '#fff', '#000');
}
}
my $engine = Engine->new( width => 80, height => 50 );