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("Troll 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 the troll.", 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);
351 private void drawTroll() {
352 Sprite trollBody = graphicEntityModule.createSprite()
353 .setImage("troll_body.png")
357 Sprite trollPants = graphicEntityModule.createSprite()
358 .setImage("pants_red.png")
361 troll = graphicEntityModule.createGroup(trollBody, trollPants)
364 .setScaleX(random.nextInt(2) == 0 ? 1 : -1)
366 trollPositionGauge = graphicEntityModule.createText()
372 .setFillColor(0xffffff);
375 trollMessage = graphicEntityModule.createText()
380 .setTextAlign(TextBasedEntity.TextAlign.CENTER)
381 .setStrokeColor(0xFFFF00)
382 .setFillColor(0xFFFF00)
384 toggleModule.displayOnToggleState(trollMessage, "verboseTrolling", true);
387 private void moveTroll() {
388 graphicEntityModule.commitEntityState(0.5, troll, trollPositionGauge);
389 int x0 = p0.castle.getX(), x1 = p1.castle.getX();
390 int y0 = p0.castle.getY(), y1 = p1.castle.getY();
391 troll.setX(x0 + model.trollPosition * (x1-x0) / model.roadLength,
393 troll.setY(y0 + model.trollPosition * (y1-y0) / model.roadLength,
396 trollPositionGauge.setX((trollPositionGauge.getX() + troll.getX()) / 2);
397 int distLeft = model.trollPosition;
398 int distRight = model.roadLength - model.trollPosition;
400 trollPositionGauge.setText("← " + distRight);
402 else if (distRight <= 0) {
403 trollPositionGauge.setText(distLeft + " →");
406 trollPositionGauge.setText(distLeft + " ↔ " + distRight);
408 graphicEntityModule.commitEntityState(0.75, trollPositionGauge);
409 trollPositionGauge.setX(troll.getX());
412 void moveTroll(Dir d) {
414 gameManager.addToGameSummary("Troll " + d.movement);
416 trollMessage.setText(selectTrollMessage(d)).setAlpha(1, Curve.NONE);
417 graphicEntityModule.commitEntityState(0.5, trollMessage);
418 trollMessage.setAlpha(0, Curve.EASE_IN);
419 graphicEntityModule.commitEntityState(1, trollMessage);
422 void animateTurnCounter() {
423 for (int i = 0; i < 10; i++) {
424 turnCounter.setText("T" + _turns + "." + i);
425 // The following line is likely not a bug.
426 graphicEntityModule.commitEntityState((double) i/9, turnCounter);
432 String[] debugModePngs = graphicEntityModule.createSpriteSheetSplitter()
433 .setSourceImage("debug.png")
442 SpriteAnimation debugMode = graphicEntityModule.createSpriteAnimation()
443 .setImages(debugModePngs)
448 toggleModule.displayOnToggleState(debugMode, "debug", true);
450 turnCounter = graphicEntityModule.createText()
455 .setStrokeColor(0xff0080)
456 .setFillColor(0xff0080)
457 .setFontFamily("monospace")
458 .setFontWeight(Text.FontWeight.BOLD)
460 toggleModule.displayOnToggleState(turnCounter, "debug", true);
461 animateTurnCounter();
464 void animateLoss(int x, int y, int size, String message) {
466 if (x < 1920/2) { startX = 1920; }
467 else if (x > 1920/2) { startX = 1920; }
468 else { startX = 1920 * random.nextInt(2); }
470 Text msg = graphicEntityModule.createText(message)
475 .setScaleX(3*random.nextDouble() - 1)
476 .setScaleY(3*random.nextDouble() - 1)
477 .setSkewX(2*random.nextDouble() - 1)
478 .setSkewY(2*random.nextDouble() - 1)
479 .setRotation(4*Math.PI * (1 + random.nextDouble())
480 * (random.nextInt(2) == 0 ? 1 : -1))
482 .setStrokeColor(0xff7f7f)
483 .setFillColor(0xff7f7f)
484 .setFontWeight(Text.FontWeight.BOLD)
485 .setTextAlign(TextBasedEntity.TextAlign.CENTER);
486 graphicEntityModule.commitEntityState(0.25, msg);
487 Curve curve = Curve.ELASTIC;
488 msg.setX(x, Curve.EASE_OUT)
489 .setY(y, Curve.ELASTIC)
494 .setRotation(2*Math.PI * (random.nextDouble() - 0.5), Curve.LINEAR)
495 .setFontSize(size, curve);
498 void doubleDefeat() {
499 gameManager.addToGameSummary(GameManager.formatErrorMessage("Everybody loses!"));
500 animateLoss(1920/2, 680, 150, "L0SERZ!");
504 gameManager.addToGameSummary("Draw.");
505 animateLoss(1920/2, 680, 200, "DRAW");
508 String selectTrollMessage(Dir d) {
509 if (random.nextInt(10000) == 0) {
510 return specials[random.nextInt(specials.length)];
514 int i = random.nextInt(directed.length + isotropic.length / 3);
515 if (i < directed.length) {
516 return directed[i][d.index];
519 return isotropic[random.nextInt(isotropic.length)];
523 // You'll never remember if you ever saw these…
524 String specials[] = {
525 "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",
526 "CG know what they're doing."
529 // Most of what ought to happen in normal play
530 String directed[][] = {
532 { "Han shot first", "I am your father", "Greedo shot first" },
533 { "Inception ends\non level zero", "BRAAAAAAM", "Inception ends\non level one" },
534 // More movie controversies sought. Apply on the puzzle contrib page.
537 { "bach > beethoven", "zimmer is overrated", "beethoven > bach" },
538 { "an octave is 12 semitones", "curse you perfect-pitched ppl", "pianos can play in tune" },
541 { "tea > coffee", "just drink\nkool-aid", "coffee > tea" },
542 { "Marvel > DC", "Disney > 50 shades", "DC > Marvel" },
543 { "cats > dogs", "humans make\ngood pets", "dogs > cats" },
544 { "the moon landing was staged", "elvis lives", "9/11 was an inside job" },
545 // Ditto. Need moar troll.
548 { "PC > console", "pong is still\nunequaled", "console > PC" },
549 { "Windows > Linux", "it's all Android anyway", "Linux > Windows" },
550 { "pad > stick", "mouse gaming is lame", "stick > pad" },
551 { "RTS > FPS", "solitaire best game", "FPS > RTS" },
552 { "YT gaming > twitch", "i watch other ppl play", "twitch > YT gaming" },
553 // I'm not exactly a gamer myself, I take hints on the topics du jour
556 { "vi < emacs", "i code with Notepad", "emacs > vi" },
557 { "tabs < spaces", "gofmt FTW", "spaces < tabs" },
558 { "LISP is the most\npowerful language", "HTML is a\nprogramming language", "FORTH is the most\npowerful language" },
559 { "static linking best", "symlinking best", "dynamic linking best" },
560 { "NPE > SIGSEGV", "kernel panic", "SIGSEGV > NPE" },
561 { "objects > functions", "it's closures\nall the way down", "functions > objects" },
562 { "GOTO FTW", "COME FROM FTW", "don't use GOTO" },
563 { "Agile > Waterfall", "SCRUM isn't Agile", "Waterfall > Agile" },
564 // This category's not too bad.
567 { "gmail > github", "copy-paste FTW", "github > gmail" },
568 { "MSIE > Safari", "Opera did it first", "Safari > MSIE" },
569 { "bing > yahoo", "duckduckgo best SE", "yahoo > bing" },
570 { "jira > trello", "bugzilla FTW", "trello > jira" },
571 { "IRC > slack", "chat is work", "discord < IRC" },
572 { "trolls > SJW", "i'm not trolling\njust misunderstood", "SJW > trolls" },
573 { "there's an app for that", "there's a bean for that", "there's an applet for that" },
574 // More always welcome here.
577 { "searcho no chokudai", "GAimax is True AI", "Smitsimax FTW" },
578 { "Automaton2000 > NN", "bots > humans", "AutomatonNN > 2000" },
579 { "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" },
580 { "light theme best", "ascii > graphics", "dark theme best" },
581 { "simulation > heuristics", "true AI is just ifs", "heuristics > simulation" },
582 { "bruteforce FTW", "you'll timeout anyway", "algorithms FTW" }
583 // And here. Especially as I'm not that active on #World or #Ru.
586 // Those for which I couldn't find a meaningful directednessability.
587 String isotropic[] = {
588 "Electron apps are the fastest",
590 "Thanos did nothing wrong",
591 "developers developers developers",
592 "the cloud is just\nother ppl's computers",
597 "ASCII stupid question\nget a stupid ANSI",
599 // I try and avoid those, but if really it fits nowhere else…