Developer Manual - felix-elugbaju/Group_B_VirtualEscapeRoom GitHub Wiki
- Software Requirements
- Software Design
- Build Instructions
- Preliminary DataTypes
- The Object Structure
- Game Mechanics
- The Command Line Interface
- Testing Strategy
Requirements are classified into three broad categories:
- Critical: Requirements that are vital for the proper functional behavior of the software and included with release 1 of the software.
- High: Requirements with a slightly lower priority than Critical. The software will be able to function without these requirements, but these are considered valuable additions.
- Low: These are general improvements to the software and is considered the lowest priority requirements. The completion of these requirements doesn’t affect the playability of the game as much, and the software is fully functional even without these requirements. These are mostly part of release 2 of the software.
Release 1:
Release 1 will emphasize designing the layout of a single stage with three different challenges. In this release, the game will end when the player successfully solves all three challenges and opens the lock on the door.
- The game shall be playable by a single player. [Critical]
- The game shall feature cross-platform capabilities. [Critical]
- Every stage shall contain a fixed number (3 to 7) of objects. [Critical]
- The player shall be able to interact with objects using the command-line interface (CLI). [Critical]
- The software shall be able to get, parse, and execute any command from the player in the form of "command arg" (without the quotes). [Critical]
- The command "help" shall display a list of common commands. [High]
- Some example commands may be:
- “examine objects”
- "open door"
- “get item”
- “use key/code”
- The command "ask hint" will provide a hint towards the solution. [High]
- Examining an object will give further information that may lead to unique commands. [High]
- Common commands, when applicable, shall return suitable results. In contrast, unique commands are situation specific and shall only work once certain conditions are satisfied. [Critical]
- The parsing of unsuited commands will be associated with a suitable message indicating that the player is doing something wrong. [Low]
- Deciphering all secret messages and solving puzzles and/or riddles in a stage shall give access to a key or a code. [Critical]
- Using the correct key or code on the lock shall open the corresponding door. [Critical]
Release 2:
The second release will focus on creating multiple stages in the game and general improvements to the software.
13. Opening the door of one stage will lead to the next stage of the game. [High]
14. The game will have three stages in total. [High]
15. Each stage will be more difficult than the previous one. [High]
16. The total number of hints will be less than the total number of stages to encourage players
to use them sparingly. [Low]
17. Some doors might contain multiple locks, and as such, will require the player to acquire
the corresponding number of keys and/or codes. [Low]
Changes to Software Requirements 7. The "Ask Hint" Command was removed in lieu of allowing the user to ask for a hint while they are solving the puzzle. The reason for this is provide a hint tailored towards a specific puzzle, which is more useful. 16. Instead of using a fixed number of times a user can ask for a hint, a reward system was implemented at the end of every stage. Using no hints earns the "gold" award, while using all the hints for the puzzle earns the "Lost Explorer" award. The reason for this change was to make completing the entire escape room easier for the user.
The design of the entire project is divided into four primary phases, namely Input Handling, Command Execution, Challenge Creation, and Passage Connection. Each of these phases corresponds to a milestone that we intend to reach after the completion of the required subgoals, defined by the processes in the flow chart. Please refer to deliverable 2 for more details.
As specified in the README
In order to compile the program:
Open the command prompt
Navigate to the root directory of the project
Enter "make executable" or "make all" (without the quotes) into the command line
Navigate to the bin directory by entering "cd bin"
The application may now be run by entering "./VER.exe" into the command line
To clean the automatically created object and binary files, enter "make clean" while inside the root directory
This data type represents what "type" of object exists in the application.
There are 7 types of objects in this application.
- type
location
- Corresponds to a stage in which the user is permissible to visit. - type
visible_object
- Corresponds to visible object in which the user may observe via the "look around" command. - type
hidden_object
- These type of objects can only be accessed via examining its parentvisible object
and then performing additional commands, which are given in the parent object's detailed_description. - type
usable_object
- Corresponds to objects that are usable. - type
puzzle
- This corresponds to an abstraction of a puzzle found in the game that must be in the solved state in order to move from stage to stage. Note this differs from the physical puzzle that the user sees. The physical puzzles are either of typevisible_object
or typehidden_object
. - type
hint
- This is an additional piece of advice that may be accessed in order to help solve apuzzle
. Using one or more hints reduces the reward for a player for that stage only. - type
actor
- This is the abstraction of the user. The primary use of this object is to track whichlocation
the user is in.
This data type represents what is the present "state" of the object of interest in the application.
These states are as follows:
-
open
orclosed
- These states are only valid for doors. Doors are ofvisible_object
datatype and need to be in theopen
state for the user to successfully go from onelocation
to another. Conversely, theclosed
state prohibits the user from doing so. -
confined
orunrestricted
- These states are only valid for stages, which have data typelocation
. Theconfined
state means that the user is unable to move from thatlocation
. If the stage has anunrestricted
state, then the user is able to move from that stage at their leisure. -
solved
orunsolved
- These states are only valid for puzzles. If the puzzle is incomplete, then it has anunsolved
state. Upon the completion of a puzzle by the user, the state is to be changed tosolved
. -
hidden
orrevealed
- These states are only valid for keys. Keys can only be in therevealed
state after completion of all the puzzles for their corresponding stage. If the puzzles are not completely solved in the stage, then the key will remain in thehidden
state. Keys in thehidden
state may not be obtained by the user via theget
command. -
used
orunused
- These states are only valid for hints for puzzles. If the user asks for a hint when they are solving a puzzle, the puzzle's corresponding hint will change from stateunused
(default) toused
. Upon completing each stage, the number of hints in theused
state shall impact the stage prize the user receives. -
regular
- This state is a generic state that has no particular significance or meaning.
An 'object' datatype is comprised of 6 fields. In order, they are:
-
const char *description
- If this is a visible object type, then this is the description returned via the look around command. For usable objects, this description is shown once they are in the user's bag. -
const char *tag
- This string is used as an abbreviation of thedescription
field to refer to a specific object. In order to interact with an object, the user must enter a command in the terminal, and use the object'stag
as an argument. Example usage:use <silver_key>
where the user is touse
an object that has the tag ofsilver_key
. -
type_t type
- This field specifies the object's type. -
state_t state
- This field specifies the object's state. -
const char *detailed_description
- This string is returned when the user examines it. This string is to provide detail about the object, which may be a clue to solving a puzzle. If the object is of type hint, then this field must be used as a hint for a particular puzzle. -
struct object location
- This field is used to determine which location the object is located in. The object must exist in one, and only one location.
For a broad overview of the mechanics of this game, please see the User Manual
The main function will run a continuous loop that will run as long as one of the locations
has a state of confined
, and as long as the user does not enter the quit
command. If the program is unable to access the user input, then the application will quit and exit graciously.
In order to proceed from one stage to the next, the user must solve puzzles as defined in the User Manual.
The user may solve these puzzles with, or without using a hint
. However, to incentivize not using hints,
at the end of each stage, the user will receive a reward. At the end of every puzzle, the application
checks to see if all the puzzles in the particular stage the user is on are solved.
If so, the application will set the state of the key
to that particular stage from hidden
to
revealed.
In addition, it shall invoke the get_reward(int stage)
function, that counts all the hints
corresponding to the stage the user is on that are in the used
state. If the amount of hints used is
0, then the user receives the gold award. The more hints that are used, the less prestigious award the user receives
for that level.
After launching the application, the user is taken to a main menu where they have a choice of executing 10 commands. This input is received from the get_input(void)
function.
The input that the user provides is then sent to parse_and_execute(char * input)
function.
parse_and_execute(char * input)
First, the input is separated into tokens using whitespace as a delimiter. A typical input is two tokens. The first token is known as the command
, and the second token is known as the arg
. If the parser receives the quit
command, the application will exit gracefully, and then quit.
For many commands, it is necessary that the arg
is to be a tag
of one of the objects defined in the application. In order to get an object
with the matching tag
, the arg
is then passed to the OBJECT_t *get_object(const char *arg)
function. This function invokes the bool has_tag(OBJECT_t*obj, const char *arg)
which will test an OBJECT *obj
to see if its tag
is equal to arg
. If so, then this function returns true
, and get_object
returns the object
that was passed to the has_tag
function.
These other 10 commands are managed in the execution.c
file.
-
look
- Description: Invokesexecute_look(const char *arg)
, which does the following based on the value ofarg
. Ifarg
is equal toaround
then the screen will display allvisible
object types in the user's givenlocation
. Ifarg
is equal toclosely
, and if the user is instage 2
, ahidden
object will have itsdetailed description
displayed relating to the fourth puzzle. Return Values: Null -
examine
- Description: Invokesexecute_examine(const char *arg)
which prints thedetailed_description
of anobject
with that has itstag
equal toarg
. Return Values: Null -
go
- Description: Invokesexecute_go(const char *arg)
which will change thelocation
of theactor
to that ofarg
if the following conditions are met: 1) There exists anobject
with itstag
equal toarg
. 2) Thetype
of that object is of typelocation
. 3) The new location is different than thelocation
of theactor
. 4) The state of the current location isunrestricted
. Return Values: Null -
get
- Description: Invokesexecute_get(const char *arg)
which will "move" an object that has atag
equal toarg
into the user'sbag
. In order for an object to be "moved", it must be ausable_object
type. An object is "moved" via setting itslocation
to that of theactor
. The default value of thelocation
of all objects with the typeusable_object
is otherwise NULL. Return Values: Null
-
use
- Description: Invokesexecute_use(const char *arg)
which will check that an object which has atag
equal toarg
, is of typeusable_object
, and that the object is in the user'sbag
(Seeget
function above). Return Values: Null -
open
- Description: Invokesexecute_open(const char *arg)
which will check that an object which has atag
equal toarg
, has alocation
equal to that of theactor
, as well as a state of beingopen
. If so, then the currentlocation
that theactor
is in will change its state fromconfined
(its default) tounrestricted
. Return Values: Null -
bag
- Description: Invokesexecute_check(void)
which invokes a helper functionlist_objects_at_location(player, usable_object)
which determines the objects that are of typeusable_object
that have the samelocation
of that as theplayer
, which is an object of typeactor
. In order to change thelocation
of the object, see theget
command. Return Values: Null -
help
- Description: Invokesexecute_help(void)
which prints out a table of the common commands a user may enter. Return Values: Null -
map
- Description: Invokesexecute_map(void)
which prints out a crude map showing the tags of all the locations the user may be able to go to.
The strategy we used for this testing is the black box testing which is a functional unit testing method. We were testing different functions especially the non-void functions that return a value and require one or more arguments. We didn't test the void functions since most of them require user input.
Our unit testing made use of the Unity testing framework which is written in C and is a good testing framework for projects written in C. We made use of one of the unity's numerical assertions for integers function which is this function
TEST_ASSERT_EQUAL_INT(expected, actual)
expected
: The result you expect.
actual
: The function or the variable to be tested.
The function compares the actual and expected results and checks if they are equal to determine if the test passed or failed. We tested each function with the right input to see if it passed and then the wrong input to see if it failed. Expected values were modified in each case to see if what we expected matched the actual results.
- For example This tests if there is a tag called clock for the clock object
void test_object_has_tag_function_correct_tag(void)
{
TEST_ASSERT_EQUAL_INT(1, object_has_tag(clock, "clock"));
}
The expected returned value is 1 and the actual value returned is the function object_has_tag(clock, "clock") which should return 1 since there is a tag called clock for the clock object. This test should pass when the expected and actual results are compared This test passed and the result is
test/test.c:108:test_object_has_tag_function_correct_tag:PASS
This test if there is a tag called wine in the clock object
void test_object_has_tag_function_wrong_tag(void)
{
TEST_ASSERT_EQUAL_INT(0, object_has_tag(clock, "wine"));
}
The expected result is 0 and the actual result is the function object_has_tag(clock, "wine") which should return 0 since there is no tag called clock for the clock object. This test should pass when the expected and actual results are compared This test passed and the result is
test/test.c:110:test_object_has_tag_function_wrong_tag:PASS
All the test cases created pass. We ran a total of 8 tests for the program.
To run the tests, while in the project root directory,
- Use "make test" or "make all" to create the "test.exe" binary
- Navigate to the bin directory using "cd bin"
- Run the "test.exe" file using the "./test.exe" command in the bin folder after creating that executable.
To see the test results, go to the program_output folder and check the "test_result.txt" file containing the results of the different tests that we performed.