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.codingame.gameengine.module.toggle.ToggleModule;
18 import com.google.inject.Inject;
21 @Inject private MultiplayerGameManager<com.codingame.game.Player> gameManager;
22 @Inject private GraphicEntityModule graphicEntityModule;
23 @Inject ToggleModule toggleModule;
26 LEFT("walks left.", 0),
27 STILL("stands still.", 1),
28 RIGHT("walks right.", 2);
30 String movement; int index;
31 Dir(String mvt, int i) { movement = mvt; index = i; }
50 void init(com.codingame.game.Player p) {
52 colorToken = p.getColorToken();
53 nicknameToken = p.getNicknameToken();
54 avatarToken = p.getAvatarToken();
56 boolean p0 = model.index == 0;
57 int x = p0 ? 280 : 1920 - 280;
60 Sprite frame = graphicEntityModule.createSprite()
61 .setImage("frame.png")
63 .setRotation(frameRot)
67 Sprite frameBg = graphicEntityModule.createSprite()
68 .setImage("frame_bg.png")
70 .setRotation(frameRot)
73 Sprite avatarSprite = graphicEntityModule.createSprite()
75 .setImage(avatarToken)
80 avatar = graphicEntityModule
81 .createGroup(frame, frameBg, avatarSprite)
84 Text text = graphicEntityModule.createText(nicknameToken)
89 .setFillColor(0x7f3f00)
92 stoneCounter = graphicEntityModule.createText()
97 .setFillColor(0x7f3f00)
101 message = graphicEntityModule.createText()
102 .setX(p0 ? 15 : 1920-15)
106 .setFillColor(0xffbf7f)
107 .setAnchorX(p0 ? 0 : 1)
110 castle = graphicEntityModule.createSprite()
111 .setImage("castle.png")
113 .setX(p0 ? 160 : 1920-160)
114 .setY(p0 ? 890 : 880)
118 .setScaleX(p0 ? 1 : -1);
120 stone = graphicEntityModule.createText()
123 .setFillColor(0x12322a)
127 stoneReminder = graphicEntityModule.createText()
128 .setX(p0 ? x + 100 : x - 100)
132 .setFontFamily("monospace")
133 .setStrokeColor(0xff0080)
134 .setFillColor(0xff0080)
135 .setAnchorX(p0 ? 0 : 1)
137 toggleModule.displayOnToggleState(stoneReminder, "debug", true);
140 void updateStoneCounter() {
141 int stones = model.getStones();
143 stoneCounter.setText("Out of stones!");
144 stoneCounter.setFillColor(0xff7777);
146 else if (stones == 1) {
147 stoneCounter.setText("1 stone");
148 stoneCounter.setFillColor(0xffbb77);
151 stoneCounter.setText(stones + " stones");
155 void animateStones(int stones) {
156 String stonesString = Integer.valueOf(stones).toString();
157 stone.setX(castle.getX());
158 stone.setY(castle.getY() - 100);
159 stone.setText(stonesString);
161 graphicEntityModule.commitEntityState(0, stone);
163 int peakX = (castle.getX() + troll.getX()) / 2;
166 stone.setY(peakY, Curve.EASE_OUT);
167 graphicEntityModule.commitEntityState(0.25,
171 stone.setX(troll.getX());
172 stone.setY(troll.getY() - 50, Curve.EASE_IN);
173 stone.setAlpha(0, Curve.EASE_IN);
174 graphicEntityModule.commitEntityState(0.5, stone);
176 stoneReminder.setText(stonesString);
177 graphicEntityModule.commitEntityState(0, stoneReminder);
180 void displayMessage(String msg) {
181 message.setText(msg);
182 graphicEntityModule.commitEntityState(0, message);
186 graphicEntityModule.commitEntityState(0.5, avatar);
187 int dir = random.nextInt(2) == 1 ? 1 : -1;
188 avatar.setRotation(dir * 170 * Math.PI / 180, Curve.ELASTIC);
192 gameManager.addToGameSummary(GameManager.formatErrorMessage(trollRace.starter + " destroys " + nicknameToken + "."));
193 graphicEntityModule.commitEntityState(0.5, castle);
194 castle.setX(castle.getX(), Curve.ELASTIC);
195 castle.setScaleY(-0.2, Curve.EASE_IN);
199 graphicEntityModule.commitEntityState(0, stoneReminder);
203 gameManager.addToGameSummary(GameManager.formatSuccessMessage(nicknameToken + " wins."));
204 graphicEntityModule.commitEntityState(0.5, avatar);
205 avatar.setScaleX(1.5, Curve.EASE_OUT);
206 avatar.setScaleY(1.5, Curve.EASE_OUT);
207 avatar.setRotation((random.nextDouble() - 0.5) * Math.PI / 18,
211 void throwStones(int stones) {
212 gameManager.addToGameSummary(String.format("%s throws %d stone%s at " + trollRace.nonStarter(), nicknameToken, stones, stones == 1 ? "" : "s"));
215 void threwMoreStonesThanHad() {
216 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!)"));
219 void failedToThrowStonesAndShouldHave() {
220 gameManager.addToGameSummary(GameManager.formatErrorMessage(nicknameToken + " tried not throwing any stones. Fixing that for them because I'm in a good mood today."));
224 animateLoss(avatar.getX(), avatar.getY(), 100, "SLOW\nPOKE");
228 animateLoss(avatar.getX(), avatar.getY(), 100, "STUPID");
232 animateLoss(avatar.getX(), avatar.getY(), 100, "CHEATER");
237 Random random = new Random();
240 Text trollPositionGauge;
241 Player p0 = new Player(), p1 = new Player();
242 Text turnCounter; int _turns = 0;
249 * Random π/2-grained rotation of the avatar frames. Avoid
250 * having them π/2 apart, though, as one of them is likely
251 * going to end upside-down and the trick would be revealed.
252 * And I'd have to "draw" a new frame. Ewww.
254 p0.frameRot = random.nextInt(4) * Math.PI / 2;
255 p0.init(gameManager.getPlayer(0));
256 p1.frameRot = p1.frameRot +
257 (random.nextInt(2) == 1 ? 1 : -1) * Math.PI / 2;
258 p1.init(gameManager.getPlayer(1));
269 trollMessage.setX(troll.getX());
271 animateTurnCounter();
276 Pos(int _x, int _y) { x = _x; y = _y; }
279 private void drawBackground() {
280 graphicEntityModule.createSprite()
281 .setImage("background.png")
284 int numMountains = random.nextInt(5);
285 while (numMountains --> 0) {
286 final int pngWidth = 366;
287 double scale = 0.5 * (1 + random.nextDouble());
288 int x = random.nextInt(1920 + (int) (scale*pngWidth))
289 - (int) (scale*pngWidth/2);
290 int baseTint = 64 + random.nextInt(128);
291 Sprite mountain = graphicEntityModule.createSprite()
292 .setImage("mountain.png")
296 .setAnchorY(283.0 / 321.0)
297 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
298 .setScaleX(random.nextInt(2) == 0 ? scale : -scale)
299 .setScaleY(scale * (1 + (random.nextDouble() - 0.5) / 2))
300 .setSkewX((random.nextDouble() - 0.5) / 4)
301 .setSkewY((random.nextDouble() - 0.5) / 8)
302 .setTint((baseTint + random.nextInt(16) - 8) * 0x010000
303 + (baseTint + random.nextInt(16) - 8) * 0x0100
304 + (baseTint + random.nextInt(16) - 8) * 0x01);
305 graphicEntityModule.createSprite().setImage("mountaintop.png")
306 .setX(mountain.getX())
307 .setY(mountain.getY())
308 .setAnchorX(mountain.getAnchorX())
309 .setAnchorY(mountain.getAnchorY())
310 .setRotation(mountain.getRotation())
311 .setScaleX(mountain.getScaleX())
312 .setScaleY(mountain.getScaleY())
313 .setSkewX(mountain.getSkewX())
314 .setSkewY(mountain.getSkewY());
317 int numTrees = random.nextInt(21);
318 ArrayList<Pos> poss = new ArrayList<Pos>(numTrees);
319 while (numTrees --> 0) {
322 x = random.nextInt(1920);
323 // yes, this biases randomness wrt perspective! :-(
324 y = 700 + random.nextInt(175);
325 } while (y > 880 && (x < 200 || x > 1720));
326 poss.add(new Pos(x, y));
328 poss.sort(new Comparator<Pos>() {
329 public int compare(Pos a, Pos b) { return a.y < b.y ? -1 : 1; }
333 double scale = ( 90.0 / 433.0 // base height from PNG
334 * (p.y - 680) / (875 - 680) ); // perspective
335 graphicEntityModule.createSprite()
336 .setImage(random.nextInt(2) == 0 ? "Alshockv1.png"
342 .setScaleX(scale * (random.nextInt(2) == 0 ? -1 : 1)
343 * (1 + (random.nextDouble() - 0.5) / 6))
344 .setScaleY(scale * (1 + (random.nextDouble() -0.5) / 6))
345 .setRotation((random.nextDouble() - 0.5) * Math.PI / 1800)
346 .setSkewX((random.nextDouble() - 0.5) /4)
347 .setSkewY((random.nextDouble() - 0.5) /8);
352 Troll("The troll", 0xfac200, "bland"),
353 IceTroll("The ice troll", 0x59a2a2, "ice"),
354 RockTroll("The rock troll", 0x78877f, "rock"),
355 WaterTroll("The water troll", 0x2b2fc6, "water"),
356 OlogHai("The Olog-Hai", 0x5b2e7d, "ologhai");
357 String starter, parser; int tint;
358 TrollRace(String s, int t, String p) {
363 String nonStarter() {
364 return Character.toLowerCase(starter.charAt(0))
365 + starter.substring(1);
370 private void drawTroll() {
371 int r, league = gameManager.getLeagueLevel();
372 if (league <= 1) r = 4;
373 else if (league <= 2) r = 8;
376 r = random.nextInt(r);
377 if (r < 4) trollRace = TrollRace.Troll;
378 else if (r < 6) trollRace = TrollRace.IceTroll;
379 else if (r < 8) trollRace = TrollRace.RockTroll;
380 else if (r < 9) trollRace = TrollRace.WaterTroll;
381 else if (r < 10) trollRace = TrollRace.OlogHai;
382 else throw new RuntimeException("Internal error: unknown troll race " + r);
384 // We read it for debugging purposes, but don't echo it back
385 // to the IDE. It is, after all, *not* a map parameter!
386 String buf = gameManager.getGameParameters().getProperty("ehtnicity");
389 for (char c : buf.toCharArray())
390 if (Character.isLetter(c))
391 key += Character.toLowerCase(c);
393 for (TrollRace race : TrollRace.values()) {
394 if (key.equals(race.parser)) {
396 break/*ing news: */ iHateJava;
399 gameManager.addToGameSummary("Ignoring unknown troll race: " + buf);
402 photoFinish: ; // The race is through, but Java has no goto :-(
404 Sprite trollBody = graphicEntityModule.createSprite()
405 .setImage("troll_body.png")
408 .setTint(trollRace.tint);
409 Sprite trollPants = graphicEntityModule.createSprite()
410 .setImage("pants_red.png")
413 troll = graphicEntityModule.createGroup(trollBody, trollPants)
416 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
418 trollPositionGauge = graphicEntityModule.createText()
424 .setFillColor(0xffffff);
427 trollMessage = graphicEntityModule.createText()
432 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
433 .setStrokeColor(0xFFFF00)
434 .setFillColor(0xFFFF00)
436 toggleModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
439 private void moveTroll() {
440 graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
441 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
442 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
443 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
445 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
448 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
449 int distLeft = model.trollPosition;
450 int distRight = model.roadLength - model.trollPosition;
452 trollPositionGauge.setText("← " + distRight);
454 else if (distRight <= 0) {
455 trollPositionGauge.setText(distLeft + " →");
458 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
460 graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
461 trollPositionGauge.setX(troll.getX());
464 void moveTroll(Dir d) {
466 gameManager.addToGameSummary(trollRace.starter + " " + d.movement);
468 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
469 graphicEntityModule.commitEntityState(0.5, trollMessage);
470 trollMessage.setAlpha(0, Curve.EASE_IN);
471 graphicEntityModule.commitEntityState(1, trollMessage);
474 void animateTurnCounter() {
475 for (int i = 0; i < 10; i++) {
476 turnCounter.setText("T" + _turns + "." + i);
477 // The following line is likely not a bug.
478 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
484 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
485 .setSourceImage("debug.png")
494 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
495 .setImages(debugModePngs)
500 toggleModule.displayOnToggleState(debugMode, "debug", true);
502 turnCounter = graphicEntityModule.createText()
507 .setStrokeColor(0xff0080)
508 .setFillColor(0xff0080)
509 .setFontFamily("monospace")
510 .setFontWeight(Text.FontWeight.BOLD)
512 toggleModule.displayOnToggleState(turnCounter, "debug", true);
513 animateTurnCounter();
516 void animateLoss(int x, int y, int size, String message) {
518 if (x < 1920/2) { startX = 1920; }
519 else if (x > 1920/2) { startX = 1920; }
520 else { startX = 1920 * random.nextInt(2); }
522 Text msg = graphicEntityModule.createText(message)
527 .setScaleX(3*random.nextDouble() - 1)
528 .setScaleY(3*random.nextDouble() - 1)
529 .setSkewX(2*random.nextDouble() - 1)
530 .setSkewY(2*random.nextDouble() - 1)
531 .setRotation(4*Math.PI * (1 + random.nextDouble())
532 * (random.nextInt(2) == 0 ? 1 : -1))
534 .setStrokeColor(0xff7f7f)
535 .setFillColor(0xff7f7f)
536 .setFontWeight(Text.FontWeight.BOLD)
537 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
538 graphicEntityModule.commitEntityState(0.25, msg);
539 Curve curve = Curve.ELASTIC;
540 msg.setX(x, Curve.EASE_OUT)
541 .setY(y, Curve.ELASTIC)
546 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
547 .setFontSize(size, curve);
550 void doubleDefeat() {
551 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
552 animateLoss(1920/2, 680, 150, "L0SERZ!");
556 gameManager.addToGameSummary("Draw.");
557 animateLoss(1920/2, 680, 200, "DRAW");
560 String selectTrollMessage(Dir d) {
561 if (random.nextInt(10000) == 0) {
562 return specials[random.nextInt(specials.length)];
566 int i = random.nextInt(directed.length + isotropic.length / 3);
567 if (i < directed.length) {
568 return directed[i][d.index];
571 return isotropic[random.nextInt(isotropic.length)];
575 // You'll never remember if you ever saw these…
576 String specials[] = {
577 "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",
578 "CG know what they're doing."
581 // Most of what ought to happen in normal play
582 String directed[][] = {
584 { "Han shot first", "I am your father", "Greedo shot first" },
585 { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
586 { "star wars > star trek", "my god, it's full of troll", "star trek > star wars" },
587 // More movie controversies sought. Apply on the puzzle contrib page.
590 { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
591 { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
594 { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
595 { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
596 { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
597 { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
598 { "santa claus is really\nthe tooth fairy", "the easter bunny tasted yummy", "the tooth fairy is\nreally santa claus" },
599 // Ditto. Need moar troll.
602 { "PC > console", "pong is still\nunequaled", "console > PC" },
603 { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
604 { "pad > stick", "mouse gaming is lame", "stick > pad" },
605 { "RTS > FPS", "solitaire best game", "FPS > RTS" },
606 { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
607 { "orcs are wusses", "the amulet is in another dungeon", "elves are wusses" },
608 { "here's a link to my patreon", "my apm > yours", "here's my soundcloud" },
609 { "all your stones is belong to us", "all your castle are belong to us", "all your rocks is belong to us" },
610 // I'm not exactly a gamer myself, I take hints on the topics du jour
613 { "vi < emacs", "i code with Notepad", "emacs > vi" },
614 { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
615 { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
616 { "static linking best", "symlinking best", "dynamic linking best" },
617 { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
618 { "objects > functions", "it's closures\nall the way down", "functions > objects" },
619 { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
620 { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
621 // This category's not too bad.
624 { "gmail > github", "copy-paste FTW", "github > gmail" },
625 { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
626 { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
627 { "jira > trello", "bugzilla FTW", "trello > jira" },
628 { "IRC > slack", "chat is work", "discord < IRC" },
629 { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
630 { "there's an app for that", "there's a bean for that", "there's an applet for that" },
631 // More always welcome here.
634 { "my nn is in python", "my language has -O3", "my code is more than 100k" },
635 { "i found a bug\nin temperatures", "i found a bug on\nthe leaderboard", "i found a bug\nin chuck norris" },
636 { "fix it", "how is ur csb", "ezpz" },
637 { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
638 { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
639 { "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" },
640 { "light theme best", "ascii > graphics", "dark theme best" },
641 { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
642 { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
643 // And here. Especially as I'm not that active on #World or #Ru.
646 // Those for which I couldn't find a meaningful directednessability.
647 String isotropic[] = {
649 "Electron apps are the fastest",
651 "Thanos did nothing wrong",
652 "developers developers developers",
653 "the cloud is just\nother ppl's computers",
657 "ASCII stupid question\nget a stupid ANSI",
659 // I try and avoid those, but if really it fits nowhere else…