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