512 lines
19 KiB
Org Mode
512 lines
19 KiB
Org Mode
|
#+title: Zita
|
||
|
#&summary
|
||
|
A simple but cumbersome text adventure game
|
||
|
#&
|
||
|
#+license: bysa, page
|
||
|
#+license: gpl 3+, program
|
||
|
#+license: cc0, Zeqy
|
||
|
#&toc
|
||
|
|
||
|
#&img;url=img/zita-logo.png, center, alt='Logo of Zita'
|
||
|
|
||
|
* Zita
|
||
|
Zita is a text adventure engine written in Python. Text adventures, also known
|
||
|
as [[http://en.wikipedia.org/wiki/Interactive_fiction][interactive fiction]] in some cases, challenge the minds of people, using no
|
||
|
graphics at all. With Zita, it is now possible to write a text adventure in
|
||
|
normal Python syntax.
|
||
|
|
||
|
|
||
|
** Installation
|
||
|
To run Zita, Python must be installed. No other depencies are needed.
|
||
|
|
||
|
[[zita.tar.gz][Download Zita]].
|
||
|
|
||
|
To run Zita, run zita.py. If a game directory is specified, Zita will attempt
|
||
|
to execute the game files in that directory. If no game directory is specified,
|
||
|
Zita will attempt to run the game Zeqy. Besides zita.py, there is also a file
|
||
|
named defs.py. This file is executed by zita.py on startup and should not be
|
||
|
used on its own. It contains functions crucial to Zita.
|
||
|
|
||
|
*** Hacking
|
||
|
|
||
|
Zita is released under the GPLv3+. Feel free to improve it.
|
||
|
|
||
|
|
||
|
** Zeqy
|
||
|
|
||
|
Zeqy is a very short text adventure developed for Zita and is shipped together
|
||
|
with the engine. It features a short "story" and proves Zita's simplicity. Zeqy
|
||
|
is released under the [[http://creativecommons.org/publicdomain/zero/1.0/][Creative Commons Zero 1.0 Universal]] license. This means
|
||
|
that you're free to do whatever you want to do with Zeqy. There is no owner of
|
||
|
Zeqy.
|
||
|
|
||
|
** Loading data
|
||
|
Games to be run in Zita's engine are saved in text files in a directory. In the
|
||
|
case of Zeqy, several files reside in the 'zeqy' directory. The files of a game
|
||
|
directory are loaded when Zita runs. In this section, Zeqy will be used as an
|
||
|
example of how Zita works.
|
||
|
|
||
|
A look at the 'zeqy' directory reveals the following files:
|
||
|
#&pre
|
||
|
commands.zt functions.zt LICENSE rooms.zt
|
||
|
defaults.zt items.zt main.zt vars.zt
|
||
|
#&
|
||
|
|
||
|
When Zita starts, it first looks for a file named 'prerun' inside the game
|
||
|
directory. If a file named that is found, Zita will execute any Python code
|
||
|
inside the file. This allows programmers to change various default
|
||
|
values. After looking for 'prerun', Zita attempts to find a main file. By
|
||
|
default, the main file must be named something that starts with 'main', though
|
||
|
this can be changed in the 'prerun' file. If several files that have names
|
||
|
starting with 'main' exist, only one of them will be used. If, for example, a
|
||
|
directory has both a file named 'main', a file named 'main.py' and a file named
|
||
|
'main.zt', the latter will be loaded (except if the 'prerun' file states
|
||
|
otherwise). This is because the '.zt' ending is the default suffix.
|
||
|
|
||
|
Looking at the file list earlier, it is now possible to deduct that the file
|
||
|
'main.zt' must be the main file for Zeqy. Here it is:
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
# Print welcome message
|
||
|
p('Welcome to Zeqy, a simple (and short) text adventure demonstrating the \
|
||
|
possibilities of Zita, an even simpler Python text adventure engine. If you\'re \
|
||
|
completely lost, try asking the engine for help.\n')
|
||
|
|
||
|
# Include files
|
||
|
include(['functions', 'rooms', 'items', 'vars', 'commands', 'defaults'])
|
||
|
|
||
|
# Starting position
|
||
|
goto('grass1')
|
||
|
#+END_SRC
|
||
|
|
||
|
Python code in the main file is executed before Zita starts a game. This means
|
||
|
that the main file is suitable to use to print a welcome message, though this
|
||
|
is of course also possible to do in the 'prerun' file.<br /> What should be
|
||
|
done in the main file and not in the 'prerun' file is including files. The
|
||
|
<span class='code'>include</span> function loads and executes both files and
|
||
|
files in subdirectories. In the above example, only files are imported, as no
|
||
|
subdirectories exist. If, however, such directories did exist, the include
|
||
|
command would look for those too.
|
||
|
|
||
|
*** An example
|
||
|
We have a game directory with a file named 'stuff' and a directory named
|
||
|
'stuff' that holds several files. In the main file we write this:
|
||
|
|
||
|
#++python2
|
||
|
: include(['stuff'])
|
||
|
|
||
|
This makes Zita load and execute both the 'stuff' file and the files in the
|
||
|
'stuff' folder.
|
||
|
|
||
|
It is possible to make the <span class='code'>include</span> function accept
|
||
|
only either one file, all files or files in subdirectories. Refer to the
|
||
|
=include= and =getfilenames= functions found in defs.py.
|
||
|
|
||
|
When Zita has succesfully loaded all data that it needs to run a game
|
||
|
succesfully, it is important to use the =goto= function to create a starting
|
||
|
position. A starting position can also be defined in another way, but using
|
||
|
=goto= ensures that a message describing the current location is shown.
|
||
|
|
||
|
|
||
|
** Data structures
|
||
|
|
||
|
When the main file has been loaded and executed, an infinite loop is
|
||
|
started. It runs until the variable =COMPLETELY_DONE= is true. When that
|
||
|
happens, Zita exits.
|
||
|
|
||
|
The code inside the loop asks the user for input, which it the processes. Input
|
||
|
is split with spaces, and the first word is always the command. A command
|
||
|
cannot exist of more than one word. All characters that come after the command
|
||
|
are considered part of an object. After the user has pressed Enter, Zita
|
||
|
searches through several global variables to see if what the user has typed
|
||
|
matches a stored command and/or an object.
|
||
|
|
||
|
The 6 important global variables in Zita are:
|
||
|
|
||
|
+ =room=
|
||
|
+ =item=
|
||
|
+ =var=
|
||
|
+ =command=
|
||
|
+ =default=
|
||
|
+ =extra=
|
||
|
|
||
|
All except =extra= are dictionaries. The =extra= variable is a string that can
|
||
|
contain Python code.
|
||
|
|
||
|
*** =>room=
|
||
|
This variable keeps track of the "rooms" in a game. Rooms are the locations in
|
||
|
which a player can be. Rooms can hold items and point to other rooms. See the
|
||
|
commented example below containing the room (taken from Zeqy's 'rooms.zt'):
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
room = {
|
||
|
'grass1': { # We start defining a room with the grass1 id
|
||
|
'name': 'grassy field', # Its name (names are currently not used by Zita)
|
||
|
'desc': 'You are standing on a grassy field covered with flowers. The wind \
|
||
|
almost blows you away.', # This is the description. The description of this
|
||
|
# room is shown when the user enters it.
|
||
|
|
||
|
# Items are included in a list containing dictionaries. The 'id' property
|
||
|
# should point to the id of an item, while the 'desc' property is optional
|
||
|
# and can be used to describe the relationship between an item and the room
|
||
|
# it is in.
|
||
|
'items': [{
|
||
|
'id': 'redflower',
|
||
|
'desc': "'To the right is a ' + item[citem]['name'] + '.'"
|
||
|
}, {
|
||
|
'id': 'multic_flower',
|
||
|
'desc': "'To the left is a ' + item[citem]['name'] + '.'"
|
||
|
}],
|
||
|
|
||
|
# Rooms includes directions in the 'dir' part. From the code below Zita
|
||
|
# understands that walking north will take the user to the room with an
|
||
|
# id of 'grass2', walking west will take the user to a swamp, going up
|
||
|
# will result in a message telling the user going up is impossible, and
|
||
|
# trying everything else will result in Zita giving the user a message.
|
||
|
'dir': {
|
||
|
'n': 'grass2',
|
||
|
'w': 'swamp',
|
||
|
'u': "!p('You can\\'t fly yet!')",
|
||
|
'&rest': "!p('Going north or west should be possible.')"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
Note that in the above code, the variable =citem= pops up. This is a special
|
||
|
variable holding the current item id. In the items list above, in the case of
|
||
|
'redflower', =citem= would be short for 'redflower', and in the case of
|
||
|
'multic_flower', =citem= would be short for 'multic_flower'. This may seem
|
||
|
useless, but in some cases it's handy. Read on.
|
||
|
|
||
|
|
||
|
*** =>item=
|
||
|
This variable keeps information about items. Items can be carried around in an
|
||
|
inventory. An item has a name and a series of commands associated with it. An
|
||
|
item can have two states. Either it's /in/ or it's /out/, i.e. it's either in
|
||
|
the inventory or in the current room. See below for a commented example (taken
|
||
|
from Zeqy's 'items.zt'):
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
item = {
|
||
|
'redflower': { # This is the id. Rooms use ids like this.
|
||
|
'name': 'red flower', # The name
|
||
|
|
||
|
# Commands associated with the item and the code to execute on activation.
|
||
|
# Apart from 'cmd', which reacts to an item no matter what state it is in,
|
||
|
# there is also 'in' and 'out'. These are simply not needed in this case.
|
||
|
|
||
|
# ┏━┓┏┓╻ ┏━╸╻ ╻┏━┓┏┳┓┏━┓╻ ┏━╸
|
||
|
# ┣━┫┃┗┫ ┣╸ ┏╋┛┣━┫┃┃┃┣━┛┃ ┣╸ ╹
|
||
|
# ╹ ╹╹ ╹ ┗━╸╹ ╹╹ ╹╹ ╹╹ ┗━╸┗━╸╹
|
||
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
|
|
||
|
# A user types:
|
||
|
# > eat red flower
|
||
|
# Zita finds this item and executes the value of the 'eat' property in the
|
||
|
# 'cmd' property. If there were no 'cmd' commands that matched the command,
|
||
|
# Zita would move on to see if any 'in' or 'out' commands, depending on the
|
||
|
# state of the item, matching the command existed.
|
||
|
|
||
|
'cmd': {
|
||
|
'eat': "p('It doesn\\'t seem edible.')",
|
||
|
'look': "p('It\\'s pretty.')",
|
||
|
'get': "p('The flower is stuck.')"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
The above example presents only a simple item. It is possible to create much
|
||
|
more complex ones. A slightly more complex item can be seen below (this one is
|
||
|
also taken from 'items.zt'):
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
item = {
|
||
|
'multic_flower': {
|
||
|
'name': 'yellow flower',
|
||
|
'cmd': {
|
||
|
'smell':
|
||
|
# Using Python's multiline indicator (three quotes), one can write code that
|
||
|
# takes up more than one line. The code below prints a message and changes the
|
||
|
# color (in the name) of the current item. This happens when the flower is
|
||
|
# smelled to.
|
||
|
"""
|
||
|
flcolor = randelm(var['colors']) # Gets a random color. The 'var' variable is
|
||
|
# explained later on.
|
||
|
p('You breathe in the fumes of the ' + item[citem]['name'] + '. In a matter of
|
||
|
picoseconds the flower changes its color to ' + flcolor + '.')
|
||
|
item[citem]['name'] = flcolor + ' flower'
|
||
|
""",
|
||
|
'look': "p('It\\'s like the sun.')"
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
Last, but not least, Zita understands the 'pref' (prefix) property:
|
||
|
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
item = {
|
||
|
'wood': {
|
||
|
'name': 'dry wood',
|
||
|
'pref': 'some'
|
||
|
# If 'some' wasn't specified as a prefix in this case, opening one's
|
||
|
# inventory would read this:
|
||
|
|
||
|
# You currently have:
|
||
|
# A dry wood
|
||
|
|
||
|
# In this case, however, 'a wood' is not what we want to write. 'some wood'
|
||
|
# is better. Setting 'pref' to 'some' solves this problem. Now it reads:
|
||
|
|
||
|
# You currently have:
|
||
|
# Some dry wood
|
||
|
|
||
|
}
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
If Zita detects more than one item matching the user's input, the user is
|
||
|
prompted to choose between the available items. This would be the case if a
|
||
|
room had to books, a red one and a green one, and the user merely states the
|
||
|
lust to read /a/ book.
|
||
|
|
||
|
*Tip:* If you get tired of typing long item names, you can choose to only type
|
||
|
the last part of an item. Instead of typing 'capture magenta-coloured space
|
||
|
creep', you can choose to simply type 'capture creep' or even 'capture p' (as
|
||
|
the last letter in 'capture magenta-coloured space creep' is a p).
|
||
|
|
||
|
|
||
|
*** =>var=
|
||
|
This variable merely holds variables and their respective values. =var=
|
||
|
variables can be used to store data. Looking at 'vars.zt' from Zeqy, we see,
|
||
|
among other stuff, this (commented version):
|
||
|
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
var = {
|
||
|
# 'imposs' does not have to exist, though Zeqy needs it in the <span class='code'>default</span>
|
||
|
# variable.
|
||
|
'imposs': ['That\'s not an option.', 'I am unable to accomplish that \
|
||
|
particular feat.', 'That seems out of the question.', 'Are you stupid?', 'I \
|
||
|
could try, but I don\'t want to.', 'I\'m not doing that!', 'No way.'],
|
||
|
|
||
|
# 'listoptions' MUST exist. If Zita prompts a user to choose between
|
||
|
# several items, a list is created using characters from 'listoptions'.
|
||
|
'listoptions': 'abcdefghijklmnopqrstuvwxyz',
|
||
|
# Using these list letters would result in multiple-choice scenarios
|
||
|
# looking like this:
|
||
|
|
||
|
# > eat python
|
||
|
# Which python?
|
||
|
# <span class='b'>a</span>. dangerous python
|
||
|
# <span class='b'>b</span>. Monty Python
|
||
|
|
||
|
# 'which' MUST exist. It holds what to output when multiple items are
|
||
|
# available. The variable <span class='code'>what</span> holds the current
|
||
|
# object specified by the user. In the above example, <span class='code'>what</span> would be python.
|
||
|
'which': "'Which ' + what + '?'",
|
||
|
|
||
|
# 'invalidwhich' MUST exist. The value of this variable is output when
|
||
|
# multiple items are available and the user attempts to get an item that
|
||
|
# doesn't exist.
|
||
|
'invalidwhich': "'Not a valid ' + what + '. Try again.'",
|
||
|
#An example:
|
||
|
|
||
|
# > c
|
||
|
# Not a valid python. Try again.
|
||
|
|
||
|
# 'steps' does not have to exist. It is self-explanatory.
|
||
|
'steps': 0,
|
||
|
|
||
|
# 'inventory' MUST exist. Normally it should be an empty list, but it is of
|
||
|
# course also possible to have the user start with one or more items.
|
||
|
'inventory': [],
|
||
|
|
||
|
# 'location' MUST exist, though it isn't completely necessary to have it
|
||
|
# defined here. Mostly, writing a <span class='code'>goto</span> function in the main file is better
|
||
|
# than defining the room here. Defining the room here means that the user
|
||
|
# will not see a startup message describing the current location.
|
||
|
'location': None,
|
||
|
|
||
|
# 'points' does not necessarily have to exist. Zita doesn't depend on it.
|
||
|
'points': 0
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
Variables are useful.
|
||
|
|
||
|
|
||
|
*** =>command=
|
||
|
If Zita is unable to match a user-typed command with a user-typed object, it
|
||
|
checks if a command independent of items matches. A command can be short ones
|
||
|
such as:
|
||
|
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
command = {
|
||
|
'steps': "p('You have taken ' + str(var['steps']) + ' steps.')"
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
..Though it can also include more complex commands, such as:
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
command = {
|
||
|
'go': # It's the common 'go' command!
|
||
|
"""
|
||
|
if len(cmd) == 1: # cmd is a list containing user input split with spaces.
|
||
|
p('You can\\'t go nowhere.')
|
||
|
else:
|
||
|
ltd = long2dir(cmd[1]) # Converts 'north' to 'n', etc.
|
||
|
if not 'dir' in room[var['location']]:
|
||
|
p('You are trapped!')
|
||
|
elif cmd[1] != '&rest' and ltd in room[var['location']]['dir']:
|
||
|
rname = room[var['location']]['dir'][ltd]
|
||
|
if rname[0] == '!':
|
||
|
exec(rname[1:]) # Only execute what's after the exclamation mark.
|
||
|
else:
|
||
|
goto(rname) # Use the <span class='code'>goto</span> function
|
||
|
elif '&rest' in room[var['location']]['dir']: # Default action
|
||
|
rname = room[var['location']]['dir']['&rest']
|
||
|
if rname[0] == '!':
|
||
|
exec(rname[1:])
|
||
|
else:
|
||
|
goto(rname)
|
||
|
else: # Converts 'w' to 'west', etc.
|
||
|
p('It is not possible to go ' + short2dir(txt[len(cmd[0])+1:]) + '.')
|
||
|
"""
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
If no available item has a command named 'go', the above code will be executed
|
||
|
in the case of a 'go' request. Typing 'go s', 'go west' and other variations
|
||
|
can, however, end up being annoying (if you're lazy), so adding 's', 'west' and
|
||
|
similar shortcut commands would easen playing a game. This can be done in the
|
||
|
following way:
|
||
|
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
command = {
|
||
|
'n': # 'go n' shortcut
|
||
|
"""
|
||
|
txt = 'go ' + cmd[0] # Not really needed
|
||
|
cmd = ['go', cmd[0]] # Fools the 'go' command
|
||
|
exec(command['go']) # Acts as if the program was the user
|
||
|
""",
|
||
|
# Applying more shortcuts are even easier:
|
||
|
'ne': "exec(command['n'])"
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
Item-independent commands can also be used to make people laugh.
|
||
|
|
||
|
*** =>default=
|
||
|
|
||
|
The =default= variable contains info on what to do when commands don't seem to
|
||
|
exist. For example, it's tiresome to insert a 'get' commands in ever item we
|
||
|
create. By using the default variable, we can create an item definition which
|
||
|
acts as a shortcut. See below:
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
default = {
|
||
|
'item': { # Default values for items
|
||
|
'cmd': { # Either 'a', 'an', 'some', etc.
|
||
|
'look': "p('It\\'s ' + getprefix(citem) + ' ' + item[citem]['name'] +
|
||
|
'.')", # The <span class='code'>exec_proper_command</span> function will find the correct place to look
|
||
|
# for a command. A hierachy is defined in Zeqy's defs.py.
|
||
|
'lookat': "exec_proper_command('look', i, i_or_o, citem)",
|
||
|
|
||
|
# The rest is simple shortcut-shortcuts.
|
||
|
'examine': "exec(default['item']['cmd']['lookat'])",
|
||
|
'e': "exec(default['item']['cmd']['lookat'])",
|
||
|
'l': "exec(default['item']['cmd']['lookat'])"
|
||
|
},
|
||
|
'out': {
|
||
|
'get': # All items should be gettable by default.
|
||
|
"""
|
||
|
p('You take the ' + item[citem]['name'] + '.')
|
||
|
inv(citem) # Adds item to inventory
|
||
|
""",
|
||
|
'take': "exec_proper_command('get', i, i_or_o, citem)",
|
||
|
'g': "exec(default['item']['out']['take'])",
|
||
|
't': "exec(default['item']['out']['take'])"
|
||
|
}
|
||
|
},
|
||
|
|
||
|
# 'item-substitutes' MUST exist. It is used as a reference to a previous item.
|
||
|
# This specific property could just as well have been placed in the <span class='code'>var</span>
|
||
|
# variable, but for now it must be in <span class='code'>default</span>.
|
||
|
'item-substitutes': ['it'],
|
||
|
|
||
|
# Zita will print a random messages of the list when there is no command match.
|
||
|
'command': "p(randelm(var['imposs']))",
|
||
|
|
||
|
# Code to execute when a load has been succesfully completed. Not strictly
|
||
|
# necessary, but quite useful.
|
||
|
'loaded': "p('Data succesfully loaded.\\n\\n')",
|
||
|
|
||
|
'room': { # Rooms can have default values too.
|
||
|
# Will be shown if no room description exists.
|
||
|
'desc': "'You have reached a ' + croom + '.'",
|
||
|
|
||
|
# Will be shown if no item description exists.
|
||
|
'item': "'You see ' + getprefix(citem) + ' ' + item[citem]['name'] + '.'"
|
||
|
}
|
||
|
}
|
||
|
#+END_SRC
|
||
|
|
||
|
Using defaults thereby make programming text adventures in Zita much easier.
|
||
|
|
||
|
|
||
|
*** =>extra=
|
||
|
Let's have another look at the zeqy directory:
|
||
|
|
||
|
#&pre
|
||
|
commands.zt&del functions.zt LICENSE rooms.zt&del
|
||
|
defaults.zt&del items.zt&del main.zt&del vars.zt&del
|
||
|
#&
|
||
|
|
||
|
The only file we haven't had a look in is the 'functions.zt' file (LICENSE does
|
||
|
not include Python code). The functions file contains.. functions. And global
|
||
|
ones at that.
|
||
|
|
||
|
|
||
|
#+BEGIN_SRC python2
|
||
|
def point(p):
|
||
|
# Adds points
|
||
|
global var
|
||
|
var['points'] += p
|
||
|
#+END_SRC</pre>
|
||
|
|
||
|
There is no 'extra' file. The job of the =extra= variable is to hold
|
||
|
information on dynamically created global content. Using the =extra_add=
|
||
|
function, one can save and execute e.g. a function at the same time. While it
|
||
|
would be easier to simply define a global function directly, problems would
|
||
|
arise when saving and loading the progress in a game, as eventual global
|
||
|
functions would not be saved/loaded. By storing them in the =extra= variable,
|
||
|
this obstacle is overcome. The problem is that it gets a little messy in that a
|
||
|
lot of backslashes may be needed to escape quote characters.</p>
|
||
|
|
||
|
But because functions in 'functions.zt' are defined when Zita loads a game and
|
||
|
not dynamically, those functions do not have to reside inside =extra=.
|
||
|
|
||
|
|
||
|
** Saving and loading progress
|
||
|
Using the built-in =save and =code= functions, it's quite easy to create a command that
|
||
|
keeps track of a player's progress. Saving and loading take place with the
|
||
|
Python =pickle= module.
|
||
|
|
||
|
|
||
|
** Footnote
|
||
|
This short manual might not have covered everything there is to say about
|
||
|
Zita. The best way to get an impression of Zita is to try it out. It's
|
||
|
recommended to use a pre-existing Zita adventure (like Zeqy) as a base.
|
||
|
|
||
|
** Prominent languages/engines
|
||
|
While I haven't actually used it, the [[http://adl.sourceforge.net/][Adventure Definition Language]] (ADL) seems
|
||
|
to be pretty good. At least it's extensively documented. Other engines
|
||
|
naturally exist too.
|