Window Class Implementation
Here is the constructor of Window:
Window::Window()
{
pX = 5;
pY = 5;
...
setupMap();
buildMachine();
}
The player starts off at position (5, 5). We then set up the map and statemachine. Let's proceed with the buildMachine() function:
void Window::buildMachine()
{
machine = new QStateMachine;
QState *inputState = new QState(machine);
inputState->assignProperty(this, "status", "Move the rogue with 2, 4, 6, and 8");
MovementTransition *transition = new MovementTransition(this);
inputState->addTransition(transition);
We enter inputState when the machine is started and from the quitState if the user wants to continue playing. We then set the status to a helpful reminder of how to play the game.
First, the Movement transition is added to the input state. This will enable the rogue to be moved with the keypad. Notice that we don't set a target state for the movement transition. This will cause the transition to be triggered (and the onTransition() function to be invoked), but the machine will not leave the inputState. If we had set inputState as the target state, we would first have left and then entered the inputState again.
QState *quitState = new QState(machine);
quitState->assignProperty(this, "status", "Really quit(y/n)?");
QKeyEventTransition *yesTransition = new
QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_Y);
yesTransition->setTargetState(new QFinalState(machine));
quitState->addTransition(yesTransition);
QKeyEventTransition *noTransition =
new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_N);
noTransition->setTargetState(inputState);
quitState->addTransition(noTransition);
When we enter quitState, we update the status bar of the window.
QKeyEventTransition is a utility class that removes the hassle of implementing transitions for QKeyEvents. We simply need to specify the key on which the transition should trigger and the target state of the transition.
QKeyEventTransition *quitTransition =
new QKeyEventTransition(this, QEvent::KeyPress, Qt::Key_Q);
quitTransition->setTargetState(quitState);
inputState->addTransition(quitTransition);
The transition from inputState allows triggering the quit state when the player types q.
machine->setInitialState(inputState);
connect(machine, SIGNAL(finished()), qApp, SLOT(quit()));
machine->start();
}
The machine is set up, so it's time to start it.
The MovementTransition Class
MovementTransition is triggered when the player request the rogue to be moved (by typing 2, 4, 6, or 8) when the machine is in the inputState.
class MovementTransition : public QEventTransition
{
Q_OBJECT
public:
MovementTransition(Window *window) :
QEventTransition(window, QEvent::KeyPress) {
this->window = window;
}
In the constructor, we tell QEventTransition to only send KeyPress events to the eventTest() function:
protected:
bool eventTest(QEvent *event) {
if (event->type() == QEvent::StateMachineWrapped &&
static_cast<QStateMachine::WrappedEvent *>(event)->event()->type() == QEvent::KeyPress) {
QEvent *wrappedEvent = static_cast<QStateMachine::WrappedEvent *>(event)->event();
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(wrappedEvent);
int key = keyEvent->key();
return key == Qt::Key_2 || key == Qt::Key_8 || key == Qt::Key_6 ||
key == Qt::Key_4;
}
return false;
}
The KeyPress events come wrapped in QStateMachine::WrappedEvents. event must be confirmed to be a wrapped event because Qt uses other events internally. After that, it is simply a matter of checking which key has been pressed.
Let's move on to the onTransition() function:
void onTransition(QEvent *event) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(
static_cast<QStateMachine::WrappedEvent *>(event)->event());
int key = keyEvent->key();
switch (key) {
case Qt::Key_4:
window->movePlayer(Window::Left);
break;
case Qt::Key_8:
window->movePlayer(Window::Up);
break;
case Qt::Key_6:
window->movePlayer(Window::Right);
break;
case Qt::Key_2:
window->movePlayer(Window::Down);
break;
default:
;
}
}
When onTransition() is invoked, we know that we have a KeyPress event with 2, 4, 6, or 8, i.e., the event is already unwrapped.
The Roguelike Tradition
You might have been wondering why the game features a rogue. Well, these kinds of text based dungeon exploration games date back to a game called, yes, "Rogue". Although outflanked by the technology of modern 3D computer games, roguelikes have a solid community of hard-core, devoted followers.
Playing these games can be surprisingly addictive (despite the lack of graphics). Angband, the perhaps most well-known rougelike, is found here: http://rephial.org/.