1 // Hyperbolic Rogue -- minor modes
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file yendor.cpp
5  *  \brief Yendor Quest/Challenge, Pure Tactics Mode, Peace Mode
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 namespace peace { extern bool on; }
12 
hiitemsMax(eItem it)13 EX int hiitemsMax(eItem it) {
14   int mx = 0;
15   for(auto& a: hiitems) if(a.second[it] > mx) mx = a.second[it];
16   return mx;
17   }
18 
19 /** 1 - just return UNKNOWN if id not assigned; 2 - assign without writing to file; 3 - assign with writing to file */
20 EX modecode_t modecode(int mode IS(3));
21 
22 typedef vector<pair<int, string> > subscoreboard;
23 
displayScore(subscoreboard & s,int x)24 void displayScore(subscoreboard& s, int x) {
25   int vf = min((vid.yres-64) / 70, vid.xres/80);
26 
27   if(get_sync_status() == 1) {
28     displayfr(x, 56, 1, vf, "(syncing)", 0xC0C0C0, 0);
29     }
30   else {
31     sort(s.begin(), s.end());
32     for(int i=0; i<isize(s); i++) {
33       int i0 = 56 + i * vf;
34       displayfr(x, i0, 1, vf, its(-s[i].first), 0xC0C0C0, 16);
35       displayfr(x+8, i0, 1, vf, s[i].second, 0xC0C0C0, 0);
36       }
37     }
38   }
39 
40 EX namespace yendor {
41 
42   EX bool on = false;
43   EX bool generating = false;
44   EX bool path = false;
45   EX bool everwon = false;
46   EX bool won = false;
47   bool easy = false;
48 
49   EX int challenge; // id of the challenge
50   EX int lastchallenge;
51 
52   #if HDR
53   #define YF_DEAD 1
54   #define YF_WALLS 2
55   #define YF_END 4
56   #define YF_DEAD5 8
57 
58   #define YF_NEAR_IVY   16
59   #define YF_NEAR_ELEM  32
60   #define YF_NEAR_OVER  64
61   #define YF_NEAR_RED   128
62   #define YF_REPEAT     512
63   #define YF_NEAR_TENT  1024
64 
65   #define YF_START_AL   2048
66   #define YF_START_CR   4096
67   #define YF_CHAOS      8192
68   #define YF_RECALL     16384
69   #define YF_NEAR_FJORD 32768
70 
71   #define YF_START_ANY  (YF_START_AL|YF_START_CR)
72 
73   struct yendorlevel {
74     eLand l;
75     int flags;
76     };
77 
78   #define YENDORLEVELS 34
79   #endif
80 
81   EX map<modecode_t, array<int, YENDORLEVELS>> bestscore;
82 
83   EX eLand nexttostart;
84 
85   yendorlevel levels[YENDORLEVELS] = {
86     {laNone,      0},
87     {laHell,      YF_DEAD}, // FORCE BARRIERS?
88     {laGraveyard, YF_DEAD5},
89     {laDesert,    YF_NEAR_IVY}, // IVY OR TENTACLE?
90     {laMinefield, YF_END}, // NOT WON, SEEMS OKAY
91     {laEmerald,   0}, // WON FINE
92     {laOvergrown, 0}, // WON, TOO EASY?
93     {laMotion,    YF_START_AL | YF_END}, // NOT WON, SEEMS OKAY
94     {laAlchemist, 0}, // ALMOST WON
95     {laIvoryTower,YF_START_CR | YF_NEAR_ELEM | YF_REPEAT}, // won cool
96     {laMirrorOld, YF_NEAR_OVER}, // OK
97     {laWhirlpool, 0}, // cool
98     {laIce,       YF_NEAR_ELEM}, // OK
99     {laHive,      YF_NEAR_RED}, // OK
100     {laCaribbean, 0}, // seems OK
101     {laOcean,     YF_WALLS}, // well... stupid, add Caribbean/Fjord
102     {laPalace,    0}, // more trapdoors!
103     {laZebra,     0}, // TOO HARD?
104     {laWineyard,  0}, // hard-ish
105     {laStorms,    0}, // ?
106     {laLivefjord, 0},
107     {laJungle,    0},
108     {laPower,     YF_START_CR},
109     {laWildWest,  0},
110     {laWhirlwind, YF_NEAR_TENT},
111     {laHell,      YF_CHAOS | YF_DEAD},
112     {laDragon,    YF_DEAD},
113     {laReptile,   0},
114     {laTortoise,  YF_RECALL},
115     {laCocytus,   YF_NEAR_FJORD},
116     {laRuins,     YF_DEAD},
117     {laCaves,     YF_DEAD5},
118     {laWestWall,  YF_START_CR},
119     {laEclectic,  0},
120     // {laVariant,   YF_DEAD5}, (I do not think this works)
121     };
122 
123   int tscorelast;
124 
uploadScore()125   void uploadScore() {
126     int tscore = 0;
127     for(int i=1; i<YENDORLEVELS; i++)
128       if(bestscore[0][i]) tscore += 999 + bestscore[0][i];
129     // printf("Yendor score = %d\n", tscore);
130 
131     if(tscore > tscorelast) {
132       tscorelast = tscore;
133       if(tscore >= 1000) achievement_gain("YENDC1", rg::global);
134       if(tscore >= 5000) achievement_gain("YENDC2", rg::global);
135       if(tscore >= 15000) achievement_gain("YENDC3", rg::global);
136       }
137 
138     achievement_score(LB_YENDOR_CHALLENGE, tscore);
139     }
140 
clev()141   EX yendorlevel& clev() { return levels[challenge]; }
142 
changeland(int i,eLand l)143   EX eLand changeland(int i, eLand l) {
144     if(l == laIvoryTower) return laNone;
145     if((clev().flags & YF_START_ANY) && i < 20 && l != clev().l) return clev().l;
146     if((clev().flags & YF_END) && i > 80 && l == clev().l) return laIce;
147     return laNone;
148     }
149 
150   eLand first, second, last;
151 
152   #if HDR
153   struct yendorinfo {
154     cell *path[YDIST];
155     cell *actualKey;
156     bool found;
157     bool foundOrb;
158     int howfar;
159     bignum age;
yendorinfohr::yendor::yendorinfo160     yendorinfo() { actualKey = NULL; }
keyhr::yendor::yendorinfo161     cell* key() { return path[YDIST-1]; }
actual_keyhr::yendor::yendorinfo162     cell *actual_key() { return actualKey ? actualKey : key(); }
orbhr::yendor::yendorinfo163     cell* orb() { return path[0]; }
164     };
165   #endif
166 
167   EX vector<yendorinfo> yi;
168 
169 #if HDR
170 #define NOYENDOR 999999
171 #endif
172   EX int yii = NOYENDOR;
173 
hardness()174   EX int hardness() {
175     if(peace::on) return 15; // just to generate monsters
176     if(!yendor::generating && !yendor::path && !yendor::on) return 0;
177     int thf = 0;
178     for(int i=0; i<isize(yi); i++) {
179       yendorinfo& ye ( yi[i] );
180       if(!ye.foundOrb && ye.howfar > 25)
181         thf += (ye.howfar - 25);
182       }
183     thf -= 2 * (YDIST - 25);
184     if(thf<0) thf = 0;
185     return items[itOrbYendor] * 5 + (thf * 5) / (YDIST-25);
186     }
187 
188   #if HDR
189   enum eState { ysUntouched, ysLocked, ysUnlocked };
190   #endif
191 
state(cell * yendor)192   EX eState state(cell *yendor) {
193     for(int i=0; i<isize(yi); i++) if(yi[i].path[0] == yendor)
194       return yi[i].found ? ysUnlocked : ysLocked;
195     return ysUntouched;
196     }
197 
control(pathgen & p,int i,cellwalker & ycw)198   EX bool control(pathgen& p, int i, cellwalker& ycw) {
199 
200     // change lands in the Challenge
201     if(i > BARLEV-6) {
202       p.last_id = i+7-BARLEV;
203       setdist(p.path[p.last_id], 7, p.path[i+6-BARLEV]);
204       if(yendor::challenge && !euclid && ycw.at->land != laIvoryTower) {
205         eLand ycl = yendor::changeland(i, ycw.at->land);
206         if(ycl) {
207           if(weirdhyperbolic) {
208             buildBarrierNowall(ycw.at, ycl);
209             }
210           else if(hyperbolic && ishept(ycw.at)) {
211             int bd = 2 + hrand(2) * 3;
212             buildBarrier(ycw.at, bd, ycl);
213             if(ycw.at->bardir != NODIR && ycw.at->bardir != NOBARRIERS)
214               extendBarrier(ycw.at);
215             }
216           }
217         }
218       }
219 
220     // follow the branch in Yendorian
221     if(ycw.at->land == laEndorian) {
222       int bestval = -2000;
223       int best = 0, qbest = 0;
224       for(int d=0; d<ycw.at->type; d++) {
225         setdist(ycw.at, 7, ycw.peek());
226         cell *c1 = (ycw+d).cpeek();
227         int val = d * (ycw.at->type - d);
228         if(c1->wall == waTrunk) val += (i < YDIST-20 ? 1000 : -1000);
229         if(val > bestval) qbest = 0, bestval = val;
230         if(val == bestval) if(hrand(++qbest) == 0) best = d;
231         }
232       ycw += best;
233       return true;
234       }
235     return false;
236     }
237 
check(cell * yendor)238   EX bool check(cell *yendor) {
239     int byi = isize(yi);
240     for(int i=0; i<isize(yi); i++) if(yi[i].path[0] == yendor) byi = i;
241     if(byi < isize(yi) && yi[byi].found) return false;
242     if(byi == isize(yi)) {
243       retry:
244       int creation_attempt = 0;
245       yendorinfo nyi;
246       nyi.howfar = 0;
247 
248       generating = true;
249 
250       auto p = generate_random_path_randomdir(yendor, YDIST-1, true);
251       for(int i=0; i<YDIST; i++) nyi.path[i] = p.path[i];
252 
253       nyi.found = false;
254       nyi.foundOrb = false;
255 
256       for(int i=1; i<YDIST; i++) {
257         setdist(nyi.path[i], 7, nyi.path[i-1]);
258         if(isEquidLand(nyi.path[i]->land)) {
259           // println(hlog, i, ": ", coastvalEdge(nyi.path[i]));
260           buildEquidistant(nyi.path[i]);
261           }
262         }
263 
264       setdist(nyi.path[YDIST-1], 7, nyi.path[YDIST-2]);
265       cell *key = nyi.path[YDIST-1];
266 
267       yendor::generating = false;
268 
269       for(int b=10; b>=5; b--) setdist(key, b, nyi.path[YDIST-2]);
270 
271       if(inmirror(key) || (geometry == gNormal && celldistance(key, yendor) < YDIST/2)) {
272         creation_attempt++;
273         if(creation_attempt > 100) {
274           yendor->item = itNone;
275           addMessage(XLAT("%The1 turned out to be an illusion!", itOrbYendor));
276           return false;
277           }
278         goto retry;
279         }
280 
281       for(int i=-1; i<key->type; i++) {
282         cell *c2 = i >= 0 ? key->move(i) : key;
283         checkTide(c2);
284         c2->monst = moNone; c2->item = itNone;
285         if(!passable(c2, NULL, P_MIRROR | P_MONSTER)) {
286           if(c2->wall == waCavewall) c2->wall = waCavefloor;
287           else if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
288           else if(c2->wall == waLake) c2->wall = waFrozenLake;
289           else if(c2->land == laCaribbean) c2->wall = waCIsland;
290           else if(c2->land == laOcean) c2->wall = waCIsland;
291           else if(c2->land == laRedRock) c2->wall = waRed3;
292           else if(c2->land == laWhirlpool)
293             c2->wall = waBoat, c2->monst = moPirate, c2->item = itOrbWater;
294           else c2->wall = waNone;
295           }
296         if(c2->wall == waReptile) c2->wall = waNone;
297         if(c2->wall == waMineMine || c2->wall == waMineUnknown)
298           c2->wall = waMineOpen;
299         if(c2->wall == waTrapdoor && i == -1)
300           c2->wall = waGargoyleFloor;
301         if(c2->land == laLivefjord) {
302           c2->wall = waSea;
303           for(int i=0; i<c2->type; i++)
304             c2->move(i)->wall = waSea;
305           }
306         if(isGravityLand(c2->land) && key->land == c2->land && c2->land != laWestWall &&
307           c2->landparam < key->landparam && c2->wall != waTrunk)
308             c2->wall = waPlatform;
309         if(c2->land == laReptile && i >= 0)
310           c2->wall = waChasm;
311         if(c2->land == laMirrorWall && i == -1)
312           c2->wall = waNone;
313         }
314       key->item = itKey;
315 
316       bool split_found = false;
317 
318       if(key->land == laWestWall && trees_known()) {
319 
320         int t = type_in(expansion, yendor, [yendor] (cell *c) { return celldistance(yendor, c); });
321         int maxage = 10;
322         for(int i=0; i<min(items[itOrbYendor], 8); i++)
323           maxage *= 10;
324 
325         nyi.age = maxage - hrand(maxage/2);
326         bignum full_id = p.full_id_0 - nyi.age;
327         bool onlychild = true;
328 
329         cellwalker ycw = p.start;
330         ycw--; if(S3 == 3) ycw--;
331 
332         for(int i=0; i<YDIST-1; i++) {
333 
334           if(i == 1) onlychild = true;
335           if(!onlychild) ycw++;
336           if(valence() == 3) ycw++;
337 
338           onlychild = false;
339 
340           for(int tch: expansion.children[t]) {
341             ycw++;
342             if(i == 1)
343               tch = type_in(expansion, ycw.cpeek(), [yendor] (cell *c) { return celldistance(yendor, c); });
344             auto& sub_id = expansion.get_descendants(YDIST-i-2, tch);
345             if(full_id < sub_id) {
346               if(!split_found && !(p.full_id_0 < sub_id)) {
347                 // ycw.at->item = itRuby;
348                 split_found = true;
349                 setdist(ycw.at, 6, NULL);
350                 auto tt = type_in(expansion, ycw.at, coastvalEdge);
351                 // println(hlog, "at split, ydist-type: ", t, " coast-type: ", tt);
352                 if(t != tt) {
353                   // try again!
354                   key->item = itNone;
355                   return check(yendor);
356                   }
357                 }
358               t = tch;
359               break;
360               }
361 
362             full_id.addmul(sub_id, -1);
363             if(!split_found) p.full_id_0.addmul(sub_id, -1);
364             onlychild = true;
365             }
366 
367           if(inmirror(ycw)) ycw = mirror::reflect(ycw);
368           ycw += wstep;
369           setdist(ycw.at, 8, ycw.peek());
370           buildEquidistant(ycw.at);
371           // println(hlog, "actual key #", i, ": ", ycw.at->landparam);
372           }
373 
374         nyi.actualKey = ycw.at;
375         ycw.at->item = itKey;
376         key->item = itNone;
377         }
378 
379       yi.push_back(nyi);
380       }
381     dynamicval<bool> b(changes.on, false);
382     addMessage(XLAT("You need to find the right Key to unlock this Orb of Yendor!"));
383     if(yi[byi].actualKey)
384       addMessage(XLAT("You feel that these directions are %1 turns old.", yi[byi].age.get_str(100)));
385     if(yii != byi) {
386       yii = byi;
387       achievement_gain_once("YENDOR1");
388       playSound(yendor, "pickup-yendor");
389       return true;
390       }
391     return false;
392     }
393 
onpath()394   EX void onpath() {
395     path = false;
396     if(yii < isize(yi)) {
397       for(int i=0; i<YDIST; i++) if(yi[yii].path[i]->cpdist <= 7) {
398         if(i > yi[yii].howfar) yi[yii].howfar = i;
399         path = true;
400         }
401       }
402     }
403 
get_land_structure()404   EX eLandStructure get_land_structure() {
405     if(clev().flags & YF_CHAOS)
406       return lsChaos;
407     if(clev().l == laWhirlpool)
408       return lsSingle;
409     return lsNiceWalls;
410     }
411 
init(int phase)412   EX void init(int phase) {
413     if(!on) return;
414 
415     if(phase == 1) {
416       won = false;
417       if(!easy) items[itOrbYendor] = bestscore[modecode()][challenge];
418       land_structure = get_land_structure();
419       specialland = clev().l;
420       if(clev().flags & YF_START_AL) {
421         specialland = laAlchemist;
422         items[itElixir] = 50;
423         items[itFeather] = 50;
424         }
425       if(specialland == laPower)
426         items[itOrbSpeed] = 60, items[itOrbWinter] = 60;
427       if(clev().flags & YF_START_CR) {
428         specialland = laCrossroads;
429         }
430       if(specialland == laGraveyard) items[itBone] = 10;
431       if(specialland == laEmerald)   items[itEmerald] = 10;
432       if(specialland == laCocytus)   items[itFjord] = 10;
433       if(!euclid) {
434         if(clev().flags & YF_DEAD)   items[itGreenStone] = 100;
435         if(clev().flags & YF_DEAD5)  items[itGreenStone] = 5;
436         }
437       if(clev().flags & YF_RECALL) {
438         int yq = items[itOrbYendor];
439         items[itOrbRecall] = 60 - yq;
440         items[itOrbTime] = 60 - yq;
441         items[itOrbEnergy] = 60 - yq;
442         items[itOrbTeleport] = 60 - yq;
443         items[itOrbSpace] = 60 - yq;
444         items[itOrbDash] = 60 - yq;
445         items[itOrbFrog] = 60 - yq;
446         }
447       nexttostart = laNone;
448       }
449 
450     if(phase == 2) {
451       cell *c2 = cwt.at->move(0);
452       c2->land = firstland;
453       if(firstland == laRlyeh) c2->wall = waNone;
454       yendor::check(c2);
455       if(clev().flags & YF_NEAR_IVY)
456         nexttostart = laJungle;
457       if(clev().flags & YF_NEAR_FJORD)
458         nexttostart = laLivefjord;
459       if(clev().flags & YF_NEAR_TENT)
460         nexttostart = laRlyeh;
461       if(clev().flags & YF_NEAR_ELEM) {
462         if(firstland == laIce) {
463           nexttostart = laEWater;
464           items[itWaterShard] = 10;
465           }
466         else nexttostart = laEAir;
467         }
468       if(clev().flags & YF_NEAR_OVER)
469         nexttostart = laOvergrown;
470       if(clev().flags & YF_NEAR_RED) {
471         nexttostart = laRedRock;
472         items[itRedGem] = 25;
473         }
474       if(clev().flags & YF_WALLS) {
475         items[itPirate] += 25;
476         items[itFjord] += 25;
477         }
478       if(clev().l == laWhirlpool) {
479         items[itWhirlpool] += 10;
480         items[itOrbWater] += 150;
481         }
482       }
483 
484     if(phase == 3) {
485       cell *c2 = cwt.at->move(0);
486       makeEmpty(c2);
487       c2->item = itOrbYendor;
488       nexttostart = laNone;
489       if(clev().flags & YF_RECALL)
490         saveRecall(cwt);
491       }
492     }
493 
levelUnlocked(int i)494   bool levelUnlocked(int i) {
495     yendorlevel& ylev(levels[i]);
496 
497     eItem t = treasureType(ylev.l);
498     if(ylev.l != laWildWest && hiitemsMax(t) < 10) return false;
499     if((ylev.flags & YF_NEAR_ELEM) && hiitemsMax(itElemental) < 10) return false;
500     if((ylev.flags & YF_NEAR_RED) && hiitemsMax(itRedGem) < 10) return false;
501     if((ylev.flags & YF_NEAR_OVER) && hiitemsMax(itMutant) < 10) return false;
502     if((ylev.flags & YF_NEAR_TENT) && hiitemsMax(itStatue) < 10) return false;
503     if((ylev.flags & YF_NEAR_FJORD) && hiitemsMax(itFjord) < 10) return false;
504     if((ylev.flags & YF_CHAOS) && !chaosUnlocked) return false;
505     if((ylev.flags & (YF_DEAD|YF_DEAD5)) && hiitemsMax(itBone) < 10) return false;
506     if((ylev.flags & YF_RECALL) && hiitemsMax(itSlime) < 10) return false;
507     return true;
508     }
509 
510   struct scoredata {
511     string username;
512     int scores[landtypes];
513     };
514   vector<scoredata> scoreboard;
515 
516   EX const char *chelp =
517     "There are many possible solutions to the Yendor Quest. In the Yendor "
518     "Challenge, you will try many of them!\n\n"
519     "Each challenge takes part in a specific land, and you have to use what "
520     "you have available.\n\n"
521 
522     "You need to obtain an Orb of Yendor in the normal game to activate "
523     "this challenge, and (ever) collect 10 treasures in one or two lands "
524     "to activate a specific level.\n\n"
525 
526     "After you complete each challenge, you can try it again, on a harder "
527     "difficulty level.\n\n"
528 
529     "All the solutions showcased in the Yendor Challenge work in the normal "
530     "play too. However, passages to other lands, and (sometimes) some land features "
531     "are disabled in the Yendor "
532     "Challenge, so that you have to use the expected method. Also, "
533     "the generation rules are changed slightly for the Palace "
534     "and Minefield while you are looking for the Orb of Yendor, "
535     "to make the challenge more balanced "
536     "(but these changes are also active during the normal Yendor Quest).\n\n"
537 
538     "You get 1000 points for each challenge won, and 1 extra point for "
539     "each extra difficulty level.";
540 
char_to_yendor(char c)541   int char_to_yendor(char c) {
542     if(c >= 'a' && c <= 'z')
543       return c - 'a' + 1;
544     if(c >= 'A' && c <= 'Z')
545       return c - 'A' + 1 + 26;
546     return 0;
547     }
548 
name(int i)549   EX string name(int i) {
550     yendorlevel& ylev(levels[i]);
551 
552     string s = XLATT1(ylev.l);
553     if(!euclid) {
554       if(ylev.flags & YF_CHAOS) { s = "Chaos mode"; }
555       if(ylev.flags & YF_NEAR_IVY) { s += "+"; s += XLATT1(laJungle); }
556       if(ylev.flags & YF_NEAR_FJORD) { s += "+"; s += XLATT1(laLivefjord); }
557       if(ylev.flags & YF_NEAR_TENT) { s += "+"; s += XLATT1(laRlyeh); }
558       if(ylev.flags & YF_NEAR_ELEM) { s += "+"; s += XLATT1(laElementalWall); }
559       if(ylev.flags & YF_NEAR_OVER) { s += "+"; s += XLATT1(laOvergrown); }
560       if(ylev.flags & YF_NEAR_RED) { s += "+"; s += XLATT1(laRedRock); }
561       if(ylev.flags & YF_START_AL) { s += "+"; s += XLATT1(laAlchemist); }
562       if(ylev.flags & YF_DEAD) { s += "+"; s += XLATT1(itGreenStone); }
563       if(ylev.flags & YF_RECALL) { s += "+"; s += XLATT1(itOrbRecall); }
564       }
565 
566     return s;
567     }
568 
569 
showMenu()570   EX void showMenu() {
571     set_priority_board(LB_YENDOR_CHALLENGE);
572     int s = vid.fsize;
573     vid.fsize = vid.fsize * 4/5;
574     dialog::init(XLAT("Yendor Challenge"), iinf[itOrbYendor].color, 150, 100);
575 
576     for(int i=1; i<YENDORLEVELS; i++) {
577       string s;
578 
579       if(autocheat || levelUnlocked(i)) {
580         s = name(i);
581         }
582 
583       else {
584         s = "(locked)";
585         }
586 
587       string v;
588       if(bestscore[modecode()][i] == 1)
589         v = XLAT(" (won!)");
590       else if(bestscore[modecode()][i])
591         v = XLAT(" (won at level %1!)", its(bestscore[modecode()][i]));
592 
593       dialog::addSelItem(s, v, i > 26 ? 'A' + i - 27 : 'a' + i-1);
594       }
595 
596     dialog::addBreak(60);
597     if (yendor::on)
598       dialog::addItem(XLAT("Return to the normal game"), '0');
599     dialog::addSelItem(
600       easy ? XLAT("Challenges do not get harder") : XLAT("Each challenge gets harder after each victory"),
601       " " + (easy ? XLAT("easy") : XLAT("challenge")), '1');
602 
603     dialog::addBack();
604     dialog::addHelp();
605     dialog::display();
606 
607     int yc = char_to_yendor(getcstat);
608 
609     if(yc > 0 && yc < YENDORLEVELS) {
610       subscoreboard scorehere;
611 
612       for(int i=0; i<isize(scoreboard); i++) {
613         int sc = scoreboard[i].scores[yc];
614         if(sc > 0)
615           scorehere.push_back(
616             make_pair(-sc, scoreboard[i].username));
617         }
618 
619       displayScore(scorehere, vid.xres / 4);
620       }
621 
622     yendor::uploadScore();
623     vid.fsize = s;
624 
625     keyhandler = [] (int sym, int uni) {
626       dialog::handleNavigation(sym, uni);
627       int new_challenge = char_to_yendor(uni);
628       if(new_challenge && new_challenge < YENDORLEVELS) {
629         if(levelUnlocked(new_challenge) || autocheat) dialog::do_if_confirmed([new_challenge] {
630           challenge = new_challenge;
631           restart_game(yendor::on ? rg::nothing : rg::yendor);
632           });
633         else
634           addMessage("Collect 10 treasures in various lands to unlock the challenges there");
635         }
636       else if(uni == '0') {
637         if(yendor::on) restart_game(rg::yendor);
638         }
639       else if(uni == '1') easy = !easy;
640       else if(uni == '2' || sym == SDLK_F1) gotoHelp(chelp);
641       else if(doexiton(sym, uni)) popScreen();
642       };
643     }
644 
collected(cell * c2)645   EX void collected(cell* c2) {
646     LATE( collected(c2); )
647     playSound(c2, "tada");
648     items[itOrbShield] += 31;
649     for(int i=0; i<isize(yendor::yi); i++)
650       if(yendor::yi[i].path[0] == c2)
651         changes.value_set(yendor::yi[i].foundOrb, true);
652     // Shielding always, so that we know that it protects!
653     int powers = 0;
654     for(int i=0; i<1000 && powers < 4; i++) {
655       vector< pair<eItem, int> > choices = {
656         {itOrbSpeed, 31},
657         {itOrbLightning, 78},
658         {itOrbFlash, 78},
659         {itOrbTime, 78},
660         {itOrbWinter, 151},
661         {itOrbDigging, 151},
662         {itOrbTeleport, 151},
663         {itOrbThorns, 151},
664         {itOrbInvis, 151},
665         {itOrbPsi, 151},
666         {itOrbAether, 151},
667         {itOrbFire, 151},
668         {itOrbSpace, 78}
669         };
670       auto p = hrand_elt(choices);
671       auto orb = p.first;
672       if(items[orb] && i < 500) continue;
673       if(among(getOLR(orb, getPrizeLand()), olrDangerous, olrUseless, olrForbidden)) continue;
674       items[orb] += p.second;
675       powers++;
676       }
677     items[itOrbYendor]++;
678     items[itKey]--;
679     changes.value_set(yendor::everwon, true);
680     if(yendor::on) {
681       changes.value_set(yendor::won, true);
682       if(!cheater) {
683         yendor::bestscore[modecode()][yendor::challenge] =
684           max(yendor::bestscore[modecode()][yendor::challenge], items[itOrbYendor]);
685         yendor::uploadScore();
686         }
687       }
688     addMessage(XLAT("CONGRATULATIONS!"));
689     achievement_collection(itOrbYendor);
690     achievement_victory(false);
691     }
692 
__anon1421716e0502() 693   auto hooks = addHook(hooks_clearmemory, 0, [] () {
694     yendor::yii = NOYENDOR; yendor::yi.clear();
695     }) + addHook(hooks_removecells, 0, [] () {
__anon1421716e0702(yendorinfo& i) 696     eliminate_if(yendor::yi, [] (yendorinfo& i) {
697       for(int j=0; j<YDIST; j++) if(is_cell_removed(i.path[j])) {
698         DEBB(DF_MEMORY, ("removing a Yendor"));
699         if(&yi[yii] == &i) yii = NOYENDOR;
700         return true;
701         }
702       return false;
703       });
704     });
705   EX }
706 
707 #define MAXTAC 20
708 EX namespace tactic {
709 
710   EX bool on = false;
711   EX int id;
712 
713   map<modecode_t, array<int, landtypes>> recordsum;
714   map<modecode_t, array<array<int, MAXTAC>, landtypes> > lsc;
715 
716   eLand lasttactic;
717 
718   struct scoredata {
719     string username;
720     int scores[landtypes];
721     };
722   map<modecode_t, vector<scoredata>> scoreboard;
723 
chances(eLand l,modecode_t xc IS (modecode ()))724   EX int chances(eLand l, modecode_t xc IS(modecode())) {
725     if(xc != 0 && l != laCamelot) return 3;
726     for(auto& ti: land_tac)
727       if(ti.l == l)
728         return ti.tries;
729     return 0;
730     }
731 
tacmultiplier(eLand l)732   int tacmultiplier(eLand l) {
733     if(modecode() != 0 && l != laCamelot) return 1;
734     if(modecode() != 0 && l == laCamelot) return 3;
735     for(auto& ti: land_tac)
736       if(ti.l == l)
737         return ti.multiplier;
738     return 0;
739     }
740 
tacticUnlocked(eLand l)741   bool tacticUnlocked(eLand l) {
742     if(autocheat) return true;
743     if(l == laWildWest || l == laDual) return true;
744     return hiitemsMax(treasureType(l)) * landMultiplier(l) >= 20;
745     }
746 
record(eLand land,int score,modecode_t xc IS (modecode ()))747   EX void record(eLand land, int score, modecode_t xc IS(modecode())) {
748     if(land >=0 && land < landtypes) {
749       for(int i=MAXTAC-1; i; i--) lsc[xc][land][i] = lsc[xc][land][i-1];
750       tactic::lsc[xc][land][0] = score;
751       }
752     int t = chances(land, xc);
753     int csum = 0;
754     for(int i=0; i<t; i++) if(lsc[xc][land][i] > 0) csum += lsc[xc][land][i];
755     if(csum > recordsum[xc][land]) recordsum[xc][land] = csum;
756     }
757 
record()758   void record() {
759     record(lasttactic, items[treasureType(lasttactic)]);
760     }
761 
unrecord(eLand land,flagtype xc=modecode ())762   void unrecord(eLand land, flagtype xc = modecode()) {
763     if(land >=0 && land < landtypes) {
764       for(int i=0; i<MAXTAC-1; i++) lsc[xc][land][i] = lsc[xc][land][i+1];
765       lsc[xc][land][MAXTAC-1] = -1;
766       }
767     }
768 
unrecord()769   void unrecord() {
770     unrecord(lasttactic);
771     }
772 
773   int tscorelast;
774 
uploadScoreCode(int code,int lb)775   void uploadScoreCode(int code, int lb) {
776     int tscore = 0;
777     for(int i=0; i<landtypes; i++)
778       tscore += recordsum[code][i] * tacmultiplier(eLand(i));
779     // printf("PTM score = %d\n", tscore);
780 
781     if(code == 0 && tscore > tscorelast) {
782       tscorelast = tscore;
783       if(tscore >= 1000) achievement_gain("PTM1", 'x');
784       if(tscore >= 5000) achievement_gain("PTM2", 'x');
785       if(tscore >= 15000) achievement_gain("PTM3", 'x');
786       }
787     achievement_score(lb, tscore);
788     }
789 
uploadScore()790   void uploadScore() {
791     uploadScoreCode(0, LB_PURE_TACTICS);
792     uploadScoreCode(2, LB_PURE_TACTICS_SHMUP);
793     uploadScoreCode(4, LB_PURE_TACTICS_COOP);
794     }
795 
showMenu()796   EX void showMenu() {
797 
798     flagtype xc = modecode();
799 
800     if(xc == 0) set_priority_board(LB_PURE_TACTICS);
801     if(xc == 2) set_priority_board(LB_PURE_TACTICS_SHMUP);
802     if(xc == 4) set_priority_board(LB_PURE_TACTICS_COOP);
803 
804     cmode = sm::ZOOMABLE;
805     if(mouseovers == "" || mouseovers == " ")
806       mouseovers = XLAT("pure tactics mode");
807     else
808       mouseovers = XLAT("pure tactics mode") + " - " + mouseovers;
809 
810     if(dialog::infix != "") mouseovers = mouseovers + " - " + dialog::infix;
811     else mouseovers = mouseovers + " - " + XLAT("press letters to search");
812 
813     {
814     dynamicval<bool> t(tactic::on, true);
815     generateLandList([] (eLand l) {
816       if(dialog::infix != "" && !dialog::hasInfix(linf[l].name)) return false;
817       return !!(land_validity(l).flags & lv::appears_in_ptm);
818       });
819     }
820 
821     int nl = isize(landlist);
822 
823     int nlm = nl;
824     int ofs = dialog::infix != "" ? 0 : dialog::handlePage(nl, nlm, (nl+1)/2);
825 
826     int vf = nlm ? min((vid.yres-64-vid.fsize) / nlm, vid.xres/40) : vid.xres/40;
827 
828     int xr = vid.xres / 64;
829 
830     if(on) record(specialland, items[treasureType(specialland)]);
831 
832     getcstat = SDLK_ESCAPE;
833 
834     map<int, int> land_for;
835 
836     for(int i=0; i<nl; i++) {
837       int i1 = i + ofs;
838       eLand l = landlist[i1];
839 
840       int i0 = 56 + i * vf;
841       color_t col;
842 
843       int ch = chances(l);
844 
845       if(!ch) continue;
846 
847       bool unlocked = tacticUnlocked(l);
848 
849       if(unlocked) col = linf[l].color; else col = 0x202020;
850 
851       int keyhere;
852       if(nlm <= 9) {
853         keyhere = '1' + i;
854         if(displayfrZH(xr*1, i0, 1, vf-4, s0+char(keyhere), 0xFFFFFF, 0))
855           getcstat = keyhere;
856         }
857       else
858         keyhere = 1000 + i1;
859       land_for[keyhere] = i1;
860 
861       if(displayfrZH(xr*(nlm <= 9 ? 2.5 : 1), i0, 1, vf-4, XLAT1(linf[l].name), col, 0) && unlocked)
862         getcstat = keyhere;
863 
864       if(unlocked || autocheat) {
865         for(int ii=0; ii<ch; ii++)
866           if(displayfrZH(xr*(24+2*ii), i0, 1, (vf-4)*4/5, lsc[xc][l][ii] > 0 ? its(lsc[xc][l][ii]) : "-", col, 16))
867             getcstat = keyhere;
868 
869         if(displayfrZH(xr*(24+2*10), i0, 1, (vf-4)*4/5,
870           its(recordsum[xc][l]) + " x" + its(tacmultiplier(l)), col, 0))
871             getcstat = keyhere;
872         }
873       else {
874         int m = landMultiplier(l);
875         displayfrZ(xr*26, i0, 1, (vf-4)*4/5,
876           XLAT("Collect %1x %2 to unlock", its((20+m-1)/m), treasureType(l)),
877           col, 0);
878         }
879       }
880 
881     dialog::displayPageButtons(3, dialog::infix == "");
882 
883     uploadScore();
884     if(on) unrecord(specialland);
885 
886     if(land_for.count(getcstat)) {
887       int ld = landlist[land_for.at(getcstat)];
888       subscoreboard scorehere;
889       for(int i=0; i<isize(scoreboard[xc]); i++) {
890         int sc = scoreboard[xc][i].scores[ld];
891         if(sc > 0)
892           scorehere.push_back(
893             make_pair(-sc, scoreboard[xc][i].username));
894         }
895       displayScore(scorehere, xr * 50);
896       }
897 
898     keyhandler = [land_for] (int sym, int uni) {
899       if(land_for.count(uni)) {
900         eLand ll = landlist[land_for.at(uni)];
901         dialog::do_if_confirmed([ll] {
902           stop_game();
903           specialland = ll;
904           restart_game(tactic::on ? rg::nothing : rg::tactic);
905           });
906         }
907       else if(uni == '0') {
908         if(tactic::on) {
909           stop_game();
910           firstland = laIce;
911           restart_game(rg::tactic);
912           }
913         else popScreen();
914         }
915 
916       else if(sym == SDLK_F1) gotoHelp(
917         "In the pure tactics mode, you concentrate on a specific land. "
918         "Your goal to obtain as high score as possible, without using "
919         "features of the other lands. You can then compare your score "
920         "with your friends!\n\n"
921 
922         "You need to be somewhat proficient in the normal game to "
923         "unlock the given land in this challenge "
924         "(collect 20 treasure in the given land, or 2 in case of Camelot).\n\n"
925 
926         "Since getting high scores in some lands is somewhat luck dependent, "
927         "you play each land N times, and your score is based on N consecutive "
928         "plays. The value of N depends on how 'fast' the land is to play, and "
929         "how random it is.\n\n"
930 
931         "In the Caribbean, you can access Orbs of Thorns, Aether, and "
932         "Space if you have ever collected 25 treasure in their native lands.\n\n"
933 
934         "The rate of treasure spawn is static in this mode. It is not "
935         "increased by killing monsters.\n\n"
936 
937         "Good luck, and have fun!"
938         );
939       else if(dialog::infix == "" && dialog::handlePageButtons(uni)) ;
940       else if(dialog::editInfix(uni)) ;
941       else if(doexiton(sym, uni)) popScreen();
942       };
943     }
944 
start()945   EX void start() {
946     dialog::infix = "";
947     popScreenAll();
948     pushScreen(tactic::showMenu);
949     }
950 EX }
951 
952 map<string, modecode_t> code_for;
953 EX map<modecode_t, string> meaning;
954 
955 char xcheat;
956 
save_mode_data(hstream & f)957 void save_mode_data(hstream& f) {
958   mapstream::save_geometry(f);
959 
960   if(yendor::on || tactic::on)
961     f.write<char>(0);
962   else
963     f.write<char>(land_structure);
964   f.write<char>(shmup::on);
965   f.write<char>(inv::on);
966   #if CAP_TOUR
967   f.write<char>(tour::on);
968   #else
969   f.write<char>(false);
970   #endif
971   f.write<char>(peace::on);
972   f.write<char>(peace::otherpuzzles);
973   f.write<char>(peace::explore_other);
974   f.write<char>(multi::players);
975   f.write<char>(xcheat);
976   if(casual) {
977     f.write<char>(1);
978     }
979   }
980 
modecode(int mode)981 EX modecode_t modecode(int mode) {
982   modecode_t x = legacy_modecode();
983   if(x != UNKNOWN) return x;
984 
985   xcheat = (cheater ? 1 : 0);
986   shstream ss;
987   ss.write(ss.vernum);
988   save_mode_data(ss);
989 
990   if(code_for.count(ss.s)) return code_for[ss.s];
991 
992   if(mode == 1) return UNKNOWN;
993 
994   modecode_t next = 100000;
995   while(meaning.count(next)) next++;
996 
997   meaning[next] = ss.s;
998   code_for[ss.s] = next;
999 
1000   if(mode == 2) return next;
1001 
1002   FILE *f = fopen(scorefile, "at");
1003   string s = as_hexstring(ss.s);
1004   fprintf(f, "MODE %d %s\n", next, s.c_str());
1005   fclose(f);
1006 
1007   return next;
1008   }
1009 
load_modecode_line(string s)1010 EX void load_modecode_line(string s) {
1011   int code = atoi(&s[5]);
1012   int pos = 5;
1013   while(s[pos] != ' ' && s[pos]) pos++;
1014   if(!s[pos]) return;
1015   pos++;
1016   string t = from_hexstring(s.substr(pos));
1017   code_for[t] = code;
1018   meaning[code] = t;
1019   }
1020 
1021 EX namespace peace {
1022 
1023   EX bool on = false;
1024   EX bool hint = false;
1025 
1026   EX bool otherpuzzles = true;
1027 
1028   EX bool explore_other = false;
1029 
1030   vector<eLand> simonlevels = {
1031     laCrossroads, laCrossroads2, laRlyeh,
1032     laDesert, laCaves, laAlchemist, laEmerald,
1033     laWineyard, laDeadCaves, laRedRock, laPalace,
1034     laLivefjord, laDragon
1035     };
1036 
1037   vector<eLand> puzzlelevels = {
1038     laBurial, laTortoise, laCamelot, laPalace
1039     };
1040 
1041   vector<eLand> explorelevels = {
1042     laIce, laJungle, laMirror, laDryForest, laCaribbean, laOcean, laZebra,
1043     laOvergrown, laWhirlwind, laWarpCoast, laReptile,
1044     laElementalWall, laAlchemist
1045     };
1046 
1047   vector<eLand> levellist;
1048 
getNext(eLand last)1049   eLand getNext(eLand last) {
1050     if(!peace::on) return laNone;
1051     if(levellist.empty()) showMenu();
1052     if(isElemental(last) && hrand(100) < 90)
1053       return laNone;
1054     else if(createOnSea(last))
1055       return getNewSealand(last);
1056     else if(isCrossroads(last)) {
1057       while(isCrossroads(last) || last == laCaribbean || last == laCamelot)
1058         last = hrand_elt(levellist);
1059       if(last == laElementalWall) last = laEFire;
1060       return last;
1061       }
1062     else return pick(laCrossroads, laCrossroads2);
1063     }
1064 
isAvailable(eLand l)1065   bool isAvailable(eLand l) {
1066     for(int i=0; explorelevels[i]; i++) if(explorelevels[i] == l) return true;
1067     return false;
1068     }
1069 
1070   EX namespace simon {
1071 
1072     vector<cell*> path;
1073     int tobuild;
1074 
build()1075     void build() {
1076       if(otherpuzzles || !on) return;
1077       while(isize(path) < tobuild) {
1078         cell *cp = path[isize(path)-1];
1079         cell *cp2 = path[isize(path)-2];
1080         vector<pair<cell*, cell*>> clister;
1081         clister.emplace_back(cp, cp);
1082 
1083         int id = 0;
1084         manual_celllister cl;
1085         while(id < isize(clister)) {
1086           cell *c = clister[id].first;
1087           cell *fr = clister[id].second;
1088           setdist(c, 5, NULL);
1089 
1090           forCellEx(c2,c)
1091             if(!cl.listed(c2) && passable(c2, c, 0) && (c2->land == specialland || c2->land == laTemple) && !c2->item) {
1092               if(!id) fr = c2;
1093               bool next;
1094               if(specialland == laRlyeh)
1095                 next = c2->land == laTemple && (cp2->land == laRlyeh || celldistAlt(c2) < celldistAlt(cp2) - 8);
1096               else
1097                 next = celldistance(c2, cp2) == 8;
1098               if(next) {
1099                 path.push_back(fr);
1100                 fr->item = itDodeca;
1101                 goto again;
1102                 }
1103               clister.emplace_back(c2, fr);
1104               cl.add(c2);
1105               }
1106           id++;
1107           }
1108         printf("Path broken, searched = %d\n", id);
1109         for(auto t: clister) t.first->item = itPirate;
1110         return;
1111         again: ;
1112         }
1113       }
1114 
extend()1115     EX void extend() {
1116       int i = 0;
1117       while(i<isize(path) && path[i]->item != itDodeca) i++;
1118       if(tobuild == i+9)
1119         addMessage("You must collect all the dodecahedra on the path!");
1120       tobuild = i + 9;
1121       build();
1122       }
1123 
init()1124     EX void init() {
1125       tobuild = 0;
1126       if(!on) return;
1127       if(otherpuzzles) { items[itGreenStone] = 500; return; }
1128       cell *c2 = cwt.at->move(0);
1129       makeEmpty(c2);
1130       c2->item = itOrbYendor;
1131 
1132       path.clear();
1133       path.push_back(cwt.at);
1134       path.push_back(c2);
1135       extend();
1136       }
1137 
restore()1138     EX void restore() {
1139       for(int i=1; i<isize(path); i++)
1140         if(path[i]->item == itNone && items[itDodeca])
1141           path[i]->item = itDodeca, items[itDodeca]--;
1142       }
1143   EX }
1144 
1145   EX bool in_minefield, in_dual;
1146 
reset_modes()1147   void reset_modes() {
1148     stop_game();
1149     if(in_minefield) {
1150       geometry = gNormal;
1151       variation = eVariation::bitruncated;
1152       in_minefield = false;
1153       }
1154     if(in_dual) {
1155       stop_game_and_switch_mode(rg::dualmode);
1156       geometry = gNormal;
1157       variation = eVariation::bitruncated;
1158       in_dual = false;
1159       }
1160     }
1161 
showMenu()1162   EX void showMenu() {
1163     string title =
1164       !otherpuzzles ? XLAT("memory game") :
1165       explore_other ? XLAT("exploration") :
1166       XLAT("puzzles");
1167     dialog::init(title, 0x40A040, 150, 100);
1168 
1169     int kind = 0;
1170 
1171     if(!otherpuzzles) {
1172       levellist = simonlevels, kind = 1;
1173       dialog::addInfo("Collect as many dodecahedra as you can!");
1174       dialog::addInfo("You have to return to the starting location!");
1175       dialog::addBreak(50);
1176       }
1177     else if(explore_other)
1178       levellist = explorelevels, kind = 2;
1179     else {
1180       levellist = puzzlelevels, kind = 0;
1181       dialog::addInfo("This mode removes roguelike elements,");
1182       dialog::addInfo("focusing on puzzles and exploration");
1183       dialog::addBreak(50);
1184       }
1185 
1186     char key = 'a';
1187     for(auto lev: levellist) {
1188       if(kind == 0) switch(lev) {
1189         case laBurial:
1190           dialog::addItem("excavate the treasures using your magical shovel", 'a');
1191           break;
1192         case laTortoise:
1193           dialog::addItem("find an adult tortoise matching the baby", 'b');
1194           break;
1195         case laCamelot:
1196           dialog::addItem("find the center of the Round Table in Camelot", 'c');
1197           break;
1198         case laPalace:
1199           dialog::addItem("follow the mouse", 'd');
1200           break;
1201         default: ;
1202         }
1203       else
1204         dialog::addItem(XLAT1(linf[lev].name), key++);
1205       dialog::add_action([lev] {
1206         dialog::do_if_confirmed([lev] {
1207           reset_modes();
1208           firstland = specialland = lev;
1209           if(!peace::on)
1210             stop_game_and_switch_mode(rg::peace);
1211           start_game();
1212           popScreenAll();
1213           });
1214         });
1215       }
1216 
1217     if(kind == 1) {
1218       dialog::addBreak(100);
1219       dialog::addItem(XLAT("other puzzles"), '1');
1220       dialog::add_action([] { otherpuzzles = true; explore_other = false; });
1221       }
1222 
1223     if(kind == 2) {
1224       dialog::addBreak(100);
1225       dialog::addItem(XLAT("other puzzles"), '1');
1226       dialog::add_action([] { otherpuzzles = true; explore_other = false; });
1227       }
1228 
1229     if(kind == 0) {
1230       dialog::addItem(XLAT("hyperbolic Minesweeper"), 'e');
1231       dialog::add_action([] {
1232         dialog::do_if_confirmed([] {
1233           reset_modes();
1234           if(peace::on) stop_game_and_switch_mode(rg::peace);
1235           specialland = firstland = laMinefield;
1236           if(!bounded) {
1237             geometry = gBring;
1238             variation = eVariation::goldberg;
1239             gp::param = gp::loc(2, 1);
1240             mine_adjacency_rule = true;
1241             in_minefield = true;
1242             bounded_mine_percentage = .2;
1243             }
1244           start_game();
1245           popScreenAll();
1246           });
1247         });
1248       dialog::addItem(XLAT("dual geometry puzzle"), 'f');
1249       dialog::add_action([] {
1250         dialog::do_if_confirmed([] {
1251           reset_modes();
1252           if(peace::on) stop_game_and_switch_mode(rg::peace);
1253           restart_game(rg::dualmode);
1254           in_dual = true;
1255           popScreenAll();
1256           pushScreen(dpgen::show_menu);
1257           });
1258         });
1259       dialog::addItem(XLAT("memory game"), 'g');
1260       dialog::add_action([] { otherpuzzles = false; });
1261       dialog::addItem(XLAT("exploration"), 'h');
1262       dialog::add_action([] { explore_other = true; });
1263       }
1264 
1265     dialog::addBreak(100);
1266 
1267     dialog::addBoolItem(XLAT("display hints"), hint, '2');
1268     dialog::add_action([] {
1269       hint = !hint; popScreen();
1270       });
1271     dialog::addItem(XLAT("Return to the normal game"), '0');
1272     dialog::add_action([] {
1273       reset_modes();
1274       if(peace::on) stop_game_and_switch_mode(rg::peace);
1275       });
1276 
1277     dialog::addBack();
1278     dialog::display();
1279     }
1280 
1281   auto aNext = addHook(hooks_nextland, 100, getNext);
1282   }
1283 
1284 #if CAP_COMMANDLINE
read_mode_args()1285 int read_mode_args() {
1286   using namespace arg;
1287   if(argis("-Y")) {
1288     yendor::on = true;
1289     shift(); yendor::challenge = argi();
1290     }
1291   else if(argis("-peace")) {
1292     peace::otherpuzzles = true;
1293     stop_game_and_switch_mode(peace::on ? 0 : rg::peace);
1294     }
1295   else if(argis("-pmem")) {
1296     peace::otherpuzzles = false;
1297     stop_game_and_switch_mode(peace::on ? 0 : rg::peace);
1298     }
1299   TOGGLE('T', tactic::on, stop_game_and_switch_mode(rg::tactic))
1300 
1301   else return 1;
1302   return 0;
1303   }
1304 
1305 auto ah = addHook(hooks_args, 0, read_mode_args);
1306 #endif
1307 }
1308