PHPIF
A PHP IF game system for web-based Interactive Fiction
Page Two
Go to Page One
by Gary Shannon
Created: Jan. 23, 2012
Last Revision: Jan. 25, 2012
Shell Scripting Language
Before I know what the shell scripting language has to look like, I need to know what kinds of statements are used in IF programming. To that end I picked apart two games of comparable size (around 1100 to 1200 actual scripting statements); one in TADS2 (Dwenodon) and one in HUGO (Colossal Cave). The resulting breakdown of programing statements looks like this:
TADS 2 HUGO Statement Type 41.5% 50.7% Print a string to the player 17.8% 15.3% IF (flag set, or two values compared) 11.6% 13.6% Call a function, including user functions and move object to location 9.6% 11.8% ELSE after an IF block 6.7% 6.2% Assign a simple value to a variable 5.5% 1.0% Increment or decrement a variable by some amount 4.7% 0.7% CASE within a SWITCH block 0% 0.7% FOR...NEXT and DO...WHILE blocks
In both games the number of algerbraic statements like x = 2*(x + y); or similar expressions was exactly zero. One could conclude, therefore, that the need to provide parsing for algebraic expressions does not exist in IF programming. Especially if there is a way to accomplish the same end result on those rare occasions when such capabilities are needed.
Whatever the source langauge looks like, the code actually run by the interpreter will be a much more compact compiled script of the type usually refered to as bytecode. Bytecode is a shorthand form of platform-independent computer code that runs in a "virtual machine", which is simply another name for the bytecode interpreter. In this case, the PHPIF system itself will be the interpreter.
Before trying to figure out what the authoring language will look like, I need to work out what the final bytecode language will look like. And I need to be sure that the bytecode language is up to the task of implementing the kind of games the system should support. So the first step should be to do some prototype implementations of various game features in the hypothetical bytecode language.
Bytecode and the Virtual Machine
Most forms of bytecode are based on the stack machine model because that is the most efficient way to evaluate complex compound statements like the example algebraic expression above. But since such complex statement will be the exception rather than the rule, I'm not sure that the stack machine model is the best approach. especially considering the overhead of pushing and popping every single parameter to every single operation call.
My first attempt at a mock-up bytecode will, therefore, be based on the register machine model. The pseudo-bytecode can be written in a format similar to traditional assembly languages. That way I can run through code sample, executing them "by hand", to be sure that the proposed bytecode can do what needs to be done. The actual "compiled" bytecode can be determined later when I know what the full instruction set will look like.
My goal is to have the script code be capable of existing in an ASCII string so that the bytecode can be embedded in an object or room definition something like this hypothetical example from the often-used example game Cloak of Darkness:
//-------FILE-----------room6.php $rm_name='dummy'; $rm_sdesc=''; $rm_ldesc=''; $on_entry='h0104F03G04.G03.';
In the above example, the "room" is a dummy object that the player enters from the foyer. Depending on whether or not the player is wearing the cloak of darkness he gets sent either to room 3, the bar, or to room 4, the dark bar. The string: 'h0104F03G03.G04.' would be executable bytecode that might be interpreted something like this:
h 01 04 ?HAS player, cloak // sets result flag to TRUE or FALSE F 03 BF skip // branch if false, ahead three bytes G 03 GOTO bar // move player to room 3 . RET // exit from this function call G 04 skip GOTO dark_bar // move player to room 4 . RET // exit from this function call
This is all hypothetical, of course, but assuming that the number in the operand portion of the instruction were base-64 {0..9,a..z,A..Z,+,-} then there could be up to 4096 rooms and 4096 objects in a game. If that isn't enough I could use 26 more ASCII special characters (~`!@#$%^&*()_={[]}:;<,>.?/) and use base 90 with the capability of supporting up to 8100 rooms and 8100 objects in a single game.
Executing a bytecode of this type would be very simple. The "opcode" is looked up in a table and a function is called to perform whatever operation that opcode calls for. Text messages could be mixed right in with the bytecode if the double quote character (") where to be treated as the bytecode for "print this text to the player." Then we might see something like the string '"In the dark? You could easily disturb something!"+011G03;' which could be interpreted as:
"... PRNT "In the dark? You could easily disturb something!"
+ 01 1 INCR damage, 1 // Increment message damage count by 1
G 03 GOTO dark_bar // go to room 4
; RET TRUE // exit from this function with TRUE flag (verb has been satisfied)
Game Structure
The other major issue that needs to be worked out is the overall structure of the game. How are actions accomplished? How does control get passed from function to function? For example, before performing some action it is often necessary to check to see if that action is possible, or makes sense. There are different ways to approach that problem. TADS uses a verify function in each object involved in the transaction. For example, in the "Cloak of Darkness" if the player types hang cloak on hook the hook object will be asked to verify that something can be hung on it, and the cloak object must verify that it can be hung on something. In ALAN the same issue is addressed with the "CHECK" section in a VERB declaration. Regardless of how the issue is resolved, the control flow must be clearly and unambiguously defined in all cases.
One way or another every object involved in a transaction must be polled as to its willingness to participate in the action, and any of the objects must be capable of vetoing the action. When the player types unlock the door with the broom the broom object must be able to cancel the whole transaction and report back "Brooms cannot be used to unlock doors." or some other appropriate error message.
Provision must also be made for a library of default check/verify scripts so that any object that is not classified as a "key" can fall back on the default check and return a message like "You can't unlock anything with that." for example.
The goal is to have the entire object defintion compiled into a block of php code that fills an array entry in an object array. Only those objects that acessable will be loaded at any one time, so the array is sparse. An example object definition might look something like this:
Intro: "Welcome to this experimental mini-game. Enjoy your stay."
move badge to lab
GOTO: Room 1
Room 1: The laboratory
Name: 'lab'
Sdesc: 'The laboratory'
Ldesc: "This is a typical mad scientist's laboratory with many gizmos that flash and sparkle."
OnGoNorth: If player has badge GOTO supply
Else "The force field prevents you from passing"
Room 2: The supply room
Name: 'supply'
Sdesc: 'The supply room'
Ldesc: "This is the supply room with many shelves filled with colorful gadgets and smelly fluids."
OnGoSouth: If player has badge GOTO lab
Else "The force field prevents you from passing"
Object 1: The player
Object 2: The badge
Name: 'badge'
Sdesc: 'Your electronic badge
Ldesc: "This badge has your picture on it, and it is electronically encoded to allow you to pass
through the forcefields between all rooms. Without this badge, you're not going anywhere."
Command syntax:
'get #object' picks up the object and puts it in the player inventory
'drop #object' drops the object and takes it out of the player inventory
'north n' attempts to walk north to a connecting room
'south s' attmepts to walk south to a connecting room
'inventory inv i' displays the contents of the player's inventory
Mutable attributes:
bool lab.visited - TRUE after lab has been visited
bool supply.visited - TRUE after supply has been visited
objid lab.contents[] - Object IDs of objects contained in lab
objid supply.contents[] - Object IDs of objects contained in supply
objid player.contents[] - Object IDs of objects contained in player inventory
Library scripts:
Default Syntax:
get, drop, inventory, north, south, east, west
Default Scripts:
OnEnter: on entering a room, display room name and
if room has not been visited before: Ldesc
else: Sdesc
OnGoNorth: "You can't go that way."
OnGoSouth: "You can't go that way."
OnGoEast: "You can't go that way."
OnGoWest: "You can't go that way."
Inventory: Display player contents array
OnGet #a: if #a is in player inventory contents
"You already have" name #a
else if if #a is in current room contents
move #a to player inventory contents "You got #a"
else
I don't see #a here
OnDrop #a:if #a is in player inventory contents
move #a to room contents "You dropped #a"
else if if #a is in current room contents
"You don't have #a"
else
I don't see #a here
Control Flow
The next step will be to work out the first attempt at dividing the necessary processes into generic engine code and specific game code, and deciding how those two separate entities should communicate with each other. Here is the control flow in outline form. Before this can be implemented in actual php and bytecode form more details need to be supplied as to how those scripts are stored, and where they're stored and how they called, specifically.
The horizontal lines of dashes represent where the php script is done executing, has sent the updated HTML page to the player's browser, and goes away. After each such break the game engine has to be fired up and started all over from scratch from whatever information it has about the state of the game.
Simulated game transcript:
-----PHP-------------------------- -----Bytecode Script---------------------
If first time:
Load Intro script
call interp with intro script
Prints welcome
Move madge to lab and go to lab
GOTO function:
include next room def
Prints current room description and contents
Marks room as having been visited
update state vector
display page
exit
else
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"n"
Call current room's OnGoNorth script
if Player has badge
GOTO supply
else
"can't pass message"
"Can't pass message"
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"s"
Call current room's OnGoSouth script
Current room has no OnGoSouth
Call default OnGoSouth
"You can't go that way."
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"get badge"
Call object's OnGet script
Object has no OnGet script
Call default OnGet script
if #a is in player inventory contents
"You already have" name #a
else if if #a is in current room contents
move #a to player inventory contents
else
I don't see #a here
moves badge to player's inventory contents
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"n"
Call current room's OnGoNorth script
if Player has badge
GOTO supply
else
"can't pass message"
GOTO supply
GOTO function:
include next room def
Prints current room description and contents
Marks room as having been visited
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"drop badge"
Call object's OnDrop script
Object has no OnDrop script
Call default OnDrop script
if #a is in player inventory contents
move #a to room contents "You dropped #a"
else if if #a is in current room contents
"You don't have #a"
else
I don't see #a here
"You dropped badge
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"s"
Call current room's OnGoSouth script
if Player has badge
GOTO lab
else
"can't pass message"
"Can't pass message"
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"get badge"
Call object's OnGet script
Object has no OnGet script
Call default OnGet script
if #a is in player inventory contents
"You already have" name #a
else if if #a is in current room contents
move #a to player inventory contents
else
I don't see #a here
moves badge to player's inventory contents
update state vector
display page
exit
-------------------------
Load state vector
include syntax array
include defs for all objects present
include current room def
Parse input using syntax array
"s"
Call current room's OnGoSouth script
if Player has badge
GOTO lab
else
"can't pass message"
GOTO lab
GOTO function:
include next room def
Prints current room description and contents
Marks room as having been visited
update state vector
display page
exit
-------------------------
Internal Implementation Details
The internal implementation details are discussed on Page Three.