Funky loss visuals. A cheater doesn't lose to timeout/illegal anymore.
[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."),
27         STILL("stands still."),
28         RIGHT("walks right.");
29
30         String movement;
31         Dir(String mvt) { movement = mvt; }
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     Sprite troll;
239     Text trollPositionGauge;
240     Player p0 = new Player(), p1 = new Player();
241     Text turnCounter; int _turns = 0;
242
243     void init(Model m) {
244         model = m;
245         drawBackground();
246
247         /*
248          * Random π/2-grained rotation of the avatar frames.  Avoid
249          * having them π/2 apart, though, as one of them is likely
250          * going to end upside-down and the trick would be revealed.
251          * And I'd have to "draw" a new frame. Ewww.
252          */
253         p0.frameRot = random.nextInt(4) * Math.PI / 2;
254         p0.init(gameManager.getPlayer(0));
255         p1.frameRot = p1.frameRot +
256             (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
257         p1.init(gameManager.getPlayer(1));
258
259         drawTroll();
260
261         drawDebug();
262     }
263
264     void startTurn() {
265         p0.startTurn();
266         p1.startTurn();
267
268         animateTurnCounter();
269     }
270
271     private class Pos {
272         int x, y;
273         Pos(int _x, int _y) { x = _x; y = _y; }
274     }
275
276     private void drawBackground() {
277         graphicEntityModule.createSprite()
278                 .setImage("background.png")
279                 .setAnchor(0);
280
281         int numMountains = random.nextInt(5);
282         while (numMountains --> 0) {
283             final int pngWidth = 366;
284             double scale = 0.5 * (1 + random.nextDouble());
285             int x = random.nextInt(1920 + (int) (scale*pngWidth))
286                 - (int) (scale*pngWidth/2);
287             int baseTint = 64 + random.nextInt(128);
288             Sprite mountain = graphicEntityModule.createSprite()
289                 .setImage("mountain.png")
290                 .setX(x)
291                 .setY(680)
292                 .setAnchorX(0.5)
293                 .setAnchorY(283.0 / 321.0)
294                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
295                 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
296                 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
297                 .setSkewX((random.nextDouble() - 0.5) / 4)
298                 .setSkewY((random.nextDouble() - 0.5) / 8)
299                 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
300                          + (baseTint + random.nextInt(16) - 8) * 0x0100
301                          + (baseTint + random.nextInt(16) - 8) * 0x01);
302             graphicEntityModule.createSprite().setImage("mountaintop.png")
303                 .setX(mountain.getX())
304                 .setY(mountain.getY())
305                 .setAnchorX(mountain.getAnchorX())
306                 .setAnchorY(mountain.getAnchorY())
307                 .setRotation(mountain.getRotation())
308                 .setScaleX(mountain.getScaleX())
309                 .setScaleY(mountain.getScaleY())
310                 .setSkewX(mountain.getSkewX())
311                 .setSkewY(mountain.getSkewY());
312         }
313
314         int numTrees = random.nextInt(21);
315         ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
316         while (numTrees --> 0) {
317             int x, y;
318             do {
319                 x = random.nextInt(1920);
320                 // yes, this biases randomness wrt perspective! :-(
321                 y = 700 + random.nextInt(175);
322             } while (y > 880 && (x < 200 || x > 1720));
323             poss.add(new Pos(x, y));
324         }
325         poss.sort(new Comparator<Pos>() {
326                 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
327             });
328
329         for (Pos p : poss) {
330             double scale = ( 90.0 / 433.0           // base height from PNG
331                              * (p.y - 680) / (875 - 680) ); // perspective
332             graphicEntityModule.createSprite()
333                 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
334                                                  : "Alshockv2.png")
335                 .setAnchorX(0.5)
336                 .setAnchorY(1)
337                 .setX(p.x)
338                 .setY(p.y)
339                 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
340                            * (1 + (random.nextDouble() - 0.5) / 6))
341                 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
342                 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
343                 .setSkewX((random.nextDouble() - 0.5) /4)
344                 .setSkewY((random.nextDouble() - 0.5) /8);
345         }
346     }
347
348     private void drawTroll() {
349         troll = graphicEntityModule.createSprite()
350             .setImage("troll.png")
351             .setAnchorX(0.5)
352             .setAnchorY(1)
353             .setX(1920/2)
354             .setY(880)
355             .setZIndex(2);
356         trollPositionGauge = graphicEntityModule.createText()
357             .setZIndex(2)
358             .setAnchor(0.5)
359             .setFontSize(40)
360             .setX(1980/2)
361             .setY(980)
362             .setFillColor(0xffffff);
363
364         moveTroll();
365     }
366
367     private void moveTroll() {
368         graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
369         int x0 = p0.castle.getX(), x1 = p1.castle.getX();
370         int y0 = p0.castle.getY(), y1 = p1.castle.getY();
371         troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
372                    Curve.ELASTIC);
373         troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
374                    Curve.ELASTIC);
375
376         trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
377         int distLeft = model.trollPosition;
378         int distRight = model.roadLength - model.trollPosition;
379         if (distLeft <= 0) {
380             trollPositionGauge.setText("← " + distRight);
381         }
382         else if (distRight <= 0) {
383             trollPositionGauge.setText(distLeft + " →");
384         }
385         else {
386             trollPositionGauge.setText(distLeft + " ↔ " + distRight);
387         }
388         graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
389         trollPositionGauge.setX(troll.getX());
390     }
391
392     void moveTroll(Dir d) {
393         moveTroll();
394         gameManager.addToGameSummary("Troll " + d.movement);
395     }
396
397     void animateTurnCounter() {
398         for (int i = 0; i < 10; i++) {
399             turnCounter.setText("T" + _turns + "." + i);
400             // The following line is likely not a bug.
401             graphicEntityModule.commitEntityState((double) i/9, turnCounter);
402         }
403         _turns++;
404     }
405
406     void drawDebug() {
407         String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
408             .setSourceImage("debug.png")
409             .setImageCount(2)
410             .setWidth(900)
411             .setHeight(150)
412             .setOrigRow(0)
413             .setOrigCol(0)
414             .setImagesPerRow(1)
415             .setName("debug")
416             .split();
417         SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
418             .setImages(debugModePngs)
419             .setX(1920 / 2)
420             .setY(60)
421             .setAnchorX(0.5)
422             .setLoop(true);
423         toggleModule.displayOnToggleState(debugMode, "debug", true);
424
425         turnCounter = graphicEntityModule.createText()
426             .setAnchorX(0.5)
427             .setAnchorY(0)
428             .setX(1920 / 2)
429             .setY(260)
430             .setStrokeColor(0xff0080)
431             .setFillColor(0xff0080)
432             .setFontFamily("monospace")
433             .setFontWeight(Text.FontWeight.BOLD)
434             .setFontSize(100);
435         toggleModule.displayOnToggleState(turnCounter, "debug", true);
436         animateTurnCounter();
437     }
438
439     void animateLoss(int x, int y, int size, String message) {
440         int startX;
441         if (x < 1920/2) { startX = 1920; }
442         else if (x > 1920/2) { startX = 1920; }
443         else { startX = 1920 * random.nextInt(2); }
444
445         Text msg = graphicEntityModule.createText(message)
446             .setX(startX)
447             .setY(1080)
448             .setAnchorX(0.5)
449             .setAnchorY(0.5)
450             .setScaleX(3*random.nextDouble() - 1)
451             .setScaleY(3*random.nextDouble() - 1)
452             .setSkewX(2*random.nextDouble() - 1)
453             .setSkewY(2*random.nextDouble() - 1)
454             .setRotation(4*Math.PI * (1 + random.nextDouble())
455                          * (random.nextInt(2) == 0 ? 1 : -1))
456             .setFontSize(0)
457             .setStrokeColor(0xff7f7f)
458             .setFillColor(0xff7f7f)
459             .setFontWeight(Text.FontWeight.BOLD)
460             .setTextAlign(TextBasedEntity.TextAlign.CENTER);
461         graphicEntityModule.commitEntityState(0.25, msg);
462         Curve curve = Curve.ELASTIC;
463         msg.setX(x, Curve.EASE_OUT)
464             .setY(y, Curve.ELASTIC)
465             .setScaleX(1, curve)
466             .setScaleY(1, curve)
467             .setSkewX(0, curve)
468             .setSkewY(0, curve)
469             .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
470             .setFontSize(size, curve);
471     }
472
473     void doubleDefeat() {
474         gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
475         animateLoss(1920/2, 680, 150, "L0SERZ!");
476     }
477
478     void draw() {
479         gameManager.addToGameSummary("Draw.");
480         animateLoss(1920/2, 680, 200, "DRAW");
481     }
482 }