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