Make the trolling optional
[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("Troll 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 the troll.", 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     Sprite 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     private void drawTroll() {
352         troll = graphicEntityModule.createSprite()
353             .setImage("troll.png")
354             .setAnchorX(0.5)
355             .setAnchorY(1)
356             .setX(1920/2)
357             .setY(880)
358             .setZIndex(2);
359         trollPositionGauge = graphicEntityModule.createText()
360             .setZIndex(2)
361             .setAnchor(0.5)
362             .setFontSize(40)
363             .setX(1980/2)
364             .setY(980)
365             .setFillColor(0xffffff);
366         moveTroll();
367
368         trollMessage = graphicEntityModule.createText()
369             .setX(1902/2)
370             .setY(680)
371             .setAnchorX(0.5)
372             .setAnchorY(0)
373             .setTextAlign(TextBasedEntity.TextAlign.CENTER)
374             .setStrokeColor(0xFFFF00)
375             .setFillColor(0xFFFF00)
376             .setFontSize(40);
377         toggleModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
378     }
379
380     private void moveTroll() {
381         graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
382         int x0 = p0.castle.getX(), x1 = p1.castle.getX();
383         int y0 = p0.castle.getY(), y1 = p1.castle.getY();
384         troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
385                    Curve.ELASTIC);
386         troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
387                    Curve.ELASTIC);
388
389         trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
390         int distLeft = model.trollPosition;
391         int distRight = model.roadLength - model.trollPosition;
392         if (distLeft <= 0) {
393             trollPositionGauge.setText("← " + distRight);
394         }
395         else if (distRight <= 0) {
396             trollPositionGauge.setText(distLeft + " →");
397         }
398         else {
399             trollPositionGauge.setText(distLeft + " ↔ " + distRight);
400         }
401         graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
402         trollPositionGauge.setX(troll.getX());
403     }
404
405     void moveTroll(Dir d) {
406         moveTroll();
407         gameManager.addToGameSummary("Troll " + d.movement);
408
409         trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
410         graphicEntityModule.commitEntityState(0.5, trollMessage);
411         trollMessage.setAlpha(0, Curve.EASE_IN);
412         graphicEntityModule.commitEntityState(1, trollMessage);
413     }
414
415     void animateTurnCounter() {
416         for (int i = 0; i < 10; i++) {
417             turnCounter.setText("T" + _turns + "." + i);
418             // The following line is likely not a bug.
419             graphicEntityModule.commitEntityState((double) i/9, turnCounter);
420         }
421         _turns++;
422     }
423
424     void drawDebug() {
425         String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
426             .setSourceImage("debug.png")
427             .setImageCount(2)
428             .setWidth(900)
429             .setHeight(150)
430             .setOrigRow(0)
431             .setOrigCol(0)
432             .setImagesPerRow(1)
433             .setName("debug")
434             .split();
435         SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
436             .setImages(debugModePngs)
437             .setX(1920 / 2)
438             .setY(60)
439             .setAnchorX(0.5)
440             .setLoop(true);
441         toggleModule.displayOnToggleState(debugMode, "debug", true);
442
443         turnCounter = graphicEntityModule.createText()
444             .setAnchorX(0.5)
445             .setAnchorY(0)
446             .setX(1920 / 2)
447             .setY(260)
448             .setStrokeColor(0xff0080)
449             .setFillColor(0xff0080)
450             .setFontFamily("monospace")
451             .setFontWeight(Text.FontWeight.BOLD)
452             .setFontSize(100);
453         toggleModule.displayOnToggleState(turnCounter, "debug", true);
454         animateTurnCounter();
455     }
456
457     void animateLoss(int x, int y, int size, String message) {
458         int startX;
459         if (x < 1920/2) { startX = 1920; }
460         else if (x > 1920/2) { startX = 1920; }
461         else { startX = 1920 * random.nextInt(2); }
462
463         Text msg = graphicEntityModule.createText(message)
464             .setX(startX)
465             .setY(1080)
466             .setAnchorX(0.5)
467             .setAnchorY(0.5)
468             .setScaleX(3*random.nextDouble() - 1)
469             .setScaleY(3*random.nextDouble() - 1)
470             .setSkewX(2*random.nextDouble() - 1)
471             .setSkewY(2*random.nextDouble() - 1)
472             .setRotation(4*Math.PI * (1 + random.nextDouble())
473                          * (random.nextInt(2) == 0 ? 1 : -1))
474             .setFontSize(0)
475             .setStrokeColor(0xff7f7f)
476             .setFillColor(0xff7f7f)
477             .setFontWeight(Text.FontWeight.BOLD)
478             .setTextAlign(TextBasedEntity.TextAlign.CENTER);
479         graphicEntityModule.commitEntityState(0.25, msg);
480         Curve curve = Curve.ELASTIC;
481         msg.setX(x, Curve.EASE_OUT)
482             .setY(y, Curve.ELASTIC)
483             .setScaleX(1, curve)
484             .setScaleY(1, curve)
485             .setSkewX(0, curve)
486             .setSkewY(0, curve)
487             .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
488             .setFontSize(size, curve);
489     }
490
491     void doubleDefeat() {
492         gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
493         animateLoss(1920/2, 680, 150, "L0SERZ!");
494     }
495
496     void draw() {
497         gameManager.addToGameSummary("Draw.");
498         animateLoss(1920/2, 680, 200, "DRAW");
499     }
500
501     String selectTrollMessage(Dir d) {
502         if (random.nextInt(10000) == 0) {
503             return specials[random.nextInt(specials.length)];
504         }
505
506         // yup, still biased
507         int i = random.nextInt(directed.length + isotropic.length / 3);
508         if (i < directed.length) {
509             return directed[i][d.index];
510         }
511         else {
512             return isotropic[random.nextInt(isotropic.length)];
513         }
514     }
515
516     String specials[] = {
517         "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",
518         "CG know what they're doing."
519     };
520
521     String directed[][] = {
522         // movies
523         { "Han shot first", "I am your father", "Greedo shot first" },
524
525         // everyday life
526         { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
527         { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
528         { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
529
530         // gaming
531         { "PC > console", "pong is still\nunequaled", "console > PC" },
532         { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
533         { "pad > stick", "mouse gaming is lame", "stick > pad" },
534         { "RTS > FPS", "solitaire best game", "FPS > RTS" },
535         { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
536
537         // programming
538         { "vi < emacs", "i code with Notepad", "emacs > vi" },
539         { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
540         { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "Forth is the most\npowerful language" },
541         { "static linking best", "symlinking best", "dynamic linking best" },
542         { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
543         { "objects > functions", "it's closures\nall the way down", "functions > objects" },
544         { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
545         { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
546
547         // Internet
548         { "gmail > github", "copy-paste FTW", "github > gmail" },
549         { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
550         { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
551         { "jira > trello", "bugzilla FTW", "trello > jira" },
552         { "IRC > slack", "chat is work", "discord < IRC" },
553         { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
554
555         // CodinGame
556         { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
557         { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
558         { "light theme best", "ascii > graphics", "dark theme best" },
559         { "simulation beats heuristics", "true AI is just ifs", "heuristics beat simulation" },
560         { "bruteforce always prevails", "you'll timeout anyway", "algorithms always prevail" }
561     };
562
563     String isotropic[] = {
564         "Electron apps are the fastest",
565         "Rosebud",
566         "Thanos did nothing wrong",
567         "developers developers developers",
568         "the cloud is just\nother ppl's computers",
569         "ur doin it rong",
570         "tortue",
571         "how is ur csb",
572         "31OCT = 25DEC",
573         "ASCII stupid question\nget a stupid ANSI",
574         "trolling is a art"
575     };
576 }