Pants color selector
[troll.git] / src / main / java / com / codingame / game / View.java
1 package com.codingame.game;
2
3 import java.util.Random;
4 import java.util.ArrayList;
5 import java.util.Comparator;
6
7 import com.codingame.gameengine.core.GameManager;
8 import com.codingame.gameengine.core.MultiplayerGameManager;
9 import com.codingame.gameengine.module.entities.GraphicEntityModule;
10 import com.codingame.gameengine.module.entities.Rectangle;
11 import com.codingame.gameengine.module.entities.Sprite;
12 import com.codingame.gameengine.module.entities.SpriteAnimation;
13 import com.codingame.gameengine.module.entities.Text;
14 import com.codingame.gameengine.module.entities.TextBasedEntity;
15 import com.codingame.gameengine.module.entities.Group;
16 import com.codingame.gameengine.module.entities.Curve;
17 import com.google.inject.Inject;
18
19 class View {
20     @Inject private MultiplayerGameManager<com.codingame.game.Player> gameManager;
21     @Inject private GraphicEntityModule graphicEntityModule;
22     @Inject PantsModule pantsModule;
23
24     enum Dir {
25         LEFT("walks left.", 0),
26         STILL("stands still.", 1),
27         RIGHT("walks right.", 2);
28
29         String movement; int index;
30         Dir(String mvt, int i) { movement = mvt; index = i; }
31     }
32
33     class Player {
34         Model.Player model;
35
36         int colorToken;
37         String nicknameToken;
38         String avatarToken;
39
40         double frameRot;
41
42         Group avatar;
43         Text stoneCounter;
44         Text message;
45         Sprite castle;
46         Text stone;
47         Text stoneReminder;
48
49         void init(com.codingame.game.Player p) {
50             model = p.model;
51             colorToken = p.getColorToken();
52             nicknameToken = p.getNicknameToken();
53             avatarToken = p.getAvatarToken();
54
55             boolean p0 = model.index == 0;
56             int x = p0 ? 280 : 1920 - 280;
57             int y = 220;
58
59             Sprite frame = graphicEntityModule.createSprite()
60                 .setImage("frame.png")
61                 .setAnchor(0.5)
62                 .setRotation(frameRot)
63                 .setZIndex(22)
64                 .setTint(colorToken);
65
66             Sprite frameBg = graphicEntityModule.createSprite()
67                 .setImage("frame_bg.png")
68                 .setAnchor(0.5)
69                 .setRotation(frameRot)
70                 .setZIndex(20);
71
72             Sprite avatarSprite = graphicEntityModule.createSprite()
73                 .setZIndex(21)
74                 .setImage(avatarToken)
75                 .setAnchor(0.5)
76                 .setBaseHeight(116)
77                 .setBaseWidth(116);
78
79             avatar = graphicEntityModule
80                 .createGroup(frame, frameBg, avatarSprite)
81                 .setX(x).setY(y);
82
83             Text text = graphicEntityModule.createText(nicknameToken)
84                     .setX(x)
85                     .setY(y + 120)
86                     .setZIndex(20)
87                     .setFontSize(40)
88                     .setFillColor(0x7f3f00)
89                     .setAnchor(0.5);
90
91             stoneCounter = graphicEntityModule.createText()
92                 .setX(x)
93                 .setY(y+200)
94                 .setZIndex(20)
95                 .setFontSize(40)
96                 .setFillColor(0x7f3f00)
97                 .setAnchor(0.5);
98             updateStoneCounter();
99
100             message = graphicEntityModule.createText()
101                 .setX(p0 ? 15 : 1920-15)
102                 .setY(680)
103                 .setZIndex(1)
104                 .setFontSize(40)
105                 .setFillColor(0xffbf7f)
106                 .setAnchorX(p0 ? 0 : 1)
107                 .setAnchorY(1);
108
109             castle = graphicEntityModule.createSprite()
110                 .setImage("castle.png")
111                 .setTint(colorToken)
112                 .setX(p0 ? 160 : 1920-160)
113                 .setY(p0 ? 890 : 880)
114                 .setZIndex(1)
115                 .setAnchorX(0.5)
116                 .setAnchorY(1)
117                 .setScaleX(p0 ? 1 : -1);
118
119             stone = graphicEntityModule.createText()
120                 .setZIndex(3)
121                 .setFontSize(150)
122                 .setFillColor(0x12322a)
123                 .setAnchor(0.5)
124                 .setAlpha(0);
125
126             stoneReminder = graphicEntityModule.createText()
127                 .setX(p0 ? x + 100 : x - 100)
128                 .setY(y)
129                 .setZIndex(20)
130                 .setFontSize(80)
131                 .setFontFamily("monospace")
132                 .setStrokeColor(0xff0080)
133                 .setFillColor(0xff0080)
134                 .setAnchorX(p0 ? 0 : 1)
135                 .setAnchorY(0.5);
136             pantsModule.displayOnToggleState(stoneReminder, "debug", true);
137         }
138
139         void updateStoneCounter() {
140             int stones = model.getStones();
141             if (stones <= 0) {
142                 stoneCounter.setText("Out of stones!");
143                 stoneCounter.setFillColor(0xff7777);
144             }
145             else if (stones == 1) {
146                 stoneCounter.setText("1 stone");
147                 stoneCounter.setFillColor(0xffbb77);
148             }
149             else {
150                 stoneCounter.setText(stones + " stones");
151             }
152         }
153
154         void animateStones(int stones) {
155             String stonesString = Integer.valueOf(stones).toString();
156             stone.setX(castle.getX());
157             stone.setY(castle.getY() - 100);
158             stone.setText(stonesString);
159             stone.setAlpha(1);
160             graphicEntityModule.commitEntityState(0, stone);
161     
162             int peakX = (castle.getX() + troll.getX()) / 2;
163             int peakY = 540;
164             stone.setX(peakX);
165             stone.setY(peakY, Curve.EASE_OUT);
166             graphicEntityModule.commitEntityState(0.25,
167                                                   stone,
168                                                   stoneCounter);
169     
170             stone.setX(troll.getX());
171             stone.setY(troll.getY() - 50, Curve.EASE_IN);
172             stone.setAlpha(0, Curve.EASE_IN);
173             graphicEntityModule.commitEntityState(0.5, stone);
174
175             stoneReminder.setText(stonesString);
176             graphicEntityModule.commitEntityState(0, stoneReminder);
177         }
178
179         void displayMessage(String msg) {
180             message.setText(msg);
181             graphicEntityModule.commitEntityState(0, message);
182         }
183
184         void markLoser() {
185             graphicEntityModule.commitEntityState(0.5, avatar);
186             int dir = random.nextInt(2) == 1 ? 1 : -1;
187             avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
188         }
189
190         void destroy() {
191             gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
192             graphicEntityModule.commitEntityState(0.5, castle);
193             castle.setX(castle.getX(), Curve.ELASTIC);
194             castle.setScaleY(-0.2, Curve.EASE_IN);
195         }
196
197         void startTurn() {
198             graphicEntityModule.commitEntityState(0, stoneReminder);
199         }
200
201         void victory() {
202             gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
203             graphicEntityModule.commitEntityState(0.5, avatar);
204             avatar.setScaleX(1.5, Curve.EASE_OUT);
205             avatar.setScaleY(1.5, Curve.EASE_OUT);
206             avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
207                                Curve.ELASTIC);
208         }
209
210         void throwStones(int stones) {
211             gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
212         }
213
214         void threwMoreStonesThanHad() {
215             gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried to throw more stones than they had.  I'll let it slide for this time.  (But not let them throw that much!)"));
216         }
217
218         void failedToThrowStonesAndShouldHave() {
219             gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones.  Fixing that for them because I'm in a good mood today."));
220         }
221
222         void markTimeout() {
223             animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
224         }
225
226         void markIllegal() {
227             animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
228         }
229
230         void markCheat() {
231             animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
232         }
233     }
234
235     Model model;
236     Random random = new Random();
237     Text trollMessage;
238     Group troll;
239     Text trollPositionGauge;
240     Player p0 = new Player(), p1 = new Player();
241     Text turnCounter; int _turns = 0;
242
243     void init(Model m) {
244         model = m;
245         drawBackground();
246
247         /*
248          * Random π/2-grained rotation of the avatar frames.  Avoid
249          * having them π/2 apart, though, as one of them is likely
250          * going to end upside-down and the trick would be revealed.
251          * And I'd have to "draw" a new frame. Ewww.
252          */
253         p0.frameRot = random.nextInt(4) * Math.PI / 2;
254         p0.init(gameManager.getPlayer(0));
255         p1.frameRot = p1.frameRot +
256             (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
257         p1.init(gameManager.getPlayer(1));
258
259         drawTroll();
260
261         drawDebug();
262     }
263
264     void startTurn() {
265         p0.startTurn();
266         p1.startTurn();
267
268         trollMessage.setX(troll.getX());
269
270         animateTurnCounter();
271     }
272
273     private class Pos {
274         int x, y;
275         Pos(int _x, int _y) { x = _x; y = _y; }
276     }
277
278     private void drawBackground() {
279         graphicEntityModule.createSprite()
280                 .setImage("background.png")
281                 .setAnchor(0);
282
283         int numMountains = random.nextInt(5);
284         while (numMountains --> 0) {
285             final int pngWidth = 366;
286             double scale = 0.5 * (1 + random.nextDouble());
287             int x = random.nextInt(1920 + (int) (scale*pngWidth))
288                 - (int) (scale*pngWidth/2);
289             int baseTint = 64 + random.nextInt(128);
290             Sprite mountain = graphicEntityModule.createSprite()
291                 .setImage("mountain.png")
292                 .setX(x)
293                 .setY(680)
294                 .setAnchorX(0.5)
295                 .setAnchorY(283.0 / 321.0)
296                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
297                 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
298                 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
299                 .setSkewX((random.nextDouble() - 0.5) / 4)
300                 .setSkewY((random.nextDouble() - 0.5) / 8)
301                 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
302                          + (baseTint + random.nextInt(16) - 8) * 0x0100
303                          + (baseTint + random.nextInt(16) - 8) * 0x01);
304             graphicEntityModule.createSprite().setImage("mountaintop.png")
305                 .setX(mountain.getX())
306                 .setY(mountain.getY())
307                 .setAnchorX(mountain.getAnchorX())
308                 .setAnchorY(mountain.getAnchorY())
309                 .setRotation(mountain.getRotation())
310                 .setScaleX(mountain.getScaleX())
311                 .setScaleY(mountain.getScaleY())
312                 .setSkewX(mountain.getSkewX())
313                 .setSkewY(mountain.getSkewY());
314         }
315
316         int numTrees = random.nextInt(21);
317         ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
318         while (numTrees --> 0) {
319             int x, y;
320             do {
321                 x = random.nextInt(1920);
322                 // yes, this biases randomness wrt perspective! :-(
323                 y = 700 + random.nextInt(175);
324             } while (y > 880 && (x < 200 || x > 1720));
325             poss.add(new Pos(x, y));
326         }
327         poss.sort(new Comparator<Pos>() {
328                 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
329             });
330
331         for (Pos p : poss) {
332             double scale = ( 90.0 / 433.0           // base height from PNG
333                              * (p.y - 680) / (875 - 680) ); // perspective
334             graphicEntityModule.createSprite()
335                 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
336                                                  : "Alshockv2.png")
337                 .setAnchorX(0.5)
338                 .setAnchorY(1)
339                 .setX(p.x)
340                 .setY(p.y)
341                 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
342                            * (1 + (random.nextDouble() - 0.5) / 6))
343                 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
344                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
345                 .setSkewX((random.nextDouble() - 0.5) /4)
346                 .setSkewY((random.nextDouble() - 0.5) /8);
347         }
348     }
349
350     enum TrollRace {
351         Troll("The troll", 0xfac200, "bland"),
352         IceTroll("The ice troll", 0x59a2a2, "ice"),
353         RockTroll("The rock troll", 0x78877f, "rock"),
354         WaterTroll("The water troll", 0x2b2fc6, "water"),
355         OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
356         String starter, parser; int tint;
357         TrollRace(String s, int t, String p) {
358             starter = s;
359             tint = t;
360             parser = p;
361         }
362         String nonStarter() {
363             return Character.toLowerCase(starter.charAt(0))
364                 + starter.substring(1);
365         }
366     }
367     TrollRace trollRace;
368
369     private void drawTroll() {
370         int r, league = gameManager.getLeagueLevel();
371         if (league <= 1) r = 4;
372         else if (league <= 2) r = 8;
373         else r = 10;
374
375         r = random.nextInt(r);
376         if (r < 4) trollRace = TrollRace.Troll;
377         else if (r < 6) trollRace = TrollRace.IceTroll;
378         else if (r < 8) trollRace = TrollRace.RockTroll;
379         else if (r < 9) trollRace = TrollRace.WaterTroll;
380         else if (r < 10) trollRace = TrollRace.OlogHai;
381         else throw new RuntimeException("Internal error: unknown troll race " + r);
382
383         // We read it for debugging purposes, but don't echo it back
384         // to the IDE.  It is, after all, *not* a map parameter!
385         String buf = gameManager.getGameParameters().getProperty("ehtnicity");
386         if (buf != null) {
387             String key = "";
388             for (char c : buf.toCharArray())
389                 if (Character.isLetter(c))
390                     key += Character.toLowerCase(c);
391             iHateJava: do {
392                 for (TrollRace race : TrollRace.values()) {
393                     if (key.equals(race.parser)) {
394                         trollRace = race;
395                         break/*ing news: */ iHateJava;
396                     }
397                 }
398                 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
399             } while (false);
400         }
401         photoFinish: ; // The race is through, but Java has no goto :-(
402
403         Sprite trollBody = graphicEntityModule.createSprite()
404             .setImage("troll_body.png")
405             .setAnchorX(0.5)
406             .setAnchorY(1)
407             .setTint(trollRace.tint);
408         Sprite trollPantsRed = graphicEntityModule.createSprite()
409             .setImage("pants_red.png")
410             .setAnchorX(0.5)
411             .setAnchorY(1);
412         pantsModule.displayOnPantsState(trollPantsRed, 1);
413         Sprite trollPantsGreen = graphicEntityModule.createSprite()
414             .setImage("pants_green.png")
415             .setAnchorX(0.5)
416             .setAnchorY(1);
417         pantsModule.displayOnPantsState(trollPantsGreen, 2);
418         Sprite trollPantsBlue = graphicEntityModule.createSprite()
419             .setImage("pants_blue.png")
420             .setAnchorX(0.5)
421             .setAnchorY(1);
422         pantsModule.displayOnPantsState(trollPantsBlue, 3);
423         Sprite trollPantsPerv = graphicEntityModule.createSprite()
424             .setImage("pants_perv.png")
425             .setAnchorX(0.5)
426             .setAnchorY(1);
427         pantsModule.displayOnPantsState(trollPantsPerv, 4);
428         troll = graphicEntityModule
429             .createGroup(trollBody, trollPantsRed,
430                          trollPantsGreen, trollPantsBlue, trollPantsPerv)
431             .setX(1920/2)
432             .setY(880)
433             .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
434             .setZIndex(2);
435         trollPositionGauge = graphicEntityModule.createText()
436             .setZIndex(2)
437             .setAnchor(0.5)
438             .setFontSize(40)
439             .setX(1980/2)
440             .setY(980)
441             .setFillColor(0xffffff);
442         moveTroll();
443
444         trollMessage = graphicEntityModule.createText()
445             .setX(1902/2)
446             .setY(680)
447             .setAnchorX(0.5)
448             .setAnchorY(0)
449             .setTextAlign(TextBasedEntity.TextAlign.CENTER)
450             .setStrokeColor(0xFFFF00)
451             .setFillColor(0xFFFF00)
452             .setFontSize(40);
453         pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
454     }
455
456     private void moveTroll() {
457         graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
458         int x0 = p0.castle.getX(), x1 = p1.castle.getX();
459         int y0 = p0.castle.getY(), y1 = p1.castle.getY();
460         troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
461                    Curve.ELASTIC);
462         troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
463                    Curve.ELASTIC);
464
465         trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
466         int distLeft = model.trollPosition;
467         int distRight = model.roadLength - model.trollPosition;
468         if (distLeft <= 0) {
469             trollPositionGauge.setText("← " + distRight);
470         }
471         else if (distRight <= 0) {
472             trollPositionGauge.setText(distLeft + " →");
473         }
474         else {
475             trollPositionGauge.setText(distLeft + " ↔ " + distRight);
476         }
477         graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
478         trollPositionGauge.setX(troll.getX());
479     }
480
481     void moveTroll(Dir d) {
482         moveTroll();
483         gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
484
485         trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
486         graphicEntityModule.commitEntityState(0.5, trollMessage);
487         trollMessage.setAlpha(0, Curve.EASE_IN);
488         graphicEntityModule.commitEntityState(1, trollMessage);
489     }
490
491     void animateTurnCounter() {
492         for (int i = 0; i < 10; i++) {
493             turnCounter.setText("T" + _turns + "." + i);
494             // The following line is likely not a bug.
495             graphicEntityModule.commitEntityState((double) i/9, turnCounter);
496         }
497         _turns++;
498     }
499
500     void drawDebug() {
501         String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
502             .setSourceImage("debug.png")
503             .setImageCount(2)
504             .setWidth(900)
505             .setHeight(150)
506             .setOrigRow(0)
507             .setOrigCol(0)
508             .setImagesPerRow(1)
509             .setName("debug")
510             .split();
511         SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
512             .setImages(debugModePngs)
513             .setX(1920 / 2)
514             .setY(60)
515             .setAnchorX(0.5)
516             .setLoop(true);
517         pantsModule.displayOnToggleState(debugMode, "debug", true);
518
519         turnCounter = graphicEntityModule.createText()
520             .setAnchorX(0.5)
521             .setAnchorY(0)
522             .setX(1920 / 2)
523             .setY(260)
524             .setStrokeColor(0xff0080)
525             .setFillColor(0xff0080)
526             .setFontFamily("monospace")
527             .setFontWeight(Text.FontWeight.BOLD)
528             .setFontSize(100);
529         pantsModule.displayOnToggleState(turnCounter, "debug", true);
530         animateTurnCounter();
531     }
532
533     void animateLoss(int x, int y, int size, String message) {
534         int startX;
535         if (x < 1920/2) { startX = 1920; }
536         else if (x > 1920/2) { startX = 1920; }
537         else { startX = 1920 * random.nextInt(2); }
538
539         Text msg = graphicEntityModule.createText(message)
540             .setX(startX)
541             .setY(1080)
542             .setAnchorX(0.5)
543             .setAnchorY(0.5)
544             .setScaleX(3*random.nextDouble() - 1)
545             .setScaleY(3*random.nextDouble() - 1)
546             .setSkewX(2*random.nextDouble() - 1)
547             .setSkewY(2*random.nextDouble() - 1)
548             .setRotation(4*Math.PI * (1 + random.nextDouble())
549                          * (random.nextInt(2) == 0 ? 1 : -1))
550             .setFontSize(0)
551             .setStrokeColor(0xff7f7f)
552             .setFillColor(0xff7f7f)
553             .setFontWeight(Text.FontWeight.BOLD)
554             .setTextAlign(TextBasedEntity.TextAlign.CENTER);
555         graphicEntityModule.commitEntityState(0.25, msg);
556         Curve curve = Curve.ELASTIC;
557         msg.setX(x, Curve.EASE_OUT)
558             .setY(y, Curve.ELASTIC)
559             .setScaleX(1, curve)
560             .setScaleY(1, curve)
561             .setSkewX(0, curve)
562             .setSkewY(0, curve)
563             .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
564             .setFontSize(size, curve);
565     }
566
567     void doubleDefeat() {
568         gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
569         animateLoss(1920/2, 680, 150, "L0SERZ!");
570     }
571
572     void draw() {
573         gameManager.addToGameSummary("Draw.");
574         animateLoss(1920/2, 680, 200, "DRAW");
575     }
576
577     String selectTrollMessage(Dir d) {
578         if (random.nextInt(10000) == 0) {
579             return specials[random.nextInt(specials.length)];
580         }
581
582         // yup, still biased
583         int i = random.nextInt(directed.length + isotropic.length / 3);
584         if (i < directed.length) {
585             return directed[i][d.index];
586         }
587         else {
588             return isotropic[random.nextInt(isotropic.length)];
589         }
590     }
591
592     // You'll never remember if you ever saw these…
593     String specials[] = {
594         "Never gonna give you up\nNever gonna let you down\nNever gonna run around and desert you\nNever gonna make you cry\nNever gonna say goodbye\nNever gonna tell a lie and hurt you",
595         "CG know what they're doing."
596     };
597
598     // Most of what ought to happen in normal play
599     String directed[][] = {
600         // movies
601         { "Han shot first", "I am your father", "Greedo shot first" },
602         { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
603         { "star wars > star trek", "my god, it's full of troll", "star trek > star wars" },
604         // More movie controversies sought.  Apply on the puzzle contrib page.
605
606         // music
607         { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
608         { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
609
610         // everyday life
611         { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
612         { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
613         { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
614         { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
615         { "santa claus is really\nthe tooth fairy", "the easter bunny tasted yummy", "the tooth fairy is\nreally santa claus" },
616         // Ditto.  Need moar troll.
617
618         // gaming
619         { "PC > console", "pong is still\nunequaled", "console > PC" },
620         { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
621         { "pad > stick", "mouse gaming is lame", "stick > pad" },
622         { "RTS > FPS", "solitaire best game", "FPS > RTS" },
623         { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
624         { "orcs are wusses", "the amulet is in another dungeon", "elves are wusses" },
625         { "here's a link to my patreon", "my apm > yours", "here's my soundcloud" },
626         { "all your stones is belong to us", "all your castle are belong to us", "all your rocks is belong to us" },
627         // I'm not exactly a gamer myself, I take hints on the topics du jour
628
629         // programming
630         { "vi < emacs", "i code with Notepad", "emacs > vi" },
631         { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
632         { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
633         { "static linking best", "symlinking best", "dynamic linking best" },
634         { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
635         { "objects > functions", "it's closures\nall the way down", "functions > objects" },
636         { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
637         { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
638         // This category's not too bad.
639
640         // Internet
641         { "gmail > github", "copy-paste FTW", "github > gmail" },
642         { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
643         { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
644         { "jira > trello", "bugzilla FTW", "trello > jira" },
645         { "IRC > slack", "chat is work", "discord < IRC" },
646         { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
647         { "there's an app for that", "there's a bean for that", "there's an applet for that" },
648         // More always welcome here.
649
650         // CodinGame
651         { "my nn is in python", "my language has -O3", "my code is more than 100k" },
652         { "i found a bug\nin temperatures", "i found a bug on\nthe leaderboard", "i found a bug\nin chuck norris" },
653         { "fix it", "how is ur csb", "ezpz" },
654         { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
655         { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
656         { "optimizing for the contest\ntestcase is cheating", "having moar accounts than cg\nstaff will get you banned", "it's not hardcoding\nif it's stochastic" },
657         { "light theme best", "ascii > graphics", "dark theme best" },
658         { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
659         { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
660         // And here.  Especially as I'm not that active on #World or #Ru.
661     };
662
663     // Those for which I couldn't find a meaningful directednessability.
664     String isotropic[] = {
665         "(unclosed",
666         "Electron apps are the fastest",
667         "Rosebud",
668         "Thanos did nothing wrong",
669         "developers developers developers",
670         "the cloud is just\nother ppl's computers",
671         "ur doin it rong",
672         "tortue",
673         "31OCT = 25DEC",
674         "ASCII stupid question\nget a stupid ANSI",
675         "trolling is a art"
676         // I try and avoid those, but if really it fits nowhere else…
677     };
678 }