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