This is a derivative work based on suggestion from @crc -- http://forth.works/share/6e7967060cc12336fb4cd7cd0474fed1
My goal here is to make a basic text-based game loop that can serve as a starting point for a full game.
To accomplish this I'll implement a few core features:
- Some global state and ways to manage it
- A single point of contact to process player input, as well as some supporting ui
- Little game loop to pull it all together
The global state is pretty simple; I create 2 sets of variables -- there is no hard-and-fast reason for these to be separate, but I liked this naming convention -- after creating the variables I initialize them with some starting values. Then, a helper word that demonstrates how to display the values stored in these variables.
{ 'xp 'loc 'whoops } [ 'Player.%s s:format var ] a:for-each
{ 'turn } [ 'Global.%s s:format var ] a:for-each
:state-init
#0 !Global.turn
#7 !Player.loc
#0 !Player.whoops
#100 !Player.xp ;
:state-display
@Global.turn
@Player.whoops
@Player.xp
@Player.loc
'_LOC:_%n\n__XP:_%n\nUhOh:_%n\n\n========\nTurn:_%n\n s:format s:put ;
Here I define a very basic word to process player input and to display game ui. I've also provided some sample words that are triggered by the player's input. The last word is the core ui for this sample, it displays some hint text about the game's controls.
:mic-check
nl 'testing_testing_1_2_3! s:put nl &Player.xp v:inc ;
:banana-boat
nl 'banana_boat! s:put nl &Player.xp v:dec ;
:unknown-input
nl '_is_not_a_known_input s:append s:put nl &Player.whoops v:inc ;
:process-input
't [ mic-check ] s:case
'b [ banana-boat ] s:case
'q [ bye ] s:case
unknown-input ;
:hint-display
nl state-display nl '(t)est_(b)anana_(q)uit s:put nl nl ;
The core game loop, like most game loops, is a while loop that'll run forever as long as some value is TRUE. I never actually bother to set that value to FALSE. To quit, the player inputs "q" which triggers the in-built bye
word to exit the loop and halt the process, returning to the host system.
Of note here is the turn word -- this is triggered at the top of each loop, after collecting the player's input. Turn isn't much more than a wrapper around process-input, the trick it pulls is to lead with an s:get (akin to Lua's io.read) to read-in whatever is input to stdin. Once input is read in a new "turn" starts (increasing the turn count in Global.turn) and then clearing the display this ensures that the display is only ever showing state for the currently active turn.
:turn
&Global.turn v:inc clear process-input hint-display ;
:welcome-message
nl 'Welcome!_╰(˙ᗜ˙)੭━☆゚.*・。゚ s:put nl ;
state-init welcome-message hint-display
[ s:get turn TRUE ] while
I'd like to expand this example to include the ability to save state to disk, and read it back so that a player can pick up where they left off without having to keep the process running.