In the rest of this document, $GSH_ROOT will represent GameShell's root
directory.
GameShell files are organized in several directories. The important ones when creating missions are the following:
-
$GSH_HOMEcontains the root of GameShell's "world": the file hierarchy where the player is expected to move around. It is initially empty, and the missions populate it. Even though this is also theHOMEdirectory of the player, using$GSH_HOMEis preferred over$HOMEor~. -
$GSH_TMPcontains all the "hidden" data that may be created by a mission: temporary files, data required to check completion, data shared between missions, etc. Except in specific case ("dummy missions"), it is a good policy that each mission removes the files it creates.
A mission is simply a directory with a set of files. This directory must be
somewhere under $GSH_ROOT/missions/, typically inside
$GSH_ROOT/missions/contrib/.
A mission has the following structure, most of the files being optional:
$GSH_ROOT/missions/.../MISSION_NAME
├── auto.sh
├── gshrc
├── bin
│ ├── ...
│ └── ...
├── check.sh REQUIRED
├── clean.sh OFTEN NEEDED
├── goal.txt / goal.sh REQUIRED
├── i18n
│ ├── ...
│ └── ...
├── init.sh ALMOST REQUIRED
├── sbin
│ ├── ...
│ └── ...
├── skip.txt
├── static.sh ALMOST REQUIRED
├── test.sh
├── treasure.sh
└── treasure-msg.txt / treasure-msg.sh
Only 2 files are really required:
check.shto check for completion of the mission,goal.txt(or variant) to display the goal of the mission.
However, most missions will also need 2 other files:
static.shto initialize the static parts of the mission,init.shto initialize the dynamic parts of the mission
The static.sh script is run during GameShell's initialisation (before the
first mission is even started). This allows to populate the world with the
different places (i.e., directories) where the missions will take place.
Th init.sh script is run whenever the mission is (re-)started. It allows to
have missions with some randomized element to prevent brute force searching or
sharing of easy solutions among students.
We will now describe in details what's expected in those 4 files. (The other files will be described in the next section.)
All the missions' sh files are sourced. This was decided in the early
versions of GameShell to make it possible to
- check the player defined an alias,
- change the current working directory of the player,
- define some environment variables.
Most of the time, the sh scripts could be run in a subshell, which has the
advantage of making sure the environment is not polluted by leftovers from the
script.
A good policy to adopt is that except when you specifically need it, the script should execute in a subshell. The easiest way to do that is to define a function
_mission_init() (
...
)Using parenthesis rather than braces ensures the function is run inside a
subshell.
Note that after sourcing a file init.sh, the function _mission_init is
automatically removed. The same is true for check.sh (where the function
_mission_check is unset) or any of the other sh files.
If not using a function, unseting all the defined variables is encouraged.
Note also that even if bash, zsh, or even dash have them, POSIX shell
functions don't have a concept of local variables. In other words,
_mission_init() {
dir=$GSH_ROOT/Castle
mkdir -p "$dir"
}defines a global variable dir!
When running in verbose debug mode (flag -D), GameShell will print all
changes in the environment it detects.
In some cases, those files may be sourced from a subshell. This happens for example if the previous mission was checked in a subshell, with something like
$ SOME_COMMAND | gsh checkIn such a case, gsh check happens in a subshell, and sourcing of the next
init.sh file happens in this same subshell.
To prevent bugs when a mission specifically requires initialisation to change
the environment, GameShell tries to detect when a script is sourced in a
subshell. If it detects the environment has changed and that this happened
in a subshell, a warning is displayed asking the player to run gsh reset.
Like all .sh files defining the mission, static.sh is sourced by
GameShell.
It is first sourced when initializing a new game, and its typical use is
creating the "world map". A mission taking place in the castle's cellar would
use
mkdir -p "$GSH_HOME/Castle/Cellar"and would thus make sure the cellar exists when the game is started, even though the first mission might take place somewhere else.
Because all missions need not be included in a customized GameShell archive,
it is important to create all the required places in static.sh and not
depend on them being created by another mission.
Because of that, sourcing static.sh shouldn't provoke an error if a place /
object already exists: you should for example use mkdir -p to create
directories.
To avoid potential problems if a player removed part of the world, this file is also sourced whenever the corresponding missions is (re-)started.
This file should always use absolute path by using $GSH_HOME. It can also
use the directory $MISSION_DIR that points to the mission's directory. This
is useful if the mission needs to copy files into GameShell's world.
Note that because this file is sourced, it can change the working directory or define environment variables. This is discouraged for the following reasons.
- Those files are not re-sourced when restarting a previous game. In that
case, only the
static.shfile corresponding to the current mission will be sourced. - It is possible, when starting a new mission, that this file is sourced from a subshell. In that case, changing directory or defining environment variables will not have any effect.
If you need to define environment variables that will be available throughout
the game, use a gshrc file in the mission.
The file goal.txt should be a UTF-8 encoded text file containing the
description of the mission. It is displayed when the player runs the command
gsh goal.
They usually follow the following pattern:
Mission goal
============
Find a frog in the swamp.
Useful commands
===============
cd PLACE
Move to the given place, if accessible from your current location.
For uniformity, meta-variables in commands should be in UPPERCASE.
If you require a "dynamic" goal, for example because it contains some
randomized data, you can replace goal.txt by goal.sh, which will be
sourced each time the player runs gsh goal.
(Note that randomized parts of the missions should probably be generated by
init.sh, not by goal.sh.)
Goal files (and treasure messages, see below) are "reflowed" to somewhat fill the terminal width, with room to spare for the ASCII-art embellishments.
Those text files should follow the following conventions
- use UTF-8 encoding for accentuated letters,
- trailing spaces at the end of lines do not end a paragraph,
- empty lines do end a paragraph,
- a non empty line ending with something different from a space or tab do end a paragraph.
Indentation of the first line of a paragraph is used to indent the whole paragraph, and list markers also indent the following paragraph.
A line starting with at least 2 spaces and containing a sequence of 2 (or more) spaces after a non space character is "protected". It is output without any processing. That makes it possible to format small tables. Those lines are best kept under 50 characters wide.
The above example could have been written as (using ~ to indicate trailing spaces)
Mission goal
============
Find a frog in~
the swamp.
Useful commands
===============
cd PLACE
Move to the~
given place,~
if accessible~
from you current location.
If reflowed at width 25, this gives
Mission goal
============
Find a frog in the swamp.
Useful commands
===============
cd PLACE
Move to the given~
place, if accessible~
from you current~
location.
This file is sourced whenever the mission is started. It is typically used to (re-)generate the dynamic parts of the mission. It is not sourced during the initialisation of GameShell.
The corresponding static.sh file is sourced just before it, so init.sh can
depend on the static part existing.
Remember that this script is sourced every time the mission is (re-)started. If
it creates files with randomized names, make sure you don't end up with
several versions of them by removing them first. (Or better yet, write a
clean.sh script.)
If the last return value is false (anything different from 0), the mission
is cancelled.
This is typically used to check that the dependencies for the mission are met (e.g., that every necessary command is available on the system). When some dependency is not met then a helpful error message is polite.
The check.sh is the only required file in a mission. It is sourced when the
player runs gsh check to validate (or not) the current mission.
Since it is sourced (for uniformity with the other scripts), it requires some
care. It must end with a command returning 0 (typically true) in case of
success, and by a command returning something else (typically false) in case
of failure. In case of failure an explanation message is expected.
Whenever checking is even slightly complex, the script check.sh usually
looks like
_mission_check() {
...
...
}
_mission_checkNote that because this file is sourced, it can change directory or define
environment variables. This is discouraged because sourcing might happen from
a subshell (in case of COMMAND | gsh check for example). In that case,
changing directory or defining environment variables will not have any effect.
If you need to define environment variables that will be available for the
remaining missions, use a treasure.sh file.
This file is sourced:
- after the mission is checked (successfully or not),
- when the mission is "skipped" with
gsh skip, - when the mission is left with
gsh goto N, - when the player exits GameShell.
It should remove the parts of the missions that won't be necessary anymore,
either because they will be regenerated if the mission is restarted (by
init.sh), or because the mission was successfully validated.
Before sourcing this file, a variable GSH_LAST_ACTION is set to the command
that sourced it:
exitskipcheck_truecheck_falsegotoresethardresetassert
The other files are not used as often but allow to customize GameShell.
This file is added to the global configuration of the shell session used during the game. It can be used to define variables, aliases, functions, etc.. They will be available throughout the game.
This file is sourced after the mission has been successfully validated (with
gsh check). It can be used to add new features as rewards to certain
missions.
Note that this file is also added to the global configuration so that the
reward is still available when you restart the game. As a consequence there
should be no output. (See the treasure-msg.txt file.)
The file treasure-msg.txt is expected to be a UTF-8 encoded text file. It is
displayed when the mission is successfully completed. If the terminal is wide
enough, a fancy treasure chest is added to the left of the message.
This can be used to inform the player that a treasure (i.e., a feature installed
by treasure.sh) has been won.
Instead of treasure-msg.txt, a script treasure-msg.sh can be used to
generate a dynamic message. If it exists, treasure-msg.sh is sourced when
the mission is successfully completed. Note that if this file finished with a
non 0 return value, the treasure.sh file is neither sourced nor installed.
It can be used to avoid problems when some treasure.sh has some
dependencies.
If the file skip.txt is present, it marks the mission as "optional": the
command gsh skip will be available without password.
If the file is non empty, it is displayed just after the goal of the mission
when using gsh goal.
The files contained in this directory will be "copied" to the directory
$GSH_BIN. The directory is in the global PATH, so those files are
directly available to the player.
The files contained in this directory will be "copied" to the directory
$GSH_SBIN. (The directory is not in the global PATH, so those
files are not directly available to the player.) This is particularly useful
for "dummy" missions.
This directory contains the files related to internationalization.
This file is sourced when the command gsh auto is run. It should
automatically validate the corresponding mission. In other words, the commands
in this file should perform whatever steps are the necessary to complete the
mission, followed by the gsh check command.
That's mainly useful for testing, and those files aren't included in GameShell archives by default.
This file is sourced by the command gsh test, which is only available in
debug mode. The test.sh script usually uses a mix of standard shell commands
and the special commands gsh assert check true (or gsh assert check false)
which make testing easier.
Those files are not included in GameShell archives.
When run without arguments GameShell will get the list of mission from the
file $GSH_ROOT/missions/default.idx.
You can give a list of default.idx files and mission directories as arguments
of GameShell if you want to customize the list / order of missions. This is
particularly useful when testing a new mission:
$ ./start.sh -Rdq missions/contrib/my_new_mission(The -R flag resets the previous game, the flag -d will run GameShell in
"debug" mode, and the -q flag removes the small messages at the start of
each mission.)
"Dummy" missions are used to share data between missions. A mission is "dummy"
- either when it doesn't contain a
check.shscript, - or it is listed with a "
!" in front of its name in thedefault.idxfile.
A dummy mission is used during the initialisation phase, so that it can
contain a static.sh file. It can for example be used to share executable
files in its bin or sbin directory.
It can also contain data that will be used by other missions:
- either the
static.shfile can copy the data to$GSH_TMP(or some other place), - or the real missions can use symbolic links (with relative path) to refer to that data.
A real mission in $GSH_MISSIONS/contrib/jungle/01_hide_elephant could for
example use
cp "$MISSION_DIR/../00_shared/ascii-art/elephant.txt" "$GSH_HOME/Jungle/"Note: don't forget to include dummy missions in the corresponding
default.idx file, or it won't be included in GameShell executable archives by
default.
If several missions share an init.sh script (or clean.sh, or whatever), it
can be included in a dummy mission, while the actual missions use a symbolic
link to it.
Note: when sourcing a symbolic link,
- the variable
$MISSION_DIRrefers to the physical directory containing the file (the symbolic link is expanded), - the variable
$MISSION_NAMErefers to the logical name of the mission containing the file (the symbolic link is not expanded). - the variable
$MISSION_NBrefers to the index of the logical mission.