_Turn System


Tickless Turn Engine

The game does not cycle through its engine logic on every turn. Instead, it only loops through on turns when something is going to happen. In addition to saving a good deal of wasted cycles, this also allows for events to occur in fractions of a turn as well as quick evaluation of certain extremely long actions (such as the main character sleeping for 8 hours). However, for the sake of simplicity and player understanding all standard actions should have integer turn lengths.

Implementation

currentTurn = 0
while(gameCoreLoop == True):
    minTurn = player.nextTurn
    for actor in game.actors:
        actor.act(currentTurn)
        minTurn = min(minTurn,actor.nextTurn)
    currentTurn = minTurn


Post-Order Actions

Picture
The idea of "preorder vs postorder" actions boils down to the following logic: in a turn-based game, actions cannot truly happen in real time due to the limitations of the turn step. In a roguelike game, most actions in fact take up several turns each, and there must be some point in these several turns where the action is evaluated by the game engine (and thus completed). Now, we can break down the really long actions into multiple sub-actions, but there's a limit to how far you can go with that and this issue will always apply.

In a preorder engine, an action is evaluated immediately upon player input (or upon an entity "deciding" to commit the action). Afterwards, the actor waits for a time period (defined by how many turns the action is supposed to take) before being able to act again. This is the standard method used by traditional roguelikes.

In a postorder engine, once the actor decides what to do, that action is delayed until the appropriate number of turns has passed, and then it's evaluated. Thus the action is only "completed" once the number of turns necessary to commit the action has passed. The actor then decides what to do next on the same turn as completing his last action.

This change ultimately affects the gameplay in a subtle way. For instance, if a monster is standing next to you and you choose to attack it, that monster might move away from you before your attack lands. However, we can turn this into a feature. Allow a small chance to "nick" a monster even if it has moved away from where we first attempted to attack it, and combat is now a little more balanced. In addition, we can significantly reduce the base dodge chance for creatures (and the player) and instead replace it with a more strategic system of using up movement actions in order to attempt a semi-reliable dodge. Naturally, this treats the player and all NPCs in the same manner (though faster creatures are inherently better dodgers).

Instead of mashing your way into an NPC and seeing the messages "You hit the ogre. You dodge the ogre's attack. You hit the ogre. You dodge the ogre's attack. etc etc" You have a system where you can choose to either attempt a hit (move into ogre) or attempt a dodge (move away from ogre or move adjacent to ogre). Both options have a chance to fail, but the combination of character skills and player strategy weigh in and make it a compelling gameplay element.


Implementation
Pre-Order

def act(self,currentTurn):
    if self.nextTurn == currentTurn:
        self.move(direction)
        self.nextTurn += 10

Post-Order

def act(self,currentTurn):
    if self.nextTurn == currentTurn:
        self.nextAction()
        self.nexAction = lambda: self.move(direction)
        self.nextTurn += 10