forked from DrSunglasses/TAEB
/
Demo.pm
256 lines (188 loc) · 6.67 KB
/
Demo.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
package TAEB::AI::Demo;
use Moose;
use TAEB::OO; # like "use Moose" but with a bit more added to it
extends 'TAEB::AI';
# The framework calls this method on the AI object to determine what action to
# do next. An action is an instance of TAEB::Action, which is basically a handy
# object wrapper around a NetHack command like "s" for search.
sub next_action {
my $self = shift;
# Try each of these behaviors (which are methods) in order...
for my $behavior (qw/pray melee hunt descend to_stairs open_door to_door explore search/) {
my $method = "try_$behavior";
my $action = $self->$method
or next;
# "currently" is for reporting what we're doing on the second-to-last
# line of the TAEB display. Optional but you should set it anyway.
$self->currently($behavior);
return $action;
}
# We must be trapped! Search for a secret door. This is a nice fallback
# since you can search indefinitely.
$self->currently('to_search');
return $self->to_search;
}
sub try_pray {
# This returns false if we prayed recently, or our god is angry, etc.
return unless TAEB::Action::Pray->is_advisable;
# Only pray if we're low on nutrition or health.
return unless TAEB->nutrition < 0
|| TAEB->in_pray_heal_range;
return TAEB::Action::Pray->new;
}
# Find an adjacent enemy and swing at it.
sub try_melee {
if_adjacent(
sub {
my $tile = shift;
$tile->has_enemy && $tile->monster->is_meleeable
} => 'melee',
);
}
# Find an enemy on the level and hunt it down.
sub try_hunt {
path_to(sub {
my $tile = shift;
return $tile->has_enemy
&& $tile->monster->is_meleeable
&& !$tile->monster->is_seen_through_warning
}, include_endpoints => 1);
}
# If we're on stairs then descend.
sub try_descend {
return unless TAEB->current_tile->type eq 'stairsdown';
return TAEB::Action::Descend->new;
}
# If we see stairs, then go to them.
sub try_to_stairs {
path_to('stairsdown');
}
# If there's an adjacent closed door, try opening it. If it's locked, kick it
# down.
sub try_open_door {
if_adjacent(closeddoor => sub {
return 'kick' if shift->is_locked;
return 'open';
});
}
# If we see a closed door, then go to it.
sub try_to_door {
path_to('closeddoor', include_endpoints => 1);
}
# If there's an unexplored tile (tracked by the framework), go to it.
sub try_explore {
path_to(sub { shift->unexplored });
}
# If there's an unsearched tile next to us, search.
sub try_search {
if_adjacent(
sub { $_[0]->is_searchable && $_[0]->searched < 30 },
'search',
);
}
# If there's an unsearched tile, go to it.
sub to_search {
path_to(
sub { $_[0]->is_searchable && $_[0]->searched < 30 },
include_endpoints => 1,
);
}
# These helper functions make our behavior code far more concise and
# declarative.
# find_adjacent finds and adjacent tile that satisfies some predicate. It takes
# a coderef and returns the (tile, direction) corresponding to the adjacent
# tile that returned true for the predicate.
sub find_adjacent {
my $code = shift;
my ($tile, $direction);
TAEB->each_adjacent(sub {
my ($t, $d) = @_;
($tile, $direction) = ($t, $d) if $code->($t, $d);
});
return wantarray ? ($tile, $direction) : $tile;
}
# if_adjacent takes a predicate and an action name. If the predicate returns
# true for any of the adjacent tiles, then the action will be instantiated and
# returned.
sub if_adjacent {
my $code = shift;
my $action = shift;
# Allow caller to pass in a tile type name to check for an adjacent tile
# with that type.
if (!ref($code)) {
my $type = $code;
$code = sub { shift->type eq $type };
}
my ($tile, $direction) = find_adjacent($code);
return if !$tile;
# If they pass in a coderef for action, then they need to do some additional
# processing based on tile type. Let them decide an action name.
$action = $action->($tile, $direction) if ref($action);
my $action_class = "TAEB::Action::\u$action";
# We only want to pass in a direction if the action cares about direction.
# Actions that care about direction do the TAEB::Action::Role::Direction
# "role". Kind of like a Java interface, but more awesome.
my %args;
$args{direction} = $direction
if $action_class->does('TAEB::Action::Role::Direction');
return $action_class->new(%args);
}
# path_to takes a predicate (and optional arguments to pass to the pathfinder)
# and finds the closest tile that satisfies that predicate. If there is such a
# tile, then a Path will be returned.
# If you need to find a path adjacent to an unwalkable tile, then pass in
# include_endpoints => 1.
sub path_to {
my $code = shift;
# Allow caller to pass in a tile type name to find a tile with that type.
if (!ref($code)) {
my $type = $code;
$code = sub { shift->type eq $type };
}
# TAEB will inflate a path into a Move action for us
return TAEB::World::Path->first_match($code, @_);
}
__PACKAGE__->meta->make_immutable;
1;
__END__
=head1 NAME
TAEB::AI::Demo - a demonstration autonomous AI
=head1 DESCRIPTION
This exists so we include have something that *plays* NetHack in the core TAEB
distro: a default AI. We could use L<TAEB::AI::Behavioral> but that is a
separate distribution, one that depends on L<TAEB> at that.
This is also an example AI for people interested in writing one.
=head1 EXERCISES
If you're interested in bot development, here are some recommended enhancements
to make to this demonstration AI. You can use these exercises to get accustomed
to the TAEB codebase.
If you get stuck, one place to look is L<TAEB::AI::Behavioral>, where we've
implemented all of these behaviors.
=over 4
=item
Have the bot write Elbereth if its HP is less than 50%.
=item
When there's an adjacent Elbereth-ignoring monster, don't write Elbereth (so
that you fall through to melee).
=item
Design a sane policy for writing Elbereth and meleeing monsters when there are
both Elbereth-respecters and Elbereth-ignorers.
Implement this policy.
=item
Pick up food (but not corpses). Is
L<TAEB::Role::Item::Food::Corpse/is_safely_edible> sufficient to
determine which food to pick up?
=item
Eat food from inventory before resorting to prayer.
Be sure to support eating inventory food while standing on a tile with food
(recall that NetHack asks you if you want to eat that floor food).
=item
Dip for Excalibur when appropriate.
=item
If you have projectiles, throw them at enemies.
=item
Retrieve projectiles you've thrown.
=item
Pick up and wear armor.
=back
=cut