summaryrefslogtreecommitdiff
path: root/src/unlock.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/unlock.py')
-rw-r--r--src/unlock.py138
1 files changed, 138 insertions, 0 deletions
diff --git a/src/unlock.py b/src/unlock.py
new file mode 100644
index 0000000..0f954d6
--- /dev/null
+++ b/src/unlock.py
@@ -0,0 +1,138 @@
+import re
+
+LEVEL_RE = re.compile(r'^([0-9]{1,2})-([0-9]{1,2})( secret)?$')
+COMBINER_RE = re.compile(r'[ ]*(and|or)[ ]*')
+
+class UnlockParseError(ValueError):
+ # todo: is this the proper way to make an Error?
+ pass
+
+def parseUnlockText(text):
+ return _parseUnlockBit(text)
+
+def _parseUnlockBit(text):
+ # blank criterion
+ if text == '':
+ return ('always',)
+
+ # is this a simple one...?
+ m = LEVEL_RE.match(text)
+ if m:
+ one, two, secret = m.groups()
+ return ('level', int(one), int(two), (secret != None))
+
+ # OK, let's parse parentheses
+ pLevel = 0
+ endAt = len(text) - 1
+
+ # this could be either AND or OR or nothing at all
+ # we won't know it until we finish parsing!
+ whatCombiner = None
+
+ subTerms = []
+ currentSubTermStart = None
+
+ skip = 0
+
+ for index, char in enumerate(text):
+ if skip > 0:
+ skip -= 1
+ continue
+
+ if char == '(':
+ if pLevel == 0:
+ currentSubTermStart = index
+ pLevel += 1
+ elif char == ')':
+ pLevel -= 1
+ if pLevel < 0:
+ raise UnlockParseError('close parenthesis without a matching open')
+ elif pLevel == 0:
+ subTerms.append((currentSubTermStart, index, text[currentSubTermStart+1:index]))
+ # are we expecting to see something else?
+ if index == endAt: break
+
+ m = COMBINER_RE.match(text, index + 1)
+ if not m:
+ raise UnlockParseError('something unexpected at %d' % (index+1))
+
+ # what is it?
+ nextCombiner = m.group(1)
+ if whatCombiner is not None and nextCombiner != whatCombiner:
+ raise UnlockParseError('mixed %s and %s in one term. use more parentheses!' % (whatCombiner,nextCombiner))
+ whatCombiner = nextCombiner
+
+ # go right past this, to the next subterm
+ skip = len(m.group(0))
+ else:
+ if pLevel == 0:
+ raise UnlockParseError('something unexpected at %d' % index)
+
+ # now that we're here, we must have parsed these subterms
+ # do we have a combiner?
+ if whatCombiner is None:
+ assert len(subTerms) == 1
+
+ return _parseUnlockBit(subTerms[0][2])
+ else:
+ return (whatCombiner, map(lambda x: _parseUnlockBit(x[2]), subTerms))
+
+
+
+if __name__ == '__main__':
+ print repr(parseUnlockText('((01-01 secret) and (01-02)) or (02-99 secret) or (01-01)'))
+
+ from sys import exit
+ exit()
+
+
+
+
+
+from common import *
+
+
+
+
+class KPUnlockSpecDialog(QtGui.QDialog):
+ def __init__(self, forWhat, unlockAdjective):
+ QtGui.QDialog.__init__(self)
+
+ self.setWindowTitle('Set Unlock Criteria')
+
+ text = """You may enter various criteria that must be fulfilled for this {0} to be {1}.<br>
+ <br>
+ Here are some examples of what you can use:
+ <ul>
+ <li>01-01 - <i>a single criterion</i></li>
+ <li>01-01 secret - <i>secret exits</i></li>
+ <li>(01-01 secret) and (01-02) - <i>combine two criteria</i></li>
+ <li>((01-01 secret) or (01-02)) and (01-04) - <i>nested criteria</i></li>
+ </ul>
+ Each criterion used on the sides of AND and OR must be surrounded by parentheses.<br>
+ <br>
+ To leave this {0} permanently unlocked, leave the box blank.
+ """.format(forWhat, unlockAdjective)
+
+ self.label = QtGui.QLabel(text)
+ self.label.setWordWrap(True)
+
+ self.textBox = QtGui.QLineEdit()
+ self.textBox.textChanged.connect(self.checkInputValidity)
+
+ self.buttons = QtGui.QDialogButtonBox(
+ QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
+
+ self.buttons.accepted.connect(self.accept)
+ self.buttons.rejected.connect(self.reject)
+
+ self.layout = QtGui.VBoxLayout()
+ self.layout.addWidget(self.label)
+ self.layout.addWidget(self.textBox)
+ self.layout.addWidget(self.buttons)
+ self.setLayout(self.layout)
+
+ def checkInputValidity(self, text):
+ self.parsed = parseUnlockText(text)
+ self.buttons.button(QtGui.QDialogButtonBox.Ok).setEnabled(not (self.parsed is None))
+