857f52742874235af2ed550dc0e0d7e6fb494691
[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         // 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 }