1 package com.codingame.game;
3 import java.util.Random;
4 import java.util.ArrayList;
5 import java.util.Comparator;
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.google.inject.Inject;
20 @Inject private MultiplayerGameManager<com.codingame.game.Player> gameManager;
21 @Inject private GraphicEntityModule graphicEntityModule;
22 @Inject PantsModule pantsModule;
25 * Frame timings, for a base frame length of 2s:
26 * - first half: stone throw
27 * - second half: troll move
28 * The troll message is anchored around the troll move.
30 private final double STONE_THROW_START = 0.0;
31 private final double STONE_THROW_PEAK = 0.25;
32 private final double STONE_THROW_END = 0.5;
33 private final double TROLL_MOVE_START = 0.5;
34 private final double TROLL_MOVE_END = 1.0;
35 private final double TROLL_MESSAGE_START = 0.5;
36 private final double TROLL_MESSAGE_END = 1.0;
39 * Castle destruction and endgame message pertain to an endgame
42 private final double AVATAR_ANIMATION_START = 0.5;
43 private final double CASTLE_DESTRUCTION_START = 0.0;
44 private final double CASTLE_DESTRUCTION_END = 0.5;
62 void init(com.codingame.game.Player p) {
64 colorToken = p.getColorToken();
65 nicknameToken = p.getNicknameToken();
66 avatarToken = p.getAvatarToken();
68 boolean p0 = model.index == 0;
69 int x = p0 ? 280 : 1920 - 280;
72 Sprite frame = graphicEntityModule.createSprite()
73 .setImage("frame.png")
75 .setRotation(frameRot)
79 Sprite frameBg = graphicEntityModule.createSprite()
80 .setImage("frame_bg.png")
82 .setRotation(frameRot)
85 Sprite avatarSprite = graphicEntityModule.createSprite()
87 .setImage(avatarToken)
92 avatar = graphicEntityModule
93 .createGroup(frame, frameBg, avatarSprite)
96 Text text = graphicEntityModule.createText(nicknameToken)
101 .setFillColor(0x7f3f00)
104 stoneCounter = graphicEntityModule.createText()
109 .setFillColor(0x7f3f00)
111 updateStoneCounter();
113 message = graphicEntityModule.createText()
114 .setX(p0 ? 15 : 1920-15)
118 .setFillColor(0xffbf7f)
119 .setAnchorX(p0 ? 0 : 1)
122 castle = graphicEntityModule.createSprite()
123 .setImage("castle.png")
125 .setX(p0 ? 160 : 1920-160)
126 .setY(p0 ? 890 : 880)
130 .setScaleX(p0 ? 1 : -1);
132 stone = graphicEntityModule.createText()
135 .setFillColor(0x12322a)
139 stoneReminder = graphicEntityModule.createText()
140 .setX(p0 ? x + 100 : x - 100)
144 .setFontFamily("monospace")
145 .setStrokeColor(0xff0080)
146 .setFillColor(0xff0080)
147 .setAnchorX(p0 ? 0 : 1)
149 pantsModule.displayOnToggleState(stoneReminder, "debug", true);
153 graphicEntityModule.commitEntityState(0.0, stoneReminder);
157 gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
158 View.this.endgameFrame();
163 gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
167 // ========== Player/avatar markings
170 animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
174 animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
178 animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
182 graphicEntityModule.commitEntityState(AVATAR_ANIMATION_START, avatar);
183 avatar.setScaleX(1.5, Curve.EASE_OUT);
184 avatar.setScaleY(1.5, Curve.EASE_OUT);
185 avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
190 graphicEntityModule.commitEntityState(AVATAR_ANIMATION_START, avatar);
191 int dir = random.nextInt(2) == 1 ? 1 : -1;
192 avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
195 // ==========Player/stones
197 void throwStones(int stones) {
198 gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
201 void threwMoreStonesThanHad() {
202 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!)"));
205 void failedToThrowStonesAndShouldHave() {
206 gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones. Fixing that for them because I'm in a good mood today."));
209 void updateStoneCounter() {
210 int stones = model.getStones();
212 stoneCounter.setText("Out of stones!");
213 stoneCounter.setFillColor(0xff7777);
215 else if (stones == 1) {
216 stoneCounter.setText("1 stone");
217 stoneCounter.setFillColor(0xffbb77);
220 stoneCounter.setText(stones + " stones");
222 graphicEntityModule.commitEntityState(STONE_THROW_PEAK, stoneCounter);
225 void animateStones(int stones) {
226 String stonesString = Integer.valueOf(stones).toString();
227 stone.setX(castle.getX());
228 stone.setY(castle.getY() - 100);
229 stone.setText(stonesString);
231 graphicEntityModule.commitEntityState(STONE_THROW_START, stone);
233 int peakX = (castle.getX() + troll.getX()) / 2;
236 stone.setY(peakY, Curve.EASE_OUT);
237 graphicEntityModule.commitEntityState(STONE_THROW_PEAK,
241 stone.setX(troll.getX());
242 stone.setY(troll.getY() - 50, Curve.EASE_IN);
243 stone.setAlpha(0, Curve.EASE_IN);
244 graphicEntityModule.commitEntityState(STONE_THROW_END, stone);
246 stoneReminder.setText(stonesString);
247 graphicEntityModule.commitEntityState(0.0, stoneReminder);
250 // ========== Player/castle
252 void displayMessage(String msg) {
253 message.setText(msg);
254 graphicEntityModule.commitEntityState(0.0, message);
257 void destroyCastle() {
258 graphicEntityModule.commitEntityState(CASTLE_DESTRUCTION_START, castle);
259 castle.setX(castle.getX(), Curve.ELASTIC);
260 castle.setScaleY(-0.2, Curve.EASE_IN);
261 graphicEntityModule.commitEntityState(CASTLE_DESTRUCTION_END, castle);
266 Random random = new Random();
269 Text trollPositionGauge;
270 Player p0 = new Player(), p1 = new Player();
271 Text turnCounter; int _turns = 0;
273 // ==================== Referee interface
280 * Random π/2-grained rotation of the avatar frames. Avoid
281 * having them π/2 apart, though, as one of them is likely
282 * going to end upside-down and the trick would be revealed.
283 * And I'd have to "draw" a new frame. Ewww.
285 p0.frameRot = random.nextInt(4) * Math.PI / 2;
286 p0.init(gameManager.getPlayer(0));
287 p1.frameRot = p1.frameRot +
288 (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
289 p1.init(gameManager.getPlayer(1));
300 trollMessage.setX(troll.getX());
302 animateTurnCounter();
305 void endgameFrame() {
306 gameManager.setFrameDuration(2000);
309 void doubleDefeat() {
310 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
312 animateLoss(1920/2, 680, 150, "L0SERZ!");
316 gameManager.addToGameSummary("Draw.");
318 animateLoss(1920/2, 680, 200, "DRAW");
321 // drawBackground() helper class
322 // @Java nerds: is this avoidable?
325 Pos(int _x, int _y) { x = _x; y = _y; }
328 private void drawBackground() {
329 graphicEntityModule.createSprite()
330 .setImage("background.png")
333 int numMountains = random.nextInt(5);
334 while (numMountains --> 0) {
335 final int pngWidth = 366;
336 double scale = 0.5 * (1 + random.nextDouble());
337 int x = random.nextInt(1920 + (int) (scale*pngWidth))
338 - (int) (scale*pngWidth/2);
339 int baseTint = 64 + random.nextInt(128);
340 Sprite mountain = graphicEntityModule.createSprite()
341 .setImage("mountain.png")
345 .setAnchorY(283.0 / 321.0)
346 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
347 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
348 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
349 .setSkewX((random.nextDouble() - 0.5) / 4)
350 .setSkewY((random.nextDouble() - 0.5) / 8)
351 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
352 + (baseTint + random.nextInt(16) - 8) * 0x0100
353 + (baseTint + random.nextInt(16) - 8) * 0x01);
354 graphicEntityModule.createSprite().setImage("mountaintop.png")
355 .setX(mountain.getX())
356 .setY(mountain.getY())
357 .setAnchorX(mountain.getAnchorX())
358 .setAnchorY(mountain.getAnchorY())
359 .setRotation(mountain.getRotation())
360 .setScaleX(mountain.getScaleX())
361 .setScaleY(mountain.getScaleY())
362 .setSkewX(mountain.getSkewX())
363 .setSkewY(mountain.getSkewY());
366 int numTrees = random.nextInt(21);
367 ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
368 while (numTrees --> 0) {
371 x = random.nextInt(1920);
372 // yes, this biases randomness wrt perspective! :-(
373 y = 700 + random.nextInt(175);
374 } while (y > 880 && (x < 200 || x > 1720));
375 poss.add(new Pos(x, y));
377 poss.sort(new Comparator<Pos>() {
378 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
382 double scale = ( 90.0 / 433.0 // base height from PNG
383 * (p.y - 680) / (875 - 680) ); // perspective
384 graphicEntityModule.createSprite()
385 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
391 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
392 * (1 + (random.nextDouble() - 0.5) / 6))
393 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
394 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
395 .setSkewX((random.nextDouble() - 0.5) /4)
396 .setSkewY((random.nextDouble() - 0.5) /8);
400 Sprite f7u12 = graphicEntityModule.createSprite()
401 .setImage("f7u12.png")
406 .setBaseWidth(514*1080/387)
409 pantsModule.displayOnToggleState(f7u12, "troll", true);
412 // ==================== Troll
415 Troll("The troll", 0xfac200, "bland"),
416 IceTroll("The ice troll", 0x59a2a2, "ice"),
417 RockTroll("The rock troll", 0x78877f, "rock"),
418 WaterTroll("The water troll", 0x2b2fc6, "water"),
419 OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
420 String starter, parser; int tint;
421 TrollRace(String s, int t, String p) {
426 String nonStarter() {
427 return Character.toLowerCase(starter.charAt(0))
428 + starter.substring(1);
433 private void drawTroll() {
434 int r, league = gameManager.getLeagueLevel();
435 if (league <= 1) r = 4;
436 else if (league <= 2) r = 8;
439 r = random.nextInt(r);
440 if (r < 4) trollRace = TrollRace.Troll;
441 else if (r < 6) trollRace = TrollRace.IceTroll;
442 else if (r < 8) trollRace = TrollRace.RockTroll;
443 else if (r < 9) trollRace = TrollRace.WaterTroll;
444 else if (r < 10) trollRace = TrollRace.OlogHai;
445 else throw new RuntimeException("Internal error: unknown troll race " + r);
447 // We read it for debugging purposes, but don't echo it back
448 // to the IDE. It is, after all, *not* a map parameter!
449 String buf = gameManager.getGameParameters().getProperty("ehtnicity");
452 for (char c : buf.toCharArray())
453 if (Character.isLetter(c))
454 key += Character.toLowerCase(c);
456 for (TrollRace race : TrollRace.values()) {
457 if (key.equals(race.parser)) {
459 break/*ing news: */ iHateJava;
462 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
465 photoFinish: ; // The race is through, but Java has no goto :-(
467 Sprite trollBody = graphicEntityModule.createSprite()
468 .setImage("troll_body.png")
471 .setTint(trollRace.tint);
472 Sprite trollPantsRed = graphicEntityModule.createSprite()
473 .setImage("pants_red.png")
476 pantsModule.displayOnPantsState(trollPantsRed, 1);
477 Sprite trollPantsGreen = graphicEntityModule.createSprite()
478 .setImage("pants_green.png")
481 pantsModule.displayOnPantsState(trollPantsGreen, 2);
482 Sprite trollPantsBlue = graphicEntityModule.createSprite()
483 .setImage("pants_blue.png")
486 pantsModule.displayOnPantsState(trollPantsBlue, 3);
487 Sprite trollPantsPerv = graphicEntityModule.createSprite()
488 .setImage("pants_perv.png")
491 pantsModule.displayOnPantsState(trollPantsPerv, 4);
492 troll = graphicEntityModule
493 .createGroup(trollBody, trollPantsRed,
494 trollPantsGreen, trollPantsBlue, trollPantsPerv)
497 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
499 trollPositionGauge = graphicEntityModule.createText()
505 .setFillColor(0xffffff);
508 trollMessage = graphicEntityModule.createText()
514 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
515 .setStrokeColor(0xFFFF00)
516 .setFillColor(0xFFFF00)
518 pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
522 graphicEntityModule.commitEntityState(TROLL_MOVE_START, troll, trollPositionGauge);
523 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
524 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
525 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
527 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
530 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
531 int distLeft = model.trollPosition;
532 int distRight = model.roadLength - model.trollPosition;
534 trollPositionGauge.setText("← " + distRight);
536 else if (distRight <= 0) {
537 trollPositionGauge.setText(distLeft + " →");
540 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
542 final double moveMid = (TROLL_MOVE_START + TROLL_MOVE_END) / 2;
543 graphicEntityModule.commitEntityState(moveMid, trollPositionGauge);
544 trollPositionGauge.setX(troll.getX());
548 LEFT("walks left.", 0),
549 STILL("stands still.", 1),
550 RIGHT("walks right.", 2);
552 String movement; int index;
553 Dir(String mvt, int i) { movement = mvt; index = i; }
556 void moveTroll(Dir d) {
558 gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
560 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
561 graphicEntityModule.commitEntityState(TROLL_MESSAGE_START, trollMessage);
562 trollMessage.setAlpha(0, Curve.EASE_IN);
563 graphicEntityModule.commitEntityState(TROLL_MESSAGE_END, trollMessage);
566 String selectTrollMessage(Dir d) {
567 if (random.nextInt(10000) == 0) {
568 return TrollText.specials[random.nextInt(TrollText.specials.length)];
572 int i = random.nextInt(TrollText.directed.length + TrollText.isotropic.length / 3);
573 if (i < TrollText.directed.length) {
574 return TrollText.directed[i][d.index];
577 return TrollText.isotropic[random.nextInt(TrollText.isotropic.length)];
581 // ==================== Debug information
583 void animateTurnCounter() {
584 for (int i = 0; i < 10; i++) {
585 turnCounter.setText("T" + _turns + "." + i);
586 // The following line is likely not a bug.
587 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
593 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
594 .setSourceImage("debug.png")
603 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
604 .setImages(debugModePngs)
609 pantsModule.displayOnToggleState(debugMode, "debug", true);
611 turnCounter = graphicEntityModule.createText()
616 .setStrokeColor(0xff0080)
617 .setFillColor(0xff0080)
618 .setFontFamily("monospace")
619 .setFontWeight(Text.FontWeight.BOLD)
621 pantsModule.displayOnToggleState(turnCounter, "debug", true);
622 animateTurnCounter();
625 // ==================== Endgame status
627 void animateLoss(int x, int y, int size, String message) {
629 if (x < 1920/2) { startX = 1920; }
630 else if (x > 1920/2) { startX = 0; }
631 else { startX = 1920 * random.nextInt(2); }
633 Text msg = graphicEntityModule.createText(message)
638 .setScaleX(3*random.nextDouble() - 1)
639 .setScaleY(3*random.nextDouble() - 1)
640 .setSkewX(2*random.nextDouble() - 1)
641 .setSkewY(2*random.nextDouble() - 1)
642 .setRotation(4*Math.PI * (1 + random.nextDouble())
643 * (random.nextInt(2) == 0 ? 1 : -1))
645 .setStrokeColor(0xff7f7f)
646 .setFillColor(0xff7f7f)
647 .setFontWeight(Text.FontWeight.BOLD)
648 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
649 graphicEntityModule.commitEntityState(0.0, msg);
650 Curve curve = Curve.ELASTIC;
651 msg.setX(x, Curve.EASE_OUT)
652 .setY(y, Curve.ELASTIC)
657 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
658 .setFontSize(size, curve);