Last active
August 29, 2015 14:10
-
-
Save bradbeattie/276db65f08730f296cb3 to your computer and use it in GitHub Desktop.
Parse and modify prison architect save files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# These functions will allow you to | |
# - Read in Prison Architect save files | |
# - Modify the read content | |
# - Save content back into Prison Architect save file formats | |
import re | |
from collections import OrderedDict | |
from pprint import PrettyPrinter | |
TOKEN_BEGIN = 1 | |
TOKEN_END = 2 | |
TOKEN_CONTENT = 3 | |
CATEGORY_PROTECTED = "Protected" | |
CATEGORY_MINSEC = "MinSec" | |
CATEGORY_MEDSEC = "Normal" | |
CATEGORY_MAXSEC = "MaxSec" | |
CATEGORY_SUPERMAX = "SuperMax" | |
PRISONER_CATEGORIES = (CATEGORY_PROTECTED, CATEGORY_MINSEC, CATEGORY_MEDSEC, CATEGORY_MAXSEC, CATEGORY_SUPERMAX) | |
RELEASED = "Released" | |
DANGEROUS_REPUTATIONS = set(("Volatile", "Instigator", "CopKiller", "Deadly")) | |
DANGEROUS_MISCONDUCTS = set(("InjuredPrisoner", "InjuredStaff", "Murder", "Destruction")) | |
def parse_tokens(tokens): | |
content = OrderedDict() | |
index = 0 | |
if type(tokens) is list: | |
tokens = enumerate(tokens) | |
for index, token in tokens: | |
if token[0] == TOKEN_END: | |
break | |
elif token[0] == TOKEN_BEGIN: | |
secondIndex, secondToken = next(tokens) | |
if secondToken[0] != TOKEN_CONTENT: | |
raise Exception("Expected a CONTENT token after a BEGIN token") | |
parsed_tokens, subcontentLength = parse_tokens(tokens) | |
content.setdefault(secondToken[1].strip('"'), []) | |
content[secondToken[1].strip('"')].append(parsed_tokens) | |
else: | |
secondIndex, secondToken = next(tokens) | |
content.setdefault(token[1].strip('"'), []) | |
content[token[1].strip('"')].append(secondToken[1]) | |
return content, index | |
def escaped(name): | |
return ('"%s"' % name) if " " in name else name | |
def generate_prison_format(parsedPrison, indent=0): | |
content = [] | |
for name, values in parsedPrison.iteritems(): | |
for value in values: | |
if type(value) is OrderedDict: | |
subContent = generate_prison_format(value, indent + 1) | |
if len(subContent) >= 2: | |
content.append("BEGIN %s" % escaped(name)) | |
for subEntry in subContent: | |
content.append("".join((" ", subEntry))) | |
content.append("END") | |
else: | |
content.append("BEGIN %s END" % " ".join([escaped(name)] + subContent)) | |
else: | |
content.append(" ".join((escaped(name), str(value)))) | |
return content | |
def security_hearings(parsedPrison): | |
for id, entry in parsedPrison["Objects"][0].iteritems(): | |
if type(entry[0]) == OrderedDict and entry[0].get("Type", None) == ["Prisoner"]: | |
prisonerGrade = grade_prisoner(parsedPrison, id, entry) | |
if prisonerGrade == RELEASED: | |
print "EARLY PAROLE FOR", id | |
entry[0]["Bio"][0]["Sentence"][0] = float(entry[0]["Bio"][0]["Served"][0]) | |
elif prisonerGrade is not None: | |
if entry[0]["Category"][0] != prisonerGrade: | |
print "CHANGING", id, "FROM", entry[0]["Category"][0], "TO", prisonerGrade | |
entry[0]["Category"][0] = prisonerGrade | |
def grade_prisoner(parsedPrison, id, entry): | |
# Don't grade unrevealed prisoners | |
reputationRevealed = entry[0]["Bio"][0]["ReputationRevealed"][0] == "true" | |
if not reputationRevealed: | |
return | |
# Collect relevant prisoner information | |
daysInPrison = float(entry[0]["Experience"][0]["Experience"][0]["TotalTime"][0].rstrip(".")) / 1440 | |
sentence = float(entry[0]["Bio"][0].get("Sentence", [100])[0]) | |
served = float(entry[0]["Bio"][0].get("Served", [0])[0]) | |
parole = float(entry[0]["Bio"][0]["Parole"][0]) | |
reputations = set(entry[0]["Bio"][0].get("Reputation", [])) | |
highReputations = set(entry[0]["Bio"][0].get("ReputationHigh", [])) | |
programsPassed = sum(int(program[0].get("Passed", [0])[0]) for program in entry[0]["Experience"][0]["Results"][0].values()) | |
dangerous = reputations & DANGEROUS_REPUTATIONS or highReputations & DANGEROUS_REPUTATIONS | |
try: | |
misconducts = len([ | |
True | |
for misconduct in parsedPrison["Misconduct"][0]["MisconductReports"][0][id][0]["MisconductEntries"][0].values() | |
if type(misconduct[0]) is OrderedDict and misconduct[0]["Convicted"][0] == "true" | |
]) | |
except KeyError: | |
misconducts = 0 | |
try: | |
violent_behavior = len([ | |
True | |
for misconduct in parsedPrison["Misconduct"][0]["MisconductReports"][0][id][0]["MisconductEntries"][0].values() | |
if type(misconduct[0]) is OrderedDict and misconduct[0]["Type"][0] in DANGEROUS_MISCONDUCTS | |
]) | |
except KeyError: | |
violent_behavior = 0 | |
# Grading a prisoner costs $100 | |
parsedPrison["Finance"][0]["Balance"][0] = "%g" % (float(parsedPrison["Finance"][0]["Balance"][0]) - 100) | |
# Consider early release for well-behaved prisoners | |
if not dangerous and not violent_behavior and served + programsPassed > parole + misconducts and programsPassed - misconducts > 2: | |
parsedPrison["Finance"][0]["Balance"][0] = "%g" % (float(parsedPrison["Finance"][0]["Balance"][0]) + 1000) | |
return RELEASED | |
# Don't recategorize Protected prisoners | |
category = entry[0]["Category"][0] | |
if category == CATEGORY_PROTECTED: | |
return | |
# Violent or dangerous MinSec prisoners will get boosted up to MedSec | |
if category == CATEGORY_MINSEC: | |
return CATEGORY_MEDSEC if violent_behavior or dangerous else CATEGORY_MINSEC | |
# Legendary prisoners have two options: MaxSec or SuperMax | |
if "Legendary" in highReputations: | |
if violent_behavior or dangerous: | |
return CATEGORY_SUPERMAX | |
else: | |
return CATEGORY_MAXSEC if daysInPrison > 8 else category | |
# All others have two options: MedSec or MaxSec | |
else: | |
if violent_behavior or dangerous: | |
return CATEGORY_MAXSEC | |
else: | |
return CATEGORY_MEDSEC if daysInPrison > 4 else category | |
inFile = r"Alpha27-600.prison" | |
outFile = r"Alpha27-600-new.prison" | |
with open(inFile, "r") as oldPrisonFile, open(outFile, "w") as newPrisonFile: | |
scanner=re.Scanner([ | |
(r"\s+", None), | |
(r"BEGIN", lambda scanner,token:(TOKEN_BEGIN, token)), | |
(r"END", lambda scanner,token:(TOKEN_END, token)), | |
(r'".*"', lambda scanner,token:(TOKEN_CONTENT, token)), | |
(r"[^\s]*", lambda scanner,token:(TOKEN_CONTENT, token)), | |
]) | |
tokens, remainder = scanner.scan(oldPrisonFile.read()) | |
parsedPrison = parse_tokens(tokens)[0] | |
security_hearings(parsedPrison) | |
newPrisonFile.write("\n".join(generate_prison_format(parsedPrison))) |
Hey thanks heaps for posting this - I've been mulling over in my head the best way to parse a .prison file for the last couple of days (for completely different reasons), I'll use your code as a reference point! I'm guessing from syntax this is in Python?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Todo: