1 /* Pushover
2  *
3  * Pushover is the legal property of its developers, whose
4  * names are listed in the COPYRIGHT file, which is included
5  * within the source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11 
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16 
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
20  */
21 
22 #include "graphicsn.h"
23 #include "textsections.h"
24 #include "levelplayer.h"
25 #include "levelset.h"
26 #include "ant.h"
27 #include "recorder.h"
28 #include "soundsys.h"
29 #include "screen.h"
30 #include "window.h"
31 #include "solvedmap.h"
32 #include "tools.h"
33 
34 #include <SDL.h>
35 
36 #include <vector>
37 #include <fstream>
38 #include <stdexcept>
39 
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
45 
46 #if defined(__AROS__)
47 #include <proto/dos.h>
48 #endif
49 
check_record(const std::string & rec_path,levelsetList_c & levelsetList,unsigned int & count,unsigned int & failed,std::string checker (const ant_c & a,const levelData_c & l))50 static void check_record(const std::string & rec_path, levelsetList_c & levelsetList,
51                          unsigned int & count, unsigned int & failed,
52                          std::string checker(const ant_c & a, const levelData_c & l)) {
53   recorder_c rec;
54   rec.load(rec_path);
55 
56   graphicsN_c gr("");
57   surface_c surf;
58   levelPlayer_c l(surf, gr);
59   levelsetList.getLevelset(rec.getLevelsetName()).loadLevel(l, rec.getLevelName(), "");
60   ant_c a(l, gr, surf);
61 
62   while (!rec.endOfRecord()) {
63     a.setKeyStates(rec.getEvent());
64     l.performDoors();
65     l.performDominos(a);
66   }
67 
68   // add a few more iterations at the end to make sure the ant has left the level
69   a.setKeyStates(0);
70   for (unsigned int j = 0; j < 100; j++)
71   {
72     l.performDoors();
73     l.performDominos(a);
74   }
75 
76   const std::string error = checker(a, l);
77   if (!error.empty()) {
78     std::cout << rec_path << " " << error << std::endl;
79     failed++;
80   }
81   count++;
82 }
83 
check(int argn,char * argv[],std::string checker (const ant_c & a,const levelData_c & l))84 void check(int argn, char * argv[], std::string checker(const ant_c & a, const levelData_c & l)) {
85   levelsetList_c levelsetList;
86   levelsetList.load("levels", "");
87 
88   unsigned int count = 0;
89   unsigned int failed = 0;
90 
91   for (int i = 0; i < argn; i++)
92   {
93     const std::string path(argv[i]);
94     struct stat st;
95     if (stat(path.c_str(), &st) != 0)
96       throw std::runtime_error("file or directory does not exist: " + path);
97     if (S_ISDIR(st.st_mode))
98     {
99       const std::vector<std::string> entries = directoryEntries(path);
100       for (std::vector<std::string>::const_iterator j = entries.begin(); j != entries.end(); j++) {
101         const std::string & filename = *j;
102         if (filename.size() > 0 && filename[0] != '.')
103           check_record(path + '/' + filename, levelsetList, count, failed, checker);
104       }
105     }
106     else
107     {
108       check_record(path, levelsetList, count, failed, checker);
109     }
110   }
111 
112   std::cout << failed << " out of " << count << " tests failed\n";
113 }
114 
checker1(const ant_c & a,const levelData_c & l)115 std::string checker1(const ant_c & a, const levelData_c & l) {
116 
117     int i;
118     // we succeeded, when the ant has vanished, then it went out of the door
119     if (a.isVisible() == false && l.levelCompleted(i))
120       return "";
121     else
122       return "Level not Finished";
123 }
124 
checker2(const ant_c & a,const levelData_c & l)125 std::string checker2(const ant_c & a, const levelData_c & l) {
126 
127     int i;
128     // we succeeded, when the ant has vanished, then it went out of the door
129     if (a.isVisible() == true || !l.levelCompleted(i))
130       return "";
131     else
132       return "Level not Failed";
133 }
134 
checker3(const ant_c & a,const levelData_c & l)135 std::string checker3(const ant_c & a, const levelData_c & l) {
136   // we succeeded, when the ant has vanished, then it went out of the door
137   for (int y = 0; y < 13; y++)
138     for (int x = 0; x < 20; x++)
139       if (l.getDominoType(x, y) >= levelData_c::DominoTypeCrash0 &&
140           l.getDominoType(x, y) <= levelData_c::DominoTypeCrash5) {
141         return "";
142       }
143 
144   return "Crashes didn't happen";
145 }
146 
getDataDir(void)147 static std::string getDataDir(void)
148 {
149 #if defined (__AROS__)
150   static char binaryPath[1024] = {0};
151   if (!*binaryPath)
152     NameFromLock(GetProgramDir(), binaryPath, sizeof(binaryPath));
153   return std::string(binaryPath);
154 #else
155   struct stat st;
156   return std::string((stat(PKGDATADIR, &st) == 0) ? PKGDATADIR : ".");
157 #endif
158 }
159 
getLocaleDir(void)160 static std::string getLocaleDir(void)
161 {
162 #if defined (__AROS__)
163   static char localePath[1024] = {0};
164   if (!*localePath)
165   {
166     AddPart(localePath, getDataDir().c_str(), sizeof(localePath));
167     AddPart(localePath, "locale", sizeof(localePath));
168   }
169   return localePath;
170 #else
171   struct stat st;
172   return std::string((stat(LOCALEDIR, &st) == 0) ? LOCALEDIR : "locale");
173 #endif
174 }
175 
getKeyMask(void)176 static unsigned int getKeyMask(void) {
177   unsigned int keyMask = 0;
178 
179   Uint8 *keystate = SDL_GetKeyState(NULL);
180 
181   if ( keystate[SDLK_UP] ) keyMask |= KEY_UP;
182   if ( keystate[SDLK_DOWN] ) keyMask |= KEY_DOWN;
183   if ( keystate[SDLK_LEFT] ) keyMask |= KEY_LEFT;
184   if ( keystate[SDLK_RIGHT] ) keyMask |= KEY_RIGHT;
185   if ( keystate[SDLK_SPACE] ) keyMask |= KEY_ACTION;
186 
187   return keyMask;
188 }
189 
190 // make a play tick
191 // return codes:
192 // 0 nothing happened
193 // 1 success
194 // 2 too slow
195 // 3 crashes
196 // 4 not all dominos fell
197 // 5 die
198 // 6 trigger not last to fall
199 // 7 trigger not flat on the ground
200 //
playTick(levelPlayer_c & l,ant_c & a)201 int playTick(levelPlayer_c & l, ant_c & a)
202 {
203   l.performDoors();
204   int res = l.performDominos(a);
205 
206   l.updateBackground();
207   l.drawDominos();
208   a.draw();
209 
210   if (l.triggerIsFalln() && !a.isVisible() && l.isExitDoorClosed() && (res == 0)) {
211 
212     if (l.someTimeLeft())
213     {
214       return 1;
215     }
216     else
217     {
218       return 2;
219     }
220   }
221 
222   if (!a.isLiving())
223     return 5;
224 
225   return res;
226 }
227 
228 // the states of the program
229 typedef enum {
230   ST_INIT,     // initial transition state
231   ST_MAIN,     // main menu
232   ST_PROFILE,  // state for profile selection
233   ST_PROFILE_INIT, // profile selection at startup
234   ST_PROFILE_IN, // profile name input
235   ST_PROFILE_DEL, // select profile to delete
236   ST_LEVELCONF,// configure while playing
237   ST_CONFIG,   // configure menu
238   ST_LEVELSET, // level set selection
239   ST_LEVEL,    // level selection
240   ST_PREREPLAY,// prepare replay for running
241   ST_REPLAY,   // replay currently running
242   ST_PREPLAY,  // prepare level for playing
243   ST_PLAY,     // currently playing
244   ST_FAILDELAY,// play a second more after failing
245   ST_SOLVED,   // current level solved
246   ST_TIMEOUT,  // took longer than level time
247   ST_FAILED,   // current level failed (reason in failreason)
248   ST_HELP,     // help dialog showing
249   ST_QUIT,     // play exit dialog showing
250   ST_EXIT,     // exiting program
251   ST_ABOUT,
252 } states_e;
253 
loadAllLevels(const std::string & datadir,const std::string & userString)254 static levelsetList_c * loadAllLevels(const std::string & datadir, const std::string & userString)
255 {
256   levelsetList_c * levelsetList = new levelsetList_c();
257 
258   levelsetList->load(datadir + "/levels", userString);
259   {
260     const std::string userleveldir(getHome() + "/.pushover/levels");
261     struct stat st;
262     if (stat(userleveldir.c_str(), &st) == 0)
263       levelsetList->load(userleveldir, userString);
264   }
265 
266   return levelsetList;
267 }
268 
269 
main(int argc,char * argv[])270 int main(int argc, char * argv[]) {
271 
272   // filter out the no graphic cases, they are special and will be treated
273   // separately
274   if (argc >= 3 && strcmp(argv[1], "-c") == 0)   // the must complete tests
275   {
276     check(argc-2, argv+2, checker1);
277     return 0;
278   }
279 
280   if (argc >= 3 && strcmp(argv[1], "-y") == 0)   // the must complete tests
281   {
282     check(argc-2, argv+2, checker2);
283     return 0;
284   }
285 
286   if (argc >= 3 && strcmp(argv[1], "-x") == 0)   // the must crash tests
287   {
288     check(argc-2, argv+2, checker3);
289     return 0;
290   }
291 
292   bool fullscreen = false;
293   if (argc >= 2 && strcmp(argv[1], "-f") == 0) fullscreen = true;
294 
295   // setup internationalization
296 #if defined (__AROS__)
297   amigaLocale(LC_MESSAGES);
298 #endif
299   setlocale(LC_MESSAGES, "");
300   bindtextdomain("pushover", getLocaleDir().c_str());
301   bind_textdomain_codeset("pushover", "UTF-8");
302   textdomain("pushover");
303 
304   // now off to all modes that use graphics
305   const std::string datadir = getDataDir();
306   std::string selectedMission;  // the mission that was selected in menu
307 
308   // initialize random number generator
309   srandFromTime();
310 
311   // initialize SDL, graphics, timer, video mode, and level data structure
312   SDL_Init(SDL_INIT_TIMER);
313   SDL_Init(SDL_INIT_VIDEO);
314   SDL_EnableUNICODE(1);
315   atexit(SDL_Quit);
316   graphicsN_c gr(datadir);
317   screen_c screen(gr);
318   if (fullscreen) screen.toggleFullscreen();
319   gr.loadGraphics();
320   initText(datadir);
321   soundSystem_c::instance()->openSound(datadir);
322   levelPlayer_c l(screen, gr);
323   recorder_c rec;
324   ant_c a(l, gr, screen);
325   solvedMap_c solved;
326   std::string lwindowLevel = "";  // when it contains a valid levelname, the level is selected when the levelwindow is opened the next time
327 
328   // prepare the list of levelsets
329   levelsetList_c * levelsetList = loadAllLevels(datadir, "");
330 
331   states_e currentState = ST_INIT, nextState = ST_INIT;
332 
333   if (argc == 3 && strcmp(argv[1], "-r") == 0)
334   {
335     // try to load the record and the level that belongs to it
336     // if it fails, fall back to main menu
337     try {
338       rec.load(argv[2]);
339       selectedMission = rec.getLevelsetName();
340       levelsetList->getLevelset(selectedMission).loadLevel(l, rec.getLevelName(), "");
341       a.initForLevel();
342       soundSystem_c::instance()->playMusic(datadir+"/themes/"+l.getTheme()+".ogg");
343 
344       nextState = ST_REPLAY;
345     }
346     catch  (...) {
347       nextState = ST_PROFILE_INIT;
348     }
349   }
350   else if (argc == 3)
351   {
352     // start with a given level set and level name
353     selectedMission = argv[1];
354     std::string levelName = argv[2];
355 
356     // try to load the given levelset and level, if
357     // it fails fall back to main menu
358     try {
359 
360       levelsetList->getLevelset(selectedMission).loadLevel(l, levelName, "");
361       a.initForLevel();
362       soundSystem_c::instance()->playMusic(datadir+"/themes/"+l.getTheme()+".ogg");
363 
364       nextState = ST_PREPLAY;
365     }
366     catch (...) {
367       nextState = ST_PROFILE_INIT;
368     }
369   }
370   else if (argc == 2)
371   {
372     // start with a given file if the file exists,
373     // load it and play is, otherwise go to main menu
374     std::ifstream file(argv[1]);
375     if (file)
376     {
377       textsections_c sections(file, true);
378       l.load(sections, "");
379       selectedMission = "Original";            // TODO we need to find out which levelset this file belongs to
380       soundSystem_c::instance()->playMusic(datadir+"/themes/"+l.getTheme()+".ogg");
381 
382       nextState = ST_PREPLAY;
383     }
384     else
385     {
386       nextState = ST_PROFILE_INIT;
387       screen.markAllDirty();
388     }
389   }
390   else
391   {
392     nextState = ST_PROFILE_INIT;
393     screen.markAllDirty();
394   }
395 
396 
397   // not we have initialized, lets get playing in the main state
398   bool exitProgram = false;
399   Uint32 ticks = SDL_GetTicks();
400 
401   window_c * window = 0; // the currently visible window
402   unsigned int failReason = 0;
403   unsigned int failDelay = 0; // a counter to delay the fail window a bit after failing
404 
405   if (nextState == ST_MAIN)
406   {
407     soundSystem_c::instance()->playMusic(datadir+"/themes/option.ogg");
408   }
409 
410   try {
411 
412     while (!exitProgram) {
413 
414       // wait for the right amount of time for the next frame
415       ticks += 1000/18;
416       if (SDL_GetTicks() < ticks)
417         SDL_Delay(ticks-SDL_GetTicks());
418 
419       while (true) {
420 
421         if (nextState != currentState) {
422 
423           // switch states, first leave the old one
424           switch (currentState) {
425 
426             case ST_MAIN:
427             case ST_HELP:
428             case ST_QUIT:
429             case ST_LEVELSET:
430             case ST_LEVEL:
431             case ST_CONFIG:
432             case ST_LEVELCONF:
433             case ST_SOLVED:
434             case ST_FAILED:
435             case ST_ABOUT:
436             case ST_PROFILE:
437             case ST_PROFILE_INIT:
438             case ST_PROFILE_IN:
439             case ST_PROFILE_DEL:
440             case ST_TIMEOUT:
441               delete window;
442               window = 0;
443               l.drawDominos();
444               a.draw();
445               break;
446 
447             case ST_PREREPLAY:
448             case ST_PREPLAY:
449             case ST_INIT:
450             case ST_REPLAY:
451             case ST_PLAY:
452             case ST_FAILDELAY:
453             case ST_EXIT:
454               break;
455 
456           }
457 
458           // now enter the new one
459           switch (nextState) {
460 
461             case ST_MAIN:     window = getMainWindow(screen, gr); break;
462             case ST_PROFILE_INIT:
463             case ST_PROFILE:  window = getProfileWindow(solved, screen, gr); break;
464             case ST_PROFILE_IN: window = getProfileInputWindow(screen, gr); break;
465             case ST_PROFILE_DEL: window = getProfileSelector(solved, screen, gr); break;
466             case ST_LEVELSET: window = getMissionWindow(*levelsetList, screen, gr, selectedMission); break;
467             case ST_QUIT:     window = getQuitWindow(screen, gr); break;
468             case ST_LEVELCONF:
469             case ST_CONFIG:   window = getConfigWindow(screen, gr); break;
470             case ST_SOLVED:   window = getSolvedWindow(screen, gr); break;
471             case ST_ABOUT:    window = getAboutWindow(screen, gr); break;
472             case ST_FAILED:   window = getFailedWindow(failReason, screen, gr); break;
473             case ST_TIMEOUT:  window = getTimeoutWindow(screen, gr); break;
474 
475 
476             case ST_LEVEL:
477                 window = getLevelWindow(levelsetList->getLevelset(selectedMission), solved, screen, gr, lwindowLevel);
478                 lwindowLevel = "";
479                 break;
480 
481             case ST_HELP:
482                               {
483                                 std::string text;
484                                 if (l.someTimeLeft())
485                                   text = _("Arrange dominos in a run so that trigger falls last. You have 1 push.");
486                                 else
487                                   text = l.getHint();
488 
489                                 window = new helpWindow_c(text, screen, gr);
490                               }
491                               break;
492 
493             case ST_PREREPLAY:
494             case ST_PREPLAY:
495                               l.updateBackground();
496                               l.drawDominos();
497                               a.draw();
498                               ticks = SDL_GetTicks();    // this might have taken some time so reinit the ticks
499                               break;
500 
501             case ST_PLAY:
502                               ticks = SDL_GetTicks();    // the flip might have taken some time too long
503                               rec.setLevel(selectedMission, l.getName());
504                               rec.reset();
505                               break;
506             case ST_INIT:
507             case ST_REPLAY:
508             case ST_FAILDELAY:
509                               break;
510 
511             case ST_EXIT:
512                               exitProgram = true;
513                               break;
514 
515           }
516 
517           currentState = nextState;
518         }
519 
520         // if there is no event to process, leave the loop, otherwise handle the event and loop again
521         SDL_Event event;
522         if (!SDL_PollEvent(&event)) break;
523 
524         // check for quit-event
525         if (event.type == SDL_QUIT)
526         {
527           // exit program, wherever we are
528           nextState = ST_EXIT;
529           break;
530         }
531 
532         switch (currentState) {
533 
534           case ST_MAIN:
535             if (!window) {
536               std::cout << "Oops no window\n";
537               nextState = ST_MAIN;
538             }
539             else
540             {
541               window->handleEvent(event);
542               if (window->isDone())
543               {
544                 switch(dynamic_cast<listWindow_c*>(window)->getSelection())
545                 {
546                   case 0: nextState = ST_LEVELSET; break;// select level set
547                   case 1: nextState = ST_CONFIG; break;  // open config menu
548                   case 2: nextState = ST_PROFILE; break; // open profile selector
549                   case 3: nextState = ST_ABOUT; break;   // about window
550                   case 4: nextState = ST_EXIT; break;    // exit program
551                 }
552               }
553             }
554             break;
555 
556           case ST_PROFILE:
557           case ST_PROFILE_INIT:
558             if (!window) {
559               std::cout << "Oops no window\n";
560               nextState = ST_MAIN;
561             }
562             else
563             {
564               window->handleEvent(event);
565               if (window->isDone())
566               {
567                 unsigned int sel = dynamic_cast<listWindow_c*>(window)->getSelection();
568                 if (sel < solved.getNumberOfUsers())
569                 {
570                   solved.selectUser(sel);
571                   delete levelsetList;
572                   levelsetList = loadAllLevels(datadir, solved.getUserString());
573 
574                   nextState = ST_MAIN;
575                 }
576                 else if (sel == solved.getNumberOfUsers())
577                 {
578                   nextState = ST_PROFILE_IN;
579                 }
580                 else if (sel == solved.getNumberOfUsers()+1)
581                 {
582                   nextState = ST_PROFILE_DEL;
583                 }
584                 else
585                 {
586                   nextState = ST_MAIN;
587                 }
588               }
589             }
590             break;
591 
592           case ST_PROFILE_DEL:
593             if (!window) {
594               std::cout << "Oops no window\n";
595               nextState = ST_MAIN;
596             }
597             else
598             {
599               window->handleEvent(event);
600               if (window->isDone())
601               {
602                 size_t s = dynamic_cast<listWindow_c*>(window)->getSelection();
603 
604                 if (s+1 < solved.getNumberOfUsers())
605                 {
606                   // valid selection available
607 
608                   // if the currently selected profile is deleted, go to default
609                   if (s+1 == solved.getCurrentUser())
610                   {
611                     solved.selectUser(0);
612                     delete levelsetList;
613                     levelsetList = loadAllLevels(datadir, solved.getUserString());
614                   }
615 
616                   solved.deleteUser(s+1);
617                 }
618 
619                 nextState = ST_PROFILE;
620               }
621             }
622             break;
623 
624           case ST_PROFILE_IN:
625             if (!window) {
626               std::cout << "Oops no window\n";
627               nextState = ST_MAIN;
628             }
629             else
630             {
631               window->handleEvent(event);
632               if (window->isDone())
633               {
634                 if (!dynamic_cast<InputWindow_c*>(window)->hasEscaped())
635                 {
636                   solved.addUser(dynamic_cast<InputWindow_c*>(window)->getText());
637                   delete levelsetList;
638                   levelsetList = loadAllLevels(datadir, solved.getUserString());
639                   nextState = ST_MAIN;
640                 }
641                 else
642                 {
643                   nextState = ST_PROFILE;
644                 }
645               }
646             }
647             break;
648 
649           case ST_CONFIG:
650           case ST_LEVELCONF:
651             if (!window) {
652               std::cout << "Oops no window\n";
653               nextState = ST_MAIN;
654             }
655             else
656             {
657               window->handleEvent(event);
658               if (window->isDone())
659               {
660                 switch(dynamic_cast<listWindow_c*>(window)->getSelection())
661                 {
662                   case 0:   // toggle full screen
663                     screen.toggleFullscreen();
664                     screen.markAllDirty();
665                     window->resetWindow();
666                     break;
667 
668                   case 1:  // toggle sound effects
669                     soundSystem_c::instance()->toggleSound();
670                     window->resetWindow();
671                     break;
672 
673                   case 2:  // toggle music
674                     soundSystem_c::instance()->toggleMusic();
675                     window->resetWindow();
676                     break;
677 
678                   default:
679                     if (currentState == ST_CONFIG)
680                       nextState = ST_MAIN; // back to main menu
681                     else
682                       nextState = ST_QUIT; // back to level quit dialog
683 
684                     break;
685                 }
686               }
687             }
688             break;
689 
690           case ST_LEVELSET:
691             if (!window) {
692               std::cout << "Oops no window\n";
693               nextState = ST_MAIN;
694             }
695             else
696             {
697               window->handleEvent(event);
698               if (window->isDone())
699               {
700                 unsigned int sel = dynamic_cast<listWindow_c*>(window)->getSelection();
701                 if (sel >= levelsetList->getLevelsetNames().size())
702                   nextState = ST_MAIN;
703                 else
704                 {
705                   nextState = ST_LEVEL;
706                   lwindowLevel = "";
707                   selectedMission = levelsetList->getLevelsetNames()[sel];
708                 }
709               }
710             }
711             break;
712 
713           case ST_LEVEL:
714             if (!window) {
715               std::cout << "Oops no window\n";
716               nextState = ST_MAIN;
717             }
718             else
719             {
720               window->handleEvent(event);
721               if (window->isDone())
722               {
723                 unsigned int sel = dynamic_cast<listWindow_c*>(window)->getSelection();
724                 levelset_c ls = levelsetList->getLevelset(selectedMission);
725 
726                 if (sel >= ls.getLevelNames().size())
727                   nextState = ST_LEVELSET;
728                 else
729                 {
730                   nextState = ST_PREPLAY;
731                   ls.loadLevel(l, ls.getLevelNames()[sel], solved.getUserString());
732                   soundSystem_c::instance()->playMusic(datadir+"/themes/"+l.getTheme()+".ogg");
733                   a.initForLevel();
734                 }
735               }
736             }
737             break;
738 
739           case ST_PLAY:
740             if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) nextState = ST_QUIT;
741             if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_F1)     nextState = ST_HELP;
742             if (event.type == SDL_KEYDOWN && event.key.keysym.sym == 'r')         rec.save("man");
743             break;
744 
745           case ST_PREPLAY:
746             if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) nextState = ST_PLAY;
747             break;
748 
749           case ST_PREREPLAY:
750             if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) nextState = ST_REPLAY;
751             break;
752 
753           case ST_REPLAY:
754             if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE)
755             {
756               rec.truncate();
757               nextState = ST_PLAY;
758             }
759             break;
760 
761           case ST_HELP:
762             if (!window) {
763               std::cout << "Oops no window\n";
764               nextState = ST_MAIN;
765             }
766             else
767             {
768               window->handleEvent(event);
769               if (window->isDone())
770                 nextState = ST_PLAY;
771             }
772             break;
773 
774           case ST_ABOUT:
775             if (!window) {
776               std::cout << "Oops no window\n";
777               nextState = ST_MAIN;
778             }
779             else
780             {
781               window->handleEvent(event);
782               if (window->isDone())
783                 nextState = ST_MAIN;
784             }
785             break;
786 
787           case ST_QUIT:
788             if (!window) {
789               std::cout << "Oops no window\n";
790               nextState = ST_MAIN;
791             }
792             else
793             {
794               window->handleEvent(event);
795               if (window->isDone())
796               {
797                 switch(dynamic_cast<listWindow_c*>(window)->getSelection())
798                 {
799                   case 3:
800                     nextState = ST_LEVEL;
801                     soundSystem_c::instance()->playMusic(datadir+"/themes/option.ogg");
802                     lwindowLevel = l.getName();
803                     break;    // return to level list
804                   case 1:
805                           {       // restart level
806                             nextState = ST_PLAY;
807                             levelset_c ls = levelsetList->getLevelset(selectedMission);
808                             ls.loadLevel(l, l.getName(), solved.getUserString());
809                             a.initForLevel();
810                           }
811                           break;
812                   case 2:  // configuration
813                     nextState = ST_LEVELCONF;
814                     break;
815                   default:
816                   case 0:
817                     nextState = ST_PLAY;
818                     break;    // return to play
819                 }
820               }
821             }
822             break;
823 
824           case ST_SOLVED:
825             if (!window) {
826               std::cout << "Oops no window\n";
827               nextState = ST_MAIN;
828             }
829             else
830             {
831               window->handleEvent(event);
832               if (window->isDone())
833               {
834                 switch(dynamic_cast<listWindow_c*>(window)->getSelection())
835                 {
836                   case 0:
837                     nextState = ST_LEVEL;
838                     lwindowLevel = "";
839                     break; // select next level to play
840                 }
841               }
842             }
843             break;
844 
845           case ST_FAILED:
846             if (!window) {
847               std::cout << "Oops no window\n";
848               nextState = ST_MAIN;
849             }
850             else
851             {
852               window->handleEvent(event);
853               if (window->isDone())
854               {
855                 switch(dynamic_cast<listWindow_c*>(window)->getSelection())
856                 {
857                   case 0:                            // try again
858                     {
859                       // find the current level
860                       levelset_c ls = levelsetList->getLevelset(selectedMission);
861 
862                       std::vector<std::string> levels = ls.getLevelNames();
863 
864                       bool foundLevel = false;
865 
866                       for (unsigned int i = 0; i < levels.size(); i++)
867                       {
868                         if (levels[i] == l.getName())
869                         {
870                           ls.loadLevel(l, levels[i], solved.getUserString());
871                           a.initForLevel();
872                           foundLevel = true;
873                           break;
874                         }
875                       }
876 
877                       if (!foundLevel)
878                       {
879                         nextState = ST_MAIN;
880                       }
881                       else
882                       {
883                         nextState = ST_PLAY;
884                       }
885                     }
886                     break;
887                   case 1:
888                     nextState = ST_LEVEL;
889                     lwindowLevel = l.getName();
890                     soundSystem_c::instance()->playMusic(datadir+"/themes/option.ogg");
891                     break;  // back to level list
892                 }
893               }
894             }
895             break;
896 
897           case ST_TIMEOUT:
898             if (!window) {
899               std::cout << "Oops no window\n";
900               nextState = ST_MAIN;
901             }
902             else
903             {
904               window->handleEvent(event);
905               if (window->isDone())
906               {
907                 switch(dynamic_cast<listWindow_c*>(window)->getSelection())
908                 {
909                   case 0:                            // try again
910                     {
911                       // find the current level
912                       levelset_c ls = levelsetList->getLevelset(selectedMission);
913 
914                       std::vector<std::string> levels = ls.getLevelNames();
915 
916                       bool foundLevel = false;
917 
918                       for (unsigned int i = 0; i < levels.size(); i++)
919                       {
920                         if (levels[i] == l.getName())
921                         {
922                           ls.loadLevel(l, levels[i], solved.getUserString());
923                           a.initForLevel();
924                           foundLevel = true;
925                           break;
926                         }
927                       }
928 
929                       if (!foundLevel)
930                       {
931                         nextState = ST_MAIN;
932                       }
933                       else
934                       {
935                         nextState = ST_PLAY;
936                       }
937                     }
938                     break;
939                   case 1:                            // continue to next level
940                     nextState = ST_LEVEL;
941                     lwindowLevel = "";
942                     break; // select next level to play
943                 }
944               }
945             }
946             break;
947 
948           case ST_INIT:
949           case ST_FAILDELAY:
950           case ST_EXIT:
951             break;
952         }
953       }
954 
955       // do the handling of the current state
956 
957       switch (currentState) {
958 
959         case ST_PREPLAY:
960           if (screen.flipAnimate()) nextState = ST_PLAY;
961           break;
962 
963         case ST_PREREPLAY:
964           if (screen.flipAnimate()) nextState = ST_REPLAY;
965           break;
966 
967         case ST_FAILDELAY:
968           if (failDelay > 0)
969           {
970             failDelay--;
971             a.setKeyStates(0);
972             playTick(l, a);
973           }
974           else
975           {
976             nextState = ST_FAILED;
977           }
978           break;
979 
980         case ST_REPLAY:
981           if (rec.endOfRecord())
982             nextState = ST_PLAY;
983           else
984           {
985             a.setKeyStates(rec.getEvent());
986             if (playTick(l, a))
987               nextState = ST_MAIN;
988             break;
989           }
990           // intentionally fall through to ST_PLAY when
991           // the record has ended to allow the player to
992           // continue playing
993 
994         case ST_PLAY:
995           {
996             unsigned int keyMask = getKeyMask();
997             rec.addEvent(keyMask);
998             a.setKeyStates(keyMask);
999           }
1000           failReason = playTick(l, a);
1001 
1002           if (l.levelInactive())
1003           {
1004             switch (failReason) {
1005               case 1:
1006                 rec.save("sol");
1007                 solved.addLevel(l.getChecksum());
1008                 nextState = ST_SOLVED;
1009                 break;
1010               case 0:
1011                 break;
1012               case 2:
1013                 rec.save("time");
1014                 solved.addLevel(l.getChecksumNoTime());
1015                 nextState = ST_TIMEOUT;
1016                 break;
1017               default:
1018                 failDelay = 36;
1019                 nextState = ST_FAILDELAY;
1020                 break;
1021             }
1022           }
1023           break;
1024 
1025         case ST_INIT:
1026         case ST_MAIN:
1027         case ST_PROFILE:
1028         case ST_PROFILE_INIT:
1029         case ST_PROFILE_IN:
1030         case ST_PROFILE_DEL:
1031         case ST_CONFIG:
1032         case ST_LEVELCONF:
1033         case ST_LEVELSET:
1034         case ST_LEVEL:
1035         case ST_SOLVED:
1036         case ST_FAILED:
1037         case ST_TIMEOUT:
1038         case ST_HELP:
1039         case ST_QUIT:
1040         case ST_EXIT:
1041         case ST_ABOUT:
1042           break;
1043       }
1044 
1045       // flip the screen, but not when in the preplaymodes
1046       if (currentState != ST_PREPLAY && currentState != ST_PREREPLAY)
1047       {
1048         screen.flipDirty();
1049         screen.clearDirty();
1050       }
1051     }
1052   }
1053 
1054   catch (...) {
1055 
1056     if (currentState == ST_PLAY)
1057       rec.save("err");
1058 
1059   }
1060 
1061   return 0;
1062 }
1063