Gopher games
by Christopher Williams
2025-09-15
Last updated 2025-09-17
DIR Up
DIR Home
As of September 2025, I’m designing a server-side system
to implement multi-player turn-based games over Gopher.
Hopefully I’ll have it more developed soon.
I’ll provide some information about the design here. I’m
still working through a lot of the details, so they’re not
set in stone.
------------------------------------------------------------
High-level overview
------------------------------------------------------------
This system will provide a common programming interface to
game engines which will allow game logic to be separated
from the user interface.
Users can set up games, modify game settings, and play games
through various generated pages (i.e., Gopher menus).
Ongoing games will be stored in some sort of database on the
server. A game record will contain its ID, admin user name,
player list, and complete game state.
------------------------------------------------------------
The user interface
------------------------------------------------------------
The user interface will consist of 3 different types of
pages:
* Games list
* Game admin
* Game player
The games list is where a user can set up a new game.
Once a user sets up a new game, they will have access to the
admin page for that game instance. On this page the user can
set various game options (e.g., Draw One or Draw Three for
a Klondike solitaire game), add/remove/replace players, and
start the game.
Once a game starts, users can access their player page. On
this page the user can view the current state of the game
(e.g., see the cards on the table and in their hand, see the
game board, or whatever), view game details, and make moves.
Game details include any information that is too verbose to
display on the player page. A player may make a move only on
their turn.
# Identifying users
Users will be identified using tripcodes. A user will
be asked for their secret at one or more places in the
interface. (This system uses tripcodes to avoid the need for
user signups or storing user information in the server.)
------------------------------------------------------------
The game engine
------------------------------------------------------------
A game engine provides the logic of a game. With any action
it will be provided with the complete state of a game, the
action (as a verb-noun tuple), and the player number. It
will return the (possibly updated) complete state of the
game, the text to display to the user, and the set of legal
actions the user can make (which can change depending on
whether it’s the player’s turn or not).
------------------------------------------------------------
The game manager
------------------------------------------------------------
The game manager maintains a list of available games;
generates the games list, game admin, and game player pages;
and passes information between the user and the game engine.
It also validates user moves to ensure that a move is not
accidentally repeated (e.g., by a user refreshing their game
player page in the Gopher client after making a move). This
is done by using a per-player move token. The move token
is refreshed at certain points in the game and is included
in each action shown in a game player page. If a selector
includes an old or bad move token, the game engine knows not
to perform the move but will return the same game state.
------------------------------------------------------------
The game engine interface
------------------------------------------------------------
Input:
* Game state (data blob)
* Player number (e.g., 1)
* Action (e.g., "move d2d3")
* Validity of move (true/false)
Output:
* New game state (data blob)
* Display text (will be shown to the user)
* List of actions (each action consists of a verb, noun,
and display text)
* Moved (true/false)
As mentioned in <<The game manager>>, the game manager
validates user moves through move tokens. The "validity of
move" input tells the game engine whether the move token is
valid. If the move token is valid and the user makes a move,
the game engine returns a true value for the "moved" output.
This tells the game manager to refresh the move token and
invalidate any previous move tokens. If the move is not
valid, the game engine should not make a move but only
display the current game state.
View-only actions can generally be performed without a valid
move token; these include viewing the current game state or
viewing game details. The game engine should not need to
check the move validity for these actions.
------------------------------------------------------------
How information is passed around in the user interface
------------------------------------------------------------
Anyone familiar with Gopher knows it doesn’t use cookies or
anything like that to remember session information. So all
necessary information will be passed around in selectors.
On each Gopher menu, the game manager will generate
selectors with all of the provided information (game
ID, user secret, etc.) and information relevant to the
specific selector (e.g., action). Since I’m designing
this with CGI in mind, most or all of this information
can be provided in a query string in the selector, e.g.,
`/game/play?secret=hunter2&game=ABC123&move=roll`.
Some game moves can be done only through user text
input, such as in a game of Zork. These types of
moves will use a search (type 7) selector like
`/game/play?secret=hunter2&game=ABC123&move=%3F`; the
percent-encoded question mark (`%3F`) will instruct the game
manager to find the move in the search string rather than in
the query string (special characters in query strings like
question marks are percent-encoded per CGI).
(I’m also considering other variations, where if
the last field in the query string doesn’t have
an equal sign, its value will automatically be
taken from the search string. So if a user enters
`GO DENNIS`, the query string will be transformed
from `/game/play?secret=hunter2&game=ABC123&move`
(in the selector) to
`/game/play?secret=hunter2&game=ABC123&move=GO%20DENNIS` (as
seen by the CGI in the `QUERY_STRING` variable). These are
minor details I’m working out.)
------------------------------------------------------------
Go implementation
------------------------------------------------------------
Here’s a rough draft of the game engine interface:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
type GameEngine interface {
Process(state State, action Action, player int, validMove bool) (newState State, display Display, actions []Action, moved bool)
}
type Action struct {
verb string
noun string
display string
}
type Display string
type State interface {}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -