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);
140 graphicEntityModule.commitEntityState(0, stoneReminder);
144 gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
149 gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
153 // ========== Player/avatar markings
156 animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
160 animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
164 animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
168 graphicEntityModule.commitEntityState(0.5, avatar);
169 avatar.setScaleX(1.5, Curve.EASE_OUT);
170 avatar.setScaleY(1.5, Curve.EASE_OUT);
171 avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
176 graphicEntityModule.commitEntityState(0.5, avatar);
177 int dir = random.nextInt(2) == 1 ? 1 : -1;
178 avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
181 // ==========Player/stones
183 void throwStones(int stones) {
184 gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
187 void threwMoreStonesThanHad() {
188 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!)"));
191 void failedToThrowStonesAndShouldHave() {
192 gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones. Fixing that for them because I'm in a good mood today."));
195 void updateStoneCounter() {
196 int stones = model.getStones();
198 stoneCounter.setText("Out of stones!");
199 stoneCounter.setFillColor(0xff7777);
201 else if (stones == 1) {
202 stoneCounter.setText("1 stone");
203 stoneCounter.setFillColor(0xffbb77);
206 stoneCounter.setText(stones + " stones");
210 void animateStones(int stones) {
211 String stonesString = Integer.valueOf(stones).toString();
212 stone.setX(castle.getX());
213 stone.setY(castle.getY() - 100);
214 stone.setText(stonesString);
216 graphicEntityModule.commitEntityState(0, stone);
218 int peakX = (castle.getX() + troll.getX()) / 2;
221 stone.setY(peakY, Curve.EASE_OUT);
222 graphicEntityModule.commitEntityState(0.25,
226 stone.setX(troll.getX());
227 stone.setY(troll.getY() - 50, Curve.EASE_IN);
228 stone.setAlpha(0, Curve.EASE_IN);
229 graphicEntityModule.commitEntityState(0.5, stone);
231 stoneReminder.setText(stonesString);
232 graphicEntityModule.commitEntityState(0, stoneReminder);
235 // ========== Player/castle
237 void displayMessage(String msg) {
238 message.setText(msg);
239 graphicEntityModule.commitEntityState(0, message);
242 void destroyCastle() {
243 graphicEntityModule.commitEntityState(0.5, castle);
244 castle.setX(castle.getX(), Curve.ELASTIC);
245 castle.setScaleY(-0.2, Curve.EASE_IN);
250 Random random = new Random();
253 Text trollPositionGauge;
254 Player p0 = new Player(), p1 = new Player();
255 Text turnCounter; int _turns = 0;
262 * Random π/2-grained rotation of the avatar frames. Avoid
263 * having them π/2 apart, though, as one of them is likely
264 * going to end upside-down and the trick would be revealed.
265 * And I'd have to "draw" a new frame. Ewww.
267 p0.frameRot = random.nextInt(4) * Math.PI / 2;
268 p0.init(gameManager.getPlayer(0));
269 p1.frameRot = p1.frameRot +
270 (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
271 p1.init(gameManager.getPlayer(1));
282 trollMessage.setX(troll.getX());
284 animateTurnCounter();
289 Pos(int _x, int _y) { x = _x; y = _y; }
292 private void drawBackground() {
293 graphicEntityModule.createSprite()
294 .setImage("background.png")
297 int numMountains = random.nextInt(5);
298 while (numMountains --> 0) {
299 final int pngWidth = 366;
300 double scale = 0.5 * (1 + random.nextDouble());
301 int x = random.nextInt(1920 + (int) (scale*pngWidth))
302 - (int) (scale*pngWidth/2);
303 int baseTint = 64 + random.nextInt(128);
304 Sprite mountain = graphicEntityModule.createSprite()
305 .setImage("mountain.png")
309 .setAnchorY(283.0 / 321.0)
310 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
311 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
312 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
313 .setSkewX((random.nextDouble() - 0.5) / 4)
314 .setSkewY((random.nextDouble() - 0.5) / 8)
315 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
316 + (baseTint + random.nextInt(16) - 8) * 0x0100
317 + (baseTint + random.nextInt(16) - 8) * 0x01);
318 graphicEntityModule.createSprite().setImage("mountaintop.png")
319 .setX(mountain.getX())
320 .setY(mountain.getY())
321 .setAnchorX(mountain.getAnchorX())
322 .setAnchorY(mountain.getAnchorY())
323 .setRotation(mountain.getRotation())
324 .setScaleX(mountain.getScaleX())
325 .setScaleY(mountain.getScaleY())
326 .setSkewX(mountain.getSkewX())
327 .setSkewY(mountain.getSkewY());
330 int numTrees = random.nextInt(21);
331 ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
332 while (numTrees --> 0) {
335 x = random.nextInt(1920);
336 // yes, this biases randomness wrt perspective! :-(
337 y = 700 + random.nextInt(175);
338 } while (y > 880 && (x < 200 || x > 1720));
339 poss.add(new Pos(x, y));
341 poss.sort(new Comparator<Pos>() {
342 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
346 double scale = ( 90.0 / 433.0 // base height from PNG
347 * (p.y - 680) / (875 - 680) ); // perspective
348 graphicEntityModule.createSprite()
349 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
355 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
356 * (1 + (random.nextDouble() - 0.5) / 6))
357 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
358 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
359 .setSkewX((random.nextDouble() - 0.5) /4)
360 .setSkewY((random.nextDouble() - 0.5) /8);
364 Sprite f7u12 = graphicEntityModule.createSprite()
365 .setImage("f7u12.png")
370 .setBaseWidth(514*1080/387)
373 pantsModule.displayOnToggleState(f7u12, "troll", true);
377 Troll("The troll", 0xfac200, "bland"),
378 IceTroll("The ice troll", 0x59a2a2, "ice"),
379 RockTroll("The rock troll", 0x78877f, "rock"),
380 WaterTroll("The water troll", 0x2b2fc6, "water"),
381 OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
382 String starter, parser; int tint;
383 TrollRace(String s, int t, String p) {
388 String nonStarter() {
389 return Character.toLowerCase(starter.charAt(0))
390 + starter.substring(1);
395 private void drawTroll() {
396 int r, league = gameManager.getLeagueLevel();
397 if (league <= 1) r = 4;
398 else if (league <= 2) r = 8;
401 r = random.nextInt(r);
402 if (r < 4) trollRace = TrollRace.Troll;
403 else if (r < 6) trollRace = TrollRace.IceTroll;
404 else if (r < 8) trollRace = TrollRace.RockTroll;
405 else if (r < 9) trollRace = TrollRace.WaterTroll;
406 else if (r < 10) trollRace = TrollRace.OlogHai;
407 else throw new RuntimeException("Internal error: unknown troll race " + r);
409 // We read it for debugging purposes, but don't echo it back
410 // to the IDE. It is, after all, *not* a map parameter!
411 String buf = gameManager.getGameParameters().getProperty("ehtnicity");
414 for (char c : buf.toCharArray())
415 if (Character.isLetter(c))
416 key += Character.toLowerCase(c);
418 for (TrollRace race : TrollRace.values()) {
419 if (key.equals(race.parser)) {
421 break/*ing news: */ iHateJava;
424 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
427 photoFinish: ; // The race is through, but Java has no goto :-(
429 Sprite trollBody = graphicEntityModule.createSprite()
430 .setImage("troll_body.png")
433 .setTint(trollRace.tint);
434 Sprite trollPantsRed = graphicEntityModule.createSprite()
435 .setImage("pants_red.png")
438 pantsModule.displayOnPantsState(trollPantsRed, 1);
439 Sprite trollPantsGreen = graphicEntityModule.createSprite()
440 .setImage("pants_green.png")
443 pantsModule.displayOnPantsState(trollPantsGreen, 2);
444 Sprite trollPantsBlue = graphicEntityModule.createSprite()
445 .setImage("pants_blue.png")
448 pantsModule.displayOnPantsState(trollPantsBlue, 3);
449 Sprite trollPantsPerv = graphicEntityModule.createSprite()
450 .setImage("pants_perv.png")
453 pantsModule.displayOnPantsState(trollPantsPerv, 4);
454 troll = graphicEntityModule
455 .createGroup(trollBody, trollPantsRed,
456 trollPantsGreen, trollPantsBlue, trollPantsPerv)
459 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
461 trollPositionGauge = graphicEntityModule.createText()
467 .setFillColor(0xffffff);
470 trollMessage = graphicEntityModule.createText()
476 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
477 .setStrokeColor(0xFFFF00)
478 .setFillColor(0xFFFF00)
480 pantsModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
483 private void moveTroll() {
484 graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
485 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
486 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
487 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
489 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
492 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
493 int distLeft = model.trollPosition;
494 int distRight = model.roadLength - model.trollPosition;
496 trollPositionGauge.setText("← " + distRight);
498 else if (distRight <= 0) {
499 trollPositionGauge.setText(distLeft + " →");
502 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
504 graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
505 trollPositionGauge.setX(troll.getX());
508 void moveTroll(Dir d) {
510 gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
512 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
513 graphicEntityModule.commitEntityState(0.5, trollMessage);
514 trollMessage.setAlpha(0, Curve.EASE_IN);
515 graphicEntityModule.commitEntityState(1, trollMessage);
518 void animateTurnCounter() {
519 for (int i = 0; i < 10; i++) {
520 turnCounter.setText("T" + _turns + "." + i);
521 // The following line is likely not a bug.
522 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
528 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
529 .setSourceImage("debug.png")
538 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
539 .setImages(debugModePngs)
544 pantsModule.displayOnToggleState(debugMode, "debug", true);
546 turnCounter = graphicEntityModule.createText()
551 .setStrokeColor(0xff0080)
552 .setFillColor(0xff0080)
553 .setFontFamily("monospace")
554 .setFontWeight(Text.FontWeight.BOLD)
556 pantsModule.displayOnToggleState(turnCounter, "debug", true);
557 animateTurnCounter();
560 void animateLoss(int x, int y, int size, String message) {
562 if (x < 1920/2) { startX = 1920; }
563 else if (x > 1920/2) { startX = 1920; }
564 else { startX = 1920 * random.nextInt(2); }
566 Text msg = graphicEntityModule.createText(message)
571 .setScaleX(3*random.nextDouble() - 1)
572 .setScaleY(3*random.nextDouble() - 1)
573 .setSkewX(2*random.nextDouble() - 1)
574 .setSkewY(2*random.nextDouble() - 1)
575 .setRotation(4*Math.PI * (1 + random.nextDouble())
576 * (random.nextInt(2) == 0 ? 1 : -1))
578 .setStrokeColor(0xff7f7f)
579 .setFillColor(0xff7f7f)
580 .setFontWeight(Text.FontWeight.BOLD)
581 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
582 graphicEntityModule.commitEntityState(0.25, msg);
583 Curve curve = Curve.ELASTIC;
584 msg.setX(x, Curve.EASE_OUT)
585 .setY(y, Curve.ELASTIC)
590 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
591 .setFontSize(size, curve);
594 void doubleDefeat() {
595 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
596 animateLoss(1920/2, 680, 150, "L0SERZ!");
600 gameManager.addToGameSummary("Draw.");
601 animateLoss(1920/2, 680, 200, "DRAW");
604 String selectTrollMessage(Dir d) {
605 if (random.nextInt(10000) == 0) {
606 return specials[random.nextInt(specials.length)];
610 int i = random.nextInt(directed.length + isotropic.length / 3);
611 if (i < directed.length) {
612 return directed[i][d.index];
615 return isotropic[random.nextInt(isotropic.length)];
619 // You'll never remember if you ever saw these…
620 String specials[] = {
621 "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",
622 "CG know what they're doing."
625 // Most of what ought to happen in normal play
626 String directed[][] = {
628 { "Han shot first", "I am your father", "Greedo shot first" },
629 { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
630 { "star wars > star trek", "my god, it's full of troll", "star trek > star wars" },
631 // More movie controversies sought. Apply on the puzzle contrib page.
634 { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
635 { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
638 { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
639 { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
640 { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
641 { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
642 { "santa claus is really\nthe tooth fairy", "the easter bunny tasted yummy", "the tooth fairy is\nreally santa claus" },
643 // Ditto. Need moar troll.
646 { "PC > console", "pong is still\nunequaled", "console > PC" },
647 { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
648 { "pad > stick", "mouse gaming is lame", "stick > pad" },
649 { "RTS > FPS", "solitaire best game", "FPS > RTS" },
650 { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
651 { "orcs are wusses", "the amulet is in another dungeon", "elves are wusses" },
652 { "here's a link to my patreon", "my apm > yours", "here's my soundcloud" },
653 { "all your stones is belong to us", "all your castle are belong to us", "all your rocks is belong to us" },
654 // I'm not exactly a gamer myself, I take hints on the topics du jour
657 { "vi < emacs", "i code with Notepad", "emacs > vi" },
658 { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
659 { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
660 { "static linking best", "symlinking best", "dynamic linking best" },
661 { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
662 { "objects > functions", "it's closures\nall the way down", "functions > objects" },
663 { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
664 { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
665 // This category's not too bad.
668 { "gmail > github", "copy-paste FTW", "github > gmail" },
669 { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
670 { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
671 { "jira > trello", "bugzilla FTW", "trello > jira" },
672 { "IRC > slack", "chat is work", "discord < IRC" },
673 { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
674 { "there's an app for that", "there's a bean for that", "there's an applet for that" },
675 // More always welcome here.
678 { "my nn is in python", "my language has -O3", "my code is more than 100k" },
679 { "i found a bug\nin temperatures", "i found a bug on\nthe leaderboard", "i found a bug\nin chuck norris" },
680 { "fix it", "how is ur csb", "ezpz" },
681 { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
682 { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
683 { "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" },
684 { "light theme best", "ascii > graphics", "dark theme best" },
685 { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
686 { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
687 // And here. Especially as I'm not that active on #World or #Ru.
690 // Those for which I couldn't find a meaningful directednessability.
691 String isotropic[] = {
693 "Electron apps are the fastest",
695 "Thanos did nothing wrong",
696 "developers developers developers",
697 "the cloud is just\nother ppl's computers",
701 "ASCII stupid question\nget a stupid ANSI",
703 // I try and avoid those, but if really it fits nowhere else…