Skip to content

Instantly share code, notes, and snippets.

@eli-oat
Created June 5, 2021 01:54
Show Gist options
  • Save eli-oat/c84ce62659bcd11050bb00f7e5b8c226 to your computer and use it in GitHub Desktop.
Save eli-oat/c84ce62659bcd11050bb00f7e5b8c226 to your computer and use it in GitHub Desktop.
A minimal example game loop implemented in RetroForth

Game loop!

Description of goals

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:

  1. Some global state and ways to manage it
  2. A single point of contact to process player input, as well as some supporting ui
  3. Little game loop to pull it all together

Global state

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 ;

Process player input

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 ;

Game loop

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

TODO

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment