Combination Lock
Once the states and transitions have been identified the combination lock can be coded up as a new Python class.
Defining the states
The first thing to do is to define the names of each of the states from the state diagram.
classes/fsm/comboLockFsm.rpy# Combination lock states. # define comboLockStates = ( 'locked', # 0 'closed', # 1 'open', # 2 'scrambled', # 3 )
Defining the class
This example uses the Fsm as a base class
so the code can work with state names rather than state numbers.
An __init__
method is provided to initialise the base class
with the list of states, and to record the correct combination which
defaults to "1234".
The state machine doesn't care what this combination string is, it could
be longer, shorter, or have letters if you are making a
cryptex .
Note: There's a syntactical difference between the way the base class is initialised between Ren'Py7/Python2 and Ren'Py8/Python3. Use the tabs below to select the appropriate code for your version.
classes/fsm/comboLockFsm.rpy (cont.)init python: class ComboLockFsm(Fsm): """ State machine for a combination lock. """ # --------------------------------------------------------------------- # Constructor # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def __init__(self, combination="1234"): """ Construct a combination lock FSM. :param combination: The combination to unlock. """ super(ComboLockFsm, self).__init__('comboLockStates') # Python 2 self._combinationM = combination
init python: class ComboLockFsm(Fsm): """ State machine for a combination lock. """ # --------------------------------------------------------------------- # Constructor # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def __init__(self, combination="1234"): """ Construct a combination lock FSM. :param combination: The combination to unlock. """ super().__init__('comboLockStates') # Python 3 self._combinationM = combination
Input: Enter combination
The first input to deal with is when the player sets a combination.
The combination could be right or wrong, and if None
is given as an input it is always wrong - effectively scrambling the
combination.
# --------------------------------------------------------------------- # Inputs # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def enterCombination(self, combination): """ Enter a combination. :param combination: The combination to try, None to scramble. """ if combination is not None and combination == self._combinationM: # Correct. if self.stateName == 'locked': self.stateName = 'closed' elif self.stateName == 'scrambled': self.stateName = 'open' else: # Incorrect. if self.stateName == 'closed': self.stateName = 'locked' elif self.stateName == 'open': self.stateName = 'scrambled'
Input: Try open
The next input is if the player tries to open the lock. This can only do anything useful if the lock is in the closed state. For convenience, this method returns True only when the case has been opened.
classes/fsm/comboLockFsm.rpy (cont.)def tryOpen(self): """ Try to open the lock. :return: True if the lock is now open. """ if self.stateName == 'closed': self.stateName = 'open' return True return False
Input: Do close
The final input is when the player closes the lock.
If it is in the open
state it becomes closed
,
but if it is in the scrambled
state it becomes
locked
.
def doClose(self): """ Close the lock. """ if self.stateName == 'open': self.stateName = 'closed' elif self.stateName == 'scrambled': self.stateName = 'locked'
That completes all the inputs and transitions from the state diagram.
Output: Is open?
The only output is whether the lock is open.
It is open in both the open
and scrambled
states.
# --------------------------------------------------------------------- # Outputs # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def isOpen(self): """ Is the lock open? :return: True if it is. """ return self.stateName in ('open', 'scrambled')
Briefcase Example
Finally, here is an example of the combination lock class in use on a briefcase. A different instance of the same class could also be used for other locks of the same general type, such as a combination padlock, or a gun case. The state machine accurately reproduces the functionality of such devices and because it is a finite state machine it can't ever get in an unexpected state. The combination is still the default "1234"!
default briefcaseLock = ComboLockFsm("1234") label exBriefcase: $ renpy.dynamic('combo', 'done', 'open', 'prompt') $ done = False $ briefcaseLock.reset() $ debugFsm = True " There is a briefcase here with a four digit combination lock. " while not done: $ open = briefcaseLock.isOpen() if open: $ prompt = "The briefcase is open." else: $ prompt = "The briefcase is closed." menu: "[prompt]" "Try to open it" if not open: if briefcaseLock.tryOpen(): "It pops open." else: "Nothing happens." "Enter a combination": $ combo = renpy.input("Enter four digits", length=4, allow='0123456789') if len(combo) != 4: "You need to set all four digits." else: "You set the digits of the combination." $ briefcaseLock.enterCombination(combo) "Scramble the combination": "You spin the numbers on the combination." $ briefcaseLock.enterCombination(None) "Close it" if open: "You shut the briefcase." $ briefcaseLock.doClose() "Back": $ done = True $ debugFsm = False return
Next steps
While this is fine as it stands, it could still be enhanced.
Once the player manages to get a combination lock open for the
first time they know the combination!
It would be easy to add two more states lockedKnown
and closedKnown
to represent this, an output
isComboKnown
and another input enterKnownCombo
.
The same state additions could work if the player were to find a clue
with the combination on it somewhere, it just needs an input such
as clueFound
.
If you know about lock sport or watch channels like the LockPickingLawyer you'll be aware that there are other ways to get such a lock open such as decoding or bypassing. Potentially more additions to this simple state machine.
By using a state machine such additions are more easily made without having to add more flags and variables for each lock in the game.
This example only uses basic menus and input. The same state machine could easily be manipulated by a dedicated screen that shows a representation of the lock itself.