1 /***************************************************************************
2 cuyo.cpp - description
3 -------------------
4 begin : Mit Jul 12 22:54:51 MEST 2000
5 copyright : (C) 2000 by Immi
6 email : cuyo@pcpool.mathematik.uni-freiburg.de
7
8 Modified 2001-2008,2010,2011,2014 by the cuyo developers
9 Maintenance modifications 2012 by Bernhard R. Link
10 Maintenance modifications 2012 by the cuyo developers
11
12 ***************************************************************************/
13
14 /***************************************************************************
15 * *
16 * This program is free software; you can redistribute it and/or modify *
17 * it under the terms of the GNU General Public License as published by *
18 * the Free Software Foundation; either version 2 of the License, or *
19 * (at your option) any later version. *
20 * *
21 ***************************************************************************/
22
23 /* 1 bedeutet, dass manche Signale abgefangen werden, um die Log-Datei
24 abzuspeichern. */
25 #define signale_fangen 0
26
27
28
29 #include <cstdlib>
30 #include <cstdio>
31
32 #if signale_fangen
33 #include <csignal>
34 #endif
35
36 #include <SDL.h>
37
38 #include "cuyointl.h"
39 #include "sound.h"
40 #include "aufnahme.h"
41 #include "prefsdaten.h"
42 #include "kiplayer.h"
43 #include "spielfeld.h"
44 #include "fehler.h"
45 #include "global.h"
46 #include "sdltools.h"
47
48 #include "ui.h"
49
50 #include "ui2cuyo.h"
51 #include "cuyo.h"
52
53 /* # Zeitschritte, die nach der Zeitbonus-Animation noch gewartet wird */
54 #define nachbonus_wartezeit 50
55
ngettext(char * msgid,const char * msgid_plural,unsigned long int n)56 char * ngettext (char * msgid, const char * msgid_plural, unsigned long int n) {return msgid;}
57
58
59
60 namespace Cuyo {
61
62 /*************************** Private Variablen **************************/
63 /* (stehen nicht in der .h-Datei) */
64
65 /***** Debug-Variablen *****/
66 /* Es gibt au�erdem noch die globale Variable gDebug; */
67 bool mEinzelschritt;
68 bool mZeitlupe;
69 int mZaehlerZeitlupe;
70 bool mRueberReihenTest;
71 bool mAbspielen;
72 bool mSchnellvorlauf;
73
74
75
76 /** Das Bild, das angezeigt wird, w�hrend das Spiel auf Pause steht. */
77 Bilddatei * mPauseBild;
78
79
80 /** Die beiden Spielfelder... */
81 Spielfeld* mSpielfeld[max_spielerzahl];
82
83 /** Computer-Spieler. */
84 KIPlayer * mKI;
85
86
87
88 /***** Zustandsvariablen *****/
89
90 /** global-Modus (Spieler�bergreifend). */
91 enum global_modus {
92 gmodus_kein_spiel,
93 gmodus_spiel_start, // wie gmodus_spiel, aber Text muss noch gel�scht werden
94 gmodus_spiel,
95 /* die folgenden beiden Modi bedeuten beide, dass das
96 Spiel (der Level) zu Ende geht, aber dass wir noch warten, bis
97 die letzten Animationen fertig abgelaufen sind */
98 gmodus_warte_verloren, // ... weil ein Spieler tot ist
99 gmodus_warte_gewonnen, // ... weil der Level fertig ist
100 gmodus_bonus_animation, // Zeit-Bonus bekommen...
101 gmodus_bonus_warte, // Nach dem Zeit-Bonus noch ein bisschen warten
102 gmodus_ende_warte // Kurz vor gmodus_kein_spiel: Benutzer darf noch
103 // seine Punkte bewundern. Wenn er eine Taste drueckt, geht's
104 // Zurueck ins Menue.
105 } mGModus;
106
107
108 /** True, wenn das Spiel auf Pause steht */
109 bool mPause;
110
111
112 /** true, wenn das Spiel grad nicht weitergehen soll, sondern auf einen
113 Tastendruck gewartet wird. */
114 bool mWarteAufTaste;
115 /* Wenn > 0, wird h�chstens so lange auf Taste gewartet */
116 int mWarteTimeout;
117
118 /** Hier wird reingespeichert,
119 welche Version (zuletzt) auf der Kommandozeile stand.
120 Achtung! Das passiert schon vor Cuyo::init(), also vor der
121 Lebenszeit von Cuyo, wenn Cuyo eine h�tte. */
122 Version mKommandoZeilenVersion;
123
124 /** Die Versionen, die �ber die Kommandozeile �bergeben wurden,
125 und die wir nicht anders verwalten. */
126 Version mZusatzVersionen;
127 int mSchwierig;
128 int mLevelpack;
129 /** Ein oder zwei Spieler? (Hat auch beim Spielen gegen die KI den Wert 2,
130 und *nicht* spz_ki) */
131 int mSpielerZahl;
132 /** Falls zwei Spieler: Gegen Computer? */
133 bool mGegenKI;
134
135 /** Aktuelle Level-Nummer, wenn ein Spiel l�uft. Sonst undefiniert. */
136 int mLevelNr;
137 /** (Interner) Name des aktuellen Levels. Enth�lt, wenn grade kein Spiel
138 l�uft, den Namen vom Level zum weiterspielen. Ist (wenn kein Spiel
139 l�uft) "", wenn es keinen gibt. */
140 Str mIntLevelName;
141 int mPunkte[max_spielerzahl];
142 /** Wird nur w�hrend der Zeitbonus-Animation gebraucht... */
143 int mZeitBonus;
144
145
146
147
148 /*************************** Private Methoden **************************/
149 /* (stehen nicht in der .h-Datei) */
150
151
152 /** tut alles, was beim Starten eines Levels
153 getan werden muss; liefert false, wenn es
154 den Level gar nicht mehr gibt. Throwt bei Fehler. */
155 bool startLevel();
156 /** tut alles, was beim Stoppen eines Levels
157 getan werden muss (ohne Animation, d. h. entweder
158 ist die Animation schon vorbei oder es gibt halt keine). */
159 void stopLevel();
160 /** Setzt die Punktzahl f�r Spieler sp */
161 void setPunkte(int sp, int pu);
162 /** Gibt die Fehlermeldung bestehend aus t und fe aus:
163 Sowohl als Text im Cuyo-Fenster
164 (bei anz_sp vielen Spielern) als auch auf stderr.
165 mitLog wird an fe.getText() weitergegeben. (D. h.: soll
166 ggf. die Send-Log-Meldung ausgegeben werden?) */
167 void printFehler(int anz_sp, Str t, const Fehler & fe,
168 bool mitLog = false);
169
170
171 /** Macht einen Schritt der Hetzrand-kommt-am-Ende-runter-Animation. */
172 void bonusAnimationSchritt();
173 /** Unterfunktion von zeitSchritt();
174 Hier passiert die eigentliche Arbeit. */
175 void zeitSchrittIntern();
176
177 /** stoppt das Spiel sofort (egal, ob grad ein Level l�uft oder nicht) */
178 void stopSpiel(bool noch_anzeigen = false);
179 /** Die Haupt-Spielschritt-Routine, w�hrend das Spiel l�uft.
180 Ruft alle anderen spielschritt()-Routinen auf. */
181 void spielSchritt();
182 /** Wird von startLevel() und von spielSchritt() aufgerufen. L�sst
183 s�mtliche Blops animieren. */
184 void animiere();
185
186 Version berechneVersion();
187
188 /* �bernimmt die Dinge, die durch version spezifiziert werden.
189 Alles andere bleibt beim alten,
190 bis auf da� der alte Wert von mZusatzVersionen verlorengeht. */
191 void setzeVersion(const Version & version);
192
193
194
195 /** Liefert den Namen und Pfad der Prefs-Datei zur�ck
196 ($HOME/.cuyo) */
197 Str getPrefsName();
198 /** Liefert den Namen und Pfad der Log-Datei zur�ck
199 ($HOME/cuyo.log) */
200 Str getLogName();
201
202
203 void signaleAn();
204 void signaleAus();
205
206
207
208 /** Startet das Spiel f�r die eingestellte Spielerzahl und mit dem
209 angegebenen Level */
startSpiel(int level)210 void startSpiel(int level) {
211 CASSERT(mGModus == gmodus_kein_spiel);
212
213 ld->ladLevelSummary(false,berechneVersion());
214
215 mLevelNr = level;
216 mIntLevelName =
217 (mLevelNr>ld->getLevelAnz() ? "" : ld->getIntLevelName(level));
218 PrefsDaten::setLastLevel(mIntLevelName);
219
220 /* Damit der Level nicht schon beim Start angehalten ist oder schnell
221 l�uft */
222 mEinzelschritt = false;
223 mSchnellvorlauf = false;
224
225 for (int i = 0; i < max_spielerzahl; i++)
226 setPunkte(i, 0);
227
228 try {
229 if (!startLevel())
230 throw Fehler("%s","There are no (more?) (working?) levels.");
231
232 } catch (Fehler f) {
233 print_to_stderr(f.getText()+"\n");
234 for (int i = 0; i < mSpielerZahl; i++)
235 mSpielfeld[i]->setText(f.getText(), true);
236
237 /* Sicherheitshalber...: */
238 stopSpiel();
239
240 return;
241 }
242 }
243
244
245
246
247 /** tut alles, was beim Starten eines Levels
248 getan werden muss; liefert false, wenn es
249 den Level gar nicht mehr gibt. Throwt bei Fehler. */
startLevel()250 bool startLevel() {
251
252 /* So viele Level gibt's doch gar nicht */
253 if (mLevelNr > ld->getLevelAnz())
254 return false;
255
256 /* Der Levelname sollte schon bekannt sein. Wir geben ihn gleich mal im
257 Fenstertitel aus. */
258 SDLTools::setLevelTitle(ld->getLevelName(mLevelNr));
259
260 /* Muss der Level neu geladen werden, oder ist er noch von vorher
261 im Speicher? */
262 if (!ld->mLevelGeladen || ld->mLevelNummer != mLevelNr) {
263
264 /* Ok, neu laden. Erst mal alten Level entladen, damit w�hrend
265 der "loading level"-Anzeige nicht noch falsche Informationen
266 �ber den alten Level angezeigt werden. */
267 ld->entladLevel();
268
269 /* F�r "Loading level" */
270 ld->setSchriftFarbe(Color(80, 80, 80));
271
272 /* Level-Daten laden */
273 for (int i = 0; i < mSpielerZahl; i++) {
274 mSpielfeld[i]->ladeLevelModus();
275 mSpielfeld[i]->setText(_sprintf(_("Score: %d\n\nLoading Level %d...\n\n"),
276 mPunkte[i], mLevelNr));
277 }
278 /* "Loading Level" gleich anzeigen. Au�erdem den Rand neu malen, da
279 wir die Schriftfarbe ge�ndert haben und das Numexplode vom alten
280 level ung�ltig ist und so. */
281 UI::randNeuMalen();
282 UI::sofortAllesAnzeigen();
283
284 /* So, und jetzt den neuen Level laden. (Braucht etwas Zeit) */
285 ld->ladLevel(mLevelNr);
286
287 }
288 else ld->erneuerLevel(mLevelNr);
289
290 //for (int i = 0; i < max_spielerzahl; i++) {
291 // /* Punkte-Anzeige muss geupdatet werden: Zu Level passende Farbe.
292 // (Etwas gepfuscht) */
293 // setPunkte(i, mPunkte[i]);
294 //}
295 /* Einige Farben haben sich durch das level-laden ge�ndert. Also
296 nach dem Level alles neu malen. Und da die mal-Routine nicht auf
297 die Idee kommt, dass die R�nder um den Level neu an den X-Server
298 geschickt werden m�ssen, selbst Area::updateAll() aufrufen. */
299 UI::randNeuMalen();
300
301 /* Aufnehmen / Abspielen starten */
302 Aufnahme::init(mAbspielen, getSpielerModus());
303
304 /***** Allen Leuten erz�hlen, dass jetzt ein Level anf�ngt. *****/
305
306 /* F�r den Global-Blop... */
307 ld->startLevel();
308
309 /* Hier ist die Stelle, wo wir darauf gucken, wie viele Spieler mitspielen;
310 nur f�r so viele Spieler wird das Spiel wirklich gestartet */
311 for (int i = 0; i < mSpielerZahl; i++) {
312
313 mSpielfeld[i]->startLevel();
314 Str PlatzAnzahlFormat;
315 if (ld->mPlatzAnzahlMin == ld->mPlatzAnzahlMax)
316 PlatzAnzahlFormat=_sprintf(ngettext("1 blop explodes",
317 "%d blops explode",
318 ld->mPlatzAnzahlMin),
319 ld->mPlatzAnzahlMin);
320 else
321 if (ld->mPlatzAnzahlAndere)
322 PlatzAnzahlFormat=_sprintf(_("between %d and %d blops explode"),
323 ld->mPlatzAnzahlMin,ld->mPlatzAnzahlMax);
324 else
325 PlatzAnzahlFormat=_sprintf(_("%d or %d blops explode"),
326 ld->mPlatzAnzahlMin,ld->mPlatzAnzahlMax);
327 // TRANSLATORS: The second %s (after "by ") is the level's author's name
328 mSpielfeld[i]->setText(_sprintf(_("Score: %d\n\n"
329 "Level %d\n%s\nby %s\n\n"
330 "%s\n%s\n"
331 "%s\n\n"
332 "Space = Start"),
333 mPunkte[i],
334 mLevelNr, ld->mLevelName.data(), ld->mLevelAutor.data(),
335 PlatzAnzahlFormat.data(),
336 ld->mGrasBeiKettenreaktion ? _("Chain reaction necessary\n") : "",
337 ld->mBeschreibung.data()));
338 }
339
340 /* Init-Events veschicken */
341 Blop::sendeGeschedulteEvents();
342
343 /* Alle Blops einmal animieren, damit sie wissen, wie sie aussehen.
344 Dabei werden auch die ganzen Init-Events verschickt. */
345 animiere();
346
347 /* Auch die KI m�chte wissen, wenn ein neuer Level anf�ngt. */
348 if (mGegenKI)
349 mKI->startLevel();
350
351
352 Sound::setMusic(ld->mMusik);
353
354 mGModus = gmodus_spiel_start;
355 mPause = false;
356 mWarteAufTaste = true;
357 mWarteTimeout = 0;
358 return true;
359 }
360
361
362
363 /** tut alles, was beim Stoppen eines Levels
364 getan werden muss (ohne Animation, d. h. entweder
365 ist die Animation schon vorbei oder es gibt halt keine). */
stopLevel()366 void stopLevel() {
367 SDLTools::setMainTitle();
368
369 for (int i = 0; i < mSpielerZahl; i++)
370 mSpielfeld[i]->stopLevel();
371 }
372
373
374
375
376 /** Setzt die Punktzahl f�r Spieler sp */
setPunkte(int sp,int pu)377 void setPunkte(int sp, int pu){
378 mPunkte[sp] = pu;
379 UI::setPunkte(sp, pu);
380 }
381
382
383
384
385
386
387
388
389
390
391
392
393 /** stoppt das Spiel sofort (egal, ob grad ein Level l�uft oder nicht) */
stopSpiel(bool noch_anzeigen)394 void stopSpiel(bool noch_anzeigen/* = false*/) {
395 /* Evtl. wird stopSpiel() aufgerufen, wenn das Spiel noch gar nicht
396 richtig fertig gestartet wurde; dann wurde mGModus vielleicht noch
397 nicht gesetzt, aber es soll trotzdem was gestoppt werden... */
398 /*if (mGModus == gmodus_kein_spiel)
399 return;*/
400
401 stopLevel();
402
403 if (noch_anzeigen) {
404 mGModus = gmodus_ende_warte;
405 mWarteAufTaste = true;
406 } else {
407 mGModus = gmodus_kein_spiel;
408 UI::stopSpiel();
409 }
410 }
411
412
413
414
415
416
417
418
419
420
421 /** Die Haupt-Spielschritt-Routine, w�hrend das Spiel l�uft.
422 Ruft alle anderen spielschritt()-Routinen auf. */
spielSchritt()423 void spielSchritt() {
424
425 /* Den Schritt aufnehmen bzw. abspielen. Beim Abspielen werden
426 hier auch ggf. Tastendr�cke abgespielt. */
427 Aufnahme::recSchritt(mSpielfeld);
428
429 /* Ggf. einen Spielschritt f�r die KI */
430 if (mGegenKI)
431 mKI->spielSchritt();
432
433
434 /*** Die einzelnen Teile eines Spielschritts ausf�hren: ***/
435
436 /* Who will be the one killing me for this #define? */
437 /* Answer: The first one to try
438 for (int i=1; i<=10; i++)
439 ALLE_SPIELER->do_stuff(i);
440 */
441 #define ALLE_SPIELER for (int i = 0; i < mSpielerZahl; i++) mSpielfeld[i]
442
443 /* ggf. Message blinken lassen */
444 ALLE_SPIELER->blinkeMessage();
445
446 /* Hetzrand runterbewegen; evtl. sterben */
447 ALLE_SPIELER->bewegeHetzrand();
448
449 /* Evtl. zuf�llige Graue an die Spieler senden. (Blops werden nicht
450 erzeugt; es wird nur gespeichert, dass das noch Graue auf das
451 ankommen warten. */
452 ALLE_SPIELER->zufallsGraue();
453
454 ALLE_SPIELER->fallSchritt();
455 Blop::sendeGeschedulteEvents();
456
457 /* Reihen hin/her. */
458 ALLE_SPIELER->rueberReihenSchritt();
459 Blop::sendeGeschedulteEvents();
460
461 /* Neue Explosionen testen. Da dabei keine Blops manipuliert werden, k�nnen
462 wir um alles eine gro�e gleichzeit machen. */
463 Blop::beginGleichzeitig();
464 ALLE_SPIELER->testeFlopp();
465 Blop::endGleichzeitig();
466
467 /* Rest vom normalen Spielschritt */
468 ALLE_SPIELER->spielSchritt();
469 Blop::sendeGeschedulteEvents();
470
471 #undef ALLE_SPIELER
472
473 /* Animationen und Grafik-Ausgabe */
474 animiere();
475
476
477
478
479 if (mGModus == gmodus_spiel) {
480 /* Nur wenn das Spiel wirklich noch l�uft (und nicht nur darauf gewartet
481 wird, das wir beenden k�nnen: Testen, ob wir gewonnen haben */
482
483 /* Wurde grade das restliche Gras vernichtet? */
484 for (int i = 0; i < mSpielerZahl; i++)
485 if (mSpielfeld[i]->getGrasAnz()>0)
486 goto noch_gras_da;
487
488 /* Dann warten wir jetzt nur noch auf einen guten Moment zum beenden. */
489 mGModus = gmodus_warte_gewonnen;
490
491 noch_gras_da:;
492 } else {
493 /* Nur wenn das Spiel kurz davor ist, beendet zu werden:
494 Checken, obs jetzt wirklich beendet werden kann. */
495
496 CASSERT(mGModus == gmodus_warte_gewonnen ||
497 mGModus == gmodus_warte_verloren);
498 /* Spiel (Level) soll beendet werden; aber sind auch beide
499 Spieler bereit? */
500
501 bool bereit = true;
502 for (int i = 0; i < mSpielerZahl; i++)
503 if (!mSpielfeld[i]->bereitZumStoppen())
504 bereit = false;
505
506 if (bereit) {
507 /* OK, alle bereit. */
508
509 /* Gewonnen oder verloren? */
510 if (mGModus == gmodus_warte_gewonnen) {
511
512 Sound::playSample(sample_levelwin,so_global);
513
514 /* Hier darf noch nicht stopLevel() aufgerufen werden, weil sonst
515 das Spielfeld nicht mehr angezeigt wird */
516
517 /* Gewonnen. Level als gewonnen abspeichern. Aber nicht im
518 Debug-Modus. */
519 if (!gDebug)
520 PrefsDaten::schreibGewonnenenLevel(mSpielerZahl > 1, mIntLevelName);
521
522 /* Jetzt kommt die Zeitbonus-Animation */
523 mGModus = gmodus_bonus_animation;
524
525 /* Noch keine Bonus-Punkte */
526 mZeitBonus = 0;
527 } else {
528 Sound::playSample(sample_levelloose, so_global);
529
530 /* Spiel zu Ende weil verloren;
531 true = Spieler darf sich noch seine Punkte anschauen */
532 stopSpiel(true);
533
534 for (int i = 0; i < mSpielerZahl; i++)
535 mSpielfeld[i]->setText(_sprintf(
536 _("Game over\n\nScore: %d\n\n"), mPunkte[i]));
537 }
538
539 } // Ende: if bereit
540 } // Ende: if warten, bis alle f�r's Spielende bereit sind
541
542
543 } // spielSchritt()
544
545
546
547
548 /** Macht einen Schritt der Hetzrand-kommt-am-Ende-runter-Animation. */
bonusAnimationSchritt()549 void bonusAnimationSchritt() {
550 /* Hetzrand runterrutschen lassen */
551 bool ba_fertig = true; // "= true" nur um eine Warnung zu ersparen
552
553 mZeitBonus += punkte_fuer_zeitbonus;
554
555 #define bonusMessage _("Level %s complete!\n\nTime Bonus: %d\nScore: %d\n\n%s")
556
557 for (int i = 0; i < mSpielerZahl; i++) {
558
559 /* Neuen Text (mit neuer Punkt-Zahl) schreiben */
560 mSpielfeld[i]->setText(_sprintf(bonusMessage,
561 ld->mLevelName.data(), mZeitBonus, mPunkte[i], " "));
562
563 /* Rand runterrutschen lassen */
564 ba_fertig = mSpielfeld[i]->bonusSchritt();
565
566 /* Punkte bekommen */
567 setPunkte(i, mPunkte[i] + punkte_fuer_zeitbonus);
568 }
569
570 /* Unten angekommen? (Sollte bei beiden Spielern gleichzeitig passieren) */
571 if (ba_fertig) {
572 mGModus = gmodus_bonus_warte;
573
574 for (int i = 0; i < mSpielerZahl; i++) {
575 mSpielfeld[i]->setText(_sprintf(bonusMessage,
576 ld->mLevelName.data(), mZeitBonus, mPunkte[i],
577 _("Space = Continue")));
578 }
579
580 mWarteAufTaste = true;
581 /* Kein Warte-Timeout mehr. Einfach Leertaste dr�cken */
582 //mWarteTimeout = nachbonus_wartezeit;
583 } // Ende: if Bonus-Animation fertig
584
585 #undef bonusMessage
586
587 }
588
589
590 /** Unterfunktion von zeitSchritt();
591 Hier passiert die eigentliche Arbeit. */
zeitSchrittIntern()592 void zeitSchrittIntern() {
593
594 /* W�hrend Pause l�uft das Spiel nicht weiter */
595 if (mPause)
596 return;
597
598 /* Warten wir grad drauf, dass der Benutzer eine Taste dr�ckt? */
599 if (mWarteAufTaste) {
600
601 if (mWarteTimeout) {
602 /* Wir warten nicht beliebig lang */
603 mWarteTimeout--;
604 if (mWarteTimeout)
605 return;
606
607 /* Zu lange gewartet. Jetzt reicht's! */
608 mWarteAufTaste = false;
609 } else
610 return;
611 }
612
613 /* Einzelschritt-Modus. Nach diesem Schritt gleich wieder auf
614 Tastendruck warten. */
615 if (mEinzelschritt) {
616 mWarteAufTaste = true;
617 }
618
619
620 if (mGModus == gmodus_ende_warte) {
621 /* Spieler hat sich seine Punkte (oder was auch immer sont)
622 fertig angeschaut. Jetzt koennen wir zurueck ins Menue */
623 mGModus = gmodus_kein_spiel;
624 UI::stopSpiel();
625 return;
626 }
627
628 if (mGModus == gmodus_bonus_warte) {
629 /* Wir haben grade ein bisschen gewartet, damit der Benutzer seine
630 Level-Endpunkte bewundern kann. Jetzt geht's weiter mit dem n�chsten
631 Level... */
632
633 /* Alten Level stoppen (ist vermutlich nicht wirklich n�tig) */
634 stopLevel();
635
636 /* neuer Level */
637
638 mLevelNr++;
639 mIntLevelName =
640 (mLevelNr>ld->getLevelAnz() ? "" : ld->getIntLevelName(mLevelNr));
641 try {
642 if (!startLevel()) {
643 /* Es gibt gar keine Level mehr; also Spiel beenden */
644 stopSpiel(true);
645 for (int i = 0; i < mSpielerZahl; i++) {
646 mSpielfeld[i]->setText(
647 _sprintf(mSpielerZahl == 2 && mPunkte[i] > mPunkte[1-i] ?
648 _("***\nYou won even a bit more!!!\n\nScore: %d\n***\n\n") :
649 _("***\nYou won!!!\n\nScore: %d\n***\n\n") ,
650 mPunkte[i]));
651 }
652 mLevelNr = 0; // Jetzt kann man nicht mehr Restart last level
653 mIntLevelName = "";
654 }
655 } catch (Fehler fe) {
656 printFehler(mSpielerZahl, "Could not start level:\n", fe);
657 stopSpiel();
658 }
659 PrefsDaten::setLastLevel(mIntLevelName);
660 return;
661 }
662
663 /* Spiel grade erst gestartet? Dann Texte l�schen */
664 if (mGModus == gmodus_spiel_start) {
665 for (int i = 0; i < mSpielerZahl; i++)
666 mSpielfeld[i]->setText("");
667 mGModus = gmodus_spiel;
668 }
669
670 CASSERT(mGModus == gmodus_bonus_animation || mGModus == gmodus_spiel ||
671 mGModus == gmodus_warte_gewonnen || mGModus == gmodus_warte_verloren);
672
673
674 /* Ok, hier kommt der eigentliche Spielschritt... */
675 if (mGModus == gmodus_bonus_animation) {
676 /* ... Spielschritt Bonusanimation */
677 bonusAnimationSchritt() ;
678
679 } else {
680 /* ... normaler Spielschritt */
681
682
683 /* Evtl. Signale abfangen, um die Logdatei abspeichern zu k�nnen. */
684 signaleAn();
685
686 try {
687 /* Hier findet das eigentliches Spiel statt */
688 spielSchritt();
689
690 } catch (Fehler fe) {
691 signaleAus();
692 bool log_gespeichert = false;
693
694 try {
695 Aufnahme::speichern(getLogName());
696 log_gespeichert = true;
697 } catch (Fehler) {}
698
699 /* Im Debug-Modus soll das Spielfeld weiter angezeigt werden,
700 wenn ein Fehler w�hrend des Spiels passiert. */
701 stopSpiel(gDebug);
702
703 /* Wenn das Speichern der log-Datei geklappt hat, soll ggf.
704 die "send log to..."-Meldung ausgegeben werden. */
705 printFehler(mSpielerZahl, "Error during the game:\n", fe,
706 log_gespeichert);
707 return;
708 }
709
710 signaleAus();
711 }
712
713
714
715 }
716
717
718 /** Wird von startLevel() und von zeitSchritt() aufgerufen. L�sst
719 s�mtliche Blops animieren. */
animiere()720 void animiere() {
721
722 Blop::beginGleichzeitig();
723
724 /* Alle Grafiken l�schen */
725 Blop::lazyLeereStapel();
726
727 /* Erst mal das globale Blop ausf�hren. */
728 ld->spielSchritt();
729
730 /* Die eigentliche Animation */
731 for (int i = 0; i < mSpielerZahl; i++)
732 mSpielfeld[i]->animiere();
733
734 Blop::endGleichzeitig();
735 }
736
737
738
739 /** */
neuePunkte(bool reSp,int pt)740 void neuePunkte(bool reSp, int pt){
741 int sp = (reSp ? 1 : 0);
742 int punkte = mPunkte[sp] + pt;
743 if (punkte<0) punkte=0;
744 setPunkte(sp,punkte);
745 }
746
747
748
749 /** wird aufgerufen, wenn ein Spieler tot ist */
spielerTot()750 void spielerTot() {
751 mGModus = gmodus_warte_verloren;
752 }
753
754
755
756
757
758 /* reSp sendet g Graue an den anderen Spieler */
sendeGraue(bool reSp,int g)759 void sendeGraue(bool reSp, int g) {
760 mSpielfeld[!reSp]->empfangeGraue(g);
761 }
762
763
764 /** reSp bittet den anderen Spieler um eine Reihe. Er selbst
765 hat H�he h. Antwort ist eine der Konstanten bewege_reihe_xxx */
bitteUmReihe(bool reSp,int h)766 int bitteUmReihe(bool reSp, int h) {
767 return mSpielfeld[!reSp]->bitteUmReihe(h);
768 }
769
770
771 /** reSp will einen Stein vom anderen Spieler (r�berreihe) */
willStein(bool reSp,Blop & s)772 void willStein(bool reSp, Blop & s) {
773 mSpielfeld[!reSp]->gebStein(s);
774 }
775
776
777
778
779
780 /** Liefert den Namen und Pfad der Log-Datei zur�ck
781 ($HOME/cuyo.log) */
getLogName()782 Str getLogName() {
783 char * ho = getenv("HOME");
784 if (!ho) {
785 /* Unter Windows zum Beispiel... */
786 print_to_stderr("Warning: Env-Variable $HOME not found. Using the current directory for cuyo.log");
787 return "cuyo.log";
788 }
789 if (ho[strlen(ho) - 1] == '/')
790 return Str(ho) + "cuyo.log";
791 else
792 return Str(ho) + "/cuyo.log";
793 }
794
795
796
797 /** Gibt die Fehlermeldung bestehend aus t und fe aus:
798 Sowohl als Text im Cuyo-Fenster
799 (bei anz_sp vielen Spielern) als auch auf stderr.
800 mitLog wird an fe.getText() weitergegeben. (D. h.: soll
801 ggf. die Send-Log-Meldung ausgegeben werden?) */
printFehler(int anz_sp,Str t,const Fehler & fe,bool mitLog)802 void printFehler(int anz_sp, Str t, const Fehler & fe,
803 bool mitLog /*= false*/) {
804 t += fe.getText(mitLog);
805 print_to_stderr(t+"\n");
806 for (int i = 0; i < anz_sp; i++) {
807 /* zweites true = Kleine Schrift... */
808 mSpielfeld[i]->setText(t, true);
809 }
810 }
811
812
813
814
815
816
817 #if signale_fangen
818
signalHandler(int s)819 void signalHandler(int s) {
820 try {
821 Aufnahme::speichern(getLogName());
822 /* send_log_string ist in fehler.h definiert. */
823 print_to_stderr(send_log_string+"\n");
824 } catch (Fehler) {}
825
826 /* Braucht man das? Geht das? */
827 raise(s);
828 }
829
830
signaleAn()831 void signaleAn() {
832 /*{
833 struct sigaction act;
834 act.sa_handler = speicherFehler;
835 act.sa_mask = 0;
836 act.sa_flags = SA_ONESHOT;
837 sigaction(SIGSEGV, &act, 0);
838 }*/
839 signal(SIGILL, signalHandler); // illegal instruction
840 signal(SIGSEGV, signalHandler); // segmentation fault
841 signal(SIGFPE, signalHandler); // floating point exception
842 }
843
signaleAus()844 void signaleAus() {
845 signal(SIGILL, SIG_DFL); // illegal instruction
846 signal(SIGSEGV, SIG_DFL); // segmentation fault
847 signal(SIGFPE, SIG_DFL); // floating point exception
848 }
849
850 #else
signaleAn()851 void signaleAn() {
852 }
853
signaleAus()854 void signaleAus() {
855 }
856 #endif
857
858
859
860 /********************** public Methoden (f�r das Spiel) **********************/
861 /* (stehen in cuyo.h) */
862
863
864
865
866
867
868
869
870
871 /** liefert true, wenn das Spiel normal l�uft, false
872 wenn das Spiel am zuende gehen ist */
getSpielLaeuft()873 bool getSpielLaeuft() {
874 CASSERT(mGModus != gmodus_kein_spiel);
875 return mGModus == gmodus_spiel;
876 }
877
878
879 /** liefert true, wenn das Spiel gepaust ist. */
getSpielPause()880 bool getSpielPause() {
881 return mPause;
882 }
883
884 /** Liefert die Anzahl der Mitspieler zur�ck. */
getSpielerZahl()885 int getSpielerZahl() {
886 return mSpielerZahl;
887 }
888
889
890 /** Liefert das Pause-Bildchen zur�ck */
getPauseBild()891 Bilddatei * getPauseBild() {
892 return mPauseBild;
893 }
894
895
896 /** Liefert true, wenn debug-R�berreihen-Test aktiv ist */
getRueberReihenTest()897 bool getRueberReihenTest() {
898 return mRueberReihenTest;
899 }
900
901
902 /** Liefert ein Spielfeld zur�ck. */
getSpielfeld(bool reSp)903 Spielfeld * getSpielfeld(bool reSp) {
904 return mSpielfeld[reSp];
905 }
906
907
908
909
910 /*********************** public Methoden f�rs ui **************************/
911 /* (stehen in ui2cuyo.h) */
912
913
init()914 void init() {
915 mEinzelschritt = false;
916 mZeitlupe = false;
917 mZaehlerZeitlupe = 0;
918 mRueberReihenTest = 0;
919 mAbspielen = 0;
920 mSchnellvorlauf = 0;
921 mPauseBild = 0;
922 mZusatzVersionen = Version();
923 mSchwierig = PrefsDaten::getDifficulty();
924
925 int spielermodus = PrefsDaten::getPlayers();
926 mGegenKI = spielermodus==spielermodus_computer;
927 mSpielerZahl = (mGegenKI ? 2 : spielermodus);
928
929 mLevelpack = PrefsDaten::getLevelTrack();
930 CASSERT(mLevelpack>=0);
931
932 /* Jetzt gehen auf der Kommandozeile spezifizierte Versionen ein. */
933 setzeVersion(mKommandoZeilenVersion);
934
935 /* Hier findet das parsen statt. (Fehler werden im try-catch-Block
936 der main()-Routine ausgegeben.) Das LevelDaten-Objekt weist
937 sich selber der globalen Variable ld zu. */
938 new LevelDaten(berechneVersion());
939
940
941 for (int i = 0; i < max_spielerzahl; i++) {
942 /* Spielfeld erzeugen */
943 mSpielfeld[i] = new Spielfeld(i > 0); // (i > 0) = rechter Spieler
944 }
945
946 /* Pause-Bild laden */
947 mPauseBild = new Bilddatei();
948 mPauseBild->laden("pause.xpm");
949
950 /* KI-Spieler erzeugen */
951 mKI = new KIPlayer(mSpielfeld[1]);
952
953 setSpielerModus(spielermodus);
954 // Sollte erst nach Erzeugen der Men�s aufgerufen werden...
955
956 mIntLevelName = PrefsDaten::getLastLevel();
957 mLevelNr = ld->getLevelNr(mIntLevelName);
958
959 mGModus = gmodus_kein_spiel;
960
961 mWarteAufTaste = false;
962 }
963
destroy()964 void destroy(){
965 delete ld;
966 delete mKI;
967 }
968
969
970
setPause(bool pause)971 void setPause(bool pause) {
972 mPause = pause;
973 for (int i = 0; i < mSpielerZahl; i++)
974 mSpielfeld[i]->setUpdateAlles();
975 UI::nachEventAllesAnzeigen();
976 }
977
978
979 /** Ein key-Event halt... (K�mmert sich um alle Tasten,
980 die w�hrend des Spiels so gedr�ckt werden...). */
keyEvent(const SDL_keysym & taste)981 void keyEvent(const SDL_keysym & taste) {
982
983 if (mPause) {
984 switch (taste.sym) {
985 case SDLK_ESCAPE:
986 stopSpiel();
987 UI::nachEventAllesAnzeigen();
988 break;
989 case ' ':
990 case SDLK_RETURN:
991 case SDLK_KP_ENTER:
992 setPause(false);
993 break;
994 default:
995 break;
996 }
997 return;
998 }
999
1000
1001 if (taste.sym == SDLK_ESCAPE) {
1002 /* Auf Pause schalten */
1003 setPause(true);
1004 return;
1005 }
1006
1007 if (mWarteAufTaste) {
1008 if (taste.sym == ' ' || taste.sym == SDLK_RETURN ||
1009 taste.sym == SDLK_KP_ENTER) {
1010 mWarteAufTaste = false;
1011 mWarteTimeout = 0;
1012 return;
1013 }
1014
1015 } else if (mGModus == gmodus_spiel) {
1016 int sp, t;
1017 if (PrefsDaten::getTaste(taste.sym, sp, t)) {
1018 /* Im 1-Spieler-Modus und im KI-Modus alle Tastendr�cke an
1019 Spieler 1 senden: */
1020 if (mSpielerZahl == 1 || mGegenKI) sp = 0;
1021
1022 Aufnahme::recTaste(sp, t);
1023 mSpielfeld[sp]->taste(t);
1024 return;
1025 }
1026 } // if spiel l�uft
1027
1028
1029 /* Event noch nicht verarbeitet. Dann vielleicht debug? */
1030 }
1031
1032
1033 /** Eine Taste wurde gedrueckt, von der das ui befunden hat, dass
1034 es sich um eine debug-Taste handeln koennte.
1035 Liefert zurueck, ob die Taste tats�chlich erkannt werden konnte. */
debugKeyEvent(const SDL_keysym & taste)1036 bool debugKeyEvent(const SDL_keysym & taste) {
1037
1038 char buch = taste.sym;
1039 if ((taste.mod & KMOD_SHIFT) && buch >= 'a' && buch <= 'z')
1040 buch = buch - 'a' + 'A';
1041
1042 if (buch == 'S') {
1043 print_to_stderr("Debug: Save log file\n");
1044 Aufnahme::speichern(getLogName());
1045 return true;
1046 }
1047
1048 /* Handle other keys only in debug mode. */
1049
1050 if (!gDebug)
1051 return false;
1052
1053 if (buch == 'b') {
1054 for (int i = 0; i < mSpielerZahl; i++)
1055 mSpielfeld[i]->empfangeGraue(1);
1056 print_to_stderr("Debug: Receive greys\n");
1057
1058 } else if (buch == 'e') {
1059 mEinzelschritt = !mEinzelschritt;
1060 print_to_stderr(_sprintf("Debug: Single step mode = %d\n",
1061 mEinzelschritt));
1062 if (!mEinzelschritt) {
1063 /* Nicht noch ein letztes Mal auf Taste warten... */
1064 mWarteAufTaste = false;
1065 }
1066
1067 } else if (buch == 'f') {
1068 if (mGModus==gmodus_spiel) {
1069 for (int i=0; i<mSpielerZahl; i++)
1070 mSpielfeld[i]->resetFall();
1071 print_to_stderr("Debug: Replace fall\n");
1072 }
1073
1074 } else if (buch == 'L') {
1075 if (mGModus == gmodus_kein_spiel) {
1076 print_to_stderr("Debug: Load log file\n");
1077 Aufnahme::laden(getLogName());
1078 setSpielerModus(Aufnahme::getSpielerModus());
1079 mIntLevelName = Aufnahme::getLevelName();
1080 mLevelNr = ld->getLevelNr(mIntLevelName);
1081 if (mLevelNr==0) mIntLevelName="";
1082 PrefsDaten::setLastLevel(mIntLevelName);
1083
1084 // TRANSLATORS: The %d is the level number
1085 print_to_stderr(_sprintf("Level %s (%d)\nFor replay start level.\n",
1086 mIntLevelName.data(), mLevelNr));
1087 mAbspielen = true;
1088 } else
1089 print_to_stderr("Debug: Do *not* load log file during the game.\n");
1090
1091 } else if (buch == 'l') {
1092 mAbspielen = !mAbspielen;
1093 print_to_stderr(_sprintf("Debug: Replay log file = %d\n", mAbspielen));
1094
1095 } else if (buch == 'g') {
1096 /* Hoffentlich darf man gModus einfach so �ndern... */
1097
1098 if (mGModus == gmodus_spiel) {
1099 mGModus = gmodus_warte_gewonnen;
1100 print_to_stderr("Debug: Win instantly\n");
1101 }
1102
1103 } else if (buch == 'h') {
1104 print_to_stderr("Debug: Help (all keys to be combined with Alt)\n"
1105 " b: Receive greys\n"
1106 " e: Single step mode on/off\n"
1107 " f: Replace fall\n"
1108 " g: Win instantly\n"
1109 " h: Help (this one)\n"
1110 " L: Load log file\n"
1111 " l: Replay log file on/off\n"
1112 " r: Reload level\n"
1113 " S: Save log file\n"
1114 " t: Test of row-exchange on/off\n"
1115 " v: Fast forward on/off\n"
1116 " z: Slow motion on/off\n");
1117
1118 } else if (buch == 'r') {
1119 if (mGModus == gmodus_kein_spiel) {
1120 print_to_stderr("Debug: Reload levelconf\n");
1121 try {
1122 ld->ladLevelSummary(true,berechneVersion());
1123 } catch (Fehler fe) {
1124 printFehler(2, "Could not reload the level description file because of the following error:\n",
1125 fe);
1126 }
1127 } else
1128 print_to_stderr("Debug: Do *not* load log file during the game.\n");
1129
1130 } else if (buch == 't') {
1131 mRueberReihenTest = !mRueberReihenTest;
1132 print_to_stderr(_sprintf("Debug: Test of row-exchange = %d\n",
1133 mRueberReihenTest));
1134
1135 } else if (buch == 'v') {
1136 mSchnellvorlauf = !mSchnellvorlauf;
1137 print_to_stderr(_sprintf("Debug: Fast forward = %d\n", mSchnellvorlauf));
1138
1139 } else if (buch == 'z') {
1140 mZeitlupe = !mZeitlupe;
1141 print_to_stderr(_sprintf("Debug: Slow motion = %d\n", mZeitlupe));
1142
1143 } else return false;
1144
1145 return true;
1146 }
1147
1148
1149
1150 /*
1151 f�r xtrace:
1152 extern "C" {
1153 int XInternAtom(Display *display, char *atom_name, int only_if_exists);
1154 }
1155 */
1156
1157
1158
1159 /** Die Haupt-Zeitschritt-Routine. Wird direkt
1160 vom ui aufgerufen. Ruft alle spielschritt()-Routinen u.�. auf. */
zeitSchritt()1161 void zeitSchritt() {
1162 CASSERT(mGModus != gmodus_kein_spiel);
1163
1164 /*
1165 f�r xtrace:
1166 XInternAtom(qt_xdisplay(),"zeitschritt A",1);
1167 */
1168
1169 /* Zeitlupen-Debug-Modus? */
1170 if (mZeitlupe) {
1171 mZaehlerZeitlupe++;
1172 if (mZaehlerZeitlupe == 5)
1173 mZaehlerZeitlupe = 0;
1174 else
1175 return;
1176 }
1177
1178 if (mSchnellvorlauf) {
1179 for (int i = 0; i < 3; i++)
1180 if (mGModus != gmodus_kein_spiel) zeitSchrittIntern();
1181 } else {
1182 zeitSchrittIntern();
1183 }
1184
1185 /* Brauche kein UI::allesAnzeigen(); darum kuemmert sich das spiel-Blatt selbst */
1186 } // zeitSchritt()
1187
1188
1189 /** Markiert alle Graphik auf upzudaten; danach muss noch malSpielfeld()
1190 aufgerufen werden, um das Update wirklich zu machen */
setUpdateAlles()1191 void setUpdateAlles() {
1192 for (int i = 0; i < mSpielerZahl; i++)
1193 mSpielfeld[i]->setUpdateAlles();
1194 }
1195
1196
1197 /** Spielfeld neu malen. Wird vom ui aufgerufen. */
malSpielfeld(int sp)1198 void malSpielfeld(int sp) {
1199 mSpielfeld[sp]->malUpdateAlles();
1200 }
1201
1202 /** NaechstesFall neu malen. Wird vom ui aufgerufen. */
malNaechstesFall(int sp)1203 void malNaechstesFall(int sp) {
1204 mSpielfeld[sp]->malUpdateNaechstesFall();
1205 }
1206
1207
1208
getSpielerModus()1209 int getSpielerModus() {
1210 if (mGegenKI)
1211 return spielermodus_computer;
1212 else
1213 return mSpielerZahl;
1214 }
1215
1216 /** Setzt #Spieler, KI-Modus; gemerkte Level-Nummer wird auf 0
1217 zurueckgesetzt. Vorbedingung: Es l�uft grad kein Spiel. */
setSpielerModus(int spm)1218 void setSpielerModus(int spm) {
1219 CASSERT(mGModus==gmodus_kein_spiel);
1220 if (spm == spielermodus_computer) {
1221 mGegenKI = true;
1222 mSpielerZahl = 2;
1223 } else {
1224 mGegenKI = false;
1225 mSpielerZahl = spm;
1226 }
1227 PrefsDaten::setPlayers(spm);
1228 }
1229
1230
1231 /** Der int ist der Index in Version::mLevelpack bzw Version::mSchwierig */
setLevelpack(int i)1232 void setLevelpack(int i) {
1233 mLevelpack = i;
1234 PrefsDaten::setLevelTrack(i);
1235 }
1236
getLevelpack()1237 int getLevelpack() {return mLevelpack;}
1238
setSchwierig(int i)1239 void setSchwierig(int i) {
1240 mSchwierig = i;
1241 PrefsDaten::setDifficulty(i);
1242 }
1243
getSchwierig()1244 int getSchwierig() {return mSchwierig;}
1245
1246
1247 /* Liefert Nr. des zuletzt gespielten Levels (oder 0) */
getLetzterLevel()1248 int getLetzterLevel() {
1249 return ld->getLevelNr(mIntLevelName);
1250 }
1251
1252
1253
berechneVersion()1254 Version berechneVersion() {
1255 Version ret = mZusatzVersionen;
1256 ret.nochEinMerkmal(mSpielerZahl==1 ? "1" : "2");
1257 if (Version::gSchwierig.mMerkmale[mSchwierig] != "")
1258 ret.nochEinMerkmal(Version::gSchwierig.mMerkmale[mSchwierig]);
1259 ret.nochEinMerkmal(Version::gLevelpack.mMerkmale[mLevelpack]);
1260 return ret;
1261 }
1262
1263
1264 /* �bernimmt die Dinge, die durch version spezifiziert werden.
1265 Alles andere bleibt beim alten,
1266 bis auf da� der alte Wert von mZusatzVersionen verlorengeht. */
setzeVersion(const Version & version)1267 void setzeVersion(const Version & version) {
1268 mZusatzVersionen = version;
1269
1270 /* Achtung! Jetzt enth�lt mZusatzVersionen erstmal zu viel.
1271 Aber der �berschu� wird gleich rausgel�scht. */
1272
1273 try {
1274 mSpielerZahl = (
1275 mZusatzVersionen.extractMerkmal(dimaa_numspieler,
1276 (mSpielerZahl==1 ? "1" : "2"))
1277 == "1"
1278 ? 1 : 2);
1279 mSchwierig = mZusatzVersionen.extractMerkmal(Version::gSchwierig,
1280 mSchwierig);
1281 mLevelpack = mZusatzVersionen.extractMerkmal(Version::gLevelpack,
1282 mLevelpack);
1283 }
1284 catch(Str konflikt) {throw Fehler("Conflicting versions: %s",
1285 konflikt.data());}
1286 }
1287
1288
1289 }
1290
1291