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);
156 void disqualify(String message) {
157 gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " " + message));
161 gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
162 View.this.endgameFrame();
167 gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
171 // ========== Player/avatar markings
174 animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
178 animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
182 animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
186 graphicEntityModule.commitEntityState(AVATAR_ANIMATION_START, avatar);
187 avatar.setScaleX(1.5, Curve.EASE_OUT);
188 avatar.setScaleY(1.5, Curve.EASE_OUT);
189 avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
194 graphicEntityModule.commitEntityState(AVATAR_ANIMATION_START, avatar);
195 int dir = random.nextInt(2) == 1 ? 1 : -1;
196 avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
199 // ==========Player/stones
201 void throwStones(int stones) {
202 gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
205 void threwMoreStonesThanHad() {
206 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!)"));
209 void failedToThrowStonesAndShouldHave() {
210 gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones. Fixing that for them because I'm in a good mood today."));
213 void updateStoneCounter() {
214 int stones = model.getStones();
216 stoneCounter.setText("Out of stones!");
217 stoneCounter.setFillColor(0xff7777);
219 else if (stones == 1) {
220 stoneCounter.setText("1 stone");
221 stoneCounter.setFillColor(0xffbb77);
224 stoneCounter.setText(stones + " stones");
226 graphicEntityModule.commitEntityState(STONE_THROW_PEAK, stoneCounter);
229 void animateStones(int stones) {
230 String stonesString = Integer.valueOf(stones).toString();
231 stone.setX(castle.getX());
232 stone.setY(castle.getY() - 100);
233 stone.setText(stonesString);
235 graphicEntityModule.commitEntityState(STONE_THROW_START, stone);
237 int peakX = (castle.getX() + troll.getX()) / 2;
240 stone.setY(peakY, Curve.EASE_OUT);
241 graphicEntityModule.commitEntityState(STONE_THROW_PEAK,
245 stone.setX(troll.getX());
246 stone.setY(troll.getY() - 50, Curve.EASE_IN);
247 stone.setAlpha(0, Curve.EASE_IN);
248 graphicEntityModule.commitEntityState(STONE_THROW_END, stone);
250 stoneReminder.setText(stonesString);
251 graphicEntityModule.commitEntityState(0.0, stoneReminder);
254 // ========== Player/castle
256 void displayMessage(String msg) {
257 message.setText(msg);
258 graphicEntityModule.commitEntityState(0.0, message);
261 void destroyCastle() {
262 graphicEntityModule.commitEntityState(CASTLE_DESTRUCTION_START, castle);
263 castle.setX(castle.getX(), Curve.ELASTIC);
264 castle.setScaleY(-0.2, Curve.EASE_IN);
265 graphicEntityModule.commitEntityState(CASTLE_DESTRUCTION_END, castle);
270 Random random = new Random();
273 Text trollPositionGauge;
274 Player p0 = new Player(), p1 = new Player();
275 Text turnCounter; int _turns = 0;
277 // ==================== Referee interface
282 gameManager.setFrameDuration(2000);
286 * Random π/2-grained rotation of the avatar frames. Avoid
287 * having them π/2 apart, though, as one of them is likely
288 * going to end upside-down and the trick would be revealed.
289 * And I'd have to "draw" a new frame. Ewww.
291 p0.frameRot = random.nextInt(4) * Math.PI / 2;
292 p0.init(gameManager.getPlayer(0));
293 p1.frameRot = p1.frameRot +
294 (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
295 p1.init(gameManager.getPlayer(1));
306 trollMessage.setX(troll.getX());
308 animateTurnCounter();
311 void endgameFrame() {
312 gameManager.setFrameDuration(2000);
315 void doubleDefeat() {
316 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
318 animateLoss(1920/2, 680, 150, "L0SERZ!");
322 gameManager.addToGameSummary("Draw.");
324 animateLoss(1920/2, 680, 200, "DRAW");
327 // drawBackground() helper class
328 // @Java nerds: is this avoidable?
331 Pos(int _x, int _y) { x = _x; y = _y; }
334 private void drawBackground() {
335 graphicEntityModule.createSprite()
336 .setImage("background.png")
339 int numMountains = random.nextInt(5);
340 while (numMountains --> 0) {
341 final int pngWidth = 366;
342 double scale = 0.5 * (1 + random.nextDouble());
343 int x = random.nextInt(1920 + (int) (scale*pngWidth))
344 - (int) (scale*pngWidth/2);
345 int baseTint = 64 + random.nextInt(128);
346 Sprite mountain = graphicEntityModule.createSprite()
347 .setImage("mountain.png")
351 .setAnchorY(283.0 / 321.0)
352 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
353 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
354 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
355 .setSkewX((random.nextDouble() - 0.5) / 4)
356 .setSkewY((random.nextDouble() - 0.5) / 8)
357 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
358 + (baseTint + random.nextInt(16) - 8) * 0x0100
359 + (baseTint + random.nextInt(16) - 8) * 0x01);
360 graphicEntityModule.createSprite().setImage("mountaintop.png")
361 .setX(mountain.getX())
362 .setY(mountain.getY())
363 .setAnchorX(mountain.getAnchorX())
364 .setAnchorY(mountain.getAnchorY())
365 .setRotation(mountain.getRotation())
366 .setScaleX(mountain.getScaleX())
367 .setScaleY(mountain.getScaleY())
368 .setSkewX(mountain.getSkewX())
369 .setSkewY(mountain.getSkewY());
372 int numTrees = random.nextInt(21);
373 ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
374 while (numTrees --> 0) {
377 x = random.nextInt(1920);
378 // yes, this biases randomness wrt perspective! :-(
379 y = 700 + random.nextInt(175);
380 } while (y > 880 && (x < 200 || x > 1720));
381 poss.add(new Pos(x, y));
383 poss.sort(new Comparator<Pos>() {
384 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
388 double scale = ( 90.0 / 433.0 // base height from PNG
389 * (p.y - 680) / (875 - 680) ); // perspective
390 graphicEntityModule.createSprite()
391 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
397 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
398 * (1 + (random.nextDouble() - 0.5) / 6))
399 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
400 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
401 .setSkewX((random.nextDouble() - 0.5) /4)
402 .setSkewY((random.nextDouble() - 0.5) /8);
406 Sprite f7u12 = graphicEntityModule.createSprite()
407 .setImage("f7u12.png")
412 .setBaseWidth(514*1080/387)
415 pantsModule.displayOnToggleState(f7u12, "troll", true);
418 // ==================== Troll
421 Troll("The troll", 0xfac200, "bland"),
422 IceTroll("The ice troll", 0x59a2a2, "ice"),
423 RockTroll("The rock troll", 0x78877f, "rock"),
424 WaterTroll("The water troll", 0x2b2fc6, "water"),
425 OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
426 String starter, parser; int tint;
427 TrollRace(String s, int t, String p) {
432 String nonStarter() {
433 return Character.toLowerCase(starter.charAt(0))
434 + starter.substring(1);
439 private void drawTroll() {
440 int r, league = gameManager.getLeagueLevel();
441 if (league <= 1) r = 4;
442 else if (league <= 2) r = 8;
445 r = random.nextInt(r);
446 if (r < 4) trollRace = TrollRace.Troll;
447 else if (r < 6) trollRace = TrollRace.IceTroll;
448 else if (r < 8) trollRace = TrollRace.RockTroll;
449 else if (r < 9) trollRace = TrollRace.WaterTroll;
450 else if (r < 10) trollRace = TrollRace.OlogHai;
451 else throw new RuntimeException("Internal error: unknown troll race " + r);
453 // We read it for debugging purposes, but don't echo it back
454 // to the IDE. It is, after all, *not* a map parameter!
455 String buf = gameManager.getGameParameters().getProperty("ehtnicity");
458 for (char c : buf.toCharArray())
459 if (Character.isLetter(c))
460 key += Character.toLowerCase(c);
462 for (TrollRace race : TrollRace.values()) {
463 if (key.equals(race.parser)) {
465 break/*ing news: */ iHateJava;
468 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
471 photoFinish: ; // The race is through, but Java has no goto :-(
473 Sprite trollBody = graphicEntityModule.createSprite()
474 .setImage("troll_body.png")
477 .setTint(trollRace.tint);
478 Sprite trollPantsRed = graphicEntityModule.createSprite()
479 .setImage("pants_red.png")
482 pantsModule.displayOnPantsState(trollPantsRed, 1);
483 Sprite trollPantsGreen = graphicEntityModule.createSprite()
484 .setImage("pants_green.png")
487 pantsModule.displayOnPantsState(trollPantsGreen, 2);
488 Sprite trollPantsBlue = graphicEntityModule.createSprite()
489 .setImage("pants_blue.png")
492 pantsModule.displayOnPantsState(trollPantsBlue, 3);
493 Sprite trollPantsPerv = graphicEntityModule.createSprite()
494 .setImage("pants_perv.png")
497 pantsModule.displayOnPantsState(trollPantsPerv, 4);
498 troll = graphicEntityModule
499 .createGroup(trollBody, trollPantsRed,
500 trollPantsGreen, trollPantsBlue, trollPantsPerv)
503 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
505 trollPositionGauge = graphicEntityModule.createText()
511 .setFillColor(0xffffff);
514 trollMessage = graphicEntityModule.createText()
520 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
521 .setStrokeColor(0xFFFF00)
522 .setFillColor(0xFFFF00)
524 pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
528 graphicEntityModule.commitEntityState(TROLL_MOVE_START, troll, trollPositionGauge);
529 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
530 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
531 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
533 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
536 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
537 int distLeft = model.trollPosition;
538 int distRight = model.roadLength - model.trollPosition;
540 trollPositionGauge.setText("← " + distRight);
542 else if (distRight <= 0) {
543 trollPositionGauge.setText(distLeft + " →");
546 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
548 final double moveMid = (TROLL_MOVE_START + TROLL_MOVE_END) / 2;
549 graphicEntityModule.commitEntityState(moveMid, trollPositionGauge);
550 trollPositionGauge.setX(troll.getX());
554 LEFT("walks left.", 0),
555 STILL("stands still.", 1),
556 RIGHT("walks right.", 2);
558 String movement; int index;
559 Dir(String mvt, int i) { movement = mvt; index = i; }
562 void moveTroll(Dir d) {
564 gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
566 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
567 graphicEntityModule.commitEntityState(TROLL_MESSAGE_START, trollMessage);
568 trollMessage.setAlpha(0, Curve.EASE_IN);
569 graphicEntityModule.commitEntityState(TROLL_MESSAGE_END, trollMessage);
572 String selectTrollMessage(Dir d) {
573 if (random.nextInt(10000) == 0) {
574 return TrollText.specials[random.nextInt(TrollText.specials.length)];
578 int i = random.nextInt(TrollText.directed.length + TrollText.isotropic.length / 3);
579 if (i < TrollText.directed.length) {
580 return TrollText.directed[i][d.index];
583 return TrollText.isotropic[random.nextInt(TrollText.isotropic.length)];
587 // ==================== Debug information
589 void animateTurnCounter() {
590 for (int i = 0; i < 10; i++) {
591 turnCounter.setText("T" + _turns + "." + i);
592 // The following line is likely not a bug.
593 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
599 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
600 .setSourceImage("debug.png")
609 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
610 .setImages(debugModePngs)
615 pantsModule.displayOnToggleState(debugMode, "debug", true);
617 turnCounter = graphicEntityModule.createText()
622 .setStrokeColor(0xff0080)
623 .setFillColor(0xff0080)
624 .setFontFamily("monospace")
625 .setFontWeight(Text.FontWeight.BOLD)
627 pantsModule.displayOnToggleState(turnCounter, "debug", true);
628 animateTurnCounter();
631 // ==================== Endgame status
633 void animateLoss(int x, int y, int size, String message) {
635 if (x < 1920/2) { startX = 1920; }
636 else if (x > 1920/2) { startX = 0; }
637 else { startX = 1920 * random.nextInt(2); }
639 Text msg = graphicEntityModule.createText(message)
644 .setScaleX(3*random.nextDouble() - 1)
645 .setScaleY(3*random.nextDouble() - 1)
646 .setSkewX(2*random.nextDouble() - 1)
647 .setSkewY(2*random.nextDouble() - 1)
648 .setRotation(4*Math.PI * (1 + random.nextDouble())
649 * (random.nextInt(2) == 0 ? 1 : -1))
651 .setStrokeColor(0xff7f7f)
652 .setFillColor(0xff7f7f)
653 .setFontWeight(Text.FontWeight.BOLD)
654 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
655 graphicEntityModule.commitEntityState(0.0, msg);
656 Curve curve = Curve.ELASTIC;
657 msg.setX(x, Curve.EASE_OUT)
658 .setY(y, Curve.ELASTIC)
663 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
664 .setFontSize(size, curve);