1 // This addon plays HyperRogue by itself.
2 // Well, not really -- it performs illegal moves, and it gets tons of treasure, orbs, and kills out of nowhere.
3 // Useful for debugging.
4 
5 #include "../hyper.h"
6 
7 namespace hr {
8 
9 bool doAutoplay;
10 eLand autoplayLand;
11 
12 namespace prairie { extern cell *enter; }
13 
sameland(eLand ll,eLand ln)14 bool sameland(eLand ll, eLand ln) {
15   if(ln == laBarrier || ln == laOceanWall)
16     return true;
17   if(ln == ll) return true;
18   if(isElemental(ln) && isElemental(ll)) return true;
19   if(isHaunted(ln) && isHaunted(ll)) return true;
20   return false;
21   }
22 
randomCheat()23 void randomCheat() {
24   int croll = hrand(50);
25   if (croll < 25) {
26     eItem b = (eItem) hrand(ittypes);
27     printf("Gain item: %s\n", iinf[b].name);
28     items[b] = (1 << hrand(11)) - 1;
29     items[itOrbYendor] &= 15;
30     reduceOrbPowers(); // in particlar, cancel out slaying+weakness, since the combination may confuse shadow
31   } else if (croll == 25) {
32     printf("Gain kills\n");
33     kills[hrand(motypes)] = (1 << hrand(11)) - 1;
34   } else if (croll == 26) {
35     printf("Princess soon\n");
36     princess::saved = true;
37     princess::everSaved = true;
38     items[itSavedPrincess]++;
39     items[itOrbLove] = 1;
40     items[itOrbTime] = 0;
41   } else if (croll == 27) {
42     printf("Gain allies\n");
43     forCellEx(cz, cwt.at)
44       if (!cz->monst)
45         cz->monst = pick(moMouse, moFriendlyGhost, moGolem, moTameBomberbird, moKnight);
46   } else if (croll == 28) {
47     printf("Place orbs with pickup effects\n");
48     forCellEx(cz, cwt.at)
49       if (!cz->item)
50         cz->item = pick(itOrbLife, itOrbFriend, itOrbSpeed, itOrbShield, itOrbChaos, itOrbPurity);
51   } else if (croll == 29) {
52     printf("Place fun walls\n");
53     forCellEx(cz, cwt.at)
54       if (!cz->wall && !cz->monst)
55         cz->wall = pick(waExplosiveBarrel, waBigStatue, waThumperOff, waBonfireOff, waCloud, waMirror);
56   } else if (croll == 30) {
57     cell *ct = dcal[hrand(isize(dcal))];
58     if (!isPlayerOn(ct) && !ct->monst && !ct->wall) {
59       eWall hazard = pick(waRose, waFireTrap, waMineMine, waTrapdoor, waChasm, waCavewall);
60       printf("Spam a hazard: %s\n", winf[hazard].name);
61       ct->wall = hazard;
62     }
63   } else if (croll == 31 && !memory_saving_mode) {
64     //printf("Saving memory\n");
65     //memory_saving_mode = true;
66     //save_memory();
67     //memory_saving_mode = false;
68   } else if (croll == 33) {
69     cell *ct = dcal[hrand(isize(dcal))];
70     if (!isPlayerOn(ct) && !ct->monst && !ct->wall) {
71       printf("Spam some slime\n");
72       ct->item = itNone;
73       ct->wall = hrand(2) ? waFloorA : waFloorB;
74       switch(hrand(4)) {
75         case 0: ct->monst = moSlime; break;
76         case 1: ct->item = itGreenStone; break;
77         default: ;
78       }
79     }
80   } else if (croll == 37) {
81     cell *ct = dcal[hrand(isize(dcal))];
82     if (!isPlayerOn(ct) && !ct->monst && !ct->wall) {
83       ct->monst = pick(moRagingBull, moTroll, moAcidBird, moMiner, moReptile, moVineBeast, moBug0, moBug1);
84       printf("Spam a monster: %s\n", minf[ct->monst].name);
85     }
86     // todo: dice
87   } else if (croll == 38) {
88     forCellEx(cz, cwt.at) {
89       if (cz->monst == moPrincessArmed) {
90         printf("Disarming a princess\n");
91         cz->monst = moPrincess;
92       }
93     }
94   } else if (croll == 39) {
95     //forCellEx(cz, cwt.at) {
96     //  if (!cz->monst) {
97     //    printf("Summoning an unarmed princess incorrectly\n");
98     //    cz->monst = moPrincess;
99     //    break;
100     //  }
101     //}
102   } else if (croll == 40) {
103     //forCellEx(cz, cwt.at) {
104     //  if (!cz->monst) {
105     //    printf("Summoning an armed princess incorrectly\n");
106     //    cz->monst = moPrincessArmed;
107     //    break;
108     //  }
109     //}
110   } else if (croll == 41) {
111     cell *ct = dcal[hrand(isize(dcal))];
112     if (among(ct->wall, waNone, waVinePlant, waFloorA, waFloorB, waTrapdoor, waChasm, waBigStatue)) {
113       // Set wparam on a cell where it shouldn't matter, so that if this wall is later converted
114       // to a walltype that does care by some code that assumes wparam was 0,
115       // we can find out if that causes bugs.
116       printf("Randomizing wparam on %s at %p\n", winf[ct->wall].name, (void *)ct);
117       ct->wparam = (unsigned char) hrand(256);
118     }
119   } else if (croll == 42) {
120     vid.wallmode = hrand(7);
121     printf("Set vid.wallmode to %d: %s\n", vid.wallmode, wdmodes[vid.wallmode]);
122   } else if (croll == 43) {
123     vid.monmode = hrand(4);
124     printf("Set vid.monmode to %d: %s\n", vid.monmode, mdmodes[vid.monmode]);
125   }
126 }
127 
cellToTarget()128 cell *cellToTarget() {
129   if (isCrossroads(cwt.at->land)) {
130     for (cell *c: dcal) {
131       if (c->land == laCamelot && c->wall == waNone) {
132         printf("Trying to teleport into Camelot\n");
133         items[itOrbTeleport] += 1;
134         return c;
135       }
136     }
137   }
138 
139   if(cwt.at->land == laCamelot) {
140     int oldDist = celldistAltRelative(cwt.at);
141     if (oldDist > 3) {
142       for (cell *c: dcal) {
143         if (c->land == laCamelot) {
144           int dist = celldistAltRelative(c);
145           if (-1 <= dist && dist <= 1 && hrand(10) == 0) {
146             printf("Trying to teleport near the round table (%d to %d)\n", oldDist, dist);
147             items[itOrbTeleport] += 1;
148             return c;
149           }
150         }
151       }
152     }
153   }
154 
155   return dcal[hrand(isize(dcal))];
156 }
157 
randomMove()158 void randomMove()
159 {
160   // don't show warning dialogs
161   items[itWarning] = 1;
162 
163   int roll = hrand(50);
164   if (roll == 0) {
165     // drop dead orb
166     bool res = movepcto(MD_DROP, 1);
167     printf("DROP: %d\n", res);
168   } else if (roll < 5) {
169     // skip turn
170     bool res = movepcto(MD_WAIT, 1);
171     printf("WAIT: %d\n", res);
172   } else if (roll < 42) {
173     // move to or attack a neighbor cell
174     int i = hrand(cwt.at->type);
175     cell *c2 = cwt.at->move(i);
176     cwt.spin = 0;
177     int d = neighborId(cwt.at, c2);
178     if (d >= 0) {
179       int subdir = (roll%2==0)?1:-1;
180       string c2info = dnameof(c2->wall) + "; " + dnameof(c2->monst) + "; " + dnameof(c2->item);
181       bool res = movepcto(d, subdir, false);
182       printf("MOVE %d [%s] sub %d: %d\n", d, c2info.c_str(), subdir, res);
183       if (!res && c2->monst) {
184         printf("clearing the monster (%s)\n", minf[c2->monst].name);
185         killMonster(c2, moNone);
186       }
187     } else {
188       printf("MOVE CONFUSED %d\n", d);
189     }
190   } else {
191     // try to use a ranged orb
192     cell *ct = cellToTarget();
193     eItem ti = targetRangedOrb(ct, roMouseForce);
194     const char *tm = (ti == eItem(-1)) ? "orb cannot be used (see message log)" : iinf[ti].name;
195     printf("TARGET %p: %s\n", (void*)ct, tm);
196   }
197 }
198 
noteUnusualSituations()199 void noteUnusualSituations()
200 {
201   if(cwt.at->monst && !isMultitile(cwt.at->monst)) {
202     // This is possible in multiple ways
203     printf("on a non-multitile monster: %s\n", minf[cwt.at->monst].name);
204     }
205   else if(isDie(cwt.at->wall)) {
206     // This is possible with aether + teleport
207     printf("on a wall-type die: %s\n", winf[cwt.at->wall].name);
208     }
209   }
210 
isAnythingWrong()211 bool isAnythingWrong()
212 {
213   uintptr_t ienter = (uintptr_t) prairie::enter;
214   if(ienter && ienter < 100000) {
215     printf("ERROR: prairie::enter has incorrect value\n");
216     return true;
217     }
218 
219   if(buggyGeneration || isize(buggycells)) {
220     println(hlog, "ERROR: buggy generation");
221     return true;
222     }
223 
224   if(isIcyLand(cwt.at)) {
225     float heat = HEAT(cwt.at);
226     // Checking for extreme values as well as NaNs and infinities
227     if (!(-1e10 < heat && heat < 1e10)) {
228       printf("ERROR: Heat is out of expected range\n");
229       return true;
230       }
231     }
232 
233   if (cwt.at->land == laCamelot) {
234     for(int i=0; i<isize(dcal); i++) {
235       cell *c = dcal[i];
236       if(c->land == laCamelot && celldistAltRelative(c) == 0 && c->wall != waRoundTable) {
237         printf("ERROR: The round table of camelot is interrupted by a cell of %s\n", winf[c->wall].name);
238         return true;
239         }
240       }
241     }
242 
243   for(int i=0; i<isize(dcal); i++) {
244     cell *c = dcal[i];
245     (void)isChild(c, NULL);
246     if(childbug) return true;
247     if(c->land == laNone) {
248       printf("ERROR: no-land found\n");
249       return true;
250       }
251     if(c->item == itBuggy || c->item == itBuggy2) {
252       printf("ERROR: buggy item found\n");
253       return true;
254       }
255     if(!euclid && isPrincess(c->monst) && princess::getPrincessInfo(c) == nullptr) {
256       printf("ERROR: missing princess info\n");
257       return true;
258       }
259     if(dice::on(c)) {
260       if(dice::data.count(c) == 0) {
261         c->item = itBuggy;
262         printf("ERROR: missing dice::data[%p]\n", (void *)c);
263         return true;
264         }
265       else if(!dice::data[c].which) {
266         // we might get here instead if someone already tried to do data[c], which creates a new element out of nothing
267         c->item = itBuggy;
268         printf("ERROR: missing dice::data[%p].which\n", (void *)c);
269         return true;
270         }
271       }
272     }
273 
274     return false;
275   }
276 
stopIfBug()277 void stopIfBug()
278 {
279   if(isAnythingWrong()) {
280     if(noGUI) {
281       exit(1);
282       }
283     else {
284       kills[moPlayer] = 0;
285       canmove = true;
286       doAutoplay = false;
287       }
288     }
289   }
290 
showAutoplayStats()291 void showAutoplayStats()
292 {
293   printf("cells travelled: %d\n", celldist(cwt.at));
294 
295   printf("\n");
296 
297   for(int i=0; i<ittypes; i++) if(items[i])
298     printf("%4dx %s\n", items[i], iinf[i].name);
299 
300   printf("\n");
301 
302   for(int i=1; i<motypes; i++) if(kills[i])
303     printf("%4dx %s <%d>\n", kills[i], minf[i].name, i);
304 
305   printf("\n\n\n");
306   }
307 
resetIfNeeded(int * gcount)308 void resetIfNeeded(int *gcount)
309 {
310   if(hrand(5000) == 0 || (isGravityLand(cwt.at->land) && coastvalEdge(cwt.at) >= 100) || *gcount > 2000 || cellcount >= 20000000) {
311     printf("RESET\n");
312     *gcount = 0;
313     cellcount = 0;
314     activateSafety(autoplayLand ? autoplayLand : landlist[hrand(isize(landlist))]);
315     if (cellcount < 0) {
316       //printf("How did cellcount become negative?\n");
317       cellcount = 1;
318       }
319     }
320 
321   if(cwt.at->land == laWestWall && cwt.at->landparam >= 30) {
322     printf("Safety generated\n");
323     forCellEx(c2, cwt.at) c2->item = itOrbSafety;
324     }
325   }
326 
327 /* auto a3 = addHook(hooks_nextland, 100, [] (eLand l) {
328   eLand l2;
329   do { l2 = pick(laRuins, laTerracotta, laPrairie); } while(l2 == l);
330   return l2;
331   }); */
332 
autoplay(int num_moves=1000000000)333 void autoplay(int num_moves = 1000000000) {
334   // drawMesh();
335   // exit(0);
336 
337   doAutoplay = true;
338 
339   cheater = 1;
340   eLand lland = laIce;
341   eLand lland2 = laIce;
342   int lcount = 0;
343   int gcount = 0;
344   int lastgold = 0;
345 
346   // landlist = { laRuins, laTerracotta, laPrairie };
347 
348   generateLandList(isLandIngame);
349 
350 #ifndef NOSDL
351   int lastdraw = 0;
352 #endif
353   while(doAutoplay) {
354 
355     if(gold() > lastgold) {
356       lastgold = gold();
357       gcount = 0;
358       }
359     else gcount++;
360 
361     if(false && sameland(lland, cwt.at->land)) lcount++;
362     else {
363       lcount = 0; lland2 = lland; lland = cwt.at->land;
364       printf("%10dcc %5dt %5de %5d$ %5dK %5dgc %-30s H%d\n", cellcount, turncount, celldist(cwt.at), gold(), tkills(), gcount, dnameof(cwt.at->land).c_str(), hrand(1000000));
365       fflush(stdout);
366 #ifndef NOSDL
367       if(int(SDL_GetTicks()) > lastdraw + 3000) {
368         lastdraw = SDL_GetTicks();
369         fullcenter();
370         msgs.clear();
371         popScreenAll();
372         drawscreen();
373         clearAnimations();
374         }
375 #endif
376       //mainloop();
377       }
378 
379     /* if(gcount < 500) for(int i=1; i<isize(dcal); i++) {
380       c2 = dcal[i];
381       if(lcount >= 50 && !sameland(lland, c2->land) && !sameland(lland2, c2->land)) break;
382       else if(lcount < 50 && c2->item && c2->item != itOrbSafety) break;
383       } */
384 
385     randomCheat();
386     randomMove();
387     if(false) if(turncount % 5000 == 0) showAutoplayStats();
388     resetIfNeeded(&gcount);
389     noteUnusualSituations();
390     stopIfBug();
391 
392     if(turncount >= num_moves) return;
393     }
394   }
395 
readArgs()396 int readArgs() {
397   using namespace arg;
398 
399   if(0) ;
400   else if(argis("-autoplayW")) {
401     // Start in this land and reset to this land
402     PHASE(3);
403     shift();
404     autoplayLand = readland(args());
405     activateSafety(autoplayLand);
406     }
407   else if(argis("-autoplay")) {
408     PHASE(3);
409     autoplay();
410     }
411   else if(argis("-autoplayN")) {
412     PHASE(3);
413     shift();
414     autoplay(argi());
415     }
416 
417   else return 1;
418   return 0;
419   }
420 
421 auto hook = addHook(hooks_args, 100, readArgs);
422 
423 }
424