Get the troll to utter stuff
[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;
31         int index;
32         Dir(String mvt, int i) { movement = mvt; index = i; }
33     }
34
35     class Player {
36         Model.Player model;
37
38         int colorToken;
39         String nicknameToken;
40         String avatarToken;
41
42         double frameRot;
43
44         Group avatar;
45         Text stoneCounter;
46         Text message;
47         Sprite castle;
48         Text stone;
49         Text stoneReminder;
50
51         void init(com.codingame.game.Player p) {
52             model = p.model;
53             colorToken = p.getColorToken();
54             nicknameToken = p.getNicknameToken();
55             avatarToken = p.getAvatarToken();
56
57             boolean p0 = model.index == 0;
58             int x = p0 ? 280 : 1920 - 280;
59             int y = 220;
60
61             Sprite frame = graphicEntityModule.createSprite()
62                 .setImage("frame.png")
63                 .setAnchor(0.5)
64                 .setRotation(frameRot)
65                 .setZIndex(22)
66                 .setTint(colorToken);
67
68             Sprite frameBg = graphicEntityModule.createSprite()
69                 .setImage("frame_bg.png")
70                 .setAnchor(0.5)
71                 .setRotation(frameRot)
72                 .setZIndex(20);
73
74             Sprite avatarSprite = graphicEntityModule.createSprite()
75                 .setZIndex(21)
76                 .setImage(avatarToken)
77                 .setAnchor(0.5)
78                 .setBaseHeight(116)
79                 .setBaseWidth(116);
80
81             avatar = graphicEntityModule
82                 .createGroup(frame, frameBg, avatarSprite)
83                 .setX(x).setY(y);
84
85             Text text = graphicEntityModule.createText(nicknameToken)
86                     .setX(x)
87                     .setY(y + 120)
88                     .setZIndex(20)
89                     .setFontSize(40)
90                     .setFillColor(0x7f3f00)
91                     .setAnchor(0.5);
92
93             stoneCounter = graphicEntityModule.createText()
94                 .setX(x)
95                 .setY(y+200)
96                 .setZIndex(20)
97                 .setFontSize(40)
98                 .setFillColor(0x7f3f00)
99                 .setAnchor(0.5);
100             updateStoneCounter();
101
102             message = graphicEntityModule.createText()
103                 .setX(p0 ? 15 : 1920-15)
104                 .setY(680)
105                 .setZIndex(1)
106                 .setFontSize(40)
107                 .setFillColor(0xffbf7f)
108                 .setAnchorX(p0 ? 0 : 1)
109                 .setAnchorY(1);
110
111             castle = graphicEntityModule.createSprite()
112                 .setImage("castle.png")
113                 .setTint(colorToken)
114                 .setX(p0 ? 160 : 1920-160)
115                 .setY(p0 ? 890 : 880)
116                 .setZIndex(1)
117                 .setAnchorX(0.5)
118                 .setAnchorY(1)
119                 .setScaleX(p0 ? 1 : -1);
120
121             stone = graphicEntityModule.createText()
122                 .setZIndex(3)
123                 .setFontSize(150)
124                 .setFillColor(0x12322a)
125                 .setAnchor(0.5)
126                 .setAlpha(0);
127
128             stoneReminder = graphicEntityModule.createText()
129                 .setX(p0 ? x + 100 : x - 100)
130                 .setY(y)
131                 .setZIndex(20)
132                 .setFontSize(80)
133                 .setFontFamily("monospace")
134                 .setStrokeColor(0xff0080)
135                 .setFillColor(0xff0080)
136                 .setAnchorX(p0 ? 0 : 1)
137                 .setAnchorY(0.5);
138             toggleModule.displayOnToggleState(stoneReminder, "debug", true);
139         }
140
141         void updateStoneCounter() {
142             int stones = model.getStones();
143             if (stones <= 0) {
144                 stoneCounter.setText("Out of stones!");
145                 stoneCounter.setFillColor(0xff7777);
146             }
147             else if (stones == 1) {
148                 stoneCounter.setText("1 stone");
149                 stoneCounter.setFillColor(0xffbb77);
150             }
151             else {
152                 stoneCounter.setText(stones + " stones");
153             }
154         }
155
156         void animateStones(int stones) {
157             String stonesString = Integer.valueOf(stones).toString();
158             stone.setX(castle.getX());
159             stone.setY(castle.getY() - 100);
160             stone.setText(stonesString);
161             stone.setAlpha(1);
162             graphicEntityModule.commitEntityState(0, stone);
163     
164             int peakX = (castle.getX() + troll.getX()) / 2;
165             int peakY = 540;
166             stone.setX(peakX);
167             stone.setY(peakY, Curve.EASE_OUT);
168             graphicEntityModule.commitEntityState(0.25,
169                                                   stone,
170                                                   stoneCounter);
171     
172             stone.setX(troll.getX());
173             stone.setY(troll.getY() - 50, Curve.EASE_IN);
174             stone.setAlpha(0, Curve.EASE_IN);
175             graphicEntityModule.commitEntityState(0.5, stone);
176
177             stoneReminder.setText(stonesString);
178             graphicEntityModule.commitEntityState(0, stoneReminder);
179         }
180
181         void displayMessage(String msg) {
182             message.setText(msg);
183             graphicEntityModule.commitEntityState(0, message);
184         }
185
186         void markLoser() {
187             graphicEntityModule.commitEntityState(0.5, avatar);
188             int dir = random.nextInt(2) == 1 ? 1 : -1;
189             avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
190         }
191
192         void destroy() {
193             gameManager.addToGameSummary(GameManager.formatErrorMessage("Troll destroys " + nicknameToken + "."));
194             graphicEntityModule.commitEntityState(0.5, castle);
195             castle.setX(castle.getX(), Curve.ELASTIC);
196             castle.setScaleY(-0.2, Curve.EASE_IN);
197         }
198
199         void startTurn() {
200             graphicEntityModule.commitEntityState(0, stoneReminder);
201         }
202
203         void victory() {
204             gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
205             graphicEntityModule.commitEntityState(0.5, avatar);
206             avatar.setScaleX(1.5, Curve.EASE_OUT);
207             avatar.setScaleY(1.5, Curve.EASE_OUT);
208             avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
209                                Curve.ELASTIC);
210         }
211
212         void throwStones(int stones) {
213             gameManager.addToGameSummary(String.format("%s throws %d stone%s at the troll.", nicknameToken, stones, stones == 1 ? "" : "s"));
214         }
215
216         void threwMoreStonesThanHad() {
217             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!)"));
218         }
219
220         void failedToThrowStonesAndShouldHave() {
221             gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones.  Fixing that for them because I'm in a good mood today."));
222         }
223
224         void markTimeout() {
225             animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
226         }
227
228         void markIllegal() {
229             animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
230         }
231
232         void markCheat() {
233             animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
234         }
235     }
236
237     Model model;
238     Random random = new Random();
239     Text trollMessage;
240     Sprite troll;
241     Text trollPositionGauge;
242     Player p0 = new Player(), p1 = new Player();
243     Text turnCounter; int _turns = 0;
244
245     void init(Model m) {
246         model = m;
247         drawBackground();
248
249         /*
250          * Random π/2-grained rotation of the avatar frames.  Avoid
251          * having them π/2 apart, though, as one of them is likely
252          * going to end upside-down and the trick would be revealed.
253          * And I'd have to "draw" a new frame. Ewww.
254          */
255         p0.frameRot = random.nextInt(4) * Math.PI / 2;
256         p0.init(gameManager.getPlayer(0));
257         p1.frameRot = p1.frameRot +
258             (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
259         p1.init(gameManager.getPlayer(1));
260
261         drawTroll();
262
263         drawDebug();
264     }
265
266     void startTurn() {
267         p0.startTurn();
268         p1.startTurn();
269
270         trollMessage.setX(troll.getX());
271
272         animateTurnCounter();
273     }
274
275     private class Pos {
276         int x, y;
277         Pos(int _x, int _y) { x = _x; y = _y; }
278     }
279
280     private void drawBackground() {
281         graphicEntityModule.createSprite()
282                 .setImage("background.png")
283                 .setAnchor(0);
284
285         int numMountains = random.nextInt(5);
286         while (numMountains --> 0) {
287             final int pngWidth = 366;
288             double scale = 0.5 * (1 + random.nextDouble());
289             int x = random.nextInt(1920 + (int) (scale*pngWidth))
290                 - (int) (scale*pngWidth/2);
291             int baseTint = 64 + random.nextInt(128);
292             Sprite mountain = graphicEntityModule.createSprite()
293                 .setImage("mountain.png")
294                 .setX(x)
295                 .setY(680)
296                 .setAnchorX(0.5)
297                 .setAnchorY(283.0 / 321.0)
298                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
299                 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
300                 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
301                 .setSkewX((random.nextDouble() - 0.5) / 4)
302                 .setSkewY((random.nextDouble() - 0.5) / 8)
303                 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
304                          + (baseTint + random.nextInt(16) - 8) * 0x0100
305                          + (baseTint + random.nextInt(16) - 8) * 0x01);
306             graphicEntityModule.createSprite().setImage("mountaintop.png")
307                 .setX(mountain.getX())
308                 .setY(mountain.getY())
309                 .setAnchorX(mountain.getAnchorX())
310                 .setAnchorY(mountain.getAnchorY())
311                 .setRotation(mountain.getRotation())
312                 .setScaleX(mountain.getScaleX())
313                 .setScaleY(mountain.getScaleY())
314                 .setSkewX(mountain.getSkewX())
315                 .setSkewY(mountain.getSkewY());
316         }
317
318         int numTrees = random.nextInt(21);
319         ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
320         while (numTrees --> 0) {
321             int x, y;
322             do {
323                 x = random.nextInt(1920);
324                 // yes, this biases randomness wrt perspective! :-(
325                 y = 700 + random.nextInt(175);
326             } while (y > 880 && (x < 200 || x > 1720));
327             poss.add(new Pos(x, y));
328         }
329         poss.sort(new Comparator<Pos>() {
330                 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
331             });
332
333         for (Pos p : poss) {
334             double scale = ( 90.0 / 433.0           // base height from PNG
335                              * (p.y - 680) / (875 - 680) ); // perspective
336             graphicEntityModule.createSprite()
337                 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
338                                                  : "Alshockv2.png")
339                 .setAnchorX(0.5)
340                 .setAnchorY(1)
341                 .setX(p.x)
342                 .setY(p.y)
343                 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
344                            * (1 + (random.nextDouble() - 0.5) / 6))
345                 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
346                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
347                 .setSkewX((random.nextDouble() - 0.5) /4)
348                 .setSkewY((random.nextDouble() - 0.5) /8);
349         }
350     }
351
352     private void drawTroll() {
353         troll = graphicEntityModule.createSprite()
354             .setImage("troll.png")
355             .setAnchorX(0.5)
356             .setAnchorY(1)
357             .setX(1920/2)
358             .setY(880)
359             .setZIndex(2);
360         trollPositionGauge = graphicEntityModule.createText()
361             .setZIndex(2)
362             .setAnchor(0.5)
363             .setFontSize(40)
364             .setX(1980/2)
365             .setY(980)
366             .setFillColor(0xffffff);
367         moveTroll();
368
369         trollMessage = graphicEntityModule.createText()
370             .setX(1902/2)
371             .setY(680)
372             .setAnchorX(0.5)
373             .setAnchorY(0)
374             .setTextAlign(TextBasedEntity.TextAlign.CENTER)
375             .setStrokeColor(0xFFFF00)
376             .setFillColor(0xFFFF00)
377             .setFontSize(40);
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         int i = random.nextInt(directed.length + isotropic.length);
507         if (i < directed.length) {
508             return directed[i][d.index];
509         }
510         else {
511             return isotropic[i-directed.length];
512         }
513     }
514
515     String specials[] = {
516         "CG know what they're doing."
517     };
518
519     String directed[][] = {
520         // movies
521         { "Han shot first", "I am your father", "Greedo shot first" },
522
523         // everyday life
524         { "tea > coffee", "XXX", "coffee > tea" },
525         { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
526
527         // troll ou incompris ou SJW
528
529         // gaming
530         { "PC > console", "pong is still\nunequaled", "console > PC" },
531         { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
532         { "pad > stick", "mouse gaming is lame", "stick > pad" },
533         { "RTS > FPS", "solitaire best game", "FPS > RTS" },
534         { "YT gaming > twitch", "I stream on dailymotion", "twitch > YT gaming" },
535
536         // programming
537         { "vi < emacs", "I code with Notepad", "emacs > vi" },
538         { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
539         { "LISP is the most\npowerful language", "HTML is a programming language", "Forth is the most\npowerful language" },
540         { "static linking best", "symbolic linking best", "dynamic linking best" },
541         { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
542         { "objects > functions", "it's closures\nall the way down", "functions > objects" },
543         { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
544         { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
545
546         // Internet
547         { "gmail > github", "copy-paste FTW", "github > gmail" },
548         { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
549
550         // CodinGame
551         { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
552         { "light theme best", "ascii > graphics", "dark theme best" },
553         { "simulation beats heuristics", "AI is a forest of ifs", "heuristics beat simulation" },
554         { "bruteforce always prevails", "you'll timeout anyway", "algorithms always prevail" }
555     };
556
557     String isotropic[] = {
558         "Electron apps are the fastest",
559         "Rosebud",
560         "Thanos did nothing wrong",
561         "developers developers developers"
562     };
563 }