├── .gitignore ├── README.md ├── config ├── Boss.py ├── config.ini ├── level1 │ └── Boss.py ├── level2 │ ├── Boss.py │ ├── Tuto_Oeuf.jpg │ ├── welcome_en.html │ └── welcome_fr.html ├── level3 │ ├── Tuto_Fight.jpg │ ├── welcome_en.html │ └── welcome_fr.html ├── level4 │ ├── Boss.py │ ├── stub.txt │ ├── welcome_en.html │ └── welcome_fr.html ├── statement_en.html.tpl ├── statement_fr.html.tpl └── stub.txt ├── full_rules.pdf ├── full_rules_fr.pdf ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── codingame │ │ │ ├── event │ │ │ ├── Animation.java │ │ │ ├── AnimationData.java │ │ │ └── EventData.java │ │ │ ├── game │ │ │ ├── AntConsumption.java │ │ │ ├── Board.java │ │ │ ├── BoardGenerator.java │ │ │ ├── Cell.java │ │ │ ├── CellType.java │ │ │ ├── CommandManager.java │ │ │ ├── Config.java │ │ │ ├── CubeCoord.java │ │ │ ├── Game.java │ │ │ ├── GameSummaryManager.java │ │ │ ├── Player.java │ │ │ ├── Referee.java │ │ │ ├── action │ │ │ │ ├── Action.java │ │ │ │ ├── ActionException.java │ │ │ │ ├── ActionType.java │ │ │ │ └── actions │ │ │ │ │ ├── BeaconAction.java │ │ │ │ │ ├── LineAction.java │ │ │ │ │ ├── MessageAction.java │ │ │ │ │ └── WaitAction.java │ │ │ ├── exception │ │ │ │ ├── GameException.java │ │ │ │ └── InvalidInputException.java │ │ │ └── move │ │ │ │ ├── AntAllocater.java │ │ │ │ ├── AntAllocation.java │ │ │ │ ├── AntDecision.java │ │ │ │ └── AntMove.java │ │ │ └── view │ │ │ ├── CellData.java │ │ │ ├── FrameViewData.java │ │ │ ├── GameDataProvider.java │ │ │ ├── GlobalViewData.java │ │ │ ├── Serializer.java │ │ │ └── ViewModule.java │ └── resources │ │ └── view │ │ ├── .eslintrc.js │ │ ├── assets │ │ ├── Background.jpg │ │ ├── logo.png │ │ ├── particle.png │ │ ├── spritesheet.json │ │ └── spritesheet.png │ │ ├── config.js │ │ ├── demo.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── ts │ │ ├── Deserializer.ts │ │ ├── TooltipManager.ts │ │ ├── ViewModule.ts │ │ ├── assetConstants.ts │ │ ├── events.ts │ │ ├── gameConstants.ts │ │ ├── global │ │ │ ├── core.d.ts │ │ │ └── global.d.ts │ │ ├── hex.ts │ │ ├── pathSegments.ts │ │ ├── types.ts │ │ └── utils.ts │ │ └── tsconfig.json └── test │ ├── java │ └── Spring2023Main.java │ └── resources │ └── log4j2.properties ├── starterAIs ├── README.md ├── Starter.cpp ├── Starter.cs ├── Starter.fs ├── Starter.java ├── Starter.js ├── Starter.php ├── Starter.py ├── Starter.rb └── Starter.ts └── typescript ├── polyfill.js ├── readline.js └── readline └── index.d.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.project 3 | /.classpath 4 | /bin/ 5 | .factorypath 6 | /.settings/ 7 | .pydevproject 8 | src/main/resources/view/node_modules/ 9 | src/main/resources/view/graphics/ 10 | statement_en.html 11 | statement_fr.html 12 | /generated-boards/ 13 | node_modules/ 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpringChallenge2023 2 | 3 | Source code for CodinGame's Spring Challenge 2023 event. 4 | 5 | https://www.codingame.com/contests/spring-challenge-2023 6 | 7 | Community starter AIs are located here: 8 | 9 | https://github.com/CodinGame/SpringChallenge2023/tree/main/starterAIs 10 | -------------------------------------------------------------------------------- /config/Boss.py: -------------------------------------------------------------------------------- 1 | while True: 2 | print('WAIT') 3 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | min_players=2 2 | max_players=2 3 | type=multi -------------------------------------------------------------------------------- /config/level1/Boss.py: -------------------------------------------------------------------------------- 1 | redacted 2 | -------------------------------------------------------------------------------- /config/level2/Boss.py: -------------------------------------------------------------------------------- 1 | redacted 2 | -------------------------------------------------------------------------------- /config/level2/Tuto_Oeuf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/config/level2/Tuto_Oeuf.jpg -------------------------------------------------------------------------------- /config/level2/welcome_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league.

3 | You now have the ability to create more ants by harvesting the EGG resource! 4 |
5 |
See the updated statement for details.
6 |
-------------------------------------------------------------------------------- /config/level2/welcome_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

Vous venez d'atteindre la ligue supérieure.

3 | Vous pouvez désormais créer plus de fourmis en récoltant la ressource OEUF ! 4 |
5 |
Consultez l'énoncé mis à jour pour plus de détails.
6 |
-------------------------------------------------------------------------------- /config/level3/Tuto_Fight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/config/level3/Tuto_Fight.jpg -------------------------------------------------------------------------------- /config/level3/welcome_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league.

3 | Opposing ants can now break each other's harvesting chains using attack chains. 4 |
5 | Larger maps are available and multiple bases may be present. 6 |
See the updated statement for details.
7 |
-------------------------------------------------------------------------------- /config/level3/welcome_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

Vous venez d'atteindre la ligue supérieure.

3 | Les fourmis adverses peuvent saboter les chaînes de récoltes grâce à des chaînes d'attaques. 4 |
5 | Des cartes plus grandes sont disponibles, et plusieurs bases peuvent s'y trouver. 6 |
Consultez l'énoncé mis à jour pour plus de détails.
7 |
-------------------------------------------------------------------------------- /config/level4/Boss.py: -------------------------------------------------------------------------------- 1 | while True: 2 | print('WAIT') -------------------------------------------------------------------------------- /config/level4/stub.txt: -------------------------------------------------------------------------------- 1 | read numberOfCells:int 2 | loop numberOfCells read type:int initialResources:int neigh0:int neigh1:int neigh2:int neigh3:int neigh4:int neigh5:int 3 | read numberOfBases:int 4 | loopline numberOfBases myBaseIndex:int 5 | loopline numberOfBases oppBaseIndex:int 6 | 7 | gameloop 8 | read myScore:int oppScore:int 9 | loop numberOfCells read resources:int myAnts:int oppAnts:int 10 | 11 | write WAIT 12 | 13 | INPUT 14 | neigh0: the index of the neighbouring cell for each direction 15 | type: 0 for empty, 1 for eggs, 2 for crystal 16 | numberOfCells: amount of hexagonal cells in this map 17 | numberOfHills: amount of bases for each player 18 | myHillIndex: a cell with a friendly base 19 | oppHillIndex: a cell with an enemy base 20 | initialResources: the initial amount of eggs/crystals on this cell 21 | resources: the current amount of eggs/crystals on this cell 22 | myAnts: the amount of your ants on this cell 23 | oppAnts: the amount of opponent ants on this cell 24 | 25 | OUTPUT 26 | WAIT | LINE | BEACON | MESSAGE 27 | -------------------------------------------------------------------------------- /config/level4/welcome_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league.

3 | The input protocol has changed. Both player's current scores are given every turn on the standard input. 4 |
Update your parsing code according to the new statement details.
5 |
-------------------------------------------------------------------------------- /config/level4/welcome_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

Vous venez d'atteindre la ligue supérieure.

3 | Le protocole est légèrement modifié. Les scores des deux joueurs sont maintenant données sur l'entrée standard. 4 |
Mettez à jour votre code selon les nouveaux détails dans l'énoncé.
5 |
-------------------------------------------------------------------------------- /config/statement_en.html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
13 |
14 | 15 |
16 |

17 | This is a league based challenge. 18 |

19 |
20 | For this challenge, multiple leagues for the same game are available. Once you have proven yourself 21 | against the 22 | first Boss, you will access a higher league and harder opponents will be available.
23 |
24 |
25 |
26 | 27 | 28 | 29 |
30 |

31 |   32 | Goal 33 |

34 | End the game with more crystal than your opponent. 35 |
36 | 38 |
39 | Crystal 40 |
41 |
42 |

The game takes place in a lab, in which two scientists in charge of robot ants are competing to 43 | find the most efficient way of gathering crystals.

44 | However, the ants cannot be controlled directly. The ants will respond to the presence of beacons. 45 | 46 |
47 | 48 | 49 | 50 | 51 |
52 |

53 |   54 | Rules 55 |

56 |
57 |
58 | The game is played in turns. On each turn, both players perform any number of actions simultaneously. 59 | 60 |

65 | The Map

66 | 67 |

On each run, the map is generated randomly and is made up of hexagonal cells.

68 | Each cell has an index and up to six neighbors. Each direction is labelled 0 to 69 | 5. 70 | 71 |
72 | 74 |
75 | Hex directions 76 |
77 |
78 |
79 | 80 | 81 | Each cell has a type, which indicates what the cell contains: 82 |
    83 |
  • 84 | 0 if it does not contain a resource. 85 |
  • 86 | 87 |
  • 88 | 1 will appear in later leagues and can be ignored for now. 89 |
  • 90 | 91 | 92 | 93 | 94 |
  • 95 | 96 | 97 |
  • 98 | 99 | 1 if it contains the egg resource. 100 | 101 | 102 |
  • 103 | 104 | 105 | 106 | 107 | 108 | 109 |
  • 110 | 2 if it contains the crystal resource. 111 |
  • 112 |
113 |

The amount of resources contained in each cell is also given, and is subject to change 114 | during the game as the ants harvest cells.

115 | 116 | A cell may also have a base on it. The players’ ants will start the game on these bases. 117 | 118 |
119 |
120 | 122 |
123 | Blue base 124 |
125 |
126 | 127 |
128 | 130 |
131 | Egg 132 |
133 |
134 | 135 |
136 | 137 |

142 | Ants & Beacons

143 | 144 |

Both players start with several ants placed on their 145 | 146 | base. 147 | 148 | 149 | bases. 150 | 151 | 152 | The players cannot move the ants 153 | directly but can place beacons to affect their movement. 154 |

155 | 156 |

Players can place any number of beacons per turn but can only place one each per cell. 157 |

158 | 159 |

When placing a beacon, players must give that beacon a strength. These beacon strengths 160 | act as weights, determining the proportion of ants that will be dispatched to each 161 | one.

162 | 163 |

In other words, the higher the beacon strength, the greater the percentage 164 | of your ants that will be sent to that beacon.

165 | 166 |

171 | Example

172 | 173 |

In the following example, there are three beacons of strength 174 | 2,1, and 2. 175 |

176 | 177 |
178 | 179 |
180 | 181 |
182 |
183 | White numbers in a colored diamond represent the ants. Here, 10 ants in 184 | total will be 185 | dispatched to the beacons. 186 |
187 |
188 |
189 |
190 | 191 |
192 |
193 | The 10 ants will move to the three beacons, keeping the same 194 | proportions 195 | as the beacon strengths. 196 |
197 |
198 |
199 |
200 | 201 |
202 |

The ants will do their best to take the shortest paths to their designated beacons, moving at 203 | a speed of one cell per turn.

204 | 205 | 209 | 210 |

In between turns, the existing beacons are powered down and removed from play.

211 | 212 |

213 | Use beacons to place your ants in such a way to create harvesting chains between your 214 | 215 | 216 | base 217 | 218 | 219 | bases 220 | 221 | 222 | and a resource. 223 |

224 | 225 |

230 | Harvesting Chains

231 | 232 |

In order to harvest crystal and score points, there must be an uninterrupted chain of 233 | cells containing your ants between the resource and your 234 | 235 | base. 236 | 237 | 238 | bases. 239 | 240 |

241 | 242 |

The amount of crystal harvested per turn is equal to the weakest link in the chain. In other 243 | words, it is the smallest amount of ants from the cells that make up the chain.

244 | 245 |
246 | 248 |
249 |
250 | Here, the blue player will harvest 4 crystal per turn. 251 |
252 |
253 | 254 | 255 | 256 | 257 | 258 |

In games with multiple bases per player, the game will choose the best chain to either one of your 259 | bases.

260 | 261 | 262 | 263 |

264 | 265 | 266 |

267 | 268 | 269 | The harvesting chains work the same way for the egg resource. 270 |

Harvesting an egg cell will spawn as many ants as resources havested. The ants will spawn on the 271 | player’s base on the start of next turn. 272 | 273 | 274 | 275 |

276 | 277 | In games with multiple bases per player, the extra ants will spawn on each base, regardless of the base 278 | present in the harvest chain.

279 | 280 |
281 | 282 | 283 | 284 |

285 | 286 | 287 | 288 |

289 | 290 | 291 | 292 | 293 |

Harvesting is calculated separately for each resource, and for each one the game will 294 | automatically choose the best chain from its cell to your base.

295 | 296 | 297 | 298 | 299 | 300 |
301 | 302 | 303 |

308 | Attack Chains

309 | 310 | 311 |

A player’s harvest chains may be broken by their opponent’s attack chains.

312 | 313 |

When computing harvest chains, some cells may have ants from both players. For each of these cells, the 314 | attack chain of both players is computed, and if one of the player has a lower value, this cell 315 | cannot be counted in the harvest chain. 316 |

317 | 318 | The attack chain value for a given cell is the weakest link in a chain from that cell to one of 319 | the player’s bases. 320 | 321 |

326 | Example

327 | 328 |
329 | 330 |
331 | 332 |
333 |
334 | The attack chains for the contested cell are: 5 for the red player and 3 335 | for the blue player.
The harvest chain is unbroken. 336 |
337 |
338 |
339 |
340 | 341 |
342 |
343 | The attack chains for the contested cell are: 5 for the red player and 8 344 | for the blue player.
The harvest chain is broken. 345 |
346 |
347 |
348 |
349 | 350 |
351 | 352 | 353 | 354 |

359 | Actions

360 |

361 | On each turn players can do any amount of valid actions, which include: 362 |

363 |
    364 |
  • 365 | BEACON index strength: place a beacon of strength 366 | strength on cell 367 | index. 368 |
  • 369 |
  • 370 | LINE index1 index2 strength: place beacons 371 | all 372 | along a path from index1 to 373 | index2, all of strength strength. A shortest path is chosen 374 | automatically. 375 |
  • 376 |
  • 377 | WAIT: do nothing. 378 |
  • 379 |
  • 380 | MESSAGE text. Displays text on your side of the HUD. 381 |
  • 382 |
383 | 384 |

389 | Action order for one turn

390 | 391 | 392 |
    393 |
  1. 394 | LINE actions are computed. 395 |
  2. 396 |
  3. 397 | BEACON actions are computed. 398 |
  4. 399 |
  5. 400 | Ants move. 401 |
  6. 402 | 403 | 404 | 405 |
  7. 406 | 407 | 408 |
  8. 409 | 410 | Eggs are harvested and new ants spawn. 411 | 412 |
  9. 413 | 414 | 415 | 416 | 417 | 418 |
  10. 419 | Crystal is harvested and points are scored. 420 |
  11. 421 |
422 |
423 | 424 | Note: when two players harvest from the same resource, they will both receive the full expected amount regardless of whether there is enough resource to support it. 425 | 426 |
427 | 428 | 429 |
430 |
431 |
432 |
Victory Conditions
433 |
434 |
    435 |
  • You have harvested at least half of the total crystal on the map before your opponent.
  • 436 |
  • You have more crystal than your opponent after 100 turns, or more ants if tied. 437 |
  • 438 |
439 |
440 |
441 |
442 | 443 |
444 |
445 |
446 |
Defeat Conditions
447 |
448 |
    449 | Your program does not provide a command in the allotted time or it provides an 450 | unrecognized command. 451 |
452 |
453 |
454 |
455 | 456 |
457 | 458 |
459 |
460 |

465 | 🐞 Debugging tips

466 |
    467 |
  • Hover over a tile to see extra information about it, including beacon 468 | strength. 469 |
  • 470 |
  • Use the MESSAGE command to display some text on your side of the HUD. 471 |
  • 472 |
  • Press the gear icon on the viewer to access extra display options.
  • 473 |
  • Use the keyboard to control the action: space to play/pause, arrows to step 1 frame at a 474 | time. 475 |
  • 476 |
477 |
478 | 479 |
480 | 481 | 493 | 494 | 495 |
496 |

497 |   498 | Game Protocol 499 |

500 | 501 | 502 |
503 |
Initialization Input
504 | First line: numberOfCells an integer for the amount of 505 | cells in the map.
506 | Next numberOfCells lines: 507 | the cells, ordered by index. Each cell is represented by 8 space-separated 508 | integers: 509 |
    510 |
  • type: 1 for egg, 2 for crystal, 0 511 | otherwise.
  • 512 |
  • initialResources for the amount of crystal/egg here.
  • 513 | 514 |
  • 515 | 6 neigh variables: Ignore for this league. 516 |
  • 517 | 518 | 519 | 520 |
  • 521 | 522 | 523 |
  • 524 | 525 | 6 neigh variables, one for each direction, containing the index 526 | of a 527 | neighboring cell or -1 if there is no neighbor. 528 | 529 |
  • 530 | 531 | 532 | 533 | 534 | 535 | 536 |
537 | Next line: one integer numberOfBases 538 | 539 | which equals 1 for this league.
540 | 541 | 542 | containing the number of bases for each player.
543 | 544 | Next line: numberOfBases integers for the cell indices 545 | where a friendly base is present.
546 | Next line: numberOfBases integers for the cell indices 547 | where an opponent base is present.. 548 |
549 |
550 |
Input for One Game Turn
551 | 552 |
553 | Next line: 2 integers myScore and oppScore for the amount of crystal each player has.
554 | 555 | Next numberOfCells lines: one line per 556 | cell, ordered by index. 3 integers per cell:
557 |
    558 |
  • resources: the amount of crystal/eggs on the cell.
  • 559 |
  • myAnts: the amount of ants you have on the cell. 560 |
  • 561 |
  • oppAnts: the amount of ants your opponent has on the cell. 562 |
563 | 564 |
565 |
Output
566 |
567 | All your actions on one line, separated by a ; 568 |
    569 |
  • 570 | BEACON index strength. 571 | Places a beacon that lasts one turn. 572 |
  • 573 |
  • 574 | LINE index1 index2 strength. Places 575 | beacons along a path between the two provided cells. 576 |
  • 577 |
  • 578 | WAIT. Does nothing. 579 |
  • 580 |
  • 581 | MESSAGE text. Displays text on your side of the HUD. 582 |
  • 583 |
584 |
585 |
586 | 587 |
588 |
Constraints
589 |
590 | 591 | numberOfBases = 1
592 | 593 | 594 | 1numberOfBases2
595 | numberOfCells < 100
596 | 597 | Response time per turn ≤ 100ms
598 | Response time for the first turn ≤ 1000ms 599 |
600 |
601 |
602 |
603 | 604 | 605 |
610 |
612 | 613 |
614 | What is in store for me in the higher leagues? 615 |
616 |
    617 | 618 |
  • The egg resource will be available.
  • 619 | 620 |
  • Larger maps will be available.
  • 621 |
  • Ants of opposing teams will interact.
  • 622 |
623 |
624 | 625 | 626 | 627 | 628 |
629 |
630 |
631 | 632 |
633 |

Starter Kit

634 | Starter AIs are available in the 635 | Starter 636 | Kit. 637 | They can help you get started with your own bot. You can modify them to suit your own coding 638 | style or start completely 639 | from scratch. 640 |


641 |

Source code

642 | The game's source will be available here. 644 | 645 |
646 |
647 |
648 |
649 |
650 | -------------------------------------------------------------------------------- /config/statement_fr.html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
13 |
14 | 15 |
16 |

17 | Ce challenge est basé sur un système de leagues. 18 |

19 |
20 | Pour ce challenge, plusieurs ligues pour le même jeu seront disponibles. Quand vous aurez prouvé votre valeur 21 | contre le premier Boss, vous accéderez à la ligue supérieure et débloquerez de nouveaux adversaires. 22 |
23 |
24 |
25 | 26 |
27 | 28 | 29 | 30 |
31 |

32 |   33 | Goal 34 |

35 | Terminer la partie avec plus de cristaux que votre adversaire. 36 |
37 | 39 |
40 | Cristaux 41 |
42 |
43 |

Le jeu se déroule dans un laboratoire, dans lequel 2 scientifiques en charge de fourmis robots 44 | s'affrontent pour trouver le moyen le plus efficace pour récupérer des cristaux.

45 | Cependant, les fourmis ne peuvent pas être contrôlées directement. Elles répondent uniquement à la présence 46 | de balises. 47 | 48 |
49 | 50 | 51 | 52 | 53 |
54 |

55 |   56 | Règles 57 |

58 |
59 |
60 | Le jeu se joue au tour par tour. A chaque tour, chaque joueur réalise autant d'actions qu'il le souhaite de 61 | manière simultanée. 62 | 63 |

68 | La carte

69 | 70 |

Pour chaque partie, la carte est générée aléatoirement et est composée de cellules hexagonales. 71 |

72 | Chaque cellule a un indice et jusqu'à 6 voisines. Chaque direction est numérotée de 0 à 73 | 5. 74 | 75 |
76 | 78 |
79 | Directions 80 |
81 |
82 |
83 | 84 | 85 | Chaque cellule a un type, qui indique ce qu'elle contient: 86 |
    87 |
  • 88 | 0 si elle ne contient aucune ressource. 89 |
  • 90 | 91 |
  • 92 | 1 apparaitra dans les prochaines ligues et peut être ignoré pour le moment. 93 |
  • 94 | 95 | 96 | 97 | 98 |
  • 99 | 100 | 101 |
  • 102 | 103 | 1 si elle contient des oeufs. 104 | 105 | 106 |
  • 107 | 108 | 109 | 110 | 111 | 112 | 113 |
  • 114 | 2 si elle contient des cristaux. 115 |
  • 116 |
117 |

La quantité de ressources contenues dans chaque cellule est également donnée, et peut changer au 118 | cours de la partie lorsque les fourmis récoltent les cellules.

119 | 120 | Une cellule peut également contenir une base. Les fourmis des deux joueurs commenceront la partie sur ces 121 | bases. 122 | 123 |
124 |
125 | 127 |
128 | Base bleue 129 |
130 |
131 | 132 |
133 | 135 |
136 | Oeufs 137 |
138 |
139 | 140 |
141 | 142 |

147 | Les fourmis et les balises

148 | 149 |

Chaque joueur commence avec plusieurs fourmis sur 150 | 151 | leurs base. 152 | 153 | 154 | leurs bases. 155 | 156 | 157 | Les joueurs ne peuvent pas contrôler les fourmis directement, mais peuvent placer des balises pour les 158 | attirer. 159 |

160 | 161 |

Les joueurs peuvent placer autant de balises par tour qu'ils le souhaitent, mais ne peuvent en placer 162 | qu'une seule par cellule. 163 |

164 | 165 |

Quand ils placent une balise, les joueurs doivent leur donner une puissance. Ces puissances se 166 | comportent comme des poids qui déterminent la proportion de fourmis qui seront réparties sur 167 | chaque balise.

168 | 169 |

Autrement dit, plus la balise sera puissante, plus le pourcentage 170 | de vos fourmis allant sur cette balise sera élevé.

171 | 172 |

177 | Exemple

178 | 179 |

Dans l'exemple suivant, il y a 3 balises de puissance 180 | 2,1, et 2. 181 |

182 | 183 |
184 | 185 |
186 | 187 |
188 |
189 | Les nombres blancs dans un diamant coloré représentent les fourmis. Ici, 10 fourmis seront 190 | réparties sur les balises. 191 |
192 |
193 |
194 |
195 | 196 |
197 |
198 | Les 10 fourmis vont se déplacer vers les 3 balises, en gardant les même proportions que 199 | les puissances des balises. 200 |
201 |
202 |
203 |
204 | 205 |
206 |

Les fourmis feront de leur mieux pour utiliser le plus court chemin vers leur balise, en se déplaçant à 207 | une vitesse d'une cellule par tour.

208 | 209 | 213 | 214 |

Entre chaque tour, les balises existantes sont désactivées et retirées du jeu.

215 | 216 |

217 | Utilisez les balises pour placer vos fourmis de manière à créer une chaîne de récolte entre 218 | 219 | 220 | votre base 221 | 222 | 223 | vos bases 224 | 225 | 226 | et une ressource. 227 |

228 | 229 |

234 | Chaîne de récolte

235 | 236 |

Afin de récolter des cristaux et marquer des points, il doit y avoir une chaîne ininterrompue 237 | de 238 | cellules contenant vos fourmis entre une ressources et 239 | 240 | votre base. 241 | 242 | 243 | vos bases. 244 | 245 |

246 | 247 |

La quantité de cristaux récoltés par tour est égale au maillon le plus faible de la chaîne. Autrement 248 | dit, c'est la plus petite quantité de fourmis parmi les cellules qui composent la chaîne.

249 | 250 |
251 | 253 |
254 |
255 | Ici, le joueur bleu récoltera 4 cristaux par tour. 256 |
257 |
258 | 259 | 260 | 261 | 262 | 263 |

Dans les jeux avec plusieurs bases par joueur, le jeu choisira la meilleure chaîne vers l'une ou l'autre de 264 | vos bases.

265 | 266 | 267 | 268 |

269 | 270 | 271 |

272 | 273 | 274 | Les chaîne de récolte fonctionne de la même façon pour les oeufs. 275 |

Récolter des oeufs créera autant de fourmis que de ressources récoltées. Les fourmis apparaîtront dans 276 | la base du joueur au début du tour suivant. 277 | 278 | 279 | 280 |

281 | 282 | Dans les parties où les joueurs ont plusieurs bases, les nouvelles fourmis apparaîtront dans chaque 283 | base, indépendamment de la base lié par la chaîne de récolte.

284 | 285 |
286 | 287 | 288 | 289 |

290 | 291 | 292 | 293 |

294 | 295 | 296 | 297 | 298 |

La récolte est calculée indépendamment pour chaque ressource, et à chaque fois le jeu choisira 299 | automatiquement la meilleure chaîne de la cellule contenant la ressource jusqu'à la base.

300 | 301 | 302 | 303 | 304 | 305 |
306 | 307 | 308 |

313 | Attaquer les chaînes

314 | 315 | 316 |

Les chaînes de récolte d'un joueur peut être interrompue par les chaînes d'attaque du joueur 317 | adverse.

318 | 319 |

Au moment du calul des chaînes de récolte, certaines cellules peuvent avoir des fourmis de chaque joueur. 320 | Pour chacune de ces cellules, la chaîne d'attaque de chaque joueur est calculée, et si l'un des 321 | joueur a une valeur plus faible, cette cellule ne sera pas prise en compte dans la chaîne de récolte.

322 | 323 | La valeur de la chaîne de récolte pour une cellule donnée est le maillon le plus faible dans la 324 | chaîne entre cette cellule et l'une des bases du joueur. 325 | 326 |

331 | Exemple

332 | 333 |
334 | 335 |
336 | 337 |
338 |
339 | Les chaînes d'attaque pour une cellule contestée sont: 5 pour le joueur rouge et 340 | 3 pour le joueur bleu.
La chaîne de récolte n'est pas interrompue. 341 |
342 |
343 |
344 |
345 | 346 |
347 |
348 | Les chaînes d'attaque pour une cellule contestée sont: 5 pour le joueur rouge et 349 | 8 pour le joueur bleu.
La chaîne de récolte est interrompue. 350 |
351 |
352 |
353 |
354 | 355 |
356 | 357 | 358 | 359 |

364 | Actions

365 |

366 | A chaque tour les joueurs peuvent faire autant d'actions valides qu'ils le souhaitent parmi: 367 |

368 |
    369 |
  • 370 | BEACON index strength: place une balise de puissance 371 | strength sur la cellule 372 | index. 373 |
  • 374 |
  • 375 | LINE index1 index2 strength: place des balises le long 376 | d'un chemin entre la cellule index1 et la cellule index2. Toutes les balises placées 377 | sont de puissance strength. Le chemin le plus court est choisi automatiquement. 378 |
  • 379 |
  • 380 | WAIT: ne rien faire. 381 |
  • 382 |
  • 383 | MESSAGE text. Affiche un texte sur votre côté du HUD. 384 |
  • 385 |
386 | 387 |

392 | Ordre des actions pour un tour

393 | 394 | 395 |
    396 |
  1. 397 | Les actions LINE sont calculées. 398 |
  2. 399 |
  3. 400 | Les actions BEACON sont calculées. 401 |
  4. 402 |
  5. 403 | Les fourmis se déplacent. 404 |
  6. 405 | 406 | 407 | 408 | 409 |
  7. 410 | 411 | 412 |
  8. 413 | 414 | Les oeufs sont récoltés et les nouvelles fourmis apparaissent. 415 | 416 |
  9. 417 | 418 | 419 | 420 | 421 | 422 |
  10. 423 | Les cristaux sont récoltés et les points sont marqués. 424 |
  11. 425 |
426 |
427 | 428 | Note : lorsque deux joueurs récoltent à partir de la même ressource, ils recevront tous les deux la quantité prévue, peu importe s'il y assez de ressources restantes pour le soutenir. 429 | 430 |
431 | 432 | 433 |
434 |
435 |
436 |
Conditions de Victoire
437 |
438 |
    439 |
  • Vous avez obtenu au moins la moitié des cristaux sur la carte avant votre adversaire.
  • 440 |
  • Vous avez plus de cristaux que votre adversaire après 100 tours, ou plus de fourmis en cas d'égalité. 441 |
  • 442 |
443 |
444 |
445 |
446 | 447 |
448 |
449 |
450 |
Conditions de Défaite
451 |
452 |
    453 | Votre programme ne répond pas dans le temps imparti ou il fournit une commande non reconnue. 454 |
455 |
456 |
457 |
458 | 459 |
460 | 461 |
462 |
463 |

468 | 🐞 Conseils de débogage

469 |
    470 |
  • Survolez une case pour voir plus d'informations sur celle-ci, y compris la puissance de la balise.
  • 471 |
  • Utilisez la commande MESSAGE pour afficher du texte sur votre côté du HUD.
  • 472 |
  • Cliquez sur la roue dentée pour afficher les options visuelles supplémentaires.
  • 473 |
  • Utilisez le clavier pour contrôler l'action : espace pour play / pause, les flèches pour avancer pas à pas 474 |
475 |
476 | 477 |
478 | 479 | 491 | 492 | 493 |
494 |

495 |   496 | Protocole de Jeu 497 |

498 | 499 | 500 |
501 |
Entrées d'Initialisation
502 | Première ligne: numberOfCells un entier pour le nombre de 503 | cellules de la carte.
504 | Les numberOfCells lignes suivantes : 505 | les cellules, ordonnées par index. Chaque cellule est représentée par 8 entiers séparés 506 | par des espaces: 507 |
    508 |
  • type: 1 pour des oeufs, 2 pour des cristaux, 0 509 | sinon.
  • 510 |
  • initialResources pour la quantité de cristaux et d'oeuf dans la cellule.
  • 511 | 512 |
  • 513 | 6 neigh variables: Ignorer pour cette ligue. 514 |
  • 515 | 516 | 517 | 518 |
  • 519 | 520 | 521 |
  • 522 | 523 | 6 neigh variables, une pour chaque direction, contenant l'index d'une cellule 524 | voisine ou -1 s'il n'y a pas de voisine. 525 | 526 |
  • 527 | 528 | 529 | 530 | 531 | 532 | 533 |
534 | Ligne suivante : un entier numberOfBases 535 | 536 | qui vaut 1 pour cette ligue.
537 | 538 | 539 | avec le nombre de bases de chaque joueur.
540 | 541 | Ligne suivante : numberOfBases pour les indices des cellules où 542 | il y a une base alliée.
543 | Ligne suivante : numberOfBases pour les indices des cellules où 544 | il y a une base ennemie. 545 |
546 |
547 |
Entrées pour Un Tour de Jeu
548 | 549 |
550 | Ligne suivante : 2 entiers myScore et oppScore pour la quantité de cristaux détenue par chaque joueur.
551 | 552 | Les numberOfCells lignes suivantes : une ligne par cellule, 553 | ordonnées par index. 3 entiers par cellule :
554 |
    555 |
  • resources : la quantité de cristaux/oeufs sur la cellule.
  • 556 |
  • myAnts : la quantité de fourmis que vous avez sur la cellule. 557 |
  • 558 |
  • oppAnts : la quantité de fourmis que votre adversaire a sur la cellule. 559 |
560 | 561 |
562 |
Sortie
563 |
564 | Toutes vos actions sur une ligne, séparées par un ; 565 |
    566 |
  • 567 | BEACON index strength. 568 | Place une balise qui dure un tour. 569 |
  • 570 |
  • 571 | LINE index1 index2 strength. Place des balises le long 572 | du chemin le plus court entre les deux cellules indiquées. 573 |
  • 574 |
  • 575 | WAIT. Ne fait rien. 576 |
  • 577 |
  • 578 | MESSAGE text. Affiche un texte sur votre côté du HUD. 579 |
  • 580 |
581 |
582 |
583 | 584 |
585 |
Contraintes
586 |
587 | 588 | numberOfBases = 1
589 | 590 | 591 | 1numberOfBases2
592 | numberOfCells < 100
593 | 594 | Temps de réponse par tour ≤ 100ms
595 | Temps de réponse pour le premier tour ≤ 1000ms 596 |
597 |
598 |
599 |
600 | 601 | 602 |
607 |
609 | 610 |
611 | Qu'est-ce qui m'attend dans les ligues suivantes ? 612 |
613 |
    614 | 615 |
  • Les oeufs seront disponibles.
  • 616 | 617 |
  • Des cartes plus grandes seront disponibles.
  • 618 |
  • Les fourmis des deux joueurs pourront interagir.
  • 619 |
620 |
621 | 622 | 623 | 624 | 625 |
626 |
627 |
628 | 629 |
630 |

Kit de Démarrage

631 | Des IAs de base sont disponibles dans le 632 | kit de 633 | démarrage. 634 | Elles peuvent 635 | vous aider à appréhender votre propre IA. 636 |


637 |

Code Dource

638 | Le code source de ce jeu est disponible ici. 640 | 641 |
642 |
643 |
644 |
645 |
646 | -------------------------------------------------------------------------------- /config/stub.txt: -------------------------------------------------------------------------------- 1 | read numberOfCells:int 2 | loop numberOfCells read type:int initialResources:int neigh0:int neigh1:int neigh2:int neigh3:int neigh4:int neigh5:int 3 | read numberOfBases:int 4 | loopline numberOfBases myBaseIndex:int 5 | loopline numberOfBases oppBaseIndex:int 6 | 7 | gameloop 8 | 9 | loop numberOfCells read resources:int myAnts:int oppAnts:int 10 | 11 | write WAIT 12 | 13 | INPUT 14 | neigh0: the index of the neighbouring cell for each direction 15 | type: 0 for empty, 1 for eggs, 2 for crystal 16 | numberOfCells: amount of hexagonal cells in this map 17 | numberOfHills: amount of bases for each player 18 | myHillIndex: a cell with a friendly base 19 | oppHillIndex: a cell with an enemy base 20 | initialResources: the initial amount of eggs/crystals on this cell 21 | resources: the current amount of eggs/crystals on this cell 22 | myAnts: the amount of your ants on this cell 23 | oppAnts: the amount of opponent ants on this cell 24 | 25 | OUTPUT 26 | WAIT | LINE | BEACON | MESSAGE 27 | -------------------------------------------------------------------------------- /full_rules.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/full_rules.pdf -------------------------------------------------------------------------------- /full_rules_fr.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/full_rules_fr.pdf -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.codingame.game 6 | spring-2023-ants 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 4.4.2 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | com.codingame.gameengine 18 | core 19 | ${gamengine.version} 20 | 21 | 22 | 23 | com.codingame.gameengine 24 | module-endscreen 25 | ${gamengine.version} 26 | 27 | 28 | 29 | com.codingame.gameengine 30 | runner 31 | ${gamengine.version} 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/event/Animation.java: -------------------------------------------------------------------------------- 1 | package com.codingame.event; 2 | 3 | import java.util.List; 4 | 5 | import com.google.inject.Singleton; 6 | 7 | @Singleton 8 | public class Animation { 9 | public static final int HUNDREDTH = 10; 10 | public static final int TWENTIETH = 50; 11 | public static final int TENTH = 100; 12 | public static final int THIRD = 300; 13 | public static final int HALF = 500; 14 | public static final int WHOLE = 1000; 15 | 16 | private int frameTime; 17 | private int endTime; 18 | 19 | public void reset() { 20 | frameTime = 0; 21 | endTime = 0; 22 | } 23 | 24 | public int wait(int time) { 25 | return frameTime += time; 26 | } 27 | 28 | public int getFrameTime() { 29 | return frameTime; 30 | } 31 | 32 | public void startAnim(List animData, int duration) { 33 | animData.add(new AnimationData(frameTime, duration)); 34 | endTime = Math.max(endTime, frameTime + duration); 35 | } 36 | 37 | public void waitForAnim(List animData, int duration) { 38 | animData.add(new AnimationData(frameTime, duration)); 39 | frameTime += duration; 40 | endTime = Math.max(endTime, frameTime); 41 | } 42 | 43 | public void chainAnims(int count, List animData, int duration, int separation) { 44 | chainAnims(count, animData, duration, separation, true); 45 | } 46 | 47 | public void chainAnims(int count, List animData, int duration, int separation, boolean waitForEnd) { 48 | for (int i = 0; i < count; ++i) { 49 | animData.add(new AnimationData(frameTime, duration)); 50 | 51 | if (i < count - 1) { 52 | frameTime += separation; 53 | } 54 | } 55 | endTime = Math.max(endTime, frameTime + duration); 56 | if (waitForEnd && count > 0) { 57 | frameTime += duration; 58 | } 59 | } 60 | 61 | public void setFrameTime(int startTime) { 62 | this.frameTime = startTime; 63 | } 64 | 65 | public int getEndTime() { 66 | return endTime; 67 | } 68 | 69 | public void catchUp() { 70 | frameTime = endTime; 71 | } 72 | 73 | public int computeEvents() { 74 | int minTime = 1000; 75 | 76 | catchUp(); 77 | 78 | int frameTime = Math.max( 79 | getFrameTime(), 80 | minTime 81 | ); 82 | return frameTime; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/event/AnimationData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.event; 2 | 3 | public class AnimationData { 4 | public int start, end; 5 | 6 | public AnimationData(int start, int duration) { 7 | this.start = start; 8 | this.end = start + duration; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/event/EventData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.event; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class EventData { 7 | public static final int BUILD = 0; 8 | public static final int MOVE = 1; 9 | public static final int FOOD = 2; 10 | public static final int BEACON = 3; 11 | 12 | public int type; 13 | public List animData; 14 | public Integer playerIdx; 15 | public Integer cellIdx, targetIdx; 16 | public Integer amount; 17 | public List path; 18 | 19 | public EventData() { 20 | animData = new ArrayList<>(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/AntConsumption.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class AntConsumption { 6 | Player player; 7 | int amount; 8 | Cell cell; 9 | LinkedList path; 10 | 11 | public AntConsumption(Player player, int amount, Cell cell, LinkedList path) { 12 | this.player = player; 13 | this.amount = amount; 14 | this.cell = cell; 15 | this.path = path; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Board.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.Deque; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.PriorityQueue; 14 | import java.util.Set; 15 | import java.util.Stack; 16 | import java.util.function.Function; 17 | import java.util.stream.Collectors; 18 | 19 | public class Board { 20 | 21 | public final Map map; 22 | // Sorted by cell index 23 | public final List cells; 24 | // Sorted by cell index 25 | public final List coords; 26 | int ringCount; 27 | List players; 28 | 29 | public Board(Map map, int ringCount, List players) { 30 | this.players = players; 31 | this.map = map; 32 | this.ringCount = ringCount; 33 | this.cells = map.values() 34 | .stream() 35 | .sorted(Comparator.comparing(cell -> cell.getIndex())) 36 | .collect(Collectors.toList()); 37 | this.coords = cells 38 | .stream() 39 | .map(Cell::getCoord) 40 | .collect(Collectors.toList()); 41 | this.distanceCache = new int[map.size()][map.size()]; 42 | this.attackCache = new ArrayList<>(); 43 | for (Player player : players) { 44 | this.attackCache.add(new HashMap<>()); 45 | } 46 | } 47 | 48 | public List getNeighbours(int i) { 49 | return getNeighbours(coords.get(i)) 50 | .stream() 51 | .map(coord -> map.get(coord).getIndex()) 52 | .collect(Collectors.toList()); 53 | } 54 | 55 | public List getNeighbours(CubeCoord coord) { 56 | return coord.neighbours().stream() 57 | .filter(neighbor -> map.containsKey(neighbor)) 58 | .collect(Collectors.toList()); 59 | } 60 | 61 | public List getNeighbours(Cell cell) { 62 | return getNeighbours(cell.getCoord()) 63 | .stream() 64 | .map(coord -> map.getOrDefault(coord, Cell.NO_CELL)) 65 | .collect(Collectors.toList()); 66 | } 67 | 68 | public String getNeighbourIds(CubeCoord coord) { 69 | List orderedNeighborIds = new ArrayList<>(CubeCoord.directions.length); 70 | for (int i = 0; i < CubeCoord.directions.length; ++i) { 71 | orderedNeighborIds.add( 72 | map.getOrDefault(coord.neighbor(i), Cell.NO_CELL).getIndex() 73 | ); 74 | } 75 | return orderedNeighborIds.stream() 76 | .map(String::valueOf) 77 | .collect(Collectors.joining(" ")); 78 | } 79 | 80 | public List getEdges() { 81 | CubeCoord center = new CubeCoord(0, 0, 0); 82 | return coords.stream() 83 | .filter(coord -> coord.distanceTo(center) == ringCount) 84 | .collect(Collectors.toList()); 85 | } 86 | 87 | public Cell get(CubeCoord coord) { 88 | return map.getOrDefault(coord, Cell.NO_CELL); 89 | } 90 | 91 | public Cell get(int index) { 92 | if (index < 0 || index >= coords.size()) { 93 | return Cell.NO_CELL; 94 | } 95 | return map.get(coords.get(index)); 96 | } 97 | 98 | // Distance cache 99 | private int[][] distanceCache; 100 | 101 | /** 102 | * @return -1 if no path exist between A and B, otherwise the length of the shortest path 103 | */ 104 | public int getDistance(int ai, int bi) { 105 | if (ai == bi) { 106 | return 0; 107 | } 108 | 109 | int cached = distanceCache[ai][bi]; 110 | if (cached > 0) { 111 | return cached; 112 | } 113 | 114 | CubeCoord a = coords.get(ai); 115 | CubeCoord b = coords.get(bi); 116 | int distance = internalGetDistance(a, b, null); 117 | distanceCache[ai][bi] = distance; 118 | distanceCache[bi][ai] = distance; 119 | return distance; 120 | } 121 | 122 | private int internalGetDistance(CubeCoord a, CubeCoord b, Integer playerIdx) { 123 | Deque path = findShortestPath(map.get(a).getIndex(), map.get(b).getIndex(), playerIdx); 124 | 125 | if (path == null) { 126 | // impossibru 127 | return -1; 128 | } 129 | return path.size() - 1; 130 | 131 | } 132 | 133 | /** 134 | * Finds the shortest path between 2 points, using a BFS. 135 | */ 136 | public Deque findShortestPath(int from, int to) { 137 | return findShortestPath(from, to, null); 138 | } 139 | 140 | public LinkedList findShortestPath(int a, int b, Integer playerIdx) { 141 | // BFS 142 | LinkedList queue = new LinkedList<>(); 143 | Map prev = new HashMap<>(); 144 | 145 | prev.put(a, null); 146 | queue.add(a); 147 | 148 | while (!queue.isEmpty()) { 149 | if (prev.containsKey(b)) { 150 | break; 151 | } 152 | Integer head = queue.pop(); 153 | 154 | List neighbours = getNeighbours(head); 155 | if (playerIdx != null) { 156 | // Order by amount of friendly ants, then beacon strength, then id of cell 157 | neighbours = neighbours.stream() 158 | .sorted( 159 | Comparator 160 | .comparing((Integer idx) -> -get(idx).getAnts(playerIdx)) 161 | .thenComparing((Integer idx) -> -get(idx).getBeaconPower(playerIdx)) 162 | .thenComparing(Function.identity()) 163 | ) 164 | .collect(Collectors.toList()); 165 | } else { 166 | // Order by id of cell 167 | Collections.sort(neighbours); 168 | } 169 | for (Integer neighbour : neighbours) { 170 | Cell cell = cells.get(neighbour); 171 | boolean visited = prev.containsKey(neighbour); 172 | if (cell.isValid() && !visited) { 173 | prev.put(neighbour, head); 174 | queue.add(neighbour); 175 | } 176 | } 177 | } 178 | 179 | if (!prev.containsKey(b)) { 180 | return null; // impossibru 181 | } 182 | 183 | // Reconstruct path 184 | LinkedList path = new LinkedList<>(); 185 | Integer current = b; 186 | while (current != null) { 187 | path.addFirst(current); 188 | current = prev.get(current); 189 | } 190 | 191 | return path; 192 | } 193 | 194 | /** 195 | * @return The path that maximizes the given player score between start and end, while minimizing the distance from start to end. 196 | */ 197 | public LinkedList getBestPath(Cell start, Cell end, int playerIdx, boolean interruptedByFight) { 198 | return getBestPath(start.getIndex(), end.getIndex(), playerIdx, interruptedByFight); 199 | } 200 | 201 | List> attackCache; 202 | int initialFood; 203 | 204 | private int getAttackPower(int cellIdx, int playerIdx) { 205 | Integer cachedAttackPower = attackCache.get(playerIdx).get(cellIdx); 206 | if (cachedAttackPower != null) { 207 | return cachedAttackPower; 208 | } 209 | 210 | List anthills = players.get(playerIdx).anthills; 211 | 212 | List> allPaths = new ArrayList<>(); 213 | for (Integer anthill : anthills) { 214 | LinkedList bestPath = getBestPath(cellIdx, anthill, playerIdx, false); 215 | 216 | if (bestPath != null) { 217 | allPaths.add(bestPath); 218 | } 219 | } 220 | 221 | int maxMin = allPaths.stream() 222 | .mapToInt(list -> { 223 | return list.stream() 224 | .mapToInt(c -> c.getAnts(playerIdx)) 225 | .min() 226 | .orElse(0); 227 | }) 228 | .max() 229 | .orElse(0); 230 | 231 | attackCache.get(playerIdx).put(cellIdx, maxMin); 232 | return maxMin; 233 | 234 | } 235 | 236 | public void resetAttackCache() { 237 | for (Map attackCache : this.attackCache) { 238 | attackCache.clear(); 239 | } 240 | } 241 | 242 | /** 243 | * @return The path that maximizes the given player score between start and end, while minimizing the distance from start to end. 244 | */ 245 | public LinkedList getBestPath(int start, int end, int playerIdx, boolean interruptedByFight) { 246 | // Dijkstra's algorithm based on the tuple (maxValue, minDist) 247 | 248 | // TODO: optim: pre-compute all distances from each cell to the end 249 | int[] maxPathValues = new int[cells.size()]; 250 | int[] prev = new int[cells.size()]; 251 | int[] distanceFromStart = new int[cells.size()]; 252 | boolean[] visited = new boolean[cells.size()]; 253 | Arrays.fill(maxPathValues, Integer.MIN_VALUE); 254 | Arrays.fill(prev, -1); 255 | Arrays.fill(visited, false); 256 | 257 | Comparator valueComparator = Comparator.comparing(cellIndex -> maxPathValues[cellIndex]); 258 | Comparator distanceComparator = Comparator.comparing(cellIndex -> distanceFromStart[cellIndex] + getDistance(cellIndex, end)); 259 | PriorityQueue queue = new PriorityQueue<>(valueComparator.reversed().thenComparing(distanceComparator)); 260 | Cell startCell = cells.get(start); 261 | maxPathValues[start] = startCell.getAnts(playerIdx); 262 | distanceFromStart[start] = 0; 263 | int startAnts = startCell.getAnts(playerIdx); 264 | if (interruptedByFight) { 265 | int myForce = getAttackPower(start, playerIdx); 266 | int otherForce = getAttackPower(start, 1 - playerIdx); 267 | if (otherForce > myForce) { 268 | startAnts = 0; 269 | } 270 | } 271 | if (startAnts > 0) { 272 | queue.add(start); 273 | } 274 | 275 | while (!queue.isEmpty() && !visited[end]) { 276 | Integer currentIndex = queue.poll(); 277 | visited[currentIndex] = true; 278 | 279 | // Update the max values of the neighbors 280 | for (Cell neighbor : getNeighbours(get(currentIndex))) { 281 | int neighborIndex = neighbor.getIndex(); 282 | int neighborAnts = neighbor.getAnts(playerIdx); 283 | if (neighborAnts > 0) { 284 | if (interruptedByFight) { 285 | int myForce = getAttackPower(neighborIndex, playerIdx); 286 | int otherForce = getAttackPower(neighborIndex, 1 - playerIdx); 287 | if (otherForce > myForce) { 288 | neighborAnts = 0; 289 | } 290 | } 291 | } 292 | 293 | if (!visited[neighborIndex] && neighborAnts > 0) { 294 | int potentialMaxPathValue = Math.min(maxPathValues[currentIndex], neighborAnts); 295 | if (potentialMaxPathValue > maxPathValues[neighborIndex]) { 296 | maxPathValues[neighborIndex] = potentialMaxPathValue; 297 | distanceFromStart[neighborIndex] = distanceFromStart[currentIndex] + 1; 298 | prev[neighborIndex] = currentIndex; 299 | queue.add(neighborIndex); 300 | } 301 | } 302 | } 303 | } 304 | 305 | if (!visited[end]) { 306 | // No path from start to end 307 | return null; 308 | } 309 | 310 | // Compute the path from start to end 311 | LinkedList path = new LinkedList<>(); 312 | int currentIndex = end; 313 | while (currentIndex != -1) { 314 | path.addFirst(get(currentIndex)); 315 | currentIndex = prev[currentIndex]; 316 | } 317 | return path; 318 | } 319 | 320 | public static boolean isConnected(List coords) { 321 | Set coordsSet = new HashSet<>(coords); 322 | Set visited = new HashSet<>(); 323 | Stack stack = new Stack<>(); 324 | 325 | CubeCoord start = coords.get(0); 326 | 327 | stack.push(start); 328 | visited.add(start); 329 | 330 | while (!stack.isEmpty()) { 331 | CubeCoord coord = stack.pop(); 332 | for (CubeCoord neighbor : coord.neighbours()) { 333 | if (coordsSet.contains(neighbor) && !visited.contains(neighbor)) { 334 | stack.push(neighbor); 335 | visited.add(neighbor); 336 | } 337 | } 338 | } 339 | 340 | return visited.size() == coords.size(); 341 | } 342 | 343 | public boolean isConnected() { 344 | return Board.isConnected(coords); 345 | } 346 | 347 | public List getFoodCells() { 348 | return map.values().stream() 349 | .filter(cell -> cell.getType() == CellType.FOOD && cell.getRichness() > 0) 350 | .collect(Collectors.toList()); 351 | } 352 | 353 | public int getRemainingFood() { 354 | return map.values().stream() 355 | .filter(cell -> cell.getType() == CellType.FOOD) 356 | .mapToInt(cell -> cell.getRichness()) 357 | .sum(); 358 | } 359 | 360 | public List getEggCells() { 361 | return map.values().stream() 362 | .filter(cell -> cell.getType() == CellType.EGG && cell.getRichness() > 0) 363 | .collect(Collectors.toList()); 364 | } 365 | 366 | public int getInitialFood() { 367 | return initialFood; 368 | } 369 | 370 | } 371 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/BoardGenerator.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | import java.util.Random; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | 14 | public class BoardGenerator { 15 | 16 | static Random random; 17 | 18 | public static Board generate(Random random, List players) { 19 | BoardGenerator.random = random; 20 | 21 | Board board = createEmptyBoard(players); 22 | 23 | addResourceCells(board, players); 24 | 25 | return board; 26 | } 27 | 28 | private static Board createEmptyBoard(List players) { 29 | Board board; 30 | int iterations = 1000; 31 | do { 32 | board = generatePotentiallyUnconnectedGraph(players); 33 | iterations--; 34 | } while (!board.isConnected() && iterations > 0); 35 | return board; 36 | } 37 | 38 | private static double VERTICAL_CUTOFF = 0.6; 39 | 40 | public static Board generatePotentiallyUnconnectedGraph(List players) { 41 | Map cells = new HashMap<>(); 42 | int nextCellIndex = 0; 43 | 44 | int ringCount = random.nextInt(Config.MAP_RING_COUNT_MAX - Config.MAP_RING_COUNT_MIN + 1) + Config.MAP_RING_COUNT_MIN; 45 | 46 | // Generate all coords as a hexagon 47 | List coordList = new ArrayList<>(); 48 | 49 | CubeCoord center = CubeCoord.CENTER; 50 | coordList.add(center); 51 | CubeCoord cur = center.neighbor(0); 52 | int verticalLimit = (int) Math.ceil(ringCount * VERTICAL_CUTOFF); 53 | for (int distance = 1; distance <= ringCount; distance++) { 54 | for (int orientation = 0; orientation < 6; orientation++) { 55 | for (int count = 0; count < distance; count++) { 56 | if (cur.z > -verticalLimit && cur.z < verticalLimit) { 57 | if (!coordList.contains(cur)) { 58 | coordList.add(cur); 59 | coordList.add(cur.getOpposite()); 60 | } 61 | } 62 | cur = cur.neighbor((orientation + 2) % 6); 63 | } 64 | } 65 | cur = cur.neighbor(0); 66 | } 67 | 68 | // Create holes 69 | int coordListSize = coordList.size(); 70 | int wantedEmptyCells = randomPercentage(Config.MIN_EMPTY_CELLS_PERCENT, Config.MAX_EMPTY_CELLS_PERCENT, coordList.size()); 71 | 72 | Set toRemove = new HashSet<>(); 73 | while (toRemove.size() < wantedEmptyCells) { 74 | int randIndex = random.nextInt(coordListSize); 75 | CubeCoord randCoord = coordList.get(randIndex); 76 | toRemove.add(randCoord); 77 | toRemove.add(randCoord.getOpposite()); 78 | 79 | // TODO: check it's still connected and remove it directly 80 | } 81 | coordList.removeAll(toRemove); 82 | 83 | boolean CORRIDOR_MODE = random.nextDouble() < 0.05; // 5% chance 84 | if (CORRIDOR_MODE) { 85 | toRemove.clear(); 86 | for (CubeCoord coord : coordList) { 87 | if (hasSixNeighbours(coord, coordList)) { 88 | toRemove.add(coord); 89 | } 90 | } 91 | coordList.removeAll(toRemove); 92 | } 93 | 94 | boolean NO_BLOB_MODE = random.nextDouble() < 0.70; // 70% chance 95 | if (NO_BLOB_MODE) { 96 | boolean changed = true; 97 | while (changed) { 98 | changed = false; 99 | Optional blobCenter = coordList.stream() 100 | .filter(c -> hasSixNeighbours(c, coordList)) 101 | .findAny(); 102 | if (blobCenter.isPresent()) { 103 | List neighbours = blobCenter.get().neighbours(); 104 | Collections.shuffle(neighbours, random); 105 | 106 | coordList.remove(neighbours.get(0)); 107 | coordList.remove(neighbours.get(0).getOpposite()); 108 | changed = true; 109 | } else { 110 | changed = false; 111 | } 112 | } 113 | } 114 | 115 | // Create empty Cells 116 | for (CubeCoord coord : coordList) { 117 | Cell cell = new Cell(nextCellIndex++, coord); 118 | cells.put(coord, cell); 119 | } 120 | 121 | return new Board(cells, ringCount, players); 122 | } 123 | 124 | private static int randomPercentage(int min, int max, int total) { 125 | int percentage = min + random.nextInt((max + 1) - min); 126 | return (int) (percentage * total / 100.0); 127 | } 128 | 129 | private static boolean hasSixNeighbours(CubeCoord coord, List coordList) { 130 | return coord.neighbours() 131 | .stream() 132 | .filter(c -> coordList.contains(c)) 133 | .count() == 6; 134 | } 135 | 136 | private static void addResourceCells(Board board, List players) { 137 | // Fill center cell 138 | if (random.nextBoolean()) { 139 | Cell centerCell = board.get(CubeCoord.CENTER); 140 | if (centerCell != Cell.NO_CELL) { 141 | centerCell.setFoodAmount(getLargeFoodAmount()); 142 | } 143 | } 144 | 145 | // Place anthills 146 | int hillsPerPlayer = Config.FORCE_SINGLE_HILL ? 1 : (random.nextDouble() < .33 ? 2 : 1); 147 | 148 | List validCoords = selectAnthillCoords(board, hillsPerPlayer); 149 | 150 | Player player1 = players.get(0); 151 | Player player2 = players.get(1); 152 | for (int i = 0; i < Math.min(hillsPerPlayer, validCoords.size()); i++) { 153 | CubeCoord coord = validCoords.get(i); 154 | 155 | Cell cell1 = board.map.get(coord); 156 | cell1.setAnthill(player1); 157 | player1.addAnthill(cell1.getIndex()); 158 | 159 | Cell cell2 = board.map.get(coord.getOpposite()); 160 | cell2.setAnthill(player2); 161 | player2.addAnthill(cell2.getIndex()); 162 | } 163 | 164 | // Place food 165 | boolean SURPLUS_MODE = Config.FORCE_SINGLE_HILL ? true : (random.nextDouble() < 0.1); // 10% chance 166 | boolean HUNGRY_MODE = Config.FORCE_SINGLE_HILL ? false : (!SURPLUS_MODE && random.nextDouble() < 0.08); // 8% chance 167 | boolean FAMINE_MODE = Config.FORCE_SINGLE_HILL ? false : (!SURPLUS_MODE && !HUNGRY_MODE && random.nextDouble() < 0.04); // 4% chance 168 | 169 | if (!FAMINE_MODE) { 170 | List validFoodCoords = board.coords.stream() 171 | .filter(coord -> board.get(coord).getAnthill() == null) 172 | .collect(Collectors.toList()); 173 | int wantedFoodCells = randomPercentage(Config.MIN_FOOD_CELLS_PERCENT, Config.MAX_FOOD_CELLS_PERCENT, validFoodCoords.size()); 174 | wantedFoodCells = Math.max(2, wantedFoodCells); 175 | 176 | Collections.shuffle(validFoodCoords, random); 177 | for (int i = 0; i < wantedFoodCells; i += 2) { 178 | CubeCoord coord = validFoodCoords.get(i); 179 | Cell cell = board.get(coord); 180 | double roll = random.nextDouble(); 181 | if (roll < 0.65) { 182 | int amount = HUNGRY_MODE ? getSmallFoodAmount() : getLargeFoodAmount(); 183 | if (SURPLUS_MODE) { 184 | amount *= Config.FORCE_SINGLE_HILL ? 5 : 10; 185 | } 186 | cell.setFoodAmount(amount); 187 | board.get(coord.getOpposite()).setFoodAmount(amount); 188 | } else { 189 | int amount = HUNGRY_MODE ? getSmallFoodAmount() : getSmallFoodAmount() / 2; 190 | if (SURPLUS_MODE) { 191 | amount *= Config.FORCE_SINGLE_HILL ? 5 : 10; 192 | } 193 | cell.setFoodAmount(amount); 194 | board.get(coord.getOpposite()).setFoodAmount(amount); 195 | } 196 | } 197 | 198 | } 199 | // Make sure there is food on the board 200 | boolean boardHasFood = board.cells.stream().anyMatch(cell -> cell.getType() == CellType.FOOD); 201 | if (!boardHasFood) { 202 | List emptyCells = board.cells.stream() 203 | .filter(cell -> cell.getType() == CellType.EMPTY && cell.isValid() && cell.getAnthill() == null) 204 | .collect(Collectors.toList()); 205 | int randomIndex = random.nextInt(emptyCells.size()); 206 | Cell emptyCell = emptyCells.get(randomIndex); 207 | int amount = getLargeFoodAmount(); 208 | if (SURPLUS_MODE) { 209 | amount *= 10; 210 | } 211 | emptyCell.setFoodAmount(amount); 212 | Cell oppositeCell = board.get(emptyCell.getCoord().getOpposite()); 213 | if (!oppositeCell.getCoord().equals(emptyCell.getCoord())) { 214 | oppositeCell.setFoodAmount(amount); 215 | } 216 | } 217 | 218 | int antPotential = 0; 219 | if (Config.ENABLE_EGGS) { 220 | // Place egg 221 | List validEggCoords = board.coords.stream() 222 | .filter( 223 | coord -> board.get(coord).getAnthill() == null && board.get(coord).getRichness() == 0 224 | ) 225 | .collect(Collectors.toList()); 226 | int wantedEggCells = randomPercentage(Config.MIN_EGG_CELLS_PERCENT, Config.MAX_EGG_CELLS_PERCENT, validEggCoords.size()); 227 | 228 | Collections.shuffle(validEggCoords, random); 229 | for (int i = 0; i < wantedEggCells; i += 2) { 230 | CubeCoord coord = validEggCoords.get(i); 231 | Cell cell = board.get(coord); 232 | double roll = random.nextDouble(); 233 | if (roll < 0.4) { 234 | int amount = getLargeEggsAmount(); 235 | cell.setSpawnPower(amount); 236 | board.get(coord.getOpposite()).setSpawnPower(amount); 237 | antPotential += amount * 2; 238 | } else { 239 | int amount = getSmallEggsAmount(); 240 | cell.setSpawnPower(amount); 241 | board.get(coord.getOpposite()).setSpawnPower(amount); 242 | antPotential += amount * 2; 243 | } 244 | } 245 | } 246 | 247 | // Place ants; 248 | int antsPerHill = Math.max(10, 60 - antPotential); 249 | players.forEach(player -> { 250 | player.anthills.forEach(idx -> { 251 | board.get(idx).placeAnts(player, antsPerHill); 252 | }); 253 | }); 254 | 255 | board.initialFood = board.getRemainingFood(); 256 | 257 | } 258 | 259 | private static int getSmallEggsAmount() { 260 | return (10 + random.nextInt(10)); 261 | } 262 | 263 | private static int getLargeEggsAmount() { 264 | return (20 + random.nextInt(20)); 265 | } 266 | 267 | private static int getSmallFoodAmount() { 268 | return (10 + random.nextInt(30)); 269 | } 270 | 271 | private static int getLargeFoodAmount() { 272 | return (40 + random.nextInt(20)); 273 | } 274 | 275 | private static List selectAnthillCoords(Board board, int hillsPerPlayer) { 276 | List validCoords = new ArrayList<>(); 277 | 278 | int iter = 1000; 279 | while (validCoords.size() < hillsPerPlayer && iter > 0) { 280 | iter--; 281 | validCoords = trySelectAnthillCoords(board, hillsPerPlayer); 282 | } 283 | // Failsafes 284 | if (validCoords.size() < hillsPerPlayer) { 285 | validCoords = board.getEdges(); 286 | } 287 | if (validCoords.size() < hillsPerPlayer) { 288 | validCoords = board.coords; 289 | } 290 | return validCoords; 291 | } 292 | 293 | /** 294 | * Select hill positions for player 1 on board edges without food 295 | */ 296 | private static List trySelectAnthillCoords(Board board, int startingHillCount) { 297 | List coordinates = new ArrayList<>(); 298 | 299 | List availableCoords = new ArrayList<>(board.coords); 300 | availableCoords.removeIf(coord -> board.get(coord).getIndex() == 0); 301 | availableCoords.removeIf(coord -> board.get(coord).getRichness() > 0); 302 | for (int i = 0; i < startingHillCount && !availableCoords.isEmpty(); i++) { 303 | int r = random.nextInt(availableCoords.size()); 304 | CubeCoord normalCoord = availableCoords.get(r); 305 | CubeCoord oppositeCoord = normalCoord.getOpposite(); 306 | availableCoords.removeIf(coord -> { 307 | return coord.distanceTo(normalCoord) <= Config.STARTING_HILL_DISTANCE || 308 | coord.distanceTo(oppositeCoord) <= Config.STARTING_HILL_DISTANCE; 309 | }); 310 | coordinates.add(normalCoord); 311 | } 312 | return coordinates; 313 | } 314 | 315 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Cell.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | public class Cell { 4 | public static final Cell NO_CELL = new Cell(-1, null) { 5 | @Override 6 | public boolean isValid() { 7 | return false; 8 | } 9 | 10 | @Override 11 | public int getIndex() { 12 | return -1; 13 | } 14 | }; 15 | 16 | private CellType type; 17 | private int richness; 18 | private int index; 19 | private Player anthill; 20 | private int[] ants; 21 | private int[] beacons; 22 | private CubeCoord coord; 23 | 24 | public Cell(int index, CubeCoord coord) { 25 | this.index = index; 26 | this.richness = 0; 27 | this.ants = new int[2]; 28 | this.beacons = new int[2]; 29 | this.coord = coord; 30 | this.type = CellType.EMPTY; 31 | } 32 | 33 | public int getIndex() { 34 | return index; 35 | } 36 | 37 | public boolean isValid() { 38 | return true; 39 | } 40 | 41 | public void setFoodAmount(int richness) { 42 | this.richness = richness; 43 | this.type = CellType.FOOD; 44 | } 45 | 46 | public int getRichness() { 47 | return richness; 48 | } 49 | 50 | public int getSpawnPower() { 51 | return richness; 52 | } 53 | 54 | public void setSpawnPower(int richness) { 55 | this.richness = richness; 56 | this.type = CellType.EGG; 57 | } 58 | 59 | public Player getAnthill() { 60 | return anthill; 61 | } 62 | 63 | public CubeCoord getCoord() { 64 | return coord; 65 | } 66 | 67 | public void setAnthill(Player anthill) { 68 | this.anthill = anthill; 69 | } 70 | 71 | public void placeAnts(Player player, int amount) { 72 | placeAnts(player.getIndex(), amount); 73 | } 74 | public void placeAnts(int playerIdx, int amount) { 75 | ants[playerIdx] += amount; 76 | } 77 | 78 | public void removeAnts(Player player, int amount) { 79 | removeAnts(player.getIndex(), amount); 80 | } 81 | 82 | public int getAnts(Player player) { 83 | return getAnts(player.getIndex()); 84 | } 85 | 86 | public int getAnts(int playerIdx) { 87 | return ants[playerIdx]; 88 | } 89 | 90 | public void setBeaconPower(int playerIdx, int power) { 91 | beacons[playerIdx] = power; 92 | } 93 | 94 | public int getBeaconPower(int playerIdx) { 95 | return beacons[playerIdx]; 96 | } 97 | 98 | public int getBeaconPower(Player player) { 99 | return beacons[player.getIndex()]; 100 | } 101 | 102 | public void removeAnts(int playerIdx, int amount) { 103 | ants[playerIdx] -= Math.min(amount, ants[playerIdx]); 104 | } 105 | 106 | public void deplete(int amount) { 107 | this.richness -= Math.min(amount, richness); 108 | } 109 | 110 | public CellType getType() { 111 | return type; 112 | } 113 | 114 | public void removeBeacons() { 115 | beacons[0] = 0; 116 | beacons[1] = 0; 117 | } 118 | 119 | public int[] getAnts() { 120 | return ants; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/CellType.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | public enum CellType { 4 | EMPTY, 5 | FOOD, 6 | EGG 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/CommandManager.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.List; 4 | import java.util.regex.Matcher; 5 | 6 | import com.codingame.game.action.Action; 7 | import com.codingame.game.action.ActionType; 8 | import com.codingame.game.action.actions.BeaconAction; 9 | import com.codingame.game.action.actions.LineAction; 10 | import com.codingame.game.action.actions.MessageAction; 11 | import com.codingame.game.action.actions.WaitAction; 12 | import com.codingame.game.exception.InvalidInputException; 13 | import com.codingame.gameengine.core.GameManager; 14 | import com.codingame.gameengine.core.MultiplayerGameManager; 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | 18 | @Singleton 19 | public class CommandManager { 20 | 21 | @Inject private MultiplayerGameManager gameManager; 22 | 23 | public void parseCommands(Player player, List lines) { 24 | String line = lines.get(0); 25 | try { 26 | String[] commands = line.split(";"); 27 | for (String command : commands) { 28 | Matcher match; 29 | boolean found = false; 30 | try { 31 | for (ActionType actionType : ActionType.values()) { 32 | match = actionType.getPattern().matcher(command.trim()); 33 | if (match.matches()) { 34 | Action action; 35 | switch (actionType) { 36 | case BEACON: { 37 | int index = Integer.parseInt(match.group("index")); 38 | int power = Integer.parseInt(match.group("power")); 39 | action = new BeaconAction(index, power); 40 | break; 41 | } 42 | case LINE: { 43 | int from = Integer.parseInt(match.group("from")); 44 | int to = Integer.parseInt(match.group("to")); 45 | int ants = Integer.parseInt(match.group("ants")); 46 | action = new LineAction(from, to, ants); 47 | break; 48 | } 49 | case MESSAGE: { 50 | String message = match.group("message"); 51 | action = new MessageAction(message); 52 | break; 53 | } 54 | case WAIT: { 55 | action = new WaitAction(); 56 | break; 57 | } 58 | default: 59 | // Impossibru 60 | action = null; 61 | break; 62 | } 63 | player.addAction(action); 64 | found = true; 65 | break; 66 | } 67 | } 68 | } catch (Exception e) { 69 | throw new InvalidInputException(Game.getExpected(command), e.toString()); 70 | } 71 | 72 | if (!found) { 73 | throw new InvalidInputException(Game.getExpected(command), command); 74 | } 75 | } 76 | 77 | } catch (InvalidInputException e) { 78 | deactivatePlayer(player, e.getMessage()); 79 | gameManager.addToGameSummary(e.getMessage()); 80 | gameManager.addToGameSummary(GameManager.formatErrorMessage(player.getNicknameToken() + ": disqualified!")); 81 | } 82 | } 83 | 84 | public void deactivatePlayer(Player player, String message) { 85 | player.deactivate(escapeHTMLEntities(message)); 86 | player.setScore(-1); 87 | } 88 | 89 | private String escapeHTMLEntities(String message) { 90 | return message 91 | .replace("<", "<") 92 | .replace(">", ">"); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Config.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.Properties; 4 | 5 | public class Config { 6 | 7 | /** 8 | * Map generation 9 | */ 10 | public static final int MAP_MIN_WIDTH = 12; 11 | public static final double MAP_ASPECT_RATIO = 1 / 2d; 12 | public static final int MAP_RING_COUNT_MIN = 4; 13 | public static int MAP_RING_COUNT_MAX = 7; 14 | public static final int MIN_EMPTY_CELLS_PERCENT = 10; 15 | public static final int MAX_EMPTY_CELLS_PERCENT = 20; 16 | public static final int MIN_EGG_CELLS_PERCENT = 10; 17 | public static final int MAX_EGG_CELLS_PERCENT = 28; 18 | public static final int MIN_FOOD_CELLS_PERCENT = 10; 19 | public static final int MAX_FOOD_CELLS_PERCENT = 30; 20 | public static final int STARTING_HILL_DISTANCE = 2; 21 | 22 | /** 23 | * Gameplay 24 | */ 25 | public static final int MAX_ANT_LOSS = 3; 26 | public static int FRAMES_PER_TURN = 1; 27 | public static final int MAX_TURNS = 100 * FRAMES_PER_TURN; 28 | 29 | /*** 30 | * Rules 31 | */ 32 | public static boolean FIGHTING_ANTS_KILL = false; 33 | public static boolean LOSING_ANTS_CANT_MOVE = false; 34 | public static boolean LOSING_ANTS_CANT_CARRY = true; 35 | public static boolean FORCE_SINGLE_HILL = false; 36 | public static boolean ENABLE_EGGS = true; 37 | public static boolean SCORES_IN_IO = false; 38 | 39 | public static void takeFrom(Properties params) { 40 | FIGHTING_ANTS_KILL = getFromParams(params, "FIGHTING_ANTS_KILL", FIGHTING_ANTS_KILL); 41 | LOSING_ANTS_CANT_MOVE = getFromParams(params, "LOSING_ANTS_CANT_MOVE", LOSING_ANTS_CANT_MOVE); 42 | FRAMES_PER_TURN = getFromParams(params, "FRAMES_PER_TURN", FRAMES_PER_TURN); 43 | LOSING_ANTS_CANT_CARRY = getFromParams(params, "LOSING_ANTS_CANT_CARRY", LOSING_ANTS_CANT_CARRY); 44 | } 45 | 46 | public static void giveTo(Properties params) { 47 | params.put("LOSING_ANTS_CANT_MOVE", LOSING_ANTS_CANT_MOVE); 48 | params.put("FIGHTING_ANTS_KILL", FIGHTING_ANTS_KILL); 49 | params.put("FRAMES_PER_TURN", FRAMES_PER_TURN); 50 | params.put("LOSING_ANTS_CANT_CARRY", LOSING_ANTS_CANT_CARRY); 51 | } 52 | 53 | private static double getFromParams(Properties params, String name, double defaultValue) { 54 | String inputValue = params.getProperty(name); 55 | if (inputValue != null) { 56 | try { 57 | return Double.parseDouble(inputValue); 58 | } catch (NumberFormatException e) { 59 | // Do naught 60 | } 61 | } 62 | return defaultValue; 63 | } 64 | 65 | private static int getFromParams(Properties params, String name, int defaultValue) { 66 | String inputValue = params.getProperty(name); 67 | if (inputValue != null) { 68 | try { 69 | return Integer.parseInt(inputValue); 70 | } catch (NumberFormatException e) { 71 | // Do naught 72 | } 73 | } 74 | return defaultValue; 75 | } 76 | 77 | private static boolean getFromParams(Properties params, String name, boolean defaultValue) { 78 | String inputValue = params.getProperty(name); 79 | if (inputValue != null) { 80 | try { 81 | return new Boolean(inputValue); 82 | } catch (NumberFormatException e) { 83 | // Do naught 84 | } 85 | } 86 | return defaultValue; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/CubeCoord.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.codingame.view.Serializer; 7 | 8 | public class CubeCoord { 9 | 10 | static int[][] directions = new int[][] { { 1, -1, 0 }, { +1, 0, -1 }, { 0, +1, -1 }, { -1, +1, 0 }, { -1, 0, +1 }, { 0, -1, +1 } }; 11 | static CubeCoord CENTER = new CubeCoord(0, 0, 0); 12 | 13 | int x, y, z; 14 | 15 | public CubeCoord(int x, int y, int z) { 16 | this.x = x; 17 | this.y = y; 18 | this.z = z; 19 | } 20 | 21 | public int getX() { 22 | return x; 23 | } 24 | 25 | public int getY() { 26 | return y; 27 | } 28 | 29 | public int getZ() { 30 | return z; 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | final int prime = 31; 36 | int result = 1; 37 | result = prime * result + x; 38 | result = prime * result + y; 39 | result = prime * result + z; 40 | return result; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object obj) { 45 | if (this == obj) 46 | return true; 47 | if (obj == null) 48 | return false; 49 | if (getClass() != obj.getClass()) 50 | return false; 51 | CubeCoord other = (CubeCoord) obj; 52 | if (x != other.x) 53 | return false; 54 | if (y != other.y) 55 | return false; 56 | if (z != other.z) 57 | return false; 58 | return true; 59 | } 60 | 61 | CubeCoord neighbor(int orientation) { 62 | return neighbor(orientation, 1); 63 | } 64 | 65 | CubeCoord neighbor(int orientation, int distance) { 66 | int nx = this.x + directions[orientation][0] * distance; 67 | int ny = this.y + directions[orientation][1] * distance; 68 | int nz = this.z + directions[orientation][2] * distance; 69 | 70 | return new CubeCoord(nx, ny, nz); 71 | } 72 | 73 | /** 74 | * Warning: this return ALL neighbor coords, even those that aren't within the board limits. 75 | */ 76 | List neighbours() { 77 | List neighborCoords = new ArrayList<>(CubeCoord.directions.length); 78 | for (int i = 0; i < CubeCoord.directions.length; ++i) { 79 | CubeCoord next = this.neighbor(i); 80 | neighborCoords.add(next); 81 | } 82 | return neighborCoords; 83 | } 84 | 85 | int distanceTo(CubeCoord dst) { 86 | return (Math.abs(x - dst.x) + Math.abs(y - dst.y) + Math.abs(z - dst.z)) / 2; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return Serializer.join(x, y, z); 92 | } 93 | 94 | public CubeCoord getOpposite() { 95 | CubeCoord oppositeCoord = new CubeCoord(-this.x, -this.y, -this.z); 96 | return oppositeCoord; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Game.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Comparator; 5 | import java.util.Deque; 6 | import java.util.HashMap; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.Random; 12 | import java.util.function.IntFunction; 13 | import java.util.stream.Collectors; 14 | 15 | import com.codingame.event.Animation; 16 | import com.codingame.event.EventData; 17 | import com.codingame.game.action.actions.BeaconAction; 18 | import com.codingame.game.action.actions.LineAction; 19 | import com.codingame.game.move.AntAllocater; 20 | import com.codingame.game.move.AntAllocation; 21 | import com.codingame.game.move.AntMove; 22 | import com.codingame.gameengine.core.MultiplayerGameManager; 23 | import com.codingame.gameengine.module.endscreen.EndScreenModule; 24 | import com.codingame.view.Serializer; 25 | import com.google.inject.Inject; 26 | import com.google.inject.Singleton; 27 | 28 | @Singleton 29 | public class Game { 30 | @Inject private MultiplayerGameManager gameManager; 31 | @Inject private EndScreenModule endScreenModule; 32 | @Inject private Animation animation; 33 | @Inject private GameSummaryManager gameSummaryManager; 34 | 35 | int STARTING_HILL_DISTANCE = 2; 36 | 37 | private Random random; 38 | 39 | List players; 40 | 41 | private List viewerEvents; 42 | 43 | private int gameTurn; 44 | private Board board; 45 | private boolean moveAnimatedThisTurn; 46 | 47 | public void init() { 48 | players = gameManager.getPlayers(); 49 | random = gameManager.getRandom(); 50 | viewerEvents = new ArrayList<>(); 51 | gameTurn = 0; 52 | board = BoardGenerator.generate(random, players); 53 | } 54 | 55 | public boolean isKeyFrame() { 56 | return gameTurn % Config.FRAMES_PER_TURN == 0; 57 | } 58 | 59 | public void resetGameTurnData() { 60 | if (isKeyFrame()) { 61 | board.cells.forEach(cell -> cell.removeBeacons()); 62 | } 63 | viewerEvents.clear(); 64 | animation.reset(); 65 | players.stream().forEach(Player::reset); 66 | moveAnimatedThisTurn = false; 67 | board.resetAttackCache(); 68 | } 69 | 70 | public List getGlobalInfoFor(Player player) { 71 | List lines = new ArrayList<>(); 72 | lines.add(String.valueOf(board.coords.size())); 73 | board.coords.forEach(coord -> { 74 | Cell cell = board.map.get(coord); 75 | lines.add( 76 | String.format( 77 | "%d %d %s", 78 | cell.getType() == CellType.EGG ? 1 : cell.getType() == CellType.FOOD ? 2 : 0, 79 | cell.getRichness(), 80 | board.getNeighbourIds(coord) 81 | ) 82 | ); 83 | }); 84 | 85 | Player other = getOpponent(player); 86 | lines.add(String.valueOf(player.anthills.size())); 87 | lines.add(Serializer.serialize(player.anthills)); 88 | lines.add(Serializer.serialize(other.anthills)); 89 | 90 | return lines; 91 | } 92 | 93 | private Player getOpponent(Player player) { 94 | return players.get(1 - player.getIndex()); 95 | } 96 | 97 | public List getCurrentFrameInfoFor(Player player) { 98 | List lines = new ArrayList<>(); 99 | Player other = getOpponent(player); 100 | if (Config.SCORES_IN_IO) { 101 | lines.add(Serializer.join(player.points, other.points)); 102 | } 103 | for (CubeCoord coord : board.coords) { 104 | Cell cell = board.get(coord); 105 | lines.add(Serializer.join(cell.getRichness(), cell.getAnts(player), cell.getAnts(other))); 106 | } 107 | 108 | return lines.stream().map(String::valueOf).collect(Collectors.toList()); 109 | } 110 | 111 | private void doBuild() { 112 | List eggCells = board.getEggCells(); 113 | List builds = new ArrayList<>(); 114 | for (Player player : gameManager.getPlayers()) { 115 | builds.addAll(computeCellConsumption(player, eggCells)); 116 | } 117 | for (AntConsumption build : builds) { 118 | for (int idx : build.player.anthills) { 119 | board.get(idx).placeAnts(build.player, build.amount); 120 | } 121 | launchBuildEvent(build.amount, build.player.getIndex(), build.path); 122 | build.cell.deplete(build.amount); 123 | gameSummaryManager.addBuild(build); 124 | } 125 | } 126 | 127 | private void doFights() { 128 | if (Config.FIGHTING_ANTS_KILL) { 129 | for (CubeCoord coord : board.coords) { 130 | Cell cell = board.get(coord); 131 | int ants0 = cell.getAnts(0); 132 | int ants1 = cell.getAnts(1); 133 | cell.removeAnts(0, Math.min(Config.MAX_ANT_LOSS, ants1)); 134 | cell.removeAnts(1, Math.min(Config.MAX_ANT_LOSS, ants0)); 135 | } 136 | } 137 | } 138 | 139 | public void performGameUpdate(int frameIdx) { 140 | doLines(); 141 | doBeacons(); 142 | doMove(); 143 | animation.catchUp(); 144 | if (moveAnimatedThisTurn) { 145 | animation.wait(Animation.THIRD); 146 | } 147 | doFights(); 148 | doBuild(); 149 | animation.catchUp(); 150 | board.resetAttackCache(); 151 | doScore(); 152 | animation.catchUp(); 153 | gameTurn++; 154 | if (checkGameOver()) { 155 | gameManager.endGame(); 156 | } 157 | 158 | gameManager.addToGameSummary(gameSummaryManager.toString()); 159 | gameSummaryManager.clear(); 160 | 161 | int frameTime = animation.computeEvents(); 162 | gameManager.setFrameDuration(frameTime); 163 | 164 | } 165 | 166 | private List computeCellConsumption(Player player, List targetCells) { 167 | List anthills = player.getAnthills(); 168 | List meals = new ArrayList<>(); 169 | 170 | for (Cell foodCell : targetCells) { 171 | List> allPaths = new ArrayList<>(); 172 | for (Integer anthill : anthills) { 173 | Cell anthillCell = board.get(anthill); 174 | 175 | // Dijkstra from food to anthill 176 | LinkedList bestPathToHill = board.getBestPath(foodCell, anthillCell, player.getIndex(), Config.LOSING_ANTS_CANT_CARRY); 177 | 178 | if (bestPathToHill != null) { 179 | allPaths.add(bestPathToHill); 180 | } 181 | } 182 | 183 | Comparator> byPathValue = Comparator.comparing(list -> pathValue(player, list)); 184 | 185 | // For this particular foodSource, this is the best path back to an anthill 186 | Optional> bestPath = allPaths.stream() 187 | .sorted( 188 | byPathValue.reversed().thenComparing(list -> list.size()) 189 | ) 190 | .findFirst(); 191 | 192 | int maxMin = bestPath 193 | .map(list -> pathValue(player, list)) 194 | .orElse(0); 195 | 196 | // What if there's only 1 food on a cell and both players eat it at the same time? 197 | // => it gets duplicated and they both eat 1 198 | int foodEaten = Math.min(maxMin, foodCell.getRichness()); 199 | if (foodEaten > 0) { 200 | meals.add(new AntConsumption(player, foodEaten, foodCell, bestPath.get())); 201 | } 202 | } 203 | return meals; 204 | 205 | } 206 | 207 | private int pathValue(Player player, LinkedList list) { 208 | return list.stream() 209 | .mapToInt(cell -> cell.getAnts(player)) 210 | .min() 211 | .orElse(0); 212 | } 213 | 214 | private void doScore() { 215 | List foodCells = board.getFoodCells(); 216 | List meals = new ArrayList<>(); 217 | for (Player player : gameManager.getPlayers()) { 218 | // For each food output, find best path that leads to one of the player's anthills. 219 | // The best path is the one with the largest minimum amount of ants on a node of the path. 220 | // e.g. food--- 10 --- 2 --- 10 --- hill 221 | // \ / / 222 | // \---5------7----6-/ should retrieve the path food-5-7-6-10-hill 223 | // the player should then be given as many points as the lowest node: 5 points. 224 | // This repeats for each food source. 225 | 226 | meals.addAll(computeCellConsumption(player, foodCells)); 227 | } 228 | for (AntConsumption meal : meals) { 229 | launchFoodEvent(meal); 230 | gameSummaryManager.addMeal(meal); 231 | 232 | meal.player.addPoints(meal.amount); 233 | meal.cell.deplete(meal.amount); 234 | } 235 | } 236 | 237 | private void launchFoodEvent(AntConsumption meal) { 238 | EventData e = new EventData(); 239 | e.type = EventData.FOOD; 240 | e.playerIdx = meal.player.getIndex(); 241 | e.path = meal.path.stream().map(Cell::getIndex).collect(Collectors.toList()); 242 | 243 | e.amount = meal.amount; 244 | animation.startAnim(e.animData, Animation.HALF); 245 | viewerEvents.add(e); 246 | } 247 | 248 | private void doMove() { 249 | for (Player player : gameManager.getPlayers()) { 250 | 251 | List playerAntCells = getPlayerAntCells(player); 252 | List playerBeaconCells = getPlayerBeaconCells(player); 253 | List allocations = AntAllocater.allocateAnts(playerAntCells, playerBeaconCells, player.getIndex(), board); 254 | 255 | Map moves = new HashMap<>(); 256 | 257 | for (AntAllocation alloc : allocations) { 258 | // Get next step in path 259 | List path = board.findShortestPath(alloc.getAntIndex(), alloc.getBeaconIndex(), player.getIndex()); 260 | 261 | if (path.size() > 1) { 262 | int neighbor = path.get(1); 263 | int from = alloc.getAntIndex(); 264 | int to = neighbor; 265 | int amount = alloc.getAmount(); 266 | 267 | AntMove antMove = new AntMove(from, to); 268 | moves.compute(antMove, (k, v) -> v == null ? amount : v + amount); 269 | } 270 | } 271 | moves.forEach( 272 | (move, amount) -> applyMove(move.getFrom(), move.getTo(), amount, player.getIndex()) 273 | ); 274 | } 275 | } 276 | 277 | private void applyMove(int fromIdx, Integer toIdx, int amount, int playerIdx) { 278 | Cell source = board.get(fromIdx); 279 | Cell target = board.get(toIdx); 280 | 281 | source.removeAnts(playerIdx, amount); 282 | target.placeAnts(playerIdx, amount); 283 | 284 | // viewer animation 285 | launchMoveEvent(source.getIndex(), target.getIndex(), amount, playerIdx); 286 | moveAnimatedThisTurn = true; 287 | } 288 | 289 | private List getPlayerAntCells(Player player) { 290 | return board.cells.stream() 291 | .filter(cell -> cell.getAnts(player.getIndex()) > 0) 292 | .collect(Collectors.toList()); 293 | } 294 | 295 | private List getPlayerBeaconCells(Player player) { 296 | return board.cells.stream() 297 | .filter(cell -> cell.getBeaconPower(player.getIndex()) > 0) 298 | .collect(Collectors.toList()); 299 | } 300 | 301 | private void launchMoveEvent(int fromIdx, int toIdx, int amount, int playerIdx) { 302 | EventData e = new EventData(); 303 | e.type = EventData.MOVE; 304 | e.playerIdx = playerIdx; 305 | e.cellIdx = fromIdx; 306 | e.targetIdx = toIdx; 307 | e.amount = amount; 308 | animation.startAnim(e.animData, Animation.HALF); 309 | viewerEvents.add(e); 310 | } 311 | 312 | private void launchBuildEvent(int amount, int playerIdx, List path) { 313 | EventData e = new EventData(); 314 | e.type = EventData.BUILD; 315 | e.playerIdx = playerIdx; 316 | e.amount = amount; 317 | e.path = path.stream().map(Cell::getIndex).collect(Collectors.toList()); 318 | animation.startAnim(e.animData, Animation.HALF); 319 | viewerEvents.add(e); 320 | } 321 | 322 | private void doBeacons() { 323 | for (Player player : gameManager.getPlayers()) { 324 | for (BeaconAction beacon : player.beacons) { 325 | int cellIdx = beacon.getCellIndex(); 326 | int power = beacon.getPower(); 327 | setBeaconPower(cellIdx, player, power); 328 | } 329 | } 330 | } 331 | 332 | private void setBeaconPower(int cellIndex, Player player, int power) { 333 | Cell cell = board.get(cellIndex); 334 | if (!cell.isValid()) { 335 | gameSummaryManager.addError( 336 | player, 337 | String.format("cannot find cell %d", cellIndex) 338 | ); 339 | return; 340 | } 341 | cell.setBeaconPower(player.getIndex(), Math.max(1, power)); 342 | } 343 | 344 | private void launchBeaconEvent(int playerIdx, int power, int cellIdx) { 345 | EventData e = new EventData(); 346 | e.type = EventData.BEACON; 347 | e.playerIdx = playerIdx; 348 | e.cellIdx = cellIdx; 349 | e.amount = power; 350 | animation.startAnim(e.animData, Animation.HALF); 351 | viewerEvents.add(e); 352 | } 353 | 354 | private void doLines() { 355 | for (Player player : gameManager.getPlayers()) { 356 | for (LineAction line : player.lines) { 357 | int from = line.getFrom(); 358 | int to = line.getTo(); 359 | int beaconPower = line.getAnts(); 360 | if (!board.get(from).isValid()) { 361 | gameSummaryManager.addError( 362 | player, 363 | String.format("cannot find cell %d", from) 364 | ); 365 | continue; 366 | } 367 | if (!board.get(to).isValid()) { 368 | gameSummaryManager.addError( 369 | player, 370 | String.format("cannot find cell %d", to) 371 | ); 372 | continue; 373 | } 374 | Deque path = board.findShortestPath(from, to); 375 | for (Integer cellIndex : path) { 376 | setBeaconPower(cellIndex, player, beaconPower); 377 | } 378 | } 379 | } 380 | } 381 | 382 | private boolean checkGameOver() { 383 | int remainingFood = board.getRemainingFood(); 384 | 385 | if (board.getRemainingFood() == 0) { 386 | gameSummaryManager.addNoMoreFood(); 387 | return true; 388 | } 389 | if (players.get(0).points >= players.get(1).points + remainingFood) { 390 | gameSummaryManager.addNotEnoughFoodLeft(players.get(0)); 391 | return true; 392 | } else if (players.get(1).points >= players.get(0).points + remainingFood) { 393 | gameSummaryManager.addNotEnoughFoodLeft(players.get(1)); 394 | return true; 395 | } 396 | return false; 397 | } 398 | 399 | public void onEnd() { 400 | players.stream().forEach(p -> { 401 | if (p.isActive()) { 402 | p.setScore(p.points); 403 | } else { 404 | p.setScore(-1); 405 | } 406 | }); 407 | 408 | if (players.get(0).getScore() == players.get(1).getScore() && players.get(0).getScore() != -1) { 409 | players.stream().forEach(p -> { 410 | p.setScore(getAntTotal(p)); 411 | }); 412 | endScreenModule.setScores( 413 | players.stream() 414 | .mapToInt(p -> p.getScore()) 415 | .toArray(), 416 | players.stream() 417 | .map(p -> String.format("%d points and %d ants", p.points, p.getScore())) 418 | .toArray(length -> new String[length]) 419 | ); 420 | } else { 421 | endScreenModule.setScores( 422 | players.stream() 423 | .mapToInt(p -> p.getScore()) 424 | .toArray() 425 | ); 426 | } 427 | 428 | } 429 | 430 | private int getAntTotal(Player p) { 431 | return board.cells.stream().mapToInt(cell -> cell.getAnts(p)).sum(); 432 | } 433 | 434 | public List getViewerEvents() { 435 | return viewerEvents; 436 | } 437 | 438 | public static String getExpected(String playerOutput) { 439 | String attempt = playerOutput.toUpperCase(); 440 | if (attempt.startsWith("BEACON")) { 441 | return "BEACON "; 442 | } 443 | if (attempt.startsWith("LINE")) { 444 | return "LINE "; 445 | } 446 | if (attempt.startsWith("MESSAGE")) { 447 | return "MESSAGE "; 448 | } 449 | if (attempt.startsWith("WAIT")) { 450 | return "WAIT"; 451 | } 452 | return "BEACON |" 453 | + " LINE |" 454 | + " MESSAGE |" 455 | + " WAIT"; 456 | } 457 | 458 | public Board getBoard() { 459 | return board; 460 | } 461 | 462 | public List getBoardCoords() { 463 | return board.coords; 464 | } 465 | 466 | } 467 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/GameSummaryManager.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.Comparator; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | import com.google.inject.Singleton; 13 | 14 | @Singleton 15 | public class GameSummaryManager { 16 | private List lines; 17 | private Map> playersErrors; 18 | private Map> mealMap; 19 | private Map> eggMap; 20 | 21 | public GameSummaryManager() { 22 | this.lines = new ArrayList<>(); 23 | this.playersErrors = new HashMap<>(); 24 | this.mealMap = new HashMap<>(); 25 | this.mealMap.put(0, new ArrayList<>()); 26 | this.mealMap.put(1, new ArrayList<>()); 27 | 28 | this.eggMap = new HashMap<>(); 29 | this.eggMap.put(0, new ArrayList<>()); 30 | this.eggMap.put(1, new ArrayList<>()); 31 | } 32 | 33 | public String getSummary() { 34 | return toString(); 35 | } 36 | 37 | public void clear() { 38 | this.lines.clear(); 39 | this.playersErrors.clear(); 40 | this.mealMap.forEach((pIdx, list) -> list.clear()); 41 | this.eggMap.forEach((pIdx, list) -> list.clear()); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return formatErrors() + formatLines(); 47 | } 48 | 49 | public void addError(Player player, String error) { 50 | String key = player.getNicknameToken(); 51 | if (!playersErrors.containsKey(key)) { 52 | playersErrors.put(key, new ArrayList()); 53 | } 54 | playersErrors.get(key).add(error); 55 | } 56 | 57 | private String formatLines() { 58 | ArrayList harvestLines = new ArrayList(); 59 | 60 | for (int pIdx = 0; pIdx < 2; ++pIdx) { 61 | List meals = this.eggMap.get(pIdx); 62 | Collections.sort(meals, Comparator.comparing(meal -> meal.cell.getIndex())); 63 | 64 | if (meals.size() == 1) { 65 | AntConsumption meal = meals.get(0); 66 | harvestLines.add( 67 | String.format( 68 | "%s has harvested %d egg from cell %d, spawning that many ants on %s %s.\n", 69 | meal.player.getNicknameToken(), meal.amount, meal.cell.getIndex(), 70 | meal.player.anthills.size() == 1 ? "cell" : "cells", 71 | formatCellList(meal.player.anthills) 72 | ) 73 | ); 74 | } else if (meals.size() > 1) { 75 | Player player = meals.get(0).player; 76 | harvestLines.add( 77 | String.format("%s has harvested:", player.getNicknameToken()) 78 | ); 79 | int total = 0; 80 | for (AntConsumption meal : meals) { 81 | harvestLines.add( 82 | String.format( 83 | "- %d egg from cell %d", 84 | meal.amount, meal.cell.getIndex() 85 | ) 86 | ); 87 | total += meal.amount; 88 | } 89 | harvestLines.add( 90 | String.format( 91 | "Spawning a total of %d ants on %s %s.\n", total, 92 | player.anthills.size() == 1 ? "cell" : "cells", 93 | formatCellList(player.anthills) 94 | ) 95 | ); 96 | } 97 | 98 | meals = this.mealMap.get(pIdx); 99 | Collections.sort(meals, Comparator.comparing(meal -> meal.cell.getIndex())); 100 | if (meals.size() == 1) { 101 | AntConsumption meal = meals.get(0); 102 | harvestLines.add( 103 | String.format( 104 | "%s has harvested %d crystal from cell %d, scoring that many points.\n", 105 | meal.player.getNicknameToken(), meal.amount, meal.cell.getIndex() 106 | ) 107 | ); 108 | } else if (meals.size() > 1) { 109 | Player player = meals.get(0).player; 110 | harvestLines.add( 111 | String.format("%s has harvested:", player.getNicknameToken()) 112 | ); 113 | int total = 0; 114 | for (AntConsumption meal : meals) { 115 | harvestLines.add( 116 | String.format( 117 | "- %d crystal from cell %d", 118 | meal.amount, meal.cell.getIndex() 119 | ) 120 | ); 121 | total += meal.amount; 122 | } 123 | harvestLines.add( 124 | String.format("Scoring a total of %d points.\n", total) 125 | ); 126 | } 127 | 128 | } 129 | 130 | return Stream.concat( 131 | harvestLines.stream(), 132 | lines.stream() 133 | ).collect(Collectors.joining("\n")); 134 | } 135 | 136 | private String formatErrors() { 137 | if (playersErrors.isEmpty()) { 138 | return ""; 139 | } 140 | 141 | return playersErrors.entrySet().stream().map(errorMap -> { 142 | List errors = errorMap.getValue(); 143 | String additionnalErrorsMessage = errors.size() > 1 ? " + " + (errors.size() - 1) + " other error" + (errors.size() > 2 ? "s" : "") : ""; 144 | return errorMap.getKey() + ": " + errors.get(0) + additionnalErrorsMessage; 145 | }).collect(Collectors.joining("\n")) 146 | + "\n\n"; 147 | } 148 | 149 | public void addNotEnoughFoodLeft(Player player) { 150 | lines.add( 151 | player.getNicknameToken() + " has harvested at least half the crystals. Game over!" 152 | ); 153 | } 154 | public void addNotEnoughFoodLeft() { 155 | lines.add( 156 | "At least half of the crystals have been harvested. Game over!" 157 | ); 158 | } 159 | 160 | public void addNoMoreFood() { 161 | lines.add( 162 | "All the crystals have been harvested. Game over!" 163 | ); 164 | } 165 | 166 | public void addBuild(AntConsumption meal) { 167 | eggMap.get(meal.player.getIndex()).add(meal); 168 | 169 | } 170 | 171 | public void addMeal(AntConsumption meal) { 172 | mealMap.get(meal.player.getIndex()).add(meal); 173 | 174 | } 175 | 176 | private String formatCellList(List list) { 177 | if (list.size() == 1) { 178 | return list.get(0).toString(); 179 | } 180 | return list.stream().map(String::valueOf).collect(Collectors.joining(" & ")); 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Player.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.codingame.game.action.Action; 7 | import com.codingame.game.action.actions.BeaconAction; 8 | import com.codingame.game.action.actions.LineAction; 9 | import com.codingame.game.action.actions.MessageAction; 10 | import com.codingame.gameengine.core.AbstractMultiplayerPlayer; 11 | 12 | public class Player extends AbstractMultiplayerPlayer { 13 | 14 | String message; 15 | List anthills; 16 | List beacons; 17 | List lines; 18 | int points; 19 | 20 | public Player() { 21 | points = 0; 22 | anthills = new ArrayList<>(); 23 | beacons = new ArrayList<>(); 24 | lines = new ArrayList<>(); 25 | } 26 | 27 | @Override 28 | public int getExpectedOutputLines() { 29 | return 1; 30 | } 31 | 32 | public String getMessage() { 33 | return message; 34 | } 35 | 36 | public void setMessage(String message) { 37 | if (message != null) { 38 | String trimmed = message.trim(); 39 | if (trimmed.length() > 48) { 40 | trimmed = trimmed.substring(0, 46) + "..."; 41 | } 42 | if (trimmed.length() > 0) { 43 | this.message = trimmed; 44 | } 45 | } 46 | } 47 | 48 | public void reset() { 49 | message = null; 50 | beacons.clear(); 51 | lines.clear(); 52 | } 53 | 54 | public void addAction(Action action) { 55 | switch (action.getType()) { 56 | case BEACON: 57 | beacons.add((BeaconAction) action); 58 | break; 59 | case LINE: 60 | lines.add((LineAction) action); 61 | break; 62 | case MESSAGE: 63 | setMessage(((MessageAction) action).getMessage()); 64 | break; 65 | default: 66 | break; 67 | } 68 | } 69 | 70 | public void addAnthill(int index) { 71 | anthills.add(index); 72 | } 73 | 74 | public List getAnthills() { 75 | return anthills; 76 | } 77 | 78 | public void addPoints(int n) { 79 | points += n; 80 | } 81 | 82 | public int getPoints() { 83 | return points; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Referee.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import com.codingame.gameengine.core.AbstractPlayer.TimeoutException; 4 | import com.codingame.gameengine.core.AbstractReferee; 5 | import com.codingame.gameengine.core.MultiplayerGameManager; 6 | import com.codingame.view.ViewModule; 7 | import com.google.inject.Inject; 8 | import com.google.inject.Singleton; 9 | 10 | @Singleton 11 | public class Referee extends AbstractReferee { 12 | 13 | @Inject private MultiplayerGameManager gameManager; 14 | @Inject private CommandManager commandManager; 15 | @Inject private Game game; 16 | @Inject private ViewModule viewModule; 17 | 18 | @Override 19 | public void init() { 20 | try { 21 | int leagueLevel = gameManager.getLeagueLevel(); 22 | 23 | if (leagueLevel == 1) { 24 | Config.FORCE_SINGLE_HILL = true; 25 | Config.ENABLE_EGGS = false; 26 | Config.LOSING_ANTS_CANT_CARRY = false; 27 | Config.MAP_RING_COUNT_MAX = 4; 28 | } else if (leagueLevel == 2) { 29 | Config.FORCE_SINGLE_HILL = true; 30 | Config.LOSING_ANTS_CANT_CARRY = false; 31 | Config.MAP_RING_COUNT_MAX = 5; 32 | } 33 | // level 3 = interactions, big map, multiple hills 34 | if (leagueLevel >= 4) { 35 | Config.SCORES_IN_IO = true; 36 | } 37 | 38 | Config.takeFrom(gameManager.getGameParameters()); 39 | 40 | game.init(); 41 | sendGlobalInfo(); 42 | 43 | gameManager.setFrameDuration(500); 44 | gameManager.setMaxTurns(Config.MAX_TURNS); 45 | gameManager.setTurnMaxTime(100); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | System.err.println("Referee failed to initialize"); 49 | abort(); 50 | } 51 | } 52 | 53 | private void abort() { 54 | gameManager.endGame(); 55 | 56 | } 57 | 58 | private void sendGlobalInfo() { 59 | // Give input to players 60 | for (Player player : gameManager.getActivePlayers()) { 61 | for (String line : game.getGlobalInfoFor(player)) { 62 | player.sendInputLine(line); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | public void gameTurn(int turn) { 69 | try { 70 | game.resetGameTurnData(); 71 | 72 | if (game.isKeyFrame()) { 73 | // Give input to players 74 | for (Player player : gameManager.getActivePlayers()) { 75 | for (String line : game.getCurrentFrameInfoFor(player)) { 76 | player.sendInputLine(line); 77 | } 78 | player.execute(); 79 | } 80 | // Get output from players 81 | handlePlayerCommands(); 82 | } 83 | 84 | game.performGameUpdate(turn); 85 | 86 | if (gameManager.getActivePlayers().size() < 2) { 87 | abort(); 88 | } 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | System.err.println("Referee failed to compute turn. seed=" + gameManager.getSeed()); 92 | abort(); 93 | } 94 | } 95 | 96 | private void handlePlayerCommands() { 97 | 98 | for (Player player : gameManager.getActivePlayers()) { 99 | try { 100 | commandManager.parseCommands(player, player.getOutputs()); 101 | } catch (TimeoutException e) { 102 | player.deactivate("Timeout!"); 103 | gameManager.addToGameSummary(player.getNicknameToken() + " has not provided " + player.getExpectedOutputLines() + " lines in time"); 104 | } 105 | } 106 | 107 | } 108 | 109 | @Override 110 | public void onEnd() { 111 | game.onEnd(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/Action.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action; 2 | 3 | public abstract class Action { 4 | 5 | private final ActionType type; 6 | 7 | protected Action(ActionType type) { 8 | this.type = type; 9 | } 10 | 11 | public ActionType getType() { 12 | return type; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/ActionException.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action; 2 | 3 | @SuppressWarnings("serial") 4 | public class ActionException extends Exception { 5 | 6 | public ActionException(String message) { 7 | super(message); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/ActionType.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public enum ActionType { 6 | 7 | BEACON( 8 | "^BEACON (?\\d+) (?\\d+)" 9 | ), 10 | LINE( 11 | "^LINE (?\\d+) (?\\d+) (?\\d+)" 12 | ), 13 | MESSAGE( 14 | "^MESSAGE (?.*)" 15 | ), 16 | WAIT( 17 | "^WAIT" 18 | ); 19 | 20 | private Pattern pattern; 21 | 22 | ActionType(String pattern) { 23 | this.pattern = Pattern.compile(pattern); 24 | } 25 | 26 | public Pattern getPattern() { 27 | return pattern; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/actions/BeaconAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action.actions; 2 | 3 | import com.codingame.game.action.Action; 4 | import com.codingame.game.action.ActionType; 5 | 6 | public class BeaconAction extends Action { 7 | 8 | private int cellIndex; 9 | private int power; 10 | 11 | public BeaconAction(int cellIndex, int power) { 12 | super(ActionType.BEACON); 13 | this.cellIndex = cellIndex; 14 | this.power = power; 15 | } 16 | 17 | public int getCellIndex() { 18 | return cellIndex; 19 | } 20 | 21 | public void setCellIndex(int cellIndex) { 22 | this.cellIndex = cellIndex; 23 | } 24 | 25 | public int getPower() { 26 | return power; 27 | } 28 | 29 | public void setPower(int power) { 30 | this.power = power; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/actions/LineAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action.actions; 2 | 3 | import com.codingame.game.action.Action; 4 | import com.codingame.game.action.ActionType; 5 | 6 | public class LineAction extends Action { 7 | 8 | private int from; 9 | private int to; 10 | private int ants; 11 | 12 | public LineAction(int from, int to, int ants) { 13 | super(ActionType.LINE); 14 | this.from = from; 15 | this.to = to; 16 | this.ants = ants; 17 | } 18 | 19 | public int getFrom() { 20 | return from; 21 | } 22 | 23 | public void setFrom(int from) { 24 | this.from = from; 25 | } 26 | 27 | public int getTo() { 28 | return to; 29 | } 30 | 31 | public void setTo(int to) { 32 | this.to = to; 33 | } 34 | 35 | public int getAnts() { 36 | return ants; 37 | } 38 | 39 | public void setAnts(int ants) { 40 | this.ants = ants; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/actions/MessageAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action.actions; 2 | 3 | import com.codingame.game.action.Action; 4 | import com.codingame.game.action.ActionType; 5 | 6 | public class MessageAction extends Action { 7 | 8 | private String message; 9 | 10 | public MessageAction(String message) { 11 | super(ActionType.MESSAGE); 12 | this.message = message; 13 | } 14 | 15 | public String getMessage() { 16 | return message; 17 | } 18 | 19 | public void setMessage(String message) { 20 | this.message = message; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/action/actions/WaitAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.action.actions; 2 | 3 | import com.codingame.game.action.Action; 4 | import com.codingame.game.action.ActionType; 5 | 6 | public class WaitAction extends Action { 7 | 8 | public WaitAction() { 9 | super(ActionType.WAIT); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/exception/GameException.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.exception; 2 | 3 | @SuppressWarnings("serial") 4 | public class GameException extends Exception { 5 | 6 | public GameException(String string) { 7 | super(string); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/exception/InvalidInputException.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.exception; 2 | 3 | @SuppressWarnings("serial") 4 | public class InvalidInputException extends Exception { 5 | private final String expected; 6 | private final String got; 7 | 8 | public InvalidInputException(String expected, String got) { 9 | super("Invalid Input: Expected " + expected + " but got '" + got + "'"); 10 | this.expected = expected; 11 | this.got = got; 12 | } 13 | 14 | public String getExpected() { 15 | return expected; 16 | } 17 | 18 | public String getGot() { 19 | return got; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/move/AntAllocater.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.move; 2 | /* 3 | * Click `Run` to execute the snippet below! 4 | */ 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | import com.codingame.game.Board; 13 | import com.codingame.game.Cell; 14 | 15 | class CellData { 16 | Cell cell; 17 | long ants, beacons, wiggleRoom; 18 | 19 | public CellData(Cell cell, int playerIdx) { 20 | this.cell = cell; 21 | this.ants = cell.getAnts(playerIdx); 22 | this.beacons = cell.getBeaconPower(playerIdx); 23 | } 24 | } 25 | 26 | class AntBeaconPair { 27 | private CellData ant, beacon; 28 | 29 | public AntBeaconPair(CellData ant, CellData beacon) { 30 | this.ant = ant; 31 | this.beacon = beacon; 32 | } 33 | 34 | public CellData getAnt() { 35 | return ant; 36 | } 37 | 38 | public CellData getBeacon() { 39 | return beacon; 40 | } 41 | } 42 | 43 | public class AntAllocater { 44 | private static List convert(List cells, int playerIdx) { 45 | return cells.stream().map(cell -> new CellData(cell, playerIdx)).collect(Collectors.toList()); 46 | } 47 | 48 | public static List allocateAnts(List antCells, List beaconCells, int playerIdx, Board board) { 49 | return innerAllocateAnts( 50 | convert(antCells, playerIdx), 51 | convert(beaconCells, playerIdx), 52 | playerIdx, 53 | board 54 | ); 55 | } 56 | 57 | private static int getDistance(AntBeaconPair p, int playerIdx, Board board) { 58 | return board.getDistance(p.getAnt().cell.getIndex(), p.getBeacon().cell.getIndex()); 59 | } 60 | 61 | private static List innerAllocateAnts(List antCells, List beaconCells, int playerIdx, Board board) { 62 | List allocations = new ArrayList<>(); 63 | long antSum = 0; 64 | for (CellData cell : antCells) { 65 | antSum += cell.ants; 66 | } 67 | 68 | long beaconSum = 0; 69 | for (CellData cell : beaconCells) { 70 | beaconSum += cell.beacons; 71 | } 72 | 73 | double scalingFactor = (double) antSum / beaconSum; 74 | for (CellData cell : beaconCells) { 75 | long highBeaconValue = (long) Math.ceil(cell.beacons * scalingFactor); 76 | long lowBeaconValue = (long) (cell.beacons * scalingFactor); 77 | cell.beacons = Math.max(1, lowBeaconValue); 78 | cell.wiggleRoom = highBeaconValue - cell.beacons; 79 | //XXX: wiggleRoom will equals 1 if the beaconValue got rounded down 80 | } 81 | 82 | List allPairs = new ArrayList<>(); 83 | 84 | for (CellData antCell : antCells) { 85 | for (CellData beaconCell : beaconCells) { 86 | AntBeaconPair pair = new AntBeaconPair(antCell, beaconCell); 87 | if (getDistance(pair, playerIdx, board) != -1) { 88 | allPairs.add(pair); 89 | } 90 | } 91 | } 92 | 93 | Collections.sort( 94 | allPairs, 95 | Comparator.comparing((AntBeaconPair pair) -> getDistance(pair, playerIdx, board)) 96 | // Tie-breakers 97 | .thenComparing(pair -> pair.getAnt().cell.getIndex()) 98 | .thenComparing(pair -> pair.getBeacon().cell.getIndex()) 99 | ); 100 | 101 | boolean stragglers = false; 102 | while (!allPairs.isEmpty()) { 103 | for (AntBeaconPair pair : allPairs) { 104 | CellData antCell = pair.getAnt(); 105 | long antCount = antCell.ants; 106 | CellData beaconCell = pair.getBeacon(); 107 | long beaconCount = beaconCell.beacons; 108 | long wiggleRoom = beaconCell.wiggleRoom; 109 | 110 | int maxAlloc = (int) (stragglers ? Math.min(antCount, beaconCount + wiggleRoom) : Math.min(antCount, beaconCount)); 111 | if (maxAlloc > 0) { 112 | allocations.add( 113 | new AntAllocation( 114 | antCell.cell.getIndex(), beaconCell.cell.getIndex(), maxAlloc 115 | ) 116 | ); 117 | 118 | antCell.ants -= maxAlloc; 119 | if (!stragglers) { 120 | beaconCell.beacons -= maxAlloc; 121 | } else { 122 | beaconCell.beacons -= (maxAlloc - wiggleRoom); 123 | beaconCell.wiggleRoom = 0; 124 | } 125 | } 126 | } 127 | allPairs.removeIf(pair -> pair.getAnt().ants <= 0); 128 | stragglers = true; 129 | } 130 | 131 | return allocations; 132 | } 133 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/move/AntAllocation.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.move; 2 | 3 | public class AntAllocation { 4 | 5 | private int antIndex, beaconIndex, amount; 6 | 7 | public AntAllocation(int antIndex, int beaconIndex, int amount) { 8 | this.antIndex = antIndex; 9 | this.beaconIndex = beaconIndex; 10 | this.amount = amount; 11 | } 12 | 13 | public int getAntIndex() { 14 | return antIndex; 15 | } 16 | 17 | public int getBeaconIndex() { 18 | return beaconIndex; 19 | } 20 | 21 | public int getAmount() { 22 | return amount; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/move/AntDecision.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.move; 2 | 3 | import com.codingame.game.CubeCoord; 4 | 5 | public class AntDecision { 6 | 7 | CubeCoord coord; 8 | int dispatch; 9 | double weight, dispatchTarget; 10 | 11 | public AntDecision(CubeCoord coord) { 12 | this.coord = coord; 13 | } 14 | public void setWeight(double weight) { 15 | this.weight = weight; 16 | } 17 | 18 | public void setDispatchTarget(double dispatchTarget) { 19 | this.dispatchTarget = dispatchTarget; 20 | } 21 | 22 | public void setDispatch(int dispatch) { 23 | this.dispatch = dispatch; 24 | } 25 | 26 | public double getRemainder() { 27 | return dispatchTarget - dispatch; 28 | } 29 | 30 | public CubeCoord getCoord() { 31 | return coord; 32 | } 33 | 34 | public double getWeight() { 35 | return weight; 36 | } 37 | 38 | public double getDispatchTarget() { 39 | return dispatchTarget; 40 | } 41 | public int getDispatch() { 42 | return dispatch; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/move/AntMove.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.move; 2 | 3 | import java.util.Objects; 4 | 5 | public class AntMove { 6 | int from, to; 7 | 8 | public AntMove(int from, int to) { 9 | this.from = from; 10 | this.to = to; 11 | } 12 | 13 | public int getFrom() { 14 | return from; 15 | } 16 | 17 | public void setFrom(int from) { 18 | this.from = from; 19 | } 20 | 21 | public int getTo() { 22 | return to; 23 | } 24 | 25 | public void setTo(int to) { 26 | this.to = to; 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return Objects.hash(from, to); 32 | } 33 | 34 | @Override 35 | public boolean equals(Object obj) { 36 | if (this == obj) return true; 37 | if (obj == null) return false; 38 | if (getClass() != obj.getClass()) return false; 39 | AntMove other = (AntMove) obj; 40 | return from == other.from && to == other.to; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/CellData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | public class CellData { 4 | int q, r, richness, index, owner, type; 5 | public int[] ants; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/FrameViewData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.List; 4 | 5 | import com.codingame.event.EventData; 6 | 7 | public class FrameViewData { 8 | public List events; 9 | public List scores; 10 | public List messages; 11 | 12 | public List beacons; 13 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/GameDataProvider.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import java.util.function.BiFunction; 6 | import java.util.function.Function; 7 | import java.util.stream.Collectors; 8 | 9 | import com.codingame.game.Cell; 10 | import com.codingame.game.CellType; 11 | import com.codingame.game.CubeCoord; 12 | import com.codingame.game.Game; 13 | import com.codingame.game.Player; 14 | import com.codingame.gameengine.core.MultiplayerGameManager; 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | 18 | @Singleton 19 | public class GameDataProvider { 20 | @Inject private Game game; 21 | @Inject private MultiplayerGameManager gameManager; 22 | 23 | public GlobalViewData getGlobalData() { 24 | GlobalViewData data = new GlobalViewData(); 25 | data.cells = game.getBoardCoords() 26 | .stream() 27 | .map(entry -> { 28 | CubeCoord coord = entry; 29 | Cell cell = game.getBoard().get(coord); 30 | 31 | CellData cellData = new CellData(); 32 | cellData.q = coord.getX(); 33 | cellData.r = coord.getZ(); 34 | cellData.richness = cell.getRichness(); 35 | cellData.index = cell.getIndex(); 36 | cellData.owner = Optional.ofNullable(cell.getAnthill()) 37 | .map(ah -> ah.getIndex()) 38 | .orElse(-1); 39 | cellData.type = cell.getType() == CellType.EGG ? 1 : 2; 40 | cellData.ants = cell.getAnts(); 41 | return cellData; 42 | }) 43 | .collect(Collectors.toList()); 44 | 45 | return data; 46 | } 47 | 48 | private List collectCellData(BiFunction getter) { 49 | return gameManager.getPlayers().stream() 50 | .map(player -> { 51 | return collectCellData(cell -> getter.apply(cell, player)); 52 | }) 53 | .collect(Collectors.toList()); 54 | } 55 | 56 | private int[] collectCellData(Function getter) { 57 | return game.getBoardCoords().stream() 58 | .mapToInt(coord -> { 59 | Cell cell = game.getBoard().get(coord); 60 | return getter.apply(cell); 61 | }) 62 | .toArray(); 63 | 64 | } 65 | 66 | public FrameViewData getCurrentFrameData() { 67 | FrameViewData data = new FrameViewData(); 68 | 69 | data.messages = gameManager.getPlayers().stream() 70 | .map(player -> player.getMessage()) 71 | .collect(Collectors.toList()); 72 | data.beacons = collectCellData((cell, player) -> cell.getBeaconPower(player)); 73 | 74 | data.scores = gameManager.getPlayers().stream() 75 | .map(Player::getPoints) 76 | .collect(Collectors.toList()); 77 | 78 | data.events = game.getViewerEvents(); 79 | 80 | return data; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/GlobalViewData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.List; 4 | 5 | public class GlobalViewData { 6 | public List cells; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/Serializer.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | public class Serializer { 10 | public static final String MAIN_SEPARATOR = ";"; 11 | 12 | static public String serialize(FrameViewData frameViewData) { 13 | List lines = new ArrayList<>(); 14 | 15 | frameViewData.scores 16 | .forEach(lines::add); 17 | frameViewData.messages.stream() 18 | .map(m -> m == null ? "" : m) 19 | .forEach(lines::add); 20 | frameViewData.beacons.stream() 21 | .map(array -> serializeArray(array)) 22 | .forEach(lines::add); 23 | 24 | lines.add(frameViewData.events.size()); 25 | frameViewData.events.stream() 26 | .flatMap( 27 | e -> Stream.of( 28 | e.type, 29 | e.animData.get(0).start, 30 | e.animData.get(0).end, 31 | e.playerIdx == null ? "" : e.playerIdx, 32 | e.amount == null ? "" : e.amount, 33 | e.cellIdx == null ? "" : e.cellIdx, 34 | e.targetIdx == null ? "" : e.targetIdx, 35 | e.path == null ? "" : serialize(e.path) 36 | ) 37 | ) 38 | .forEach(lines::add); 39 | 40 | return lines.stream() 41 | .map(String::valueOf) 42 | .collect(Collectors.joining(MAIN_SEPARATOR)); 43 | } 44 | 45 | static public String serialize(GlobalViewData globalViewData) { 46 | 47 | List lines = new ArrayList<>(); 48 | lines.add(globalViewData.cells.size()); 49 | globalViewData.cells.stream().forEach(cellData -> { 50 | lines.add( 51 | join( 52 | cellData.q, 53 | cellData.r, 54 | cellData.richness, 55 | cellData.type, 56 | cellData.ants[0], 57 | cellData.ants[1], 58 | cellData.owner 59 | ) 60 | ); 61 | }); 62 | 63 | return lines.stream() 64 | .map(String::valueOf) 65 | .collect(Collectors.joining(MAIN_SEPARATOR)); 66 | 67 | } 68 | 69 | static public String serialize(List list) { 70 | return list.stream().map(String::valueOf).collect(Collectors.joining(" ")); 71 | } 72 | 73 | static public String serializeArray(int[] list) { 74 | return Arrays.stream(list).mapToObj(String::valueOf).collect(Collectors.joining(" ")); 75 | } 76 | 77 | static public String join(Object... args) { 78 | return Stream.of(args) 79 | .map(String::valueOf) 80 | .collect(Collectors.joining(" ")); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/ViewModule.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import com.codingame.gameengine.core.AbstractPlayer; 4 | import com.codingame.gameengine.core.GameManager; 5 | import com.codingame.gameengine.core.Module; 6 | import com.google.inject.Inject; 7 | import com.google.inject.Singleton; 8 | 9 | @Singleton 10 | public class ViewModule implements Module { 11 | 12 | private GameManager gameManager; 13 | private GameDataProvider gameDataProvider; 14 | 15 | @Inject 16 | ViewModule(GameManager gameManager, GameDataProvider gameDataProvider) { 17 | this.gameManager = gameManager; 18 | this.gameDataProvider = gameDataProvider; 19 | gameManager.registerModule(this); 20 | } 21 | 22 | @Override 23 | public final void onGameInit() { 24 | sendGlobalData(); 25 | sendFrameData(); 26 | } 27 | 28 | private void sendFrameData() { 29 | FrameViewData data = gameDataProvider.getCurrentFrameData(); 30 | gameManager.setViewData("graphics", Serializer.serialize(data)); 31 | } 32 | 33 | private void sendGlobalData() { 34 | GlobalViewData data = gameDataProvider.getGlobalData(); 35 | gameManager.setViewGlobalData("graphics", Serializer.serialize(data)); 36 | 37 | } 38 | 39 | @Override 40 | public final void onAfterGameTurn() { 41 | sendFrameData(); 42 | } 43 | 44 | @Override 45 | public final void onAfterOnEnd() { 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/view/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | jest: true 6 | }, 7 | extends: [ 8 | 'standard', 'standard-jsx' 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly' 13 | }, 14 | parser: '@typescript-eslint/parser', 15 | parserOptions: { 16 | ecmaFeatures: { 17 | jsx: true 18 | }, 19 | ecmaVersion: 2018, 20 | sourceType: 'module', 21 | project: './tsconfig.json' 22 | }, 23 | plugins: [ 24 | '@typescript-eslint' 25 | ], 26 | rules: { 27 | '@typescript-eslint/member-delimiter-style': ['error', { multiline: { delimiter: 'none' }, singleline: { delimiter: 'comma' } }], 28 | '@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }], 29 | '@typescript-eslint/no-useless-constructor': 'error', 30 | '@typescript-eslint/type-annotation-spacing': 'error', 31 | 'no-unused-vars': 'off', 32 | 'no-mixed-operators': 'off', 33 | 'no-undef': 'off', 34 | 'no-useless-constructor': 'off', 35 | 'prefer-const': 'off' 36 | }, 37 | ignorePatterns: ['.eslintrc.js', 'config.js', 'demo.js'] 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/view/assets/Background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/src/main/resources/view/assets/Background.jpg -------------------------------------------------------------------------------- /src/main/resources/view/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/src/main/resources/view/assets/logo.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/src/main/resources/view/assets/particle.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/spritesheet.json: -------------------------------------------------------------------------------- 1 | {"frames":{"Balise_Bleu.png":{"frame":{"x":0,"y":0,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Balise_Rouge.png":{"frame":{"x":651,"y":0,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Balise_Rouge_Bleu.png":{"frame":{"x":0,"y":751,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Case.png":{"frame":{"x":651,"y":751,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"convey3.png":{"frame":{"x":2037,"y":3102,"w":27,"h":32},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":27,"h":32},"sourceSize":{"w":27,"h":32}},"convey5.png":{"frame":{"x":2037,"y":3135,"w":27,"h":32},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":27,"h":32},"sourceSize":{"w":27,"h":32}},"Cristaux_1.png":{"frame":{"x":1302,"y":0,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Cristaux_2.png":{"frame":{"x":1302,"y":751,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Cristaux_3.png":{"frame":{"x":0,"y":1502,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Fleche_Bleu.png":{"frame":{"x":1953,"y":2637,"w":209,"h":180},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":209,"h":180},"sourceSize":{"w":209,"h":180}},"Fleche_Rouge.png":{"frame":{"x":1953,"y":2818,"w":209,"h":180},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":209,"h":180},"sourceSize":{"w":209,"h":180}},"Fourmie_Bleu.png":{"frame":{"x":1921,"y":3102,"w":57,"h":70},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":57,"h":70},"sourceSize":{"w":57,"h":70}},"Fourmie_Rouge.png":{"frame":{"x":1979,"y":3102,"w":57,"h":70},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":57,"h":70},"sourceSize":{"w":57,"h":70}},"Fourmies_Nombre_Bleu.png":{"frame":{"x":1953,"y":2253,"w":331,"h":191},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":331,"h":191},"sourceSize":{"w":331,"h":191}},"Fourmies_Nombre_Rouge.png":{"frame":{"x":1953,"y":2445,"w":331,"h":191},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":331,"h":191},"sourceSize":{"w":331,"h":191}},"Fourmiliere_Bleu.png":{"frame":{"x":651,"y":1502,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Fourmiliere_Rouge.png":{"frame":{"x":1302,"y":1502,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"HUD.png":{"frame":{"x":0,"y":3004,"w":1920,"h":174},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":1920,"h":174},"sourceSize":{"w":1920,"h":174}},"Jauge_Bleu.png":{"frame":{"x":1921,"y":3004,"w":308,"h":48},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":308,"h":48},"sourceSize":{"w":308,"h":48}},"Jauge_Rouge.png":{"frame":{"x":1921,"y":3053,"w":307,"h":48},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":307,"h":48},"sourceSize":{"w":307,"h":48}},"Oeufs_1.png":{"frame":{"x":1953,"y":0,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Oeufs_2.png":{"frame":{"x":1953,"y":751,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Oeufs_3.png":{"frame":{"x":1953,"y":1502,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Oeufs_4.png":{"frame":{"x":0,"y":2253,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Oeufs_Nombre.png":{"frame":{"x":2163,"y":2818,"w":159,"h":84},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":159,"h":84},"sourceSize":{"w":159,"h":84}},"Oeufs.png":{"frame":{"x":651,"y":2253,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}},"Oeufs_5.png":{"frame":{"x":1302,"y":2253,"w":650,"h":750},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":650,"h":750},"sourceSize":{"w":650,"h":750}}},"meta":{"app":"https://www.leshylabs.com/apps/sstool/","version":"Leshy SpriteSheet Tool v0.8.4","image":"spritesheet.png","size":{"w":2603,"h":3178},"scale":1}} -------------------------------------------------------------------------------- /src/main/resources/view/assets/spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGame/SpringChallenge2023/dd151f9f8ce725f6012e312f188f11db70dd7616/src/main/resources/view/assets/spritesheet.png -------------------------------------------------------------------------------- /src/main/resources/view/config.js: -------------------------------------------------------------------------------- 1 | import { ViewModule, api } from './graphics/ViewModule.js' 2 | import { EndScreenModule } from './endscreen-module/EndScreenModule.js'; 3 | 4 | // List of viewer modules that you want to use in your game 5 | export const modules = [ 6 | ViewModule, 7 | EndScreenModule 8 | ] 9 | 10 | export const playerColors = [ 11 | '#22a1e4', // curious blue 12 | '#ff1d5c' // radical red 13 | ] 14 | 15 | export const options = [{ 16 | title: 'DEBUG MODE', 17 | get: function () { 18 | return api.options.debugMode 19 | }, 20 | set: function (value) { 21 | api.options.debugMode = value 22 | api.setDebug(value) 23 | }, 24 | values: { 25 | 'ON': true, 26 | 'BLUE': 0, 27 | 'RED': 1, 28 | 'OFF': false 29 | } 30 | },{ 31 | title: 'ANTS', 32 | get: function () { 33 | return api.options.seeAnts 34 | }, 35 | set: function (value) { 36 | api.options.seeAnts = value 37 | api.setSeeAnts(value) 38 | }, 39 | values: { 40 | 'NUMBERS': false, 41 | 'PARTICLES': true 42 | } 43 | }] 44 | 45 | 46 | export const gameName = 'UTG2022' 47 | 48 | export const stepByStepAnimateSpeed = 1 49 | -------------------------------------------------------------------------------- /src/main/resources/view/demo.js: -------------------------------------------------------------------------------- 1 | export const demo = 2 | {"views":["KEY_FRAME 0\n{\"global\":{\"graphics\":\"23;0 0 0 2 0 0 -1;1 0 18 2 0 0 -1;-1 0 18 2 0 0 -1;1 -1 0 2 0 0 -1;-1 1 0 2 0 0 -1;2 0 25 2 0 0 -1;-2 0 25 2 0 0 -1;2 -2 0 2 0 0 -1;-2 2 0 2 0 0 -1;1 -2 0 2 0 0 -1;-1 2 0 2 0 0 -1;0 -2 0 2 0 0 -1;0 2 0 2 0 0 -1;-1 -1 0 2 0 0 -1;1 1 0 2 0 0 -1;3 0 0 2 0 0 -1;-3 0 0 2 0 0 -1;3 -1 18 2 0 0 -1;-3 1 18 2 0 0 -1;-1 -2 0 2 0 0 -1;1 2 0 2 0 0 -1;-2 -1 0 2 0 60 1;2 1 0 2 60 0 0\"},\"frame\":{\"duration\":500,\"graphics\":\"0;0;;;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0;0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0;0\"}}\n","INTERMEDIATE_FRAME 1\n","KEY_FRAME 2\n{\"duration\":1000,\"graphics\":\"20;6;;;0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1;1 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 1 1 0 0 1 0;7;1;0;500;0;20;22;5;;1;0;500;0;20;22;14;;1;0;500;1;6;21;6;;1;0;500;1;36;21;13;;1;0;500;1;6;21;16;;2;500;1000;0;20;;;5 22;2;500;1000;1;6;;;6 21\"}\n","INTERMEDIATE_FRAME 3\n","KEY_FRAME 4\n{\"graphics\":\"38;24;;;0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1;1 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 1 1 0 0 1 0;7;1;0;500;0;20;5;1;;1;0;500;1;6;16;18;;1;0;500;1;30;13;2;;2;500;1000;0;18;;;1 14 22;2;500;1000;1;6;;;6 21;2;500;1000;1;6;;;18 6 21;2;500;1000;1;6;;;2 13 21\"}\n","INTERMEDIATE_FRAME 5\n","KEY_FRAME 6\n{\"graphics\":\"38;42;;;0 0 1 0 1 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 1;1 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 1 1 0 0 1 0;7;1;0;500;0;20;1;0;;1;0;500;0;10;22;14;;1;0;500;0;10;14;12;;1;0;500;1;24;2;0;;2;500;1000;1;6;;;6 21;2;500;1000;1;6;;;18 6 21;2;500;1000;1;6;;;2 13 21\"}\n","INTERMEDIATE_FRAME 7\n","KEY_FRAME 8\n{\"graphics\":\"38;60;;;0 0 1 0 1 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 1;1 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 1 1 0 0 1 0;7;1;0;500;0;10;0;2;;1;0;500;0;10;0;4;;1;0;500;0;10;14;12;;1;0;500;1;18;0;1;;2;500;1000;1;6;;;6 21;2;500;1000;1;6;;;18 6 21;2;500;1000;1;6;;;2 13 21\"}\n","INTERMEDIATE_FRAME 9\n","KEY_FRAME 10\n{\"graphics\":\"43;66;;;0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1;1 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0;11;1;0;500;0;20;12;14;;1;0;500;0;10;14;5;;1;0;500;0;10;4;0;;1;0;500;0;10;2;0;;1;0;500;1;2;1;0;;1;0;500;1;6;18;6;;1;0;500;1;9;1;5;;1;0;500;1;1;21;13;;2;500;1000;0;5;;;5 22;2;500;1000;1;5;;;5 1 0 2 13 21;2;500;1000;1;1;;;6 21\",\"endScreen\":[[43,66],\"logo.png\",null]}\n"],"agents":[{"index":0,"name":"Boss1","avatar":"https://static.codingame.com/servlet/fileservlet?id=16085734516701&format=viewer_avatar","agentId":0},{"index":1,"name":"Boss2","avatar":"https://static.codingame.com/servlet/fileservlet?id=16085846089817&format=viewer_avatar","agentId":1}]}; 3 | -------------------------------------------------------------------------------- /src/main/resources/view/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "view", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "config.js", 6 | "dependencies": { 7 | "pixi.js": "^5.0.0" 8 | }, 9 | "devDependencies": { 10 | "@types/pixi.js": "^5.0.0", 11 | "@typescript-eslint/eslint-plugin": "2.28.0", 12 | "@typescript-eslint/parser": "2.28.0", 13 | "eslint": "6.8.0", 14 | "eslint-config-standard": "14.1.1", 15 | "eslint-config-standard-jsx": "8.1.0", 16 | "eslint-loader": "3.0.4", 17 | "eslint-plugin-import": "2.20.2", 18 | "eslint-plugin-node": "11.1.0", 19 | "eslint-plugin-promise": "4.2.1", 20 | "eslint-plugin-react": "7.19.0", 21 | "eslint-plugin-react-hooks": "2.5.1", 22 | "eslint-plugin-standard": "4.0.1", 23 | "typescript": "3.8.3" 24 | }, 25 | "scripts": { 26 | "test": "echo \"Error: no test specified\" && exit 1", 27 | "start": "tsc -w" 28 | }, 29 | "author": "", 30 | "license": "ISC" 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/Deserializer.ts: -------------------------------------------------------------------------------- 1 | 2 | import { CellDto, EventDto, FrameDataDto, GlobalData, GlobalDataDto } from './types.js' 3 | 4 | const MAIN_SEPARATOR = ';' 5 | 6 | function splitLine (str) { 7 | return str.length === 0 ? [] : str.split(' ') 8 | } 9 | 10 | export function parseData (unsplit: string, globalData: GlobalData): FrameDataDto { 11 | const raw = unsplit.split(MAIN_SEPARATOR) 12 | 13 | let idx = 0 14 | const scores = [+raw[idx++], +raw[idx++]] 15 | const messages = [raw[idx++], raw[idx++]] 16 | const beacons = [] 17 | for (let i = 0; i < globalData.playerCount; ++i) { 18 | beacons.push(splitLine(raw[idx++]).map(v => +v)) 19 | } 20 | const events: EventDto[] = [] 21 | const eventCount = +raw[idx++] 22 | for (let i = 0; i < eventCount; ++i) { 23 | const type = +raw[idx++] 24 | const start = +raw[idx++] 25 | const end = +raw[idx++] 26 | const playerIdx = +raw[idx++] 27 | const amount = +raw[idx++] 28 | const cellIdx = +raw[idx++] 29 | const targetIdx = +raw[idx++] 30 | const path = splitLine(raw[idx++]).map(v => +v) 31 | const animData = { start, end } 32 | 33 | events.push({ 34 | playerIdx, 35 | amount, 36 | cellIdx, 37 | targetIdx, 38 | path, 39 | type, 40 | animData 41 | }) 42 | } 43 | 44 | const parsed = { 45 | events, 46 | scores, 47 | messages, 48 | beacons 49 | } 50 | 51 | return parsed 52 | } 53 | 54 | export function parseGlobalData (unsplit: string): GlobalDataDto { 55 | const raw = unsplit.split(MAIN_SEPARATOR) 56 | let idx = 0 57 | 58 | const cells: CellDto[] = [] 59 | const cellCount = +raw[idx++] 60 | for (let i = 0; i < cellCount; ++i) { 61 | const rawCell = splitLine(raw[idx++]) 62 | const cell: CellDto = { 63 | index: i, 64 | q: +rawCell[0], 65 | r: +rawCell[1], 66 | richness: +rawCell[2], 67 | type: +rawCell[3], 68 | ants: [+rawCell[4], +rawCell[5]], 69 | owner: +rawCell[6] 70 | } 71 | cells.push(cell) 72 | } 73 | const parsed = { 74 | cells 75 | } 76 | return parsed 77 | } 78 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/TooltipManager.ts: -------------------------------------------------------------------------------- 1 | import { HEIGHT } from '../core/constants.js' 2 | 3 | /* global PIXI */ 4 | 5 | const PADDING = 5 6 | const CURSOR_WIDTH = 20 7 | 8 | function generateText (text, size, color, align): PIXI.Text { 9 | var textEl = new PIXI.Text(text, { 10 | fontSize: Math.round(size / 1.2) + 'px', 11 | fontFamily: 'Lato', 12 | fontWeight: 'bold', 13 | fill: color, 14 | lineHeight: size 15 | }) 16 | 17 | if (align === 'right') { 18 | textEl.anchor.x = 1 19 | } else if (align === 'center') { 20 | textEl.anchor.x = 0.5 21 | } 22 | 23 | return textEl 24 | }; 25 | 26 | export class TooltipManager { 27 | tooltip: PIXI.Container 28 | tooltipContainer: PIXI.Container 29 | tooltipLabel: PIXI.Text 30 | tooltipBackground: PIXI.Graphics 31 | tooltipOffset: number 32 | registry: {element: PIXI.DisplayObject, getText: () => string}[] 33 | inside: {[id: number]: boolean} 34 | getGlobalText: (data: PIXI.InteractionData) => string 35 | lastEvent: PIXI.InteractionEvent 36 | 37 | reinit () { 38 | const container = new PIXI.Container() 39 | const tooltip = new PIXI.Container() 40 | const background = new PIXI.Graphics() 41 | const label = generateText('DEFAULT', 36, 0xFFFFFF, 'left') 42 | 43 | label.position.x = PADDING 44 | label.position.y = PADDING 45 | 46 | tooltip.visible = false 47 | tooltip.addChild(background) 48 | tooltip.addChild(label) 49 | this.tooltipBackground = background 50 | this.tooltipLabel = label 51 | this.tooltipContainer = container 52 | this.tooltip = tooltip 53 | this.registry = [] 54 | this.inside = {} 55 | this.tooltipOffset = 0 56 | this.getGlobalText = null 57 | 58 | container.addChild(this.tooltip) 59 | return container 60 | } 61 | 62 | clear () { 63 | this.inside = {} 64 | } 65 | 66 | registerGlobal (getText: (data: PIXI.InteractionData) => string) { 67 | this.getGlobalText = getText 68 | } 69 | 70 | register (element: PIXI.DisplayObject, getText: () => string) { 71 | const registryIdx = this.registry.length 72 | this.registry.push({ element, getText }) 73 | element.on('mouseover', () => { 74 | this.inside[registryIdx] = true 75 | }) 76 | 77 | element.on('mouseout', () => { 78 | delete this.inside[registryIdx] 79 | }) 80 | } 81 | 82 | showTooltip (text: string) { 83 | this.setTooltipText(this.tooltip, text) 84 | } 85 | 86 | setTooltipText (tooltip: PIXI.Container, text: string) { 87 | this.tooltipLabel.text = text 88 | 89 | const width = this.tooltipLabel.width + PADDING * 2 90 | const height = this.tooltipLabel.height + PADDING * 2 91 | 92 | this.tooltipOffset = -width 93 | 94 | this.tooltipBackground.clear() 95 | this.tooltipBackground.beginFill(0x0, 0.9) 96 | this.tooltipBackground.drawRect(0, 0, width, height) 97 | this.tooltipBackground.endFill() 98 | 99 | tooltip.visible = true 100 | } 101 | 102 | updateGlobalText () { 103 | if (this.lastEvent != null) { 104 | this.moveTooltip(this.lastEvent) 105 | } 106 | } 107 | 108 | moveTooltip (event: PIXI.InteractionEvent) { 109 | this.lastEvent = event 110 | 111 | const newPosition = event.data.getLocalPosition(this.tooltipContainer) 112 | 113 | let xOffset = this.tooltipOffset - 20 114 | let yOffset = +40 115 | 116 | if (newPosition.x + xOffset < 0) { 117 | xOffset = CURSOR_WIDTH 118 | } 119 | 120 | if (newPosition.y + this.tooltip.height > HEIGHT) { 121 | yOffset = HEIGHT - newPosition.y - this.tooltip.height 122 | } 123 | 124 | this.tooltip.position.x = newPosition.x + xOffset 125 | this.tooltip.position.y = newPosition.y + yOffset 126 | 127 | const textBlocks = [] 128 | for (const key of Object.keys(this.inside)) { 129 | const registryIdx = parseInt(key) 130 | const { getText } = this.registry[registryIdx] 131 | 132 | const text = getText() 133 | if (text != null && text.length > 0) { 134 | textBlocks.push(text) 135 | } 136 | } 137 | 138 | if (this.getGlobalText != null) { 139 | const text = this.getGlobalText(event.data) 140 | if (text != null && text.length > 0) { 141 | textBlocks.push(text) 142 | } 143 | } 144 | if (textBlocks.length > 0) { 145 | this.showTooltip(textBlocks.join('\n--------\n')) 146 | } else { 147 | this.hideTooltip() 148 | } 149 | } 150 | 151 | hideTooltip () { 152 | this.tooltip.visible = false 153 | } 154 | } 155 | 156 | function pointWithinBound (position: PIXI.IPoint, bounds: PIXI.Rectangle) { 157 | return position.x <= bounds.x + bounds.width && 158 | position.y <= bounds.y + bounds.height && 159 | position.x >= bounds.x && 160 | position.y >= bounds.y 161 | } 162 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/assetConstants.ts: -------------------------------------------------------------------------------- 1 | export const BACKGROUND = 'Background.jpg' 2 | 3 | export const HUD_COLOR_PLAYER = [0x00ffff, 0xffa7a7] 4 | 5 | export const SPEECH_WIDTH = 454 6 | export const SPEECH_HEIGHT = 72 7 | export const SPEECH_PAD_X = 24 8 | export const SPEECH_PAD_Y = 2 9 | export const SPEECH_OFFSET_X = 120 10 | export const SPEECH_Y = 69 11 | 12 | export const HUD_HEIGHT = 174 13 | export const TILE_HEIGHT = 750 14 | export const ANT_HEIGHT = 69 15 | export const BEACON_HEIGHT = 750 16 | export const CONVEYOR_HEIGHT = 32 17 | export const CONVEYOR_WIDTH = 27 18 | export const INDICATOR_OFFSET = 334 19 | 20 | export const COLOR_NAMES = ['Bleu', 'Rouge'] 21 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/events.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | BUILD: 0, 3 | MOVE: 1, 4 | FOOD: 2, 5 | BEACON: 3 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/gameConstants.ts: -------------------------------------------------------------------------------- 1 | export const EGG = 1 2 | export const POINTS = 2 3 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/global/core.d.ts: -------------------------------------------------------------------------------- 1 | declare module '../core/*.js' { 2 | export const WIDTH: number 3 | export const HEIGHT: number 4 | export const BASE_FRAME_DURATION: number 5 | export function bell (t: number): number 6 | export function elastic (t: number): number 7 | export function ease (t: number): number 8 | export function easeIn (t: number): number 9 | export function easeOut (t: number): number 10 | export function randInt (a: number, b?: number): number 11 | export function lerp (a: number, b: number, u: number): number 12 | export function unlerpUnclamped (a: number, b: number, v: number): number 13 | export function lerpAngle (start: number, end: number, amount: number, maxDelta?: number): number 14 | export function lerpPosition (from: {x: number, y: number}, to: {x: number, y: number}, p: number): {x: number, y: number} 15 | export function lerpColor (start: number, end: number, amount: number): number 16 | export function unlerp (a: number, b: number, v): number 17 | export function pushAll (self: {push: (item: T) => void}, arr: T[]): void 18 | export function fitAspectRatio (srcWidth: number, srcHeight: number, maxWidth: number, maxHeight: number, padding?: number): number 19 | export function paddingString (word: string, width: number, char: string): string 20 | export function flagForDestructionOnReinit (destroyable: any): void 21 | export function getRenderer(): PIXI.Renderer 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/global/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | debug: any 3 | } 4 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/hex.ts: -------------------------------------------------------------------------------- 1 | import { TILE_HEIGHT } from './assetConstants.js' 2 | import { Point } from './utils.js' 3 | 4 | interface Hex { 5 | q: number 6 | r: number 7 | } 8 | 9 | interface Cube { 10 | q: number 11 | r: number 12 | s: number 13 | } 14 | 15 | const TILE_SEPERATION = 20 16 | 17 | export const HEXAGON_HEIGHT = TILE_HEIGHT + TILE_SEPERATION 18 | export const HEXAGON_RADIUS = HEXAGON_HEIGHT / 2 19 | export const HEXAGON_WIDTH = HEXAGON_RADIUS * Math.sqrt(3) 20 | export const HEXAGON_Y_SEP = HEXAGON_RADIUS * 3 / 2 21 | 22 | export function hexToScreen (q: number, r: number): Point { 23 | const x = HEXAGON_RADIUS * (Math.sqrt(3) * q + Math.sqrt(3) / 2 * r) 24 | const y = HEXAGON_RADIUS * 3 / 2 * r 25 | 26 | return { x, y } 27 | } 28 | 29 | /** 30 | * https://www.redblobgames.com/grids/hexagons/#pixel-to-hex 31 | */ 32 | export function screenToHex (point: PIXI.IPointData): Hex { 33 | const q = (Math.sqrt(3) / 3 * point.x - 1.0 / 3 * point.y) / HEXAGON_RADIUS 34 | const r = (2.0 / 3 * point.y) / HEXAGON_RADIUS 35 | return axialRound({ q, r }) 36 | } 37 | 38 | function axialRound (hex: Hex): Hex { 39 | return cubeToAxial(cubeRound(axialToCube(hex))) 40 | } 41 | 42 | function axialToCube (hex: Hex): Cube { 43 | const q = hex.q 44 | const r = hex.r 45 | const s = -q - r 46 | return { q, r, s } 47 | } 48 | 49 | function cubeToAxial (cube: Cube): Hex { 50 | const q = cube.q 51 | const r = cube.r 52 | return { q, r } 53 | } 54 | 55 | function cubeRound (frac: Cube): Cube { 56 | let q = Math.round(frac.q) 57 | let r = Math.round(frac.r) 58 | let s = Math.round(frac.s) 59 | 60 | const qDiff = Math.abs(q - frac.q) 61 | const rDiff = Math.abs(r - frac.r) 62 | const sDiff = Math.abs(s - frac.s) 63 | 64 | if (qDiff > rDiff && qDiff > sDiff) { 65 | q = -r - s 66 | } else if (rDiff > sDiff) { 67 | r = -q - s 68 | } else { 69 | s = -q - r 70 | } 71 | 72 | return { q, r, s } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/pathSegments.ts: -------------------------------------------------------------------------------- 1 | import { AnimData, EventDto, AggregatedPathsEvent, PathSegment } from './types' 2 | import { keyOf, last } from './utils.js' 3 | 4 | interface PathSegmentKey { 5 | key: string 6 | from: number 7 | to: number 8 | } 9 | 10 | function * pathToKeys (path: number[]): Generator { 11 | let fromIdx = path[0] 12 | 13 | for (const toIdx of path.slice(1)) { 14 | const k = keyOf(fromIdx, toIdx) 15 | yield { key: k, from: fromIdx, to: toIdx } 16 | fromIdx = toIdx 17 | } 18 | } 19 | 20 | export function computePathSegments (events: EventDto[], playerIdx: number, type: number): AggregatedPathsEvent { 21 | if (events.length === 0) { 22 | return null 23 | } 24 | const segmentMap: Record = {} 25 | let startAnim = Infinity 26 | let endAnim = -Infinity 27 | let total = 0 28 | const bouncing = [] 29 | 30 | let totalMap = {} 31 | 32 | for (let event of events) { 33 | startAnim = Math.min(startAnim, event.animData.start) 34 | endAnim = Math.max(endAnim, event.animData.end) 35 | for (const { key, from, to } of pathToKeys(event.path)) { 36 | segmentMap[key] = segmentMap[key] ?? { 37 | pathKeys: new Set(), 38 | amount: 0, 39 | from, 40 | to, 41 | key 42 | } 43 | const segment = segmentMap[key] 44 | 45 | segment.amount += event.amount 46 | total += event.amount 47 | 48 | for (const { key } of pathToKeys(event.path)) { 49 | segment.pathKeys.add(key) 50 | } 51 | } 52 | const hillIdx = last(event.path) 53 | totalMap[hillIdx] = (totalMap[hillIdx] ?? 0) + event.amount 54 | bouncing.push(event.path[0]) 55 | } 56 | 57 | return { 58 | animData: { 59 | start: startAnim, 60 | end: endAnim 61 | }, 62 | segmentMap: segmentMap, 63 | segments: Object.values(segmentMap), 64 | totals: Object.entries(totalMap).map(([k, v]) => ({ cellIdx: +k, amount: v })), 65 | bouncing, 66 | type, 67 | playerIdx 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/types.ts: -------------------------------------------------------------------------------- 1 | import { IPoint, IPointData } from 'pixi.js' 2 | 3 | export type ContainerConsumer = (layer: PIXI.Container) => void 4 | 5 | /** 6 | * Given by the SDK 7 | */ 8 | export interface FrameInfo { 9 | number: number 10 | frameDuration: number 11 | date: number 12 | } 13 | /** 14 | * Given by the SDK 15 | */ 16 | export interface CanvasInfo { 17 | width: number 18 | height: number 19 | oversampling: number 20 | } 21 | /** 22 | * Given by the SDK 23 | */ 24 | export interface PlayerInfo { 25 | name: string 26 | avatar: PIXI.Texture 27 | color: number 28 | index: number 29 | isMe: boolean 30 | number: number 31 | type?: string 32 | } 33 | 34 | export interface EventDto { 35 | type: number 36 | animData: AnimData 37 | cellIdx: number 38 | targetIdx: number 39 | amount: number 40 | playerIdx: number 41 | path: number[] 42 | 43 | /* Generated locally */ 44 | double?: boolean 45 | crisscross?: boolean 46 | } 47 | 48 | export interface FrameDataDto { 49 | scores: number[] 50 | events: EventDto[] 51 | messages: string[] 52 | beacons: number[][] 53 | } 54 | 55 | export interface PathSegment { 56 | key: string 57 | from: number 58 | to: number 59 | amount: number 60 | pathKeys: Set 61 | } 62 | 63 | export interface AggregatedPathsEvent { 64 | type: number 65 | animData: AnimData 66 | segments: PathSegment[] 67 | segmentMap: Record 68 | totals: {cellIdx: number, amount: number}[] 69 | playerIdx: number 70 | bouncing: number[] 71 | } 72 | 73 | export interface FrameData extends FrameDataDto, FrameInfo { 74 | number: number 75 | previous: FrameData 76 | ants: number[][] 77 | richness: number[] 78 | syntheticEvents: AggregatedPathsEvent[] 79 | buildAmount: number[][] 80 | antTotals: number[] 81 | consumedFrom: Set[] 82 | } 83 | 84 | export interface CoordDto { 85 | x: number 86 | y: number 87 | } 88 | 89 | export interface CellDto { 90 | q: number 91 | r: number 92 | richness: number 93 | index: number 94 | owner: number 95 | type: number 96 | ants: number[] 97 | } 98 | 99 | export interface GlobalDataDto { 100 | cells: CellDto[] 101 | } 102 | export interface GlobalData extends GlobalDataDto { 103 | players: PlayerInfo[] 104 | playerCount: number 105 | anthills: number[][] 106 | maxScore: number 107 | } 108 | 109 | export interface AnimData { 110 | start: number 111 | end: number 112 | } 113 | 114 | export interface Effect { 115 | busy: boolean 116 | display: T 117 | } 118 | 119 | /* View entities */ 120 | export interface Hex { 121 | container: PIXI.Container 122 | data: CellDto 123 | texts: PIXI.Text[] 124 | indicators: PIXI.Sprite[] 125 | foodText: PIXI.Text | null 126 | foodTextBackground: PIXI.Sprite | null 127 | icon: PIXI.Sprite | null 128 | iconBounceContainer: PIXI.Container | null 129 | bouncing: boolean 130 | indicatorLayer: PIXI.Container 131 | } 132 | export interface Tile { 133 | container: PIXI.Container 134 | sprite: PIXI.Sprite 135 | } 136 | 137 | export interface AntParticleGroup { 138 | particles: AntParticle[] 139 | fromIdx?: number 140 | toIdx?: number 141 | cellIdx?: number 142 | playerIdx: number 143 | animData?: AnimData 144 | } 145 | 146 | export interface AntParticle { 147 | offset: IPointData 148 | random: number 149 | direction: number 150 | sprite: PIXI.Sprite 151 | placed: boolean 152 | } 153 | 154 | export interface SfxData { 155 | angle: number 156 | speed: number 157 | size: number 158 | deathAt: number 159 | } 160 | 161 | export interface Explosion extends AnimData { 162 | cellIdx: number 163 | data: SfxData[] 164 | } 165 | -------------------------------------------------------------------------------- /src/main/resources/view/ts/utils.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '../core/utils.js' 2 | 3 | export function setAnimationProgress (fx: PIXI.AnimatedSprite, progress: number): void { 4 | let idx = Math.floor(progress * fx.totalFrames) 5 | idx = Math.min(fx.totalFrames - 1, idx) 6 | fx.gotoAndStop(idx) 7 | } 8 | 9 | export function distance (a, b) { 10 | return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)) 11 | } 12 | 13 | export function fit (entity: PIXI.DisplayObject & {width: number, height: number}, maxWidth: number, maxHeight: number): void { 14 | entity.scale.set(utils.fitAspectRatio(entity.width, entity.height, maxWidth, maxHeight)) 15 | } 16 | 17 | export interface Point { 18 | x: number 19 | y: number 20 | } 21 | 22 | export function setSize (sprite: PIXI.Sprite | PIXI.Container, size: number): void { 23 | sprite.width = size 24 | sprite.height = size 25 | } 26 | 27 | export function bounce (t: number): number { 28 | return 1 + (Math.sin(t * 10) * 0.5 * Math.cos(t * 3.14 / 2)) * (1 - t) * (1 - t) 29 | } 30 | 31 | export function generateText (text: string | number, color: number, size: number, strokeThickness = 4): PIXI.Text { 32 | const drawnText = new PIXI.Text(typeof text === 'number' ? '' + text : text, { 33 | fontSize: Math.round(size) + 'px', 34 | fontFamily: 'Arial', 35 | fontWeight: 'bold', 36 | fill: color, 37 | stroke: 0x0, 38 | strokeThickness, 39 | lineHeight: Math.round(size) 40 | }) 41 | drawnText.anchor.x = 0.5 42 | drawnText.anchor.y = 0.5 43 | return drawnText 44 | } 45 | 46 | export function last (arr: T[]): T { 47 | return arr[arr.length - 1] 48 | } 49 | 50 | export function keyOf (x: number, y: number): string { 51 | return `${x},${y}` 52 | } 53 | export function angleDiff (a: number, b: number): number { 54 | return Math.abs(utils.lerpAngle(a, b, 0) - utils.lerpAngle(a, b, 1)) 55 | } 56 | 57 | export function randomChoice (rand: number, coeffs: number[]): number { 58 | const total = coeffs.reduce((a, b) => a + b, 0) 59 | const b = 1 / total 60 | const weights = coeffs.map(v => v * b) 61 | let cur = 0 62 | for (let i = 0; i < weights.length; ++i) { 63 | cur += weights[i] 64 | if (cur >= rand) { 65 | return i 66 | } 67 | } 68 | return 0 69 | } 70 | 71 | export function sum (arr: number[]): number { 72 | return arr.reduce((a, b) => a + b, 0) 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/view/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "checkJs": false, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "jsx": "react", 10 | "lib": [ 11 | "dom", 12 | "dom.iterable", 13 | "esnext" 14 | ], 15 | "module": "es2015", 16 | "moduleResolution": "node", 17 | "noEmit": false, 18 | "noImplicitReturns": true, 19 | "noImplicitAny": false, 20 | "resolveJsonModule": false, 21 | "skipLibCheck": true, 22 | "sourceMap": false, 23 | "target": "es6", 24 | "outDir": "graphics", 25 | "types": ["pixi.js"] 26 | }, 27 | "exclude": [ 28 | "node_modules", 29 | "config.js", 30 | "demo.js", 31 | "graphics", 32 | "endscreen", 33 | "event", 34 | ".vscode" 35 | ] 36 | } -------------------------------------------------------------------------------- /src/test/java/Spring2023Main.java: -------------------------------------------------------------------------------- 1 | import java.io.File; 2 | import java.io.IOException; 3 | 4 | import com.codingame.gameengine.runner.MultiplayerGameRunner; 5 | import com.google.common.io.Files; 6 | 7 | public class Spring2023Main { 8 | 9 | public static void main(String[] args) throws IOException, InterruptedException { 10 | 11 | MultiplayerGameRunner gameRunner = new MultiplayerGameRunner(); 12 | // gameRunner.setSeed(-8358938852454912011l); 13 | gameRunner.addAgent("python3 config/Boss.py", "TestBoss_1"); 14 | gameRunner.addAgent("python3 config/Boss.py", "TestBoss_2"); 15 | gameRunner.setLeagueLevel(3); 16 | 17 | gameRunner.start(); 18 | } 19 | 20 | private static String compile(String botFile) throws IOException, InterruptedException { 21 | 22 | File outFolder = Files.createTempDir(); 23 | 24 | System.out.println("Compiling Boss.java... " + botFile); 25 | Process compileProcess = Runtime.getRuntime() 26 | .exec(new String[] { "bash", "-c", "javac " + botFile + " -d " + outFolder.getAbsolutePath() }); 27 | compileProcess.waitFor(); 28 | return "java -cp " + outFolder + " Player"; 29 | } 30 | 31 | private static String[] compileTS(String botFile) throws IOException, InterruptedException { 32 | 33 | System.out.println("Compiling ... " + botFile); 34 | 35 | Process compileProcess = Runtime.getRuntime().exec( 36 | new String[] { "bash", "-c", "npx tsc --target ES2018 --inlineSourceMap --types ./typescript/readline/ " 37 | + botFile + " --outFile /tmp/Boss.js" } 38 | ); 39 | compileProcess.waitFor(); 40 | 41 | return new String[] { "bash", "-c", "node -r ./typescript/polyfill.js /tmp/Boss.js" }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | # Configuration 2 | name = PropertiesConfig 3 | status = WARN 4 | 5 | appender.console.type = Console 6 | appender.console.name = CONSOLE 7 | appender.console.layout.type = PatternLayout 8 | appender.console.layout.pattern = [%d{HH:mm:ss}] %-5p : %c{1} - %m%n 9 | 10 | rootLogger.level = warn 11 | rootLogger.appenderRef.console.ref = CONSOLE 12 | -------------------------------------------------------------------------------- /starterAIs/README.md: -------------------------------------------------------------------------------- 1 | # Starter AIs 2 | 3 | These starter AIs are meant to help new players to parse the input and have a small structure to begin with. 4 | 5 | A few lines of logic on top of these should be enough to beat the first boss. 6 | 7 | Feel free to contribute your own starter AI in another language to help other players by making a PR. We'll be happy to add your starter AI to the list. 8 | 9 | Thank you! 10 | -------------------------------------------------------------------------------- /starterAIs/Starter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | class Cell { 9 | public: 10 | int index, cellType, resources, myAnts, oppAnts; 11 | vector neighbors; 12 | 13 | Cell(int index, int cellType, int resources, vector neighbors, int myAnts = 0, int oppAnts = 0) { 14 | this->index = index; 15 | this->cellType = cellType; 16 | this->resources = resources; 17 | this->neighbors = neighbors; 18 | this->myAnts = myAnts; 19 | this->oppAnts = oppAnts; 20 | } 21 | }; 22 | 23 | int main() 24 | { 25 | vector cells; 26 | vector myBases, oppBases; 27 | int numberOfCells; // amount of hexagonal cells in this map 28 | cin >> numberOfCells; cin.ignore(); 29 | for (int i = 0; i < numberOfCells; i++) { 30 | int type; // 0 for empty, 1 for eggs, 2 for crystal 31 | int initialResources; // the initial amount of eggs/crystals on this cell 32 | int neigh0; // the index of the neighbouring cell for each direction 33 | int neigh1; 34 | int neigh2; 35 | int neigh3; 36 | int neigh4; 37 | int neigh5; 38 | cin >> type >> initialResources >> neigh0 >> neigh1 >> neigh2 >> neigh3 >> neigh4 >> neigh5; cin.ignore(); 39 | Cell cell(i, type, initialResources, {neigh0, neigh1, neigh2, neigh3, neigh4, neigh5}, 0, 0); 40 | cells.push_back(cell); 41 | } 42 | 43 | int numberOfBases; 44 | cin >> numberOfBases; cin.ignore(); 45 | for (int i = 0; i < numberOfBases; i++) { 46 | int myBaseIndex; 47 | cin >> myBaseIndex; cin.ignore(); 48 | myBases.push_back(myBaseIndex); 49 | } 50 | for (int i = 0; i < numberOfBases; i++) { 51 | int oppBaseIndex; 52 | cin >> oppBaseIndex; cin.ignore(); 53 | oppBases.push_back(oppBaseIndex); 54 | } 55 | 56 | // game loop 57 | while (1) { 58 | string actions = "WAIT"; 59 | for (int i = 0; i < numberOfCells; i++) { 60 | int resources; // the current amount of eggs/crystals on this cell 61 | int myAnts; // the amount of your ants on this cell 62 | int oppAnts; // the amount of opponent ants on this cell 63 | cin >> resources >> myAnts >> oppAnts; cin.ignore(); 64 | 65 | cells[i].resources = resources; 66 | cells[i].myAnts = myAnts; 67 | cells[i].oppAnts = oppAnts; 68 | } 69 | 70 | //TODO: choose actions to perform and add them into actions. E.g: 71 | for (const auto& cell : cells) { 72 | if (cell.resources > 0) { 73 | actions.push_back("LINE " + std::to_string(my_bases[0]) + " " + std::to_string(cell.index) + " 1"); 74 | break; 75 | } 76 | } 77 | 78 | 79 | // To debug: cerr << "Debug messages..." << endl; 80 | // WAIT | LINE | BEACON | MESSAGE 81 | if (actions.length() == 0){ 82 | cout << "WAIT" << endl; 83 | } else { 84 | actions += ';'; 85 | cout << actions << endl; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /starterAIs/Starter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.IO; 4 | using System.Text; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | /** 9 | * Auto-generated code below aims at helping you parse 10 | * the standard input according to the problem statement. 11 | **/ 12 | class Player 13 | { 14 | static void Main(string[] args) 15 | { 16 | string[] inputs; 17 | int numberOfCells = int.Parse(Console.ReadLine()); // amount of hexagonal cells in this map 18 | 19 | List cells = new List(); 20 | 21 | for (int i = 0; i < numberOfCells; i++) 22 | { 23 | inputs = Console.ReadLine().Split(' '); 24 | int type = int.Parse(inputs[0]); // 0 for empty, 1 for eggs, 2 for crystal 25 | int initialResources = int.Parse(inputs[1]); // the initial amount of eggs/crystals on this cell 26 | int neigh0 = int.Parse(inputs[2]); // the index of the neighbouring cell for each direction 27 | int neigh1 = int.Parse(inputs[3]); 28 | int neigh2 = int.Parse(inputs[4]); 29 | int neigh3 = int.Parse(inputs[5]); 30 | int neigh4 = int.Parse(inputs[6]); 31 | int neigh5 = int.Parse(inputs[7]); 32 | 33 | Cell cell = new Cell(); 34 | cell.index = i; 35 | cell.cell_type = type; 36 | cell.resources = initialResources; 37 | 38 | cell.neighbors = new int[6] { neigh0, neigh1, neigh2, neigh3, neigh4, neigh5 }; 39 | 40 | cell.my_ants = 0; 41 | cell.opp_ants = 0; 42 | 43 | cells.Add(cell); 44 | } 45 | int numberOfBases = int.Parse(Console.ReadLine()); 46 | 47 | List my_bases = new List(); 48 | List opp_bases = new List(); 49 | 50 | inputs = Console.ReadLine().Split(' '); 51 | for (int i = 0; i < numberOfBases; i++) 52 | { 53 | int myBaseIndex = int.Parse(inputs[i]); 54 | my_bases.Add(myBaseIndex); 55 | } 56 | inputs = Console.ReadLine().Split(' '); 57 | for (int i = 0; i < numberOfBases; i++) 58 | { 59 | int oppBaseIndex = int.Parse(inputs[i]); 60 | opp_bases.Add(oppBaseIndex); 61 | } 62 | 63 | // game loop 64 | while (true) 65 | { 66 | for (int i = 0; i < numberOfCells; i++) 67 | { 68 | inputs = Console.ReadLine().Split(' '); 69 | int resources = int.Parse(inputs[0]); // the current amount of eggs/crystals on this cell 70 | int myAnts = int.Parse(inputs[1]); // the amount of your ants on this cell 71 | int oppAnts = int.Parse(inputs[2]); // the amount of opponent ants on this cell 72 | 73 | cells[i].resources = resources; 74 | cells[i].my_ants = myAnts; 75 | cells[i].opp_ants = oppAnts; 76 | } 77 | 78 | // Write an action using Console.WriteLine() 79 | // To debug: Console.Error.WriteLine("Debug messages..."); 80 | List actions = new List(); 81 | 82 | 83 | // ------- ! ----- Now you can generate a list of actions to do -------- 84 | // WAIT | LINE | BEACON | MESSAGE 85 | 86 | foreach (var cell in cells) 87 | { 88 | if (cell.resources > 0) 89 | { 90 | actions.Add($"LINE {my_bases[0]} {cell.index} 1"); 91 | break; 92 | } 93 | } 94 | 95 | 96 | if (actions.Count == 0) 97 | { 98 | Console.WriteLine("WAIT"); 99 | } 100 | else 101 | { 102 | string final_action = String.Join(";", actions); 103 | Console.WriteLine(final_action); 104 | } 105 | 106 | } 107 | } 108 | 109 | public class Cell 110 | { 111 | public int index { get; set; } 112 | public int cell_type { get; set; } 113 | public int resources { get; set; } 114 | public int[]? neighbors { get; set; } 115 | public int my_ants { get; set; } 116 | public int opp_ants { get; set; } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /starterAIs/Starter.fs: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | type Ressource = 4 | | Empty 5 | | Eggs of int 6 | | Crystal of int 7 | module Ressource = 8 | let parse (token: string array) = 9 | let _type = token.[0] (* 0 for empty, 1 for eggs, 2 for crystal *) 10 | let initialResources = int token[1] (* the initial amount of eggs/crystals on this cell *) 11 | match _type[0] with 12 | | '0' -> Empty 13 | | '1' -> Eggs(initialResources) 14 | | '2' -> Crystal(int token[1]) 15 | | _ -> failwithf "Invalid cell type %A" token 16 | 17 | let update (ressource: Ressource) (newCount:int) = 18 | match ressource with 19 | | Empty -> Empty 20 | | Eggs(_) -> Eggs(newCount) 21 | | Crystal(_) -> Crystal(newCount) 22 | 23 | type Cell = 24 | { 25 | mutable Ressource: Ressource 26 | Neighbours: int array 27 | mutable MyAnts: int 28 | mutable HisAnts: int 29 | } 30 | module Cell = 31 | let parse (token: string array) = 32 | (* neigh0: the index of the neighbouring cell for each direction *) 33 | { 34 | Ressource = Ressource.parse token 35 | Neighbours = [| int token[2]; int token[3]; int token[4]; int token[5]; int token[6]; int token[7] |] 36 | MyAnts = 0 37 | HisAnts = 0 38 | } 39 | 40 | let numberOfCells = int(Console.In.ReadLine()) (* amount of hexagonal cells in this map *) 41 | let cells = 42 | [| for i in 0 .. numberOfCells - 1 do 43 | let token = (Console.In.ReadLine()).Split [|' '|] 44 | Cell.parse token 45 | |] 46 | 47 | let numberOfBases = int(Console.In.ReadLine()) 48 | let myBaseWords = (Console.In.ReadLine()).Split [|' '|] 49 | let myBases = 50 | [| for baseWord in myBaseWords do 51 | int baseWord 52 | |] 53 | 54 | let hisBaseWords = (Console.In.ReadLine()).Split [|' '|] 55 | let hisBases = 56 | [| for baseWord in hisBaseWords do 57 | int baseWord 58 | |] 59 | 60 | 61 | (* game loop *) 62 | while true do 63 | for i in 0 .. numberOfCells - 1 do 64 | (* resources: the current amount of eggs/crystals on this cell *) 65 | (* myAnts: the amount of your ants on this cell *) 66 | (* oppAnts: the amount of opponent ants on this cell *) 67 | let token1 = (Console.In.ReadLine()).Split [|' '|] 68 | let resources = int token1[0] 69 | let myAnts = int token1[1] 70 | let oppAnts = int token1[2] 71 | cells[i].Ressource <- Ressource.update cells[i].Ressource resources 72 | cells[i].MyAnts <- myAnts 73 | cells[i].HisAnts <- oppAnts 74 | 75 | 76 | (* WAIT | LINE | BEACON | MESSAGE *) 77 | let actions = [] 78 | 79 | (* Write an action using printfn *) 80 | (* To debug: eprintfn "Debug message" *) 81 | if actions.Length = 0 then 82 | printfn "WAIT" 83 | else 84 | printfn "%s" (String.concat ";" actions) 85 | -------------------------------------------------------------------------------- /starterAIs/Starter.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | import java.math.*; 4 | 5 | 6 | class Cell { 7 | int index; 8 | int cellType; 9 | int resources; 10 | List neighbors; 11 | int myAnts; 12 | int oppAnts; 13 | 14 | public Cell(int index, int cellType, int resources, List neighbors, int myAnts, int oppAnts) { 15 | this.index = index; 16 | this.cellType = cellType; 17 | this.resources = resources; 18 | this.neighbors = neighbors; 19 | this.myAnts = myAnts; 20 | this.oppAnts = oppAnts; 21 | } 22 | } 23 | 24 | class Player { 25 | 26 | public static void main(String args[]) { 27 | 28 | List cells = new ArrayList<>(); 29 | 30 | Scanner in = new Scanner(System.in); 31 | int numberOfCells = in.nextInt(); // amount of hexagonal cells in this map 32 | for (int i = 0; i < numberOfCells; i++) { 33 | int cellType = in.nextInt(); // 0 for empty, 1 for eggs, 2 for crystal 34 | int initialResources = in.nextInt(); // the initial amount of eggs/crystals on this cell 35 | List neighbors = new ArrayList<>(); // the index of the neighbouring cell for each direction 36 | neighbors.add(in.nextInt()); 37 | neighbors.add(in.nextInt()); 38 | neighbors.add(in.nextInt()); 39 | neighbors.add(in.nextInt()); 40 | neighbors.add(in.nextInt()); 41 | neighbors.add(in.nextInt()); 42 | Cell cell = new Cell(i, cellType, initialResources, neighbors, 0, 0); 43 | cells.add(cell); 44 | } 45 | int numberOfBases = in.nextInt(); 46 | List myBases = new ArrayList<>(); 47 | List oppBases = new ArrayList<>(); 48 | 49 | for (int i = 0; i < numberOfBases; i++) { 50 | int myBaseIndex = in.nextInt(); 51 | myBases.add(myBaseIndex); 52 | } 53 | for (int i = 0; i < numberOfBases; i++) { 54 | int oppBaseIndex = in.nextInt(); 55 | oppBases.add(oppBaseIndex); 56 | } 57 | 58 | // game loop 59 | while (true) { 60 | for (int i = 0; i < numberOfCells; i++) { 61 | int resources = in.nextInt(); // the current amount of eggs/crystals on this cell 62 | int myAnts = in.nextInt(); // the amount of your ants on this cell 63 | int oppAnts = in.nextInt(); // the amount of opponent ants on this cell 64 | 65 | cells.get(i).resources = resources; 66 | cells.get(i).myAnts = myAnts; 67 | cells.get(i).oppAnts = oppAnts; 68 | } 69 | 70 | // Write an action using System.out.println() 71 | // To debug: System.err.println("Debug messages..."); 72 | 73 | List actions = new ArrayList<>(); 74 | 75 | //TODO: choose actions to perform and push them into actions. E.g: 76 | for (Cell cell : cells) { 77 | if (cell.resources > 0) { 78 | actions.add("LINE " + myBases.get(0) + " " + cell.index + " 1"); 79 | break; 80 | } 81 | } 82 | 83 | if (actions.size() == 0) { 84 | System.out.println("WAIT"); 85 | } else { 86 | // WAIT | LINE | BEACON | MESSAGE 87 | System.out.println(String.join(";", actions)); 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /starterAIs/Starter.js: -------------------------------------------------------------------------------- 1 | class Cell { 2 | constructor(index, type, resources, neighbors, myAnts, oppAnts) { 3 | this.index = index 4 | this.type = type 5 | this.resources = resources 6 | this.neighbors = neighbors 7 | this.myAnts = myAnts 8 | this.oppAnts = oppAnts 9 | } 10 | } 11 | 12 | const cells = [] 13 | 14 | const numberOfCells = parseInt(readline()); // amount of hexagonal cells in this map 15 | for (let i = 0; i < numberOfCells; i++) { 16 | const inputs = readline().split(' '); 17 | const type = parseInt(inputs[0]); // 0 for empty, 1 for eggs, 2 for food 18 | const initialResources = parseInt(inputs[1]); // the initial amount of eggs/crystals on this cell 19 | const neigh0 = parseInt(inputs[2]); // the index of the neighbouring cell for each direction 20 | const neigh1 = parseInt(inputs[3]); 21 | const neigh2 = parseInt(inputs[4]); 22 | const neigh3 = parseInt(inputs[5]); 23 | const neigh4 = parseInt(inputs[6]); 24 | const neigh5 = parseInt(inputs[7]); 25 | 26 | const cell = new Cell( 27 | i, 28 | type, 29 | initialResources, 30 | [neigh0, neigh1, neigh2, neigh3, neigh4, neigh5].filter(id => id > -1), 31 | 0, 32 | 0 33 | ) 34 | cells.push(cell) 35 | } 36 | 37 | const numberOfBases = parseInt(readline()); 38 | const myBases = readline().split(' ').map(n => parseInt(n)) 39 | const oppBases = readline().split(' ').map(n => parseInt(n)) 40 | 41 | // game loop 42 | while (true) { 43 | for (let i = 0; i < numberOfCells; i++) { 44 | const inputs = readline().split(' ') 45 | const resources = parseInt(inputs[0]); // the current amount of eggs/crystals on this cell 46 | const myAnts = parseInt(inputs[1]); // the amount of your ants on this cell 47 | const oppAnts = parseInt(inputs[2]); // the amount of opponent ants on this cell 48 | 49 | cells[i].resources = resources 50 | cells[i].myAnts = myAnts 51 | cells[i].oppAnts = oppAnts 52 | } 53 | 54 | 55 | // WAIT | LINE | BEACON | MESSAGE 56 | const actions = [] 57 | 58 | // TODO: choose actions to perform and push them into actions. E.g: 59 | for (const cell of cells) { 60 | if (cell.resources > 0) { 61 | actions.push(`LINE ${myBases[0]} ${cell.index} 1`) 62 | break 63 | } 64 | } 65 | 66 | // To debug: console.error('Debug messages...'); 67 | if (actions.length === 0) { 68 | console.log('WAIT'); 69 | } else { 70 | console.log(actions.join(';')) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /starterAIs/Starter.php: -------------------------------------------------------------------------------- 1 | index = $index; 15 | $this->cell_type = $cell_type; 16 | $this->resources = $resources; 17 | $this->neighbors = $neighbors; 18 | $this->my_ants = $my_ants; 19 | $this->opp_ants = $opp_ants; 20 | } 21 | } 22 | 23 | $cells = array(); 24 | 25 | fscanf(STDIN, "%d", $numberOfCells); 26 | for ($i = 0; $i < $numberOfCells; $i++) 27 | { 28 | // $type: 0 for empty, 1 for eggs, 2 for crystal 29 | // $Resources: initial amount of eggs/crystals on this cell 30 | // $neigh0: index of the neighbouring cell for each direction 31 | fscanf(STDIN, "%d %d %d %d %d %d %d %d", $cell_type, $resources, $neigh0, $neigh1, $neigh2, $neigh3, $neigh4, $neigh5); 32 | $neighbors = [$neigh0, $neigh1, $neigh2, $neigh3, $neigh4, $neigh5]; 33 | $cells[$i] = new Cell( $i, $cell_type, $resources, $neighbors, 0, 0 ); 34 | } 35 | 36 | fscanf(STDIN, "%d", $numberOfBases); 37 | $myBases = array(); 38 | $oppBases = array(); 39 | $inputs = explode(" ", fgets(STDIN)); 40 | for ($i = 0; $i < $numberOfBases; $i++) 41 | { 42 | $myBaseIndex = intval($inputs[$i]); 43 | $myBases[$i] = $myBaseIndex; 44 | } 45 | $inputs = explode(" ", fgets(STDIN)); 46 | for ($i = 0; $i < $numberOfBases; $i++) 47 | { 48 | $oppBaseIndex = intval($inputs[$i]); 49 | $oppBases[$i] = $oppBaseIndex; 50 | } 51 | 52 | // game loop 53 | while (TRUE) 54 | { 55 | $actions = []; 56 | 57 | for ($i = 0; $i < $numberOfCells; $i++) 58 | { 59 | // $resources: amount of eggs/crystals on this cell 60 | // $my_ants: amount of your ants on this cell 61 | // $opp_ants: amount of opponent ants on this cell 62 | fscanf(STDIN, "%d %d %d", $resources, $my_ants, $opp_ants); 63 | $cells[$i]->resources = $resources; 64 | $cells[$i]->my_ants = $my_ants; 65 | $cells[$i]->opp_ants = $opp_ants; 66 | } 67 | 68 | // Write an action using echo(). DON'T FORGET THE TRAILING \n 69 | // To debug: error_log(var_export($var, true)); (equivalent to var_dump) 70 | 71 | //TODO: choose actions to perform and push them into actions. E.g: 72 | foreach ($cells as $cell) { 73 | if ($cell->resources > 0) { 74 | $actions[] = "LINE " . $myBases[0] . " " . $cell->index . " 1"; 75 | break; 76 | } 77 | } 78 | 79 | // WAIT | LINE | BEACON | MESSAGE 80 | if ( count( $actions ) == 0 ) echo("WAIT\n"); 81 | else echo( implode( ';', $actions ) . "\n" ); 82 | } 83 | 84 | 85 | -------------------------------------------------------------------------------- /starterAIs/Starter.py: -------------------------------------------------------------------------------- 1 | class Cell(object): 2 | index: int 3 | cell_type: int 4 | resources: int 5 | neighbors: list[int] 6 | my_ants: int 7 | opp_ants: int 8 | 9 | def __init__(self, index: int, cell_type: int, resources: int, neighbors: list[int], my_ants: int, opp_ants: int): 10 | self.index = index 11 | self.cell_type = cell_type 12 | self.resources = resources 13 | self.neighbors = neighbors 14 | self.my_ants = my_ants 15 | self.opp_ants = opp_ants 16 | 17 | 18 | cells: list[Cell] = [] 19 | 20 | number_of_cells = int(input()) # amount of hexagonal cells in this map 21 | for i in range(number_of_cells): 22 | inputs = [int(j) for j in input().split()] 23 | cell_type = inputs[0] # 0 for empty, 1 for eggs, 2 for crystal 24 | initial_resources = inputs[1] # the initial amount of eggs/crystals on this cell 25 | neigh_0 = inputs[2] # the index of the neighbouring cell for each direction 26 | neigh_1 = inputs[3] 27 | neigh_2 = inputs[4] 28 | neigh_3 = inputs[5] 29 | neigh_4 = inputs[6] 30 | neigh_5 = inputs[7] 31 | cell: Cell = Cell( 32 | index = i, 33 | cell_type = cell_type, 34 | resources = initial_resources, 35 | neighbors = list(filter(lambda id: id > -1,[neigh_0, neigh_1, neigh_2, neigh_3, neigh_4, neigh_5])), 36 | my_ants = 0, 37 | opp_ants = 0 38 | ) 39 | cells.append(cell) 40 | number_of_bases = int(input()) 41 | my_bases: list[int] = [] 42 | for i in input().split(): 43 | my_base_index = int(i) 44 | my_bases.append(my_base_index) 45 | opp_bases: list[int] = [] 46 | for i in input().split(): 47 | opp_base_index = int(i) 48 | opp_bases.append(opp_base_index) 49 | 50 | # game loop 51 | while True: 52 | for i in range(number_of_cells): 53 | inputs = [int(j) for j in input().split()] 54 | resources = inputs[0] # the current amount of eggs/crystals on this cell 55 | my_ants = inputs[1] # the amount of your ants on this cell 56 | opp_ants = inputs[2] # the amount of opponent ants on this cell 57 | 58 | cells[i].resources = resources 59 | cells[i].my_ants = my_ants 60 | cells[i].opp_ants = opp_ants 61 | 62 | # WAIT | LINE | BEACON | MESSAGE 63 | actions = [] 64 | 65 | # TODO: choose actions to perform and push them into actions. E.g: 66 | for cell in cells: 67 | if cell.resources > 0: 68 | actions.append(f'LINE {my_bases[0]} {cell.index} 1') 69 | break 70 | 71 | # To debug: print("Debug messages...", file=sys.stderr, flush=True) 72 | if len(actions) == 0: 73 | print('WAIT') 74 | else: 75 | print(';'.join(actions)) -------------------------------------------------------------------------------- /starterAIs/Starter.rb: -------------------------------------------------------------------------------- 1 | STDOUT.sync = true # DO NOT REMOVE 2 | 3 | class Cell 4 | attr_accessor :index 5 | attr_accessor :type 6 | attr_accessor :resources 7 | attr_accessor :neighbors 8 | attr_accessor :my_ants 9 | attr_accessor :opp_ants 10 | 11 | def initialize(index, type, resources, neighbors, my_ants, opp_ants) 12 | @index = index 13 | @type = type 14 | @resources = resources 15 | @neighbors = neighbors 16 | @my_ants = my_ants 17 | @opp_ants = opp_ants 18 | end 19 | end 20 | 21 | cells = [] 22 | 23 | number_of_cells = gets.to_i # amount of hexagonal cells in this map 24 | number_of_cells.times do |i| 25 | inputs = gets.split.map &:to_i 26 | _type = inputs[0] # 0 for empty, 1 for eggs, 2 for crystal 27 | initial_resources = inputs[1] # the initial amount of eggs/crystals on this cell 28 | neigh_0 = inputs[2] # the index of the neighbouring cell for each direction 29 | neigh_1 = inputs[3] 30 | neigh_2 = inputs[4] 31 | neigh_3 = inputs[5] 32 | neigh_4 = inputs[6] 33 | neigh_5 = inputs[7] 34 | cell = Cell.new( 35 | i, 36 | _type, 37 | initial_resources, 38 | [neigh_0, neigh_1, neigh_2, neigh_3, neigh_4, neigh_5].select { |id| id > -1 }, 39 | 0, 40 | 0 41 | ) 42 | cells.append cell 43 | end 44 | number_of_bases = gets.to_i 45 | inputs = gets.split 46 | my_bases = [] 47 | for i in 0..(number_of_bases-1) 48 | my_base_index = inputs[i].to_i 49 | my_bases.append my_base_index 50 | end 51 | inputs = gets.split 52 | opp_bases = [] 53 | for i in 0..(number_of_bases-1) 54 | opp_base_index = inputs[i].to_i 55 | opp_bases.append opp_base_index 56 | end 57 | 58 | # game loop 59 | loop do 60 | number_of_cells.times do |i| 61 | inputs = gets.split.map &:to_i 62 | resources = inputs[0] # the current amount of eggs/crystals on this cell 63 | my_ants = inputs[1] # the amount of your ants on this cell 64 | opp_ants = inputs[2] # the amount of opponent ants on this cell 65 | 66 | cells[i].resources = resources 67 | cells[i].my_ants = my_ants 68 | cells[i].opp_ants = opp_ants 69 | end 70 | 71 | # WAIT | LINE | BEACON | MESSAGE 72 | actions = [] 73 | 74 | # TODO: choose actions to perform and push them into actions. E.g: 75 | cells.each do |cell| 76 | if cell.resources > 0 77 | actions << "LINE #{my_bases[0]} #{cell.index} 1" 78 | break 79 | end 80 | end 81 | 82 | # To debug: STDERR.puts "Debug messages..." 83 | if actions.length == 0 84 | puts 'WAIT' 85 | else 86 | puts actions.join ";" 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /starterAIs/Starter.ts: -------------------------------------------------------------------------------- 1 | enum CellType { 2 | EMPTY = 0, 3 | EGG = 1, 4 | CRYSTAL = 2 5 | } 6 | 7 | interface Cell { 8 | type: CellType 9 | resources: number 10 | neighbors: number[] 11 | myAnts: number 12 | oppAnts: number 13 | index: number 14 | } 15 | 16 | const cells: Cell[] = [] 17 | 18 | const numberOfCells: number = parseInt(readline()); // amount of hexagonal cells in this map 19 | for (let i = 0; i < numberOfCells; i++) { 20 | const inputs: string[] = readline().split(' '); 21 | const type: number = parseInt(inputs[0]); // 0 for empty, 1 for eggs, 2 for food 22 | const initialResources: number = parseInt(inputs[1]); // the initial amount of eggs/crystals on this cell 23 | const neigh0: number = parseInt(inputs[2]); // the index of the neighbouring cell for each direction 24 | const neigh1: number = parseInt(inputs[3]); 25 | const neigh2: number = parseInt(inputs[4]); 26 | const neigh3: number = parseInt(inputs[5]); 27 | const neigh4: number = parseInt(inputs[6]); 28 | const neigh5: number = parseInt(inputs[7]); 29 | 30 | const cell: Cell = { 31 | type, 32 | index: i, 33 | resources: initialResources, 34 | neighbors: [neigh0, neigh1, neigh2, neigh3, neigh4, neigh5].filter(id => id > -1), 35 | myAnts: 0, 36 | oppAnts: 0 37 | } 38 | cells.push(cell) 39 | } 40 | 41 | const numberOfBases: number = parseInt(readline()); 42 | const myBases: number[] = readline().split(' ').map(n => parseInt(n)) 43 | const oppBases: number[] = readline().split(' ').map(n => parseInt(n)) 44 | 45 | // game loop 46 | while (true) { 47 | for (let i = 0; i < numberOfCells; i++) { 48 | const inputs = readline().split(' ') 49 | const resources: number = parseInt(inputs[0]); // the current amount of eggs/crystals on this cell 50 | const myAnts: number = parseInt(inputs[1]); // the amount of your ants on this cell 51 | const oppAnts: number = parseInt(inputs[2]); // the amount of opponent ants on this cell 52 | 53 | cells[i].resources = resources 54 | cells[i].myAnts = myAnts 55 | cells[i].oppAnts = oppAnts 56 | } 57 | 58 | 59 | // WAIT | LINE | BEACON | MESSAGE 60 | const actions: string[] = [] 61 | 62 | // TODO: choose actions to perform and push them into actions. E.g: 63 | for (const cell of cells) { 64 | if (cell.resources > 0) { 65 | actions.push(`LINE ${myBases[0]} ${cell.index} 1`) 66 | break 67 | } 68 | } 69 | 70 | // To debug: console.error('Debug messages...'); 71 | if (actions.length === 0) { 72 | console.log('WAIT'); 73 | } else { 74 | console.log(actions.join(';')) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /typescript/polyfill.js: -------------------------------------------------------------------------------- 1 | // Add print, printErr and readline functions 2 | 3 | const { readline } = require('./readline.js') 4 | 5 | // Polyfill missing SpiderMonkey functions: 6 | global.print = console.log 7 | global.printErr = console.warn 8 | global.readline = readline 9 | global.putstr = process.stdout.write 10 | -------------------------------------------------------------------------------- /typescript/readline.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | // Variable used to store the remain of stdin (happen if several lines have been read at once by getStdin) 4 | let stdin = '' 5 | // File descriptor for stdin 6 | let fd = null 7 | 8 | // Read stdin until endByte of end of file is reached 9 | // Beware that endByte is not necessarly the last character! 10 | function getStdin (endByte) { 11 | const BUFSIZE = 256 12 | let buf = Buffer.allocUnsafe(BUFSIZE) 13 | let totalBuf = Buffer.allocUnsafe(0) 14 | let bytesRead 15 | let endBytePos 16 | 17 | if (fd === null) { 18 | fd = fs.openSync('/dev/stdin', 'rs') 19 | } 20 | 21 | do { 22 | bytesRead = fs.readSync(fd, buf, 0, BUFSIZE, null) 23 | totalBuf = Buffer.concat([totalBuf, buf], totalBuf.length + bytesRead) 24 | endBytePos = buf.indexOf(endByte) 25 | } while (bytesRead > 0 && (endBytePos < 0 || endBytePos >= bytesRead)) 26 | 27 | return totalBuf 28 | } 29 | 30 | function readline () { 31 | if (stdin.indexOf('\n') === -1) { 32 | stdin = stdin + getStdin('\n'.charCodeAt(0)).toString('utf-8') 33 | } 34 | 35 | // If still empty then EOF reached. Return null to keep the same behaviour as SpiderMonkey. 36 | if (stdin.length === 0) { 37 | return null 38 | } 39 | 40 | // At this point, either stdin contains '\n' or it's the end of the file: we have something to return 41 | const newline = stdin.indexOf('\n') 42 | if (newline !== -1) { 43 | const line = stdin.slice(0, newline) 44 | 45 | stdin = stdin.slice(newline + 1) 46 | return line 47 | } else { 48 | const line = stdin 49 | 50 | stdin = '' 51 | return line 52 | } 53 | } 54 | 55 | module.exports = { 56 | readline 57 | } 58 | -------------------------------------------------------------------------------- /typescript/readline/index.d.ts: -------------------------------------------------------------------------------- 1 | declare function readline(): string 2 | --------------------------------------------------------------------------------