Forth Warrior is a game of programming, stabbing and low cunning. Your Forth code controls the actions of a valiant adventurer as she plunges ever deeper into a mysterious dungeon. Gather precious gems, defeat slime creatures and watch your step! Your program must fit in 8 kilobytes and is scored based on how many cycles it takes to execute, how many actions are taken, how many gems are collected and how many slimes are defeated. Once you've made it through all the levels, try to optimize your code and hone your strategy to maximize your score!
This game is intended to give intermediate Forth enthusiasts a fun, well-defined task to cut their teeth on. While there are many differences in gameplay, I consider it to be a spiritual successor to Ruby Warrior. When Forth Warrior boots up, you're given a command prompt which allows you to interactively edit and test Forth code. If you've never programmed in Forth before, read a few chapters of Leo Brodie's canonical Starting Forth. For a faster-paced and somewhat less whimsical guide, take a look at A Beginner's Guide to Forth. As noted below, the dialect of Forth used in this game varies a bit from that used in these references, but the bulk of the language is the same.
To build Forth Warrior from source, you'll need the Java SDK and Apache Ant. Download the Mako repository, build the "Maker" compiler and use it to compile and run Forth Warrior itself:
git clone https://github.com/JohnEarnest/Mako.git
cd Mako
ant
./maker games/Warrior2/Warrior2.fs --run
Maker can also be used to output a ROM which can be baked into a standalone JAR, for easier distribution:
./maker games/Warrior2/Warrior2.fs tools/Standalone/Data
cd tools/Standalone
ant
java -jar Mako.jar
To begin a game, use the begin
command. It reads the name of a word, so begin example
will start the game using the word example
as an entrypoint. An individual level may be tested independently by using the test
command, which works like begin
but additionally takes a level number from the stack. Thus, 2 test example
will start on level 2 and use the word example
as an entrypoint.
The words
command provides a listing of all currently defined words. To find out more information about a particular word, use help
followed by the name of a word. For example, help +
. An asterisk after the stack effect indicates an immediate word.
If you create a file called warrior.fs
in the same directory as the game, the load
command can be used to execute all the code in this file- this makes it easy to work on your Warrior in your favorite text editor. If you aren't starting the game "cold" every time you make some changes, you may want to use forget
to clear out old definitions from your dictionary before issuing load
again. You can also specify a filename with load
, which is useful if you're tinkering with several AIs- for example load dummy.fs
.
While the game is running, any debugging output will be shown a line at a time at the bottom of the screen, with brief pauses between lines. This means if you wish to print less than 40 characters at a time you should be using typeln
or terminating your output with cr
, the command for printing a carriage return. Control+C will immediately halt the program and return to the Forth prompt.
Our heroine, Liz, is a courageous and daring dungeon-delver. While she can ably dispatch many a foe in battle, she's not the best original thinker. Fortunately, like any adventurer worth their salt, Liz is fluent in Forth. With your instructions as her guide, she will plumb the depths of a mysterious and deadly ruin! Liz can be hurt by spikes and slimes, so keep a close eye on her health meter.
These stairs lead deeper into the dungeon. Completing a level is a simple matter of making it to the stairs. Easy, right?
Gems are exceedingly valuable, both for the way they catch the light and the more utilitarian fact that they will heal one heart's worth of an adventurer's wounds when picked up. Get as many as you can!
Slime beasts lurk in this dungeon. Beware their acidic pseudopods and for heaven's sake don't step in them. I wonder where they all come from?
The roughly finished stone floor of a dungeon is littered with the bones of previous visitors and marred by the viscous ichors of slime beasts. Otherwise unremarkable.
The floor menaces with spikes of iron! Stepping onto a spiked panel will hurt Liz by one heart, but she only takes damage upon entering the tile.
Locked doors impede progress in your adventure. They must be open
ed with a key.
Problem, meet solution. Keys can be carried between rooms, and odds are you'll need all of them eventually.
An extremely simple brain that can make it past a few obstacles:
: dummy ( -- )
E loop
dup look
dup STAIRS = swap FLOOR = or if
dup walk
else
1 + 4 mod
then
again
;
A more sophisticated brain which handles many types of obstacles and attempts to traverse mazes by following the left wall:
create actions
' walk , # floor
0 , # wall
' walk , # stairs
' open , # door
' take , # key
' take , # gem
' attack , # slime
' walk , # spikes
: act ( dir -- dir f )
dup 4 mod dup look
dup WALL = if drop drop 0 exit then
actions + @ exec -1
;
: try ( dir -- dir' )
1 - act if exit then
1 + act if exit then
1 + act if exit then
;
: lefthand ( -- )
E loop try again
;
Forth Warrior provides a very small Forth kernel with only the essential primitives. Most of these words will seem familiar if you've programmed in Forth before. The following is a summary of major deviations from a common ANS-style Forth.
-
Word names become available immediately after
:
. Thus, recursive procedures can simply refer to themselves. Mutual recursion is best accomplished by using vectored words and'
. -
'
is state-smart. If used in interpreting mode, it will push an xt onto the stack. Otherwise it will compile a literal into the current definition. -
#
is a single-line comment, rather than\
. -
String constants have special-cased syntax for convenience. When compiling, simply enclose a string in double quotes and the address to the head of the resulting null-terminated string will be compiled as a literal. All string-oriented routines use C-style null-terminated strings. Thus, a hello world program can simply be
: hello "Hello, World!" typeln ;
-
Loop constructs are a little different from usual. This dialect provides a word
loop
to begin a loop, and this can be matched withagain
,until
orwhile
to loop unconditionally, until a flag is true or while a flag is true, respectively.break
exits the innermost loop.
To demonstrate, here's a simple counted loop written three different ways:
: A 0 loop dup . 1 + dup 10 > until ;
: B 0 loop dup . 1 + dup 11 < while ;
: C 0 loop dup . 1 + dup 10 > if break then again ;
Each will print out:
0 1 2 3 4 5 6 7 8 9 10
Many common words that are not included in the kernel can be synthesized in terms of the primitives available. For example, here's how we might define allot
:
: allot loop 0 , 1 - dup while drop ; ( length -- )
# used like:
create buffer 45 allot
Direction Constants:
N
(0)E
(1)S
(2)W
(3)
Type Constants:
FLOOR
(0) Passable ground terrain.WALL
(1) Impassible.STAIRS
(2) The exit to a given level.DOOR
(3) Locked, impassible barrier. Use a key toopen
it.KEY
(4) Key for opening doors.take
them to collect them.GEM
(5) Worth points and heals damage.take
them to collect them.SLIME
(6) Deadly enemy.attack
them to kill them and don't step on them.SPIKES
(7) These hurt to walk on. Avoid it when possible.
Queries:
health
( -- n ) The number of hearts the player has.level
( -- n ) The current floor of the dungeon.gems
( -- n ) How many gems the player has collected.keys
( -- n ) How many keys the player is carrying.listen
( -- n ) How many enemies are still on this level.look
( dir -- type ) View the type of thing/tile in an adjacent space. (These actions do not advance time in the game world)
Commands:
wait
( -- ) Do nothing for a game tick.walk
( dir -- ) Move to an adjacent tile.attack
( dir -- ) Attack any enemies in an adjacent tile.take
( dir -- ) Pick up an item in an adjacent tile.open
( dir -- ) Unlock an adjacent door.
Misc:
fast
( -- ) Display game animations more quickly.slow
( -- ) Display game animations at normal speed. (default)help
( -- ) Given a word name, print a brief explanation of what it does.load
( -- ) Reload the source file.