def change_relation(faction, type_of_change):
"""Create a message to describe the change in the relationship
faction and type_of_change are case insensitive and have to correspond to
class variables of Factions nd RelationshipChanges.
"""
message_template = getattr(RelationshipChanges, type_of_change.upper())
return message_template.format(getattr(Factions, faction.upper()))
With this, change_relation("civilians", "great_increase") would generate the same output as previously seen. The function uses Python's built-in getattr(...) function to programmatically access members of the class by their name. As an example, getattr(Factions, "ARMY") would be the same as Factions.ARMY. Neat, isn't it?
If you were even more keen on saving some typing, this function would easily allow to add a "translation" dictionaries as an intermediate. These dict could then map '+++' to RelationshipChanges.GREAT_INCREASE or 'civ' to Factions.CIVILIANS and shorten the previous function call to change_relation('civ', '+++'). I will leave that as an exercise to you.I will leave that as an exercise to you. See the updated version below.
One way would be to put them into a class such as we did before with the other constant values. A dict might also be a viable solution. But wait! We have already started somethind related to those changes, haven't we? Well observed. Time to have another look at RelationshipChanges. At the moment this class simply holds the template message for each of the changes. With just one more level of "nesting", we can add the score modifiers as well. As we wrote a function to help us handle those relationship changes, everything will stay smooth.
ULTIMATE_SCORE_CHANGE = 15
MAJOR_SCORE_CHANGE = 2
NORMAL_SCORE_CHANGE = 1
SLIGHT_SCORE_CHANGE = 0.5
class RelationshipChanges:
"""Holds templates and modifiers to decribe changes in the relationships"""
HEORISM = {
'message': '{} looks at you as a hero.',
'modifier': ULTIMATE_SCORE_CHANGE
}
GREAT_INCREASE = {
'message': 'This greatly improves your relationship with {}.',
'modifier': MAJOR_SCORE_CHANGE
}
INCREASE = {
'message': 'This improves your relationship with {}.',
'modifier': NORMAL_SCORE_CHANGE
}
SLIGHT_INCREASE = {
'message': 'This slightly improves your relationship with {}.',
'modifier': SLIGHT_SCORE_CHANGE
}
SLIGHT_DECREASE = {
'message': 'This slightly decreases your relationship with {}.',
'modifier': -SLIGHT_SCORE_CHANGE
}
DECREASE = {
'message': 'This worsens your relationship with {}.',
'modifier': -NORMAL_SCORE_CHANGE
}
GREAT_DECREASE = {
'message': 'This greatly worsens your relationship with {}.',
'modifier': -MAJOR_SCORE_CHANGE
}
TREASON = {
'message': '{} wants you dead.',
'modifier': -ULTIMATE_SCORE_CHANGE
}
def change_relation(faction, type_of_change):
"""Create a message to describe the change in the relationship
faction and type_of_change are case insensitive and have to correspond to
class variables of Factions nd RelationshipChanges.
"""
change_descr = getattr(RelationshipChanges, type_of_change.upper())
faction_name = getattr(Factions, faction.upper())
return change_descr['modifier'], change_descr['message'].format(faction_name)
Now that those messages and the actual changes to the score are linked more closely, it would be a great moment to remove those change messages from the static game text. A benefit of this would be that if you ever decided to change the effects of an action, you would only have to do this in on place, namely on of the event functions, and not there and somewhere else hidden in all the storyline text. Since those message are IIRC merely appended to the storyline text, the output should not change significantly. Of course the implementation of change_relation has to be adapted to fit these changes, and since all that stops change_relation from actually updating the relationship score is not knowing about relationships it is easy to adapt it to do more repetive work for us:
def change_relation(relationships, faction, type_of_change):
"""Documentation omitted for brevity"""
type_translation = {
"---": "great_decrease", "--": "decrease", "-": "slight_decrease",
"+++": "great_increase", "++": "increase", "+": "slight_increase"
}
if type_of_change in type_translation:
# only apply the translation if it's own of ---/--/.../+++
type_of_change = type_translation[type_of_change]
change_descr = getattr(RelationshipChanges, type_of_change.upper())
faction_name = getattr(Factions, faction.upper())
relationships[faction_name] += change_descr['modifier']
return change_descr['message'].format(faction_name)
You can now use something like print(change_relation(relationships, "civilians", "---")) to adapt the game state and tell the user about the consequences of his/her decision. (Note: The code above builds upon a change to relationships that will be explained in the following section.)
At the moment the user input handling is not very robust. Once you enter an invalid command, e.g. by smashing the enter key to long, the program bails out and you have to start all over. This can be very annoying. A better approach would be to ask for invalid input several times and only bail out if a termination character like q/Q is entered or the user did not provide a valid input six times in a row. An implementation of this approach might look like:
Mini demo
def prompt_for_input(prompt, valid_inputs, max_tries=6):
print(prompt)
for _ in range(max_tries):
user_input = input('> ').upper()
if user_input in valid_inputs:
return user_input
if user_input == 'Q':
break
# the input was not valid, show the roadblock
roadblock()
# Either Q or excessive retrying brought us here
print('Seems like you are not willing to play. Goodbye!')
sys.exit(0)
With those changes above you are now at a stage wher you can write code like:
Mini demo
modifier, message = change_relation("civilians", "great_increase")
relationships[Factions.CIVILIANS] += modifier
final_standing = get_final_standing(relationships[Factions.CIVILIANS], (-7, -4, -2, 2, 5, 7))
print(message)
print(f'You left the {Factions.CIVILIANS} feeling {final_standing}.')
The answer contains some a lof of proposals that change the code drastically. Since you requested to see more code to better understand it and the answer is already quite long, I decided to implement a reduced version of your game that implements those proposed changes and upload it into a gist. The gist is hidden from search engines but accessible to everyone with the link.