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 LEFT("walks left.", 0),
26 STILL("stands still.", 1),
27 RIGHT("walks right.", 2);
29 String movement; int index;
30 Dir(String mvt, int i) { movement = mvt; index = i; }
49 void init(com.codingame.game.Player p) {
51 colorToken = p.getColorToken();
52 nicknameToken = p.getNicknameToken();
53 avatarToken = p.getAvatarToken();
55 boolean p0 = model.index == 0;
56 int x = p0 ? 280 : 1920 - 280;
59 Sprite frame = graphicEntityModule.createSprite()
60 .setImage("frame.png")
62 .setRotation(frameRot)
66 Sprite frameBg = graphicEntityModule.createSprite()
67 .setImage("frame_bg.png")
69 .setRotation(frameRot)
72 Sprite avatarSprite = graphicEntityModule.createSprite()
74 .setImage(avatarToken)
79 avatar = graphicEntityModule
80 .createGroup(frame, frameBg, avatarSprite)
83 Text text = graphicEntityModule.createText(nicknameToken)
88 .setFillColor(0x7f3f00)
91 stoneCounter = graphicEntityModule.createText()
96 .setFillColor(0x7f3f00)
100 message = graphicEntityModule.createText()
101 .setX(p0 ? 15 : 1920-15)
105 .setFillColor(0xffbf7f)
106 .setAnchorX(p0 ? 0 : 1)
109 castle = graphicEntityModule.createSprite()
110 .setImage("castle.png")
112 .setX(p0 ? 160 : 1920-160)
113 .setY(p0 ? 890 : 880)
117 .setScaleX(p0 ? 1 : -1);
119 stone = graphicEntityModule.createText()
122 .setFillColor(0x12322a)
126 stoneReminder = graphicEntityModule.createText()
127 .setX(p0 ? x + 100 : x - 100)
131 .setFontFamily("monospace")
132 .setStrokeColor(0xff0080)
133 .setFillColor(0xff0080)
134 .setAnchorX(p0 ? 0 : 1)
136 pantsModule.displayOnToggleState(stoneReminder, "debug", true);
139 void updateStoneCounter() {
140 int stones = model.getStones();
142 stoneCounter.setText("Out of stones!");
143 stoneCounter.setFillColor(0xff7777);
145 else if (stones == 1) {
146 stoneCounter.setText("1 stone");
147 stoneCounter.setFillColor(0xffbb77);
150 stoneCounter.setText(stones + " stones");
154 void animateStones(int stones) {
155 String stonesString = Integer.valueOf(stones).toString();
156 stone.setX(castle.getX());
157 stone.setY(castle.getY() - 100);
158 stone.setText(stonesString);
160 graphicEntityModule.commitEntityState(0, stone);
162 int peakX = (castle.getX() + troll.getX()) / 2;
165 stone.setY(peakY, Curve.EASE_OUT);
166 graphicEntityModule.commitEntityState(0.25,
170 stone.setX(troll.getX());
171 stone.setY(troll.getY() - 50, Curve.EASE_IN);
172 stone.setAlpha(0, Curve.EASE_IN);
173 graphicEntityModule.commitEntityState(0.5, stone);
175 stoneReminder.setText(stonesString);
176 graphicEntityModule.commitEntityState(0, stoneReminder);
179 void displayMessage(String msg) {
180 message.setText(msg);
181 graphicEntityModule.commitEntityState(0, message);
185 graphicEntityModule.commitEntityState(0.5, avatar);
186 int dir = random.nextInt(2) == 1 ? 1 : -1;
187 avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
191 gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
192 graphicEntityModule.commitEntityState(0.5, castle);
193 castle.setX(castle.getX(), Curve.ELASTIC);
194 castle.setScaleY(-0.2, Curve.EASE_IN);
198 graphicEntityModule.commitEntityState(0, stoneReminder);
202 gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
203 graphicEntityModule.commitEntityState(0.5, avatar);
204 avatar.setScaleX(1.5, Curve.EASE_OUT);
205 avatar.setScaleY(1.5, Curve.EASE_OUT);
206 avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
210 void throwStones(int stones) {
211 gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
214 void threwMoreStonesThanHad() {
215 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 void failedToThrowStonesAndShouldHave() {
219 gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones. Fixing that for them because I'm in a good mood today."));
223 animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
227 animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
231 animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
236 Random random = new Random();
239 Text trollPositionGauge;
240 Player p0 = new Player(), p1 = new Player();
241 Text turnCounter; int _turns = 0;
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.
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));
268 trollMessage.setX(troll.getX());
270 animateTurnCounter();
275 Pos(int _x, int _y) { x = _x; y = _y; }
278 private void drawBackground() {
279 graphicEntityModule.createSprite()
280 .setImage("background.png")
283 int numMountains = random.nextInt(5);
284 while (numMountains --> 0) {
285 final int pngWidth = 366;
286 double scale = 0.5 * (1 + random.nextDouble());
287 int x = random.nextInt(1920 + (int) (scale*pngWidth))
288 - (int) (scale*pngWidth/2);
289 int baseTint = 64 + random.nextInt(128);
290 Sprite mountain = graphicEntityModule.createSprite()
291 .setImage("mountain.png")
295 .setAnchorY(283.0 / 321.0)
296 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
297 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
298 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
299 .setSkewX((random.nextDouble() - 0.5) / 4)
300 .setSkewY((random.nextDouble() - 0.5) / 8)
301 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
302 + (baseTint + random.nextInt(16) - 8) * 0x0100
303 + (baseTint + random.nextInt(16) - 8) * 0x01);
304 graphicEntityModule.createSprite().setImage("mountaintop.png")
305 .setX(mountain.getX())
306 .setY(mountain.getY())
307 .setAnchorX(mountain.getAnchorX())
308 .setAnchorY(mountain.getAnchorY())
309 .setRotation(mountain.getRotation())
310 .setScaleX(mountain.getScaleX())
311 .setScaleY(mountain.getScaleY())
312 .setSkewX(mountain.getSkewX())
313 .setSkewY(mountain.getSkewY());
316 int numTrees = random.nextInt(21);
317 ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
318 while (numTrees --> 0) {
321 x = random.nextInt(1920);
322 // yes, this biases randomness wrt perspective! :-(
323 y = 700 + random.nextInt(175);
324 } while (y > 880 && (x < 200 || x > 1720));
325 poss.add(new Pos(x, y));
327 poss.sort(new Comparator<Pos>() {
328 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
332 double scale = ( 90.0 / 433.0 // base height from PNG
333 * (p.y - 680) / (875 - 680) ); // perspective
334 graphicEntityModule.createSprite()
335 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
341 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
342 * (1 + (random.nextDouble() - 0.5) / 6))
343 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
344 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
345 .setSkewX((random.nextDouble() - 0.5) /4)
346 .setSkewY((random.nextDouble() - 0.5) /8);
350 Sprite f7u12 = graphicEntityModule.createSprite()
351 .setImage("f7u12.png")
356 .setBaseWidth(514*1080/387)
359 pantsModule.displayOnToggleState(f7u12, "troll", true);
363 Troll("The troll", 0xfac200, "bland"),
364 IceTroll("The ice troll", 0x59a2a2, "ice"),
365 RockTroll("The rock troll", 0x78877f, "rock"),
366 WaterTroll("The water troll", 0x2b2fc6, "water"),
367 OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
368 String starter, parser; int tint;
369 TrollRace(String s, int t, String p) {
374 String nonStarter() {
375 return Character.toLowerCase(starter.charAt(0))
376 + starter.substring(1);
381 private void drawTroll() {
382 int r, league = gameManager.getLeagueLevel();
383 if (league <= 1) r = 4;
384 else if (league <= 2) r = 8;
387 r = random.nextInt(r);
388 if (r < 4) trollRace = TrollRace.Troll;
389 else if (r < 6) trollRace = TrollRace.IceTroll;
390 else if (r < 8) trollRace = TrollRace.RockTroll;
391 else if (r < 9) trollRace = TrollRace.WaterTroll;
392 else if (r < 10) trollRace = TrollRace.OlogHai;
393 else throw new RuntimeException("Internal error: unknown troll race " + r);
395 // We read it for debugging purposes, but don't echo it back
396 // to the IDE. It is, after all, *not* a map parameter!
397 String buf = gameManager.getGameParameters().getProperty("ehtnicity");
400 for (char c : buf.toCharArray())
401 if (Character.isLetter(c))
402 key += Character.toLowerCase(c);
404 for (TrollRace race : TrollRace.values()) {
405 if (key.equals(race.parser)) {
407 break/*ing news: */ iHateJava;
410 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
413 photoFinish: ; // The race is through, but Java has no goto :-(
415 Sprite trollBody = graphicEntityModule.createSprite()
416 .setImage("troll_body.png")
419 .setTint(trollRace.tint);
420 Sprite trollPantsRed = graphicEntityModule.createSprite()
421 .setImage("pants_red.png")
424 pantsModule.displayOnPantsState(trollPantsRed, 1);
425 Sprite trollPantsGreen = graphicEntityModule.createSprite()
426 .setImage("pants_green.png")
429 pantsModule.displayOnPantsState(trollPantsGreen, 2);
430 Sprite trollPantsBlue = graphicEntityModule.createSprite()
431 .setImage("pants_blue.png")
434 pantsModule.displayOnPantsState(trollPantsBlue, 3);
435 Sprite trollPantsPerv = graphicEntityModule.createSprite()
436 .setImage("pants_perv.png")
439 pantsModule.displayOnPantsState(trollPantsPerv, 4);
440 troll = graphicEntityModule
441 .createGroup(trollBody, trollPantsRed,
442 trollPantsGreen, trollPantsBlue, trollPantsPerv)
445 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
447 trollPositionGauge = graphicEntityModule.createText()
453 .setFillColor(0xffffff);
456 trollMessage = graphicEntityModule.createText()
461 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
462 .setStrokeColor(0xFFFF00)
463 .setFillColor(0xFFFF00)
465 pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
468 private void moveTroll() {
469 graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
470 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
471 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
472 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
474 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
477 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
478 int distLeft = model.trollPosition;
479 int distRight = model.roadLength - model.trollPosition;
481 trollPositionGauge.setText("← " + distRight);
483 else if (distRight <= 0) {
484 trollPositionGauge.setText(distLeft + " →");
487 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
489 graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
490 trollPositionGauge.setX(troll.getX());
493 void moveTroll(Dir d) {
495 gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
497 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
498 graphicEntityModule.commitEntityState(0.5, trollMessage);
499 trollMessage.setAlpha(0, Curve.EASE_IN);
500 graphicEntityModule.commitEntityState(1, trollMessage);
503 void animateTurnCounter() {
504 for (int i = 0; i < 10; i++) {
505 turnCounter.setText("T" + _turns + "." + i);
506 // The following line is likely not a bug.
507 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
513 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
514 .setSourceImage("debug.png")
523 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
524 .setImages(debugModePngs)
529 pantsModule.displayOnToggleState(debugMode, "debug", true);
531 turnCounter = graphicEntityModule.createText()
536 .setStrokeColor(0xff0080)
537 .setFillColor(0xff0080)
538 .setFontFamily("monospace")
539 .setFontWeight(Text.FontWeight.BOLD)
541 pantsModule.displayOnToggleState(turnCounter, "debug", true);
542 animateTurnCounter();
545 void animateLoss(int x, int y, int size, String message) {
547 if (x < 1920/2) { startX = 1920; }
548 else if (x > 1920/2) { startX = 1920; }
549 else { startX = 1920 * random.nextInt(2); }
551 Text msg = graphicEntityModule.createText(message)
556 .setScaleX(3*random.nextDouble() - 1)
557 .setScaleY(3*random.nextDouble() - 1)
558 .setSkewX(2*random.nextDouble() - 1)
559 .setSkewY(2*random.nextDouble() - 1)
560 .setRotation(4*Math.PI * (1 + random.nextDouble())
561 * (random.nextInt(2) == 0 ? 1 : -1))
563 .setStrokeColor(0xff7f7f)
564 .setFillColor(0xff7f7f)
565 .setFontWeight(Text.FontWeight.BOLD)
566 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
567 graphicEntityModule.commitEntityState(0.25, msg);
568 Curve curve = Curve.ELASTIC;
569 msg.setX(x, Curve.EASE_OUT)
570 .setY(y, Curve.ELASTIC)
575 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
576 .setFontSize(size, curve);
579 void doubleDefeat() {
580 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
581 animateLoss(1920/2, 680, 150, "L0SERZ!");
585 gameManager.addToGameSummary("Draw.");
586 animateLoss(1920/2, 680, 200, "DRAW");
589 String selectTrollMessage(Dir d) {
590 if (random.nextInt(10000) == 0) {
591 return specials[random.nextInt(specials.length)];
595 int i = random.nextInt(directed.length + isotropic.length / 3);
596 if (i < directed.length) {
597 return directed[i][d.index];
600 return isotropic[random.nextInt(isotropic.length)];
604 // You'll never remember if you ever saw these…
605 String specials[] = {
606 "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",
607 "CG know what they're doing."
610 // Most of what ought to happen in normal play
611 String directed[][] = {
613 { "Han shot first", "I am your father", "Greedo shot first" },
614 { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
615 { "star wars > star trek", "my god, it's full of troll", "star trek > star wars" },
616 // More movie controversies sought. Apply on the puzzle contrib page.
619 { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
620 { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
623 { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
624 { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
625 { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
626 { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
627 { "santa claus is really\nthe tooth fairy", "the easter bunny tasted yummy", "the tooth fairy is\nreally santa claus" },
628 // Ditto. Need moar troll.
631 { "PC > console", "pong is still\nunequaled", "console > PC" },
632 { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
633 { "pad > stick", "mouse gaming is lame", "stick > pad" },
634 { "RTS > FPS", "solitaire best game", "FPS > RTS" },
635 { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
636 { "orcs are wusses", "the amulet is in another dungeon", "elves are wusses" },
637 { "here's a link to my patreon", "my apm > yours", "here's my soundcloud" },
638 { "all your stones is belong to us", "all your castle are belong to us", "all your rocks is belong to us" },
639 // I'm not exactly a gamer myself, I take hints on the topics du jour
642 { "vi < emacs", "i code with Notepad", "emacs > vi" },
643 { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
644 { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
645 { "static linking best", "symlinking best", "dynamic linking best" },
646 { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
647 { "objects > functions", "it's closures\nall the way down", "functions > objects" },
648 { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
649 { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
650 // This category's not too bad.
653 { "gmail > github", "copy-paste FTW", "github > gmail" },
654 { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
655 { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
656 { "jira > trello", "bugzilla FTW", "trello > jira" },
657 { "IRC > slack", "chat is work", "discord < IRC" },
658 { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
659 { "there's an app for that", "there's a bean for that", "there's an applet for that" },
660 // More always welcome here.
663 { "my nn is in python", "my language has -O3", "my code is more than 100k" },
664 { "i found a bug\nin temperatures", "i found a bug on\nthe leaderboard", "i found a bug\nin chuck norris" },
665 { "fix it", "how is ur csb", "ezpz" },
666 { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
667 { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
668 { "optimizing for the contest\ntestcase is cheating", "having moar accounts than cg\nstaff will get you banned", "it's not hardcoding\nif it's stochastic" },
669 { "light theme best", "ascii > graphics", "dark theme best" },
670 { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
671 { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
672 // And here. Especially as I'm not that active on #World or #Ru.
675 // Those for which I couldn't find a meaningful directednessability.
676 String isotropic[] = {
678 "Electron apps are the fastest",
680 "Thanos did nothing wrong",
681 "developers developers developers",
682 "the cloud is just\nother ppl's computers",
686 "ASCII stupid question\nget a stupid ANSI",
688 // I try and avoid those, but if really it fits nowhere else…