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);
351 Troll("The troll", 0xfac200, "bland"),
352 IceTroll("The ice troll", 0x59a2a2, "ice"),
353 RockTroll("The rock troll", 0x78877f, "rock"),
354 WaterTroll("The water troll", 0x2b2fc6, "water"),
355 OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
356 String starter, parser; int tint;
357 TrollRace(String s, int t, String p) {
362 String nonStarter() {
363 return Character.toLowerCase(starter.charAt(0))
364 + starter.substring(1);
369 private void drawTroll() {
370 int r, league = gameManager.getLeagueLevel();
371 if (league <= 1) r = 4;
372 else if (league <= 2) r = 8;
375 r = random.nextInt(r);
376 if (r < 4) trollRace = TrollRace.Troll;
377 else if (r < 6) trollRace = TrollRace.IceTroll;
378 else if (r < 8) trollRace = TrollRace.RockTroll;
379 else if (r < 9) trollRace = TrollRace.WaterTroll;
380 else if (r < 10) trollRace = TrollRace.OlogHai;
381 else throw new RuntimeException("Internal error: unknown troll race " + r);
383 // We read it for debugging purposes, but don't echo it back
384 // to the IDE. It is, after all, *not* a map parameter!
385 String buf = gameManager.getGameParameters().getProperty("ehtnicity");
388 for (char c : buf.toCharArray())
389 if (Character.isLetter(c))
390 key += Character.toLowerCase(c);
392 for (TrollRace race : TrollRace.values()) {
393 if (key.equals(race.parser)) {
395 break/*ing news: */ iHateJava;
398 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
401 photoFinish: ; // The race is through, but Java has no goto :-(
403 Sprite trollBody = graphicEntityModule.createSprite()
404 .setImage("troll_body.png")
407 .setTint(trollRace.tint);
408 Sprite trollPantsRed = graphicEntityModule.createSprite()
409 .setImage("pants_red.png")
412 pantsModule.displayOnPantsState(trollPantsRed, 1);
413 Sprite trollPantsGreen = graphicEntityModule.createSprite()
414 .setImage("pants_green.png")
417 pantsModule.displayOnPantsState(trollPantsGreen, 2);
418 Sprite trollPantsBlue = graphicEntityModule.createSprite()
419 .setImage("pants_blue.png")
422 pantsModule.displayOnPantsState(trollPantsBlue, 3);
423 Sprite trollPantsPerv = graphicEntityModule.createSprite()
424 .setImage("pants_perv.png")
427 pantsModule.displayOnPantsState(trollPantsPerv, 4);
428 troll = graphicEntityModule
429 .createGroup(trollBody, trollPantsRed,
430 trollPantsGreen, trollPantsBlue, trollPantsPerv)
433 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
435 trollPositionGauge = graphicEntityModule.createText()
441 .setFillColor(0xffffff);
444 trollMessage = graphicEntityModule.createText()
449 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
450 .setStrokeColor(0xFFFF00)
451 .setFillColor(0xFFFF00)
453 pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
456 private void moveTroll() {
457 graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
458 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
459 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
460 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
462 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
465 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
466 int distLeft = model.trollPosition;
467 int distRight = model.roadLength - model.trollPosition;
469 trollPositionGauge.setText("← " + distRight);
471 else if (distRight <= 0) {
472 trollPositionGauge.setText(distLeft + " →");
475 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
477 graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
478 trollPositionGauge.setX(troll.getX());
481 void moveTroll(Dir d) {
483 gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
485 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
486 graphicEntityModule.commitEntityState(0.5, trollMessage);
487 trollMessage.setAlpha(0, Curve.EASE_IN);
488 graphicEntityModule.commitEntityState(1, trollMessage);
491 void animateTurnCounter() {
492 for (int i = 0; i < 10; i++) {
493 turnCounter.setText("T" + _turns + "." + i);
494 // The following line is likely not a bug.
495 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
501 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
502 .setSourceImage("debug.png")
511 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
512 .setImages(debugModePngs)
517 pantsModule.displayOnToggleState(debugMode, "debug", true);
519 turnCounter = graphicEntityModule.createText()
524 .setStrokeColor(0xff0080)
525 .setFillColor(0xff0080)
526 .setFontFamily("monospace")
527 .setFontWeight(Text.FontWeight.BOLD)
529 pantsModule.displayOnToggleState(turnCounter, "debug", true);
530 animateTurnCounter();
533 void animateLoss(int x, int y, int size, String message) {
535 if (x < 1920/2) { startX = 1920; }
536 else if (x > 1920/2) { startX = 1920; }
537 else { startX = 1920 * random.nextInt(2); }
539 Text msg = graphicEntityModule.createText(message)
544 .setScaleX(3*random.nextDouble() - 1)
545 .setScaleY(3*random.nextDouble() - 1)
546 .setSkewX(2*random.nextDouble() - 1)
547 .setSkewY(2*random.nextDouble() - 1)
548 .setRotation(4*Math.PI * (1 + random.nextDouble())
549 * (random.nextInt(2) == 0 ? 1 : -1))
551 .setStrokeColor(0xff7f7f)
552 .setFillColor(0xff7f7f)
553 .setFontWeight(Text.FontWeight.BOLD)
554 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
555 graphicEntityModule.commitEntityState(0.25, msg);
556 Curve curve = Curve.ELASTIC;
557 msg.setX(x, Curve.EASE_OUT)
558 .setY(y, Curve.ELASTIC)
563 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
564 .setFontSize(size, curve);
567 void doubleDefeat() {
568 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
569 animateLoss(1920/2, 680, 150, "L0SERZ!");
573 gameManager.addToGameSummary("Draw.");
574 animateLoss(1920/2, 680, 200, "DRAW");
577 String selectTrollMessage(Dir d) {
578 if (random.nextInt(10000) == 0) {
579 return specials[random.nextInt(specials.length)];
583 int i = random.nextInt(directed.length + isotropic.length / 3);
584 if (i < directed.length) {
585 return directed[i][d.index];
588 return isotropic[random.nextInt(isotropic.length)];
592 // You'll never remember if you ever saw these…
593 String specials[] = {
594 "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",
595 "CG know what they're doing."
598 // Most of what ought to happen in normal play
599 String directed[][] = {
601 { "Han shot first", "I am your father", "Greedo shot first" },
602 { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
603 { "star wars > star trek", "my god, it's full of troll", "star trek > star wars" },
604 // More movie controversies sought. Apply on the puzzle contrib page.
607 { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
608 { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
611 { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
612 { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
613 { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
614 { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
615 { "santa claus is really\nthe tooth fairy", "the easter bunny tasted yummy", "the tooth fairy is\nreally santa claus" },
616 // Ditto. Need moar troll.
619 { "PC > console", "pong is still\nunequaled", "console > PC" },
620 { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
621 { "pad > stick", "mouse gaming is lame", "stick > pad" },
622 { "RTS > FPS", "solitaire best game", "FPS > RTS" },
623 { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
624 { "orcs are wusses", "the amulet is in another dungeon", "elves are wusses" },
625 { "here's a link to my patreon", "my apm > yours", "here's my soundcloud" },
626 { "all your stones is belong to us", "all your castle are belong to us", "all your rocks is belong to us" },
627 // I'm not exactly a gamer myself, I take hints on the topics du jour
630 { "vi < emacs", "i code with Notepad", "emacs > vi" },
631 { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
632 { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
633 { "static linking best", "symlinking best", "dynamic linking best" },
634 { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
635 { "objects > functions", "it's closures\nall the way down", "functions > objects" },
636 { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
637 { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
638 // This category's not too bad.
641 { "gmail > github", "copy-paste FTW", "github > gmail" },
642 { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
643 { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
644 { "jira > trello", "bugzilla FTW", "trello > jira" },
645 { "IRC > slack", "chat is work", "discord < IRC" },
646 { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
647 { "there's an app for that", "there's a bean for that", "there's an applet for that" },
648 // More always welcome here.
651 { "my nn is in python", "my language has -O3", "my code is more than 100k" },
652 { "i found a bug\nin temperatures", "i found a bug on\nthe leaderboard", "i found a bug\nin chuck norris" },
653 { "fix it", "how is ur csb", "ezpz" },
654 { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
655 { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
656 { "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" },
657 { "light theme best", "ascii > graphics", "dark theme best" },
658 { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
659 { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
660 // And here. Especially as I'm not that active on #World or #Ru.
663 // Those for which I couldn't find a meaningful directednessability.
664 String isotropic[] = {
666 "Electron apps are the fastest",
668 "Thanos did nothing wrong",
669 "developers developers developers",
670 "the cloud is just\nother ppl's computers",
674 "ASCII stupid question\nget a stupid ANSI",
676 // I try and avoid those, but if really it fits nowhere else…