Add a side seed for @pb4. Cry before the in-IDE HTML rendering.
[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         // base png: 514×387
350         Sprite f7u12 = graphicEntityModule.createSprite()
351             .setImage("f7u12.png")
352             .setX(1920 / 2)
353             .setY(1080 / 2)
354             .setAnchorX(0.5)
355             .setAnchorY(0.5)
356             .setBaseWidth(514*1080/387)
357             .setBaseHeight(1080)
358             .setZIndex(200);
359         pantsModule.displayOnToggleState(f7u12, "troll", true);
360     }
361
362     enum TrollRace {
363         Troll("The troll", 0xfac200, "bland"),
364         IceTroll("The ice troll", 0x59a2a2, "ice"),
365         RockTroll("The rock troll", 0x78877f, "rock"),
366         WaterTroll("The water troll", 0x2b2fc6, "water"),
367         OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
368         String starter, parser; int tint;
369         TrollRace(String s, int t, String p) {
370             starter = s;
371             tint = t;
372             parser = p;
373         }
374         String nonStarter() {
375             return Character.toLowerCase(starter.charAt(0))
376                 + starter.substring(1);
377         }
378     }
379     TrollRace trollRace;
380
381     private void drawTroll() {
382         int r, league = gameManager.getLeagueLevel();
383         if (league <= 1) r = 4;
384         else if (league <= 2) r = 8;
385         else r = 10;
386
387         r = random.nextInt(r);
388         if (r < 4) trollRace = TrollRace.Troll;
389         else if (r < 6) trollRace = TrollRace.IceTroll;
390         else if (r < 8) trollRace = TrollRace.RockTroll;
391         else if (r < 9) trollRace = TrollRace.WaterTroll;
392         else if (r < 10) trollRace = TrollRace.OlogHai;
393         else throw new RuntimeException("Internal error: unknown troll race " + r);
394
395         // We read it for debugging purposes, but don't echo it back
396         // to the IDE.  It is, after all, *not* a map parameter!
397         String buf = gameManager.getGameParameters().getProperty("ehtnicity");
398         if (buf != null) {
399             String key = "";
400             for (char c : buf.toCharArray())
401                 if (Character.isLetter(c))
402                     key += Character.toLowerCase(c);
403             iHateJava: do {
404                 for (TrollRace race : TrollRace.values()) {
405                     if (key.equals(race.parser)) {
406                         trollRace = race;
407                         break/*ing news: */ iHateJava;
408                     }
409                 }
410                 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
411             } while (false);
412         }
413         photoFinish: ; // The race is through, but Java has no goto :-(
414
415         Sprite trollBody = graphicEntityModule.createSprite()
416             .setImage("troll_body.png")
417             .setAnchorX(0.5)
418             .setAnchorY(1)
419             .setTint(trollRace.tint);
420         Sprite trollPantsRed = graphicEntityModule.createSprite()
421             .setImage("pants_red.png")
422             .setAnchorX(0.5)
423             .setAnchorY(1);
424         pantsModule.displayOnPantsState(trollPantsRed, 1);
425         Sprite trollPantsGreen = graphicEntityModule.createSprite()
426             .setImage("pants_green.png")
427             .setAnchorX(0.5)
428             .setAnchorY(1);
429         pantsModule.displayOnPantsState(trollPantsGreen, 2);
430         Sprite trollPantsBlue = graphicEntityModule.createSprite()
431             .setImage("pants_blue.png")
432             .setAnchorX(0.5)
433             .setAnchorY(1);
434         pantsModule.displayOnPantsState(trollPantsBlue, 3);
435         Sprite trollPantsPerv = graphicEntityModule.createSprite()
436             .setImage("pants_perv.png")
437             .setAnchorX(0.5)
438             .setAnchorY(1);
439         pantsModule.displayOnPantsState(trollPantsPerv, 4);
440         troll = graphicEntityModule
441             .createGroup(trollBody, trollPantsRed,
442                          trollPantsGreen, trollPantsBlue, trollPantsPerv)
443             .setX(1920/2)
444             .setY(880)
445             .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
446             .setZIndex(2);
447         trollPositionGauge = graphicEntityModule.createText()
448             .setZIndex(2)
449             .setAnchor(0.5)
450             .setFontSize(40)
451             .setX(1980/2)
452             .setY(980)
453             .setFillColor(0xffffff);
454         moveTroll();
455
456         trollMessage = graphicEntityModule.createText()
457             .setZIndex(1)
458             .setX(1902/2)
459             .setY(680)
460             .setAnchorX(0.5)
461             .setAnchorY(0)
462             .setTextAlign(TextBasedEntity.TextAlign.CENTER)
463             .setStrokeColor(0xFFFF00)
464             .setFillColor(0xFFFF00)
465             .setFontSize(40);
466         pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
467     }
468
469     private void moveTroll() {
470         graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
471         int x0 = p0.castle.getX(), x1 = p1.castle.getX();
472         int y0 = p0.castle.getY(), y1 = p1.castle.getY();
473         troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
474                    Curve.ELASTIC);
475         troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
476                    Curve.ELASTIC);
477
478         trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
479         int distLeft = model.trollPosition;
480         int distRight = model.roadLength - model.trollPosition;
481         if (distLeft <= 0) {
482             trollPositionGauge.setText("← " + distRight);
483         }
484         else if (distRight <= 0) {
485             trollPositionGauge.setText(distLeft + " →");
486         }
487         else {
488             trollPositionGauge.setText(distLeft + " ↔ " + distRight);
489         }
490         graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
491         trollPositionGauge.setX(troll.getX());
492     }
493
494     void moveTroll(Dir d) {
495         moveTroll();
496         gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
497
498         trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
499         graphicEntityModule.commitEntityState(0.5, trollMessage);
500         trollMessage.setAlpha(0, Curve.EASE_IN);
501         graphicEntityModule.commitEntityState(1, trollMessage);
502     }
503
504     void animateTurnCounter() {
505         for (int i = 0; i < 10; i++) {
506             turnCounter.setText("T" + _turns + "." + i);
507             // The following line is likely not a bug.
508             graphicEntityModule.commitEntityState((double) i/9, turnCounter);
509         }
510         _turns++;
511     }
512
513     void drawDebug() {
514         String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
515             .setSourceImage("debug.png")
516             .setImageCount(2)
517             .setWidth(900)
518             .setHeight(150)
519             .setOrigRow(0)
520             .setOrigCol(0)
521             .setImagesPerRow(1)
522             .setName("debug")
523             .split();
524         SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
525             .setImages(debugModePngs)
526             .setX(1920 / 2)
527             .setY(60)
528             .setAnchorX(0.5)
529             .setLoop(true);
530         pantsModule.displayOnToggleState(debugMode, "debug", true);
531
532         turnCounter = graphicEntityModule.createText()
533             .setAnchorX(0.5)
534             .setAnchorY(0)
535             .setX(1920 / 2)
536             .setY(260)
537             .setStrokeColor(0xff0080)
538             .setFillColor(0xff0080)
539             .setFontFamily("monospace")
540             .setFontWeight(Text.FontWeight.BOLD)
541             .setFontSize(100);
542         pantsModule.displayOnToggleState(turnCounter, "debug", true);
543         animateTurnCounter();
544     }
545
546     void animateLoss(int x, int y, int size, String message) {
547         int startX;
548         if (x < 1920/2) { startX = 1920; }
549         else if (x > 1920/2) { startX = 1920; }
550         else { startX = 1920 * random.nextInt(2); }
551
552         Text msg = graphicEntityModule.createText(message)
553             .setX(startX)
554             .setY(1080)
555             .setAnchorX(0.5)
556             .setAnchorY(0.5)
557             .setScaleX(3*random.nextDouble() - 1)
558             .setScaleY(3*random.nextDouble() - 1)
559             .setSkewX(2*random.nextDouble() - 1)
560             .setSkewY(2*random.nextDouble() - 1)
561             .setRotation(4*Math.PI * (1 + random.nextDouble())
562                          * (random.nextInt(2) == 0 ? 1 : -1))
563             .setFontSize(0)
564             .setStrokeColor(0xff7f7f)
565             .setFillColor(0xff7f7f)
566             .setFontWeight(Text.FontWeight.BOLD)
567             .setTextAlign(TextBasedEntity.TextAlign.CENTER);
568         graphicEntityModule.commitEntityState(0.25, msg);
569         Curve curve = Curve.ELASTIC;
570         msg.setX(x, Curve.EASE_OUT)
571             .setY(y, Curve.ELASTIC)
572             .setScaleX(1, curve)
573             .setScaleY(1, curve)
574             .setSkewX(0, curve)
575             .setSkewY(0, curve)
576             .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
577             .setFontSize(size, curve);
578     }
579
580     void doubleDefeat() {
581         gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
582         animateLoss(1920/2, 680, 150, "L0SERZ!");
583     }
584
585     void draw() {
586         gameManager.addToGameSummary("Draw.");
587         animateLoss(1920/2, 680, 200, "DRAW");
588     }
589
590     String selectTrollMessage(Dir d) {
591         if (random.nextInt(10000) == 0) {
592             return specials[random.nextInt(specials.length)];
593         }
594
595         // yup, still biased
596         int i = random.nextInt(directed.length + isotropic.length / 3);
597         if (i < directed.length) {
598             return directed[i][d.index];
599         }
600         else {
601             return isotropic[random.nextInt(isotropic.length)];
602         }
603     }
604
605     // You'll never remember if you ever saw these…
606     String specials[] = {
607         "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",
608         "CG know what they're doing."
609     };
610
611     // Most of what ought to happen in normal play
612     String directed[][] = {
613         // movies
614         { "Han shot first", "I am your father", "Greedo shot first" },
615         { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
616         { "star wars > star trek", "my god, it's full of troll", "star trek > star wars" },
617         // More movie controversies sought.  Apply on the puzzle contrib page.
618
619         // music
620         { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
621         { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
622
623         // everyday life
624         { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
625         { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
626         { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
627         { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
628         { "santa claus is really\nthe tooth fairy", "the easter bunny tasted yummy", "the tooth fairy is\nreally santa claus" },
629         // Ditto.  Need moar troll.
630
631         // gaming
632         { "PC > console", "pong is still\nunequaled", "console > PC" },
633         { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
634         { "pad > stick", "mouse gaming is lame", "stick > pad" },
635         { "RTS > FPS", "solitaire best game", "FPS > RTS" },
636         { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
637         { "orcs are wusses", "the amulet is in another dungeon", "elves are wusses" },
638         { "here's a link to my patreon", "my apm > yours", "here's my soundcloud" },
639         { "all your stones is belong to us", "all your castle are belong to us", "all your rocks is belong to us" },
640         // I'm not exactly a gamer myself, I take hints on the topics du jour
641
642         // programming
643         { "vi < emacs", "i code with Notepad", "emacs > vi" },
644         { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
645         { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
646         { "static linking best", "symlinking best", "dynamic linking best" },
647         { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
648         { "objects > functions", "it's closures\nall the way down", "functions > objects" },
649         { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
650         { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
651         // This category's not too bad.
652
653         // Internet
654         { "gmail > github", "copy-paste FTW", "github > gmail" },
655         { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
656         { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
657         { "jira > trello", "bugzilla FTW", "trello > jira" },
658         { "IRC > slack", "chat is work", "discord < IRC" },
659         { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
660         { "there's an app for that", "there's a bean for that", "there's an applet for that" },
661         // More always welcome here.
662
663         // CodinGame
664         { "my nn is in python", "my language has -O3", "my code is more than 100k" },
665         { "i found a bug\nin temperatures", "i found a bug on\nthe leaderboard", "i found a bug\nin chuck norris" },
666         { "fix it", "how is ur csb", "ezpz" },
667         { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
668         { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
669         { "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" },
670         { "light theme best", "ascii > graphics", "dark theme best" },
671         { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
672         { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
673         // And here.  Especially as I'm not that active on #World or #Ru.
674     };
675
676     // Those for which I couldn't find a meaningful directednessability.
677     String isotropic[] = {
678         "(unclosed",
679         "Electron apps are the fastest",
680         "Rosebud",
681         "Thanos did nothing wrong",
682         "developers developers developers",
683         "the cloud is just\nother ppl's computers",
684         "ur doin it rong",
685         "tortue",
686         "31OCT = 25DEC",
687         "ASCII stupid question\nget a stupid ANSI",
688         "trolling is a art"
689         // I try and avoid those, but if really it fits nowhere else…
690     };
691 }