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.
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
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.
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!
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
loopto begin a loop, and this can be matched with
whileto loop unconditionally, until a flag is true or while a flag is true, respectively.
breakexits 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 loop 0 , 1 - dup while drop ; ( length -- ) # used like: create buffer 45 allot
FLOOR(0) Passable ground terrain.
STAIRS(2) The exit to a given level.
DOOR(3) Locked, impassible barrier. Use a key to
KEY(4) Key for opening doors.
takethem to collect them.
GEM(5) Worth points and heals damage.
takethem to collect them.
SLIME(6) Deadly enemy.
attackthem to kill them and don't step on them.
SPIKES(7) These hurt to walk on. Avoid it when possible.
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)
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.
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.