I'm currently working on reincarnation of the Cactus project, Cactus Reborn, and I've gotten the basic implementation so far. At this point, you can create simple, playable games.
There are currently three usable classes/functions:
Location
- This class describes data about a location in aGameFlowchart
class, like a title, description, location references, etcetera.GameFlowchart
- This class describes the "map", or "flowchart" that the player traverses while playing the game.play_game
- This function runs the user-created game.
I'd like to know the following things:
- Is it appropriate to require the user to specify argument names with
*
? - Where am I making things too complex? What can be simplified?
- Am I over-documenting?
- Are there any design issues that I should be concerned about here?
- Is there a simpler way to have the user describe data for games?
- Anything else?
location.py
"""
location.py
The classes and methods in this file are used to describe
a location, contained in a GameFlowchart class.
"""
import re
import sys
import time
class Location:
"""Represents a location in a GameFlowchart map.
A location represents a "location" on the game's
map, and contains data like a title, locations
to other positions, etc.
Keyword arguments:
title -- The title of the position.
description -- The description of the position.
locations -- A dictionary of possible inputs, and reference keys.
"""
def __init__(self, *, title, description_enter, description_exit, on_exit_function, locations):
self.title = title
self.description_enter = description_enter
self.description_exit = description_exit
self.on_exit_function = on_exit_function
self.locations = locations
def on_enter(self):
"""This function is run when the user enters.
When the user "enters" a positions, this function will
display the title, and position description.
"""
print("-" * len(self.title))
print(self.title)
print(self.description_enter)
if len(self.locations) != 0:
print("Locations: ", end="")
for key, value in self.locations.items():
print(key + ", ", end="")
def on_exit(self):
"""This function is run when the user exits.
When the user "exits" as position, this function will
display the exit message, and a newline.
"""
print(self.description_exit)
print("-" * len(self.description_exit) + "\n")
if self.on_exit_function is not None:
time.sleep(5)
self.on_exit_function()
def get_user_input(self, prompt, error_message, case_sensitive, global_commands):
"""Get user input, and check to make sure it's valid.
This function takes user input, sanitizes it, and
then checks to make sure that it's valid by checking
it against the locations. If it is valid, then the
referenced map key is returned. It it's invalid, then
the function returns None.
Keyword arguments:
prompt -- The prompt to use with user input.
error_message -- The error message to display when the input is invalid.
"""
if self.on_exit_function != sys.exit:
user_input = re.sub(r"([^\s\w]|_)", "", input("\n" + prompt).strip())
user_input = user_input.lower() if not case_sensitive else user_input
if user_input in self.locations:
return self.locations[user_input]
elif user_input in global_commands:
global_commands[user_input]()
return
print(error_message)
return
game_flowchart.py
"""
game_flowchart.py
The classes and methods in this file are used to create a
flowchart. A flowchart is essentialy a "map" of how your
game is played.
"""
import re
class GameFlowchart:
"""Represents a flowchart, or "map".
This class is a representation of a "map" in a
Cactus game. Essentially, a Flowchart is a
dictionary, where the keys are locations to
each position.
Keyword arguments:
locations -- A dictionary of socks.
"""
def __init__(self, *, locations):
self.locations = locations
def iterate_locations(self):
for key, value in self.locations:
yield (key, value)
def find_by_reference(self, reference):
"""Find, and obtain the value of a position, by reference.
This function checks to see if a reference value is in
the dictionary of locations. If it is, then return the
value, and if it isn't, then return None.
Keyword arguments:
reference -- The reference value.
"""
if reference in self.locations:
return self.locations[reference]
return
def find_start(self):
"""Returns the "starting position" in the locations.
This function will find the starting position in
the locations. It does not matter if the starting
position is capitalized, contains odd characters, or
has spaces. As long as it contains the characters
"start", it (should) work. This will return None
if the item is not found.
"""
for key, item in self.locations.items():
if re.sub(r"([^\s\w]|_)", "", key) == "start":
return self.locations[key]
return
main_game.py
"""
main_game.py
The classes and methods in this file are used to create,
and specify and additional data about your game, and then
wrap it up in a nice container, ready to play.
"""
import sys
def play_game(*, name, description, prompt, flowchart, case_sensitive, error_message, global_commands):
"""Play a user-created game.
Keyword arguments:
name -- The name of the game.
description -- The description of the game.
prompt -- The game's prompt.
flowchart -- The flowchart that the player traverses.
case_sensitive -- Whether or not user input is lowered.
error_message -- The message to display when the user enters invalid input.
global_commands -- Global commands that can be executed anywhere.
"""
current_location = flowchart.find_start()
if current_location is not None:
while True:
current_location.on_enter()
new_key = current_location.get_user_input(prompt, error_message, case_sensitive, global_commands)
current_location.on_exit()
if new_key is not None:
if new_key in flowchart.locations:
current_location = flowchart.locations[new_key]
else:
raise KeyError("Invalid location key \"{0}\"".format(new_key))
The below is a very simple example game, but I think it shows what you can do with Cactus.
import sys import cactus FLOWCHART = cactus.game_flowchart.GameFlowchart( locations={ "start": cactus.location.Location( title="The Shire", description_enter="As you enter the Shire, you are surrounded by the endless rolling hills.", description_exit="As you leave the Shire, you look back and wish that you could stay longer.", on_exit_function=None, locations={ "mordor": "mordor", "laketown": "laketown" } ), "mordor": cactus.location.Location( title="Mordor", description_enter="As you enter Mordor, the Dark Lord Sauron spots you and kills you.", description_exit="As you pass out of Arda, you reflect on your bad decision.", on_exit_function=sys.exit, locations={} ), "laketown": cactus.location.Location( title="Laketown", description_enter="As soon as you enter Laketown, you realize that there is no Laketown.", description_exit="You leave Laketown, disappointed.", on_exit_function=sys.exit, locations={} ) } ) cactus.main_game.play_game( name="LOTR Quest", description="Some dumb LOTR quest.", prompt="> ", flowchart=FLOWCHART, case_sensitive=False, error_message="Enter the correct input!", global_commands={ "exit": sys.exit } )
If you're interested about Cactus, you can visit the new repository, the /r/CactusEngine subreddit, or the official chat room.