├── .gitignore ├── .settings └── .gitignore ├── README.md ├── config ├── config.ini ├── level1 │ ├── Boss.java │ ├── statement_en.html │ ├── statement_fr.html │ └── stub.txt ├── level2 │ ├── Boss.java │ ├── statement_en.html │ ├── statement_fr.html │ └── stub.txt ├── level3 │ ├── Boss.java │ ├── statement_en.html │ ├── statement_fr.html │ └── stub.txt └── level4 │ ├── Boss.java │ ├── statement_en.html │ ├── statement_fr.html │ └── stub.txt ├── leaguePopups ├── bronze.png ├── bronze_en.html ├── bronze_fr.html ├── wood1.png ├── wood1_en.html ├── wood1_fr.html ├── wood2.png ├── wood2_en.html └── wood2_fr.html ├── pom.xml └── src ├── main ├── java │ ├── anims │ │ ├── Anim.java │ │ └── AnimModule.java │ └── tooltipModule │ │ └── TooltipModule.java ├── kotlin │ └── com │ │ └── codingame │ │ └── game │ │ ├── Characters.kt │ │ ├── Constants.kt │ │ ├── MapBuilding.kt │ │ ├── Player.kt │ │ ├── PlayerHUD.kt │ │ ├── Referee.kt │ │ ├── Structures.kt │ │ ├── Utility.kt │ │ └── Vector2.kt └── resources │ └── view │ ├── anims │ ├── AnimData.js │ └── AnimModule.js │ ├── assets │ ├── Background.jpg │ ├── Hud.png │ ├── construction.json │ ├── construction.png │ ├── death.json │ ├── death.png │ ├── destruction.json │ ├── destruction.png │ ├── game.json │ ├── game.png │ └── logo.png │ ├── config.js │ ├── demo.js │ └── tooltips │ └── TooltipModule.js └── test ├── java ├── Main.java └── WaitBot.java ├── kotlin └── com │ └── codingame │ └── game │ ├── AllArcherPlayer.kt │ ├── AllGiantsPlayer.kt │ ├── AllKnightPlayer.kt │ ├── AllTowersPlayer.kt │ ├── BasePlayer.kt │ ├── CSJPlayer.kt │ ├── Level1Player.kt │ ├── Level2Player.kt │ ├── ThibaudPlayer.kt │ └── Types.kt └── resources └── log4j2.properties /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.project 3 | /.classpath 4 | /.idea/ 5 | /.settings/ 6 | *.iml 7 | -------------------------------------------------------------------------------- /.settings/.gitignore: -------------------------------------------------------------------------------- 1 | /org.eclipse.jdt.core.prefs 2 | /org.eclipse.m2e.core.prefs 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Referee for Code Royale: https://www.codingame.com/contests/code-royale 2 | 3 | Just run `Main.java` and then send your browser to localhost:8888. You will be playing in level 1 (i.e. Wood3). 4 | 5 | To play in a different league, look for reads of `gameManager.leagueLevel`. 6 | Hardcode it to a different value instead (there is an example commented out in the code): 7 | - 1 = Wood3 8 | - 2 = Wood2 9 | - 3 = Wood1 10 | - 4 = Bronze 11 | - 5 = Silver 12 | - 6 = Gold 13 | - 7 = Legend 14 | 15 | You can load any bots you want. See the example WaitBot.java (for a java version) 16 | or CSJPlayer (or any other) for Kotlin examples. 17 | 18 | No, I don't apologize for using Kotlin to build this game. My productivity has 19 | soared because of it! Feel free to ask me about anything that doesn't make sense. 20 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | title=code-royale 2 | min_players=2 3 | max_players=2 4 | -------------------------------------------------------------------------------- /config/level1/Boss.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | 3 | /* 4 | Level 1 Boss: Peon 5 | Expected Player Skills: Build structures and train minions in an efficient manner 6 | Strategy: Build a single Knight Barracks and train a wave of knight troops every 12 turns. 7 | */ 8 | class Player { 9 | 10 | public static void main(String args[]) { 11 | Scanner in = new Scanner(System.in); 12 | int numObstacles = in.nextInt(); 13 | ArrayList obstacles = new ArrayList(); 14 | for (int i = 0; i < numObstacles; i++) { 15 | int obstacleId = in.nextInt(); 16 | int x = in.nextInt(); 17 | int y = in.nextInt(); 18 | int radius = in.nextInt(); 19 | obstacles.add(new Obst(obstacleId, x, y)); 20 | } 21 | 22 | Obst barracks = null; 23 | int myQueenX = -1, myQueenY = -1; 24 | int count = 0; 25 | 26 | // game loop 27 | while (true) { 28 | int gold = in.nextInt(); 29 | int touching = in.nextInt(); 30 | for (int i = 0; i < numObstacles; i++) { 31 | int obstacleId = in.nextInt(); 32 | int goldRemaining = in.nextInt(); // -1 if unknown 33 | int maxMineSize = in.nextInt(); // -1 if unknown 34 | int structureType = in.nextInt(); // -1 = No structure, 0 = Resource Mine, 1 = Tower, 2 = Barracks 35 | int owner = in.nextInt(); // -1 = No structure, 0 = Friendly, 1 = Enemy 36 | int param1 = in.nextInt(); 37 | int param2 = in.nextInt(); 38 | 39 | if (barracks != null && barracks.id == obstacleId){ 40 | barracks.type = structureType; 41 | } 42 | } 43 | int numUnits = in.nextInt(); 44 | for (int i = 0; i < numUnits; i++) { 45 | int x = in.nextInt(); 46 | int y = in.nextInt(); 47 | int owner = in.nextInt(); 48 | int type = in.nextInt(); 49 | int health = in.nextInt(); 50 | 51 | if (type == -1 && owner == 0){ 52 | myQueenX = x; 53 | myQueenY = y; 54 | } 55 | } 56 | 57 | if(barracks == null){ 58 | double minDist = Double.MAX_VALUE; 59 | for (Obst o: obstacles) { 60 | double dist = Math.sqrt(Math.pow(myQueenX - o.x, 2) + Math.pow(myQueenY - o.y, 2)); 61 | if(dist < minDist){ 62 | minDist = dist; 63 | barracks = o; 64 | } 65 | } 66 | } 67 | 68 | String action = "WAIT"; 69 | if(barracks.type == -1){ 70 | action = String.format("BUILD %d BARRACKS-KNIGHT", barracks.id); 71 | } 72 | 73 | count++; 74 | String train = "TRAIN"; 75 | if (count == 12){ 76 | count = 0; 77 | if (barracks.type == 2){ 78 | train = train + " " + barracks.id; 79 | } 80 | } 81 | 82 | // First line: A valid queen action 83 | // Second line: A set of training instructions 84 | System.out.println(action); 85 | System.out.println(train); 86 | } 87 | } 88 | } 89 | 90 | class Obst{ 91 | int id, x, y; 92 | int type = -1; 93 | 94 | public Obst(int id, int x, int y){ 95 | this.id = id; 96 | this.x = x; 97 | this.y = y; 98 | } 99 | } -------------------------------------------------------------------------------- /config/level1/statement_en.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
13 |
14 | 15 |
16 | 17 |

18 | This is a league based puzzle 19 |

20 | 21 | For this puzzle, multiple versions of the same game are available.
22 | Once you have proven your skills on this first version, you will access a higher league and extra rules will be unlocked.
23 |
24 |
25 | 26 | 27 | 28 |
29 |

30 |   31 | The Goal 32 |

33 |
34 | 35 | Build structures and train troops in order to destroy the enemy Queen. 36 |
37 |
38 | 39 | 40 | 41 |
42 |

43 |   44 | Rules 45 |

46 |
47 |

Field

48 |

49 | The game is played by two players on a field 1920 units wide and 1000 50 | units high, littered with circular building sites. The top-left corner is 51 | 0,0. 52 |

53 |
54 | 55 |

Queens 56 |

57 | Each player controls a single Queen character, who can establish different structures on those 58 | sites. 59 | The Queen is the player’s only way to directly interact with the field. 60 |

61 |

62 | The Queen is modeled as a circle with radius 63 | 30 64 | units, and moves up to 65 | 60 66 | units every turn (using the MOVE command). 67 | Each Queen starts with 68 | 100 69 | HP, and will be destroyed if this drops to zero -- if this happens, the player will lose the game. 70 | If both Queens are destroyed on the same turn, the game will end in a draw. 71 |

72 |
73 | 74 |

Structures

75 |

76 | Structures can be built at no cost. 77 |
78 | A Queen character can build a structure (using the BUILD command) on a building site that she is in contact with. 79 | The variable touchedSite indicates the identifier of the building site that she is in contact with, -1 if not. 80 |

81 |

82 | There is one type of structure: 83 |

    84 |
  • 85 | BARRACKS-{type}: A player needs to build a BARRACKS with their Queen in order to train creeps 86 | ({type} can be KNIGHT or ARCHER). 87 |
    88 | Every barracks is built with the ability to train a single creep type. 89 | Barracks are available for use on the turn immediately following their construction. 90 |
    91 | A barracks is destroyed if it is touched by the enemy Queen. 92 |
  • 93 |
94 |

95 |

96 | If she so chooses, a Queen can replace an existing friendly structure simply by building another one in 97 | its place (unless it is a Barracks that is currently training). 98 |

99 |
100 | 101 |

Gold

102 |

103 | Each player begins with 104 | 100 105 | gold and will gain 106 | 10 107 | gold at the end of each turn. 108 |

109 |
110 | 111 |

Creeps

112 |

113 | In order to destroy the enemy Queen, a player will need to build creeps. 114 |
115 | Once built, creeps follow very simple behaviours (see below), and cannot be controlled by the player. 116 | Creeps will eventually age and die on their own, losing 117 | 1 118 | HP every turn. 119 |

120 |

121 | There are two different creep types that can be built. 122 |

123 |
    124 |
  • 125 | KNIGHT units are light, fast units which attack the enemy Queen. 126 | They cost 127 | 80 128 | gold to train a group of 4. 129 |
  • 130 |
  • 131 | ARCHER units are slower, ranged units which move toward and attack nearby enemy creeps from a short 132 | distance. 133 | They cost 134 | 100 135 | gold to train a group of 2. Note: ARCHER units do not attack the enemy Queen! 136 |
  • 137 |
138 |
139 | 140 |

Training Creeps

141 |

142 | A player trains creeps (using the TRAIN command) by indicating the identifiers of which sites containing a barracks they wish to train 143 | creeps at. 144 |
145 | A barracks that is already in the middle of training cannot begin training again until the current creep 146 | set is built. 147 | Also, such a barracks cannot be replaced by another structure. 148 |
149 | Examples: 150 |

151 |
    152 |
  • 153 | TRAIN 13 6 19 154 | - Three barracks begin training creeps 155 |
  • 156 |
  • 157 | TRAIN 14 158 | - One barracks begins training creeps 159 |
  • 160 |
161 |

162 | When the train commands are sent, the player pays the total cost in gold, and indicated barracks will 163 | begin training the appropriate set of units. 164 | After the appointed number of turns elapses, a set of creeps emerges from the barracks and begins acting 165 | by themselves on the following turn. 166 |

167 |

168 | The training of creeps represent an extra mandatory command every turn. 169 | For barracks not to begin training new units, a player has to use the TRAIN command with no identifier. 170 |

171 |

172 | 173 |
174 |

175 | 176 | 177 | 178 |
179 |
180 |
181 |
Victory Conditions
182 |
183 |
    184 |
  • Destroy the enemy Queen.
  • 185 |
  • After 200 turns, your Queen has more HP than the enemy Queen.
  • 186 |
187 |
188 |
189 |
190 | 191 | 192 |
193 |
194 |
195 |
Lose Conditions
196 |
197 |
    198 |
  • Output an invalid command.
  • 199 |
200 |
201 |
202 |
203 |
204 |
205 | 206 |
207 |

208 |   209 | Expert Rules 210 |

211 |
212 | An expanded statement with full simulation details will be available in Bronze league. 213 |
214 |
215 | 216 | 217 | 218 |
219 |

220 |   221 | Game Input 222 |

223 | 224 |
225 |
Initialization input
226 |
227 | Line 1: an integer numSites, indicating the number of 228 | building sites on the map.
229 | Next numSites lines: 4 integers siteId x 230 | y radius
231 |
    232 |
  • siteId: The numeric identifier of the site
  • 233 |
  • x y: The numeric coordinates of the site's center
  • 234 |
  • radius: The radius of the site
  • 235 |
236 |
237 |
238 | 239 | 240 |
241 |
Input for one game turn
242 |
243 | 244 | Line 1 and 2: Two integers gold touchedSite
245 |
    246 |
  • gold: the current amount of gold available to be spent
  • 247 |
  • touchedSite: the id of the site your Queen is touching, or 248 | -1 249 | if none 250 |
  • 251 |
252 | Next numSites lines: Seven integers siteId 253 | ignore1 254 | ignore2 structureType owner param1 param2
255 |
    256 |
  • siteId: The numeric identifier of the site
  • 257 |
  • ignore1: Reserved for future leagues.
  • 258 |
  • ignore2: Reserved for future leagues.
  • 259 |
  • structureType: 260 |
      261 |
    • 262 | -1: No structure 263 |
    • 264 |
    • 265 | 2: Barracks 266 |
    • 267 |
    268 |
  • 269 |
  • owner: 270 |
      271 |
    • 272 | -1: No structure 273 |
    • 274 |
    • 275 | 0: Friendly 276 |
    • 277 |
    • 278 | 1: Enemy 279 |
    • 280 |
    281 |
  • 282 |
  • param1: 283 |
      284 |
    • When no structure: 285 | -1 286 |
    • 287 |
    • When barracks, the number of turns before a new set of creeps can be trained (if 288 | 0, then training may be started this turn) 289 |
    • 290 |
    291 |
  • 292 |
  • param2 293 |
      294 |
    • When no structure: 295 | -1 296 |
    • 297 |
    • When barracks: the creep type: 298 | 0 299 | for KNIGHT, 300 | 1 301 | for ARCHER 302 |
    • 303 |
    304 |
  • 305 |
306 |
307 | Next Line: numUnits: Number of total active units, 308 | including Queens
309 | Next numUnits lines: 5 integers x y owner unitType health
310 |
    311 |
  • x y: Coordinates of the unit
  • 312 |
  • owner: 313 | 0 314 | = Friendly; 315 | 1 316 | = Enemy 317 |
  • 318 |
  • unitType: The unit type: 319 | -1 320 | for Queen, 321 | 0 322 | for KNIGHT, 323 | 1 324 | for ARCHER 325 |
  • 326 |
  • health: Current HP of the unit
  • 327 |
328 |
329 |
330 | 331 | 332 |
333 |
Output for one game turn
334 | First line: One of the following 335 |
    336 |
  • 337 | WAIT 338 | Do nothing 339 |
  • 340 |
  • 341 | MOVE x y 342 | Will attempt to move towards the provided coordinates (x,y given as integers) 343 |
  • 344 |
  • 345 | BUILD {siteId} BARRACKS-{type} 346 | Will attempt to build a barracks at the indicated site. 347 | If too far away, the Queen will instead move towards the site. The type must be given as 348 | either 349 | KNIGHT 350 | or 351 | ARCHER. 352 |
  • 353 |
354 | Second line: 355 | TRAIN 356 | optionally followed by a list of 357 | siteId 358 | integers to start training at. 359 |
360 | 361 | 362 |
363 |
Constraints
364 |
365 | Response time for first turn ≤ 1000ms
366 | Response time for one turn ≤ 50ms 367 |
368 |
369 |
370 | 371 | 372 |
377 |
378 | 379 |
380 |

381 | What is in store for me in the higher leagues? 382 |

383 | The extra rules available in higher leagues are: 384 | 385 |
    386 |
  • You will be able to build TOWER structures to defend against enemy creeps, and GIANT creeps to attack enemy towers.
  • 387 |
  • You will need to build MINE structures in order to generate gold.
  • 388 |
  • Queens will start with less health.
  • 389 |
390 |
391 |
392 | -------------------------------------------------------------------------------- /config/level1/statement_fr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
13 |
14 | 15 |
16 | 17 |

18 | Ce puzzle se déroule en ligues. 19 |

20 | 21 | Pour ce puzzle, plusieurs versions du même jeu seront disponibles. Quand vous aurez prouvé votre valeur dans la première version, vous accéderez à la ligue supérieure et débloquerez de nouvelles règles. 22 | 23 |
24 | 25 | 26 | 27 |
28 |

29 |   30 | Objectif 31 |

32 |
33 | 34 | Construire des bâtiments et créer des armées pour détruire la Reine de votre adversaire. 35 |
36 |
37 | 38 | 39 | 40 |
41 |

42 |   43 | Règles 44 |

45 |
46 |

La carte

47 |

48 | Deux joueurs s'affrontent sur une carte rectangulaire de 1920 x 1000 49 | unités, parsemée de sites de construction circulaires. Les coordonnées 50 | 0,0 correspondent au pixel situé en haut à gauche. 51 |

52 | 53 |

La Reine 54 |

55 | Chaque joueur contrôle une Reine, qui peut construire différents types de bâtiments sur les sites de construction. 56 | La Reine est le seul moyen pour un joueur d'interagir avec la carte. 57 | 58 |

59 |

60 | La Reine est représentée par un cercle de rayon de 61 | 30 62 | unités. Elle se déplace à l'aide de la commande MOVE de 60 unités au plus par tour. 63 |

64 |

65 | Chaque Reine démarre la partie avec 66 | 100 67 | points de vie (PV). A 0 PV, la Reine est détruite et le joueur correspondant perd la partie. Si les deux Reines sont détruites pendant le même tour de jeu, il y a match nul. 68 |

69 | 70 |

Bâtiments

71 |

72 | Les bâtiments ne coûtent rien à construire. 73 |
74 | La Reine peut construire un bâtiment sur un site de construction (à l'aide de la commande BUILD), si elle est en contact avec ce site. La variable touchedSite indique l'identifiant du site avec lequel la Reine est en contact (-1 sinon). 75 |

76 |

77 | Il y a un seul type de bâtiment : 78 |

    79 |
  • 80 | BARRACKS-{type}: les casernes servent à entraîner des armées de différents types ({type} peut valoir KNIGHT ou ARCHER). 81 |
    82 | Une caserne peut commencer à entraîner des armées un tour seulement après sa construction. 83 | Si la Reine rentre en contact avec une caserne ennemie, cette dernière est détruite. 84 |
  • 85 |
86 |

87 |

88 | La Reine peut remplacer une caserne alliée en construisant un autre bâtiment par dessus (sauf si la caserne entraîne des armées à ce moment). 89 |

90 | 91 |

L'Or

92 |

93 | L'or sert à entraîner des armées. 94 |

95 |

96 | Chaque joueur démarre la partie avec 97 | 100 98 | d'or et gagne 99 | 10 100 | d'or à la fin de chaque tour. 101 |

102 | 103 |

Les armées

104 |

105 | Pour combattre la Reine de l'adversaire, un joueur doit entraîner des armées dans une caserne. 106 |
107 | Une fois entraînées (ce qui prend plusieurs tours), les armées ont un comportement très simple et ne peuvent pas être contrôlées. 108 | Chaque armée a un temps de vie limité, perdant 109 | 1 110 | PV par tour. 111 |

112 |

113 | Il y a deux types d'armées. 114 |

    115 |
  • 116 | Les chevaliers (KNIGHT) sont des unités rapides qui se déplacent vers la Reine ennemie et l'attaquent au corps à corps. 117 | Il faut 118 | 80 119 | d'or pour entraîner un groupe de 4 chevaliers à la caserne de chevaliers (BARRACKS-KNIGHT). 120 |
  • 121 |
  • 122 | Les archers (ARCHER) sont des unités lentes qui se déplacent vers l'armée ennemie la plus proche et l'attaquent à courte portée. 123 | Il faut 124 | 100 125 | d'or pour entraîner un groupe de 2 archers à la caserne d'archers (BARRACKS-ARCHER). 126 | Note: les archers n'attaquent pas la Reine ennemie ; ce sont des unités purement défensives. 127 |
  • 128 |
129 |

130 | 131 |

L'entraînement des armées

132 |

133 | Un joueur peut entraîner des armées en utilisant la commande TRAIN et en indiquant les identifiants de casernes alliées dans lesquelles il souhaite créer des troupes. 134 |
135 | Une caserne qui entraîne des armées ne peut pas recommencer un cycle d'entraînement avant que les premières armées soient créées. 136 |
137 | Exemples: 138 |

139 |
    140 |
  • 141 | TRAIN 13 6 19 142 | Les 3 casernes alliées 13, 6 et 19 commencent à entraîner des armées. 143 |
  • 144 |
  • 145 | TRAIN 14 146 | La caserne alliée 14 commence à entraîner des armées. 147 |
  • 148 |
149 |

150 | Une fois que les commandes d'entraînement sont envoyées, le joueur paie le prix total en or, et chaque caserne commence à entraîner le nombre correspondant d'unités. 151 | Au dernier tour d'entraînement, les unités apparaissent autour de la caserne. Elles commencent à agir au tour suivant. 152 |

153 |

154 | L'entraînement des armées constitue une commande supplémentaire obligatoire chaque tour. 155 | Pour n'entraîner aucune armée, il suffit d'utiliser la commande TRAIN sans identifiant. 156 |

157 |
158 | 159 | 160 | 161 |
162 |
163 |
164 |
Conditions de victoire
165 |
166 |
    167 |
  • Détruire la Reine ennemie
  • 168 |
  • Après 200 tours, votre Reine a plus de PV que la Reine ennemie.
  • 169 |
170 |
171 |
172 |
173 | 174 | 175 |
176 |
177 |
178 |
Conditions de défaite
179 |
180 |
    181 |
  • Vous effectuez une action invalide ou votre programme ne répond pas dans les temps
  • 182 |
183 |
184 |
185 |
186 |
187 |
188 | 189 |
190 |

191 |   192 | Règles avancées 193 |

194 |
195 | Un énoncé plus complet sera disponible à partir de la ligue Bronze. 196 |
197 |
198 | 199 | 200 | 201 |
202 |

203 |   204 | Entrées du jeu 205 |

206 | 207 |
208 |
Entrées d'initialisation
209 |
210 | Ligne 1: un entier numSites, indiquant le nombre de sites de construction présents sur la carte.
211 | Les numSites lignes suivantes : 4 entiers représentant l'identifiant siteId, les coordonnées x et y, et le rayon radius
d'un site de construction. 212 |
213 |
214 | 215 | 216 |
217 |
Entrées pour un tour de jeu
218 |
219 | 220 | Ligne 1: Un entier gold représentant l'or disponible du joueur.
221 | Ligne 2: Un entier touchedSite représentant l'identifiant du site de construction en contact avec la Reine s'il existe, -1 sinon.
222 | Les numSites lignes suivantes : 7 entiers siteId ignore1 223 | ignore2 structureType owner param1 param2
224 |
    225 |
  • siteId: L'identifiant d'un site de construction
  • 226 |
  • ignore1: A ignorer pour cette ligue
  • 227 |
  • ignore2: A ignorer pour cette ligue
  • 228 |
  • structureType: Le type de bâtiment construit sur le site. 229 |
      230 |
    • -1: Pas de bâtiment construit
    • 231 |
    • 2: Une caserne
    • 232 |
    233 |
  • 234 |
  • owner: Le propriétaire du bâtiment 235 |
      236 |
    • -1: Pas de bâtiment construit
    • 237 |
    • 0: Bâtiment allié
    • 238 |
    • 1: Bâtiment ennemi
    • 239 |
    240 |
  • 241 |
  • param1: 242 |
      243 |
    • Quand il n'y a pas de bâtiment construit : -1
    • 244 |
    • Si c'est une caserne, le nombre de tours restant avant que la caserne puisse à nouveau lancer un cycle d'entraînement d'armées, 0 si elle est disponible.
    • 245 |
    246 |
  • 247 |
  • param2 248 |
      249 |
    • Quand il n'y a pas de bâtiment construit : -1
    • 250 |
    • Si c'est une caserne, le type d'armée qu'elle produit 251 | 0 252 | pour une caserne de chevaliers, 253 | 1 254 | pour une caserne d'archers. 255 |
    • 256 |
    257 |
  • 258 |
259 | Ligne suivante : numUnits le nombre total d'unités actives, incluant les Reines
260 | Les numUnits lignes suivantes : 5 entiers x, y, owner, unitType, et health
261 |
    262 |
  • x et y: les coordonnées de l'unité.
  • 263 |
  • owner: 0 = alliée; 1 = ennemie.
  • 264 |
  • unitType: le type d'unité 265 | -1 266 | pour une Reine, 267 | 0 268 | pour un chevalier, et 269 | 1 270 | pour un archer. 271 |
  • 272 |
  • health : Le nombre de points de vie restants de l'unité
  • 273 |
274 |
275 |
276 | 277 | 278 |
279 |
Sortie pour un tour de jeu
280 | Première ligne : L'une des commandes suivantes 281 |
    282 |
  • WAIT Ne rien faire
  • 283 |
  • MOVE x y Déplacer la Reine vers les coordonnées indiquées (x et y étant des entiers)
  • 284 |
  • 285 | BUILD {siteId} BARRACKS-{type} 286 | Construire une caserne sur le site de construction indiqué. 287 | Si la Reine est trop éloignée du site de construction, cette commande déplacera la Reine vers le site. Le type de caserne est soit 288 | KNIGHT (chevalier) 289 | soit 290 | ARCHER (archer). 291 |
  • 292 |
293 | Seconde ligne : TRAIN suvi de zéro, un ou plusieurs identifiants siteId de casernes alliées pour commencer à entraîner des armées. 294 |
295 | 296 | 297 |
298 |
Contraintes
299 |
300 | Temps de réponse pour le premier tour ≤ 1000ms
301 | Temps de réponse pour un tour ≤ 50ms
302 |
303 |
304 | 305 | 306 |
311 |
312 | 313 |
314 |

315 | Qu'est-ce qui m'attend dans les prochaines ligues ? 316 |

317 | Les nouvelles règles débloquées dans les prochaines ligues sont : 318 |
    319 |
  • Vous pourrez construire des tours pour vous défendre et entraîner des géants qui attaquent les tours.
  • 320 |
  • Vous devrez construire des mines pour produire de l'or.
  • 321 |
322 |
323 |
324 | -------------------------------------------------------------------------------- /config/level1/stub.txt: -------------------------------------------------------------------------------- 1 | read numSites:int 2 | loop numSites read siteId:int x:int y:int radius:int 3 | gameloop 4 | read gold:int touchedSite:int 5 | loop numSites read siteId:int ignore1:int ignore2:int structureType:int owner:int param1:int param2:int 6 | read numUnits:int 7 | loop numUnits read x:int y:int owner:int unitType:int health:int 8 | write WAIT 9 | TRAIN 10 | 11 | INPUT 12 | touchedSite: -1 if none 13 | ignore1: used in future leagues 14 | ignore2: used in future leagues 15 | structureType: -1 = No structure, 2 = Barracks 16 | owner: -1 = No structure, 0 = Friendly, 1 = Enemy 17 | unitType: -1 = QUEEN, 0 = KNIGHT, 1 = ARCHER 18 | 19 | OUTPUT 20 | First line: A valid queen action 21 | Second line: A set of training instructions -------------------------------------------------------------------------------- /config/level2/Boss.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | import java.math.*; 4 | 5 | /* 6 | Level 2 Boss: Knight 7 | Expected Player Skills: Building towers for defense, building Giants to take down towers 8 | Boss strategy: Build a single barracks and train Knight minions whenever possible. 9 | Then, find the empty obstacle closest to the barracks and build a tower on it. 10 | */ 11 | class Player { 12 | 13 | public static void main(String args[]) { 14 | Scanner in = new Scanner(System.in); 15 | int numObstacles = in.nextInt(); 16 | Obst[] obstacles = new Obst[numObstacles]; 17 | for (int i = 0; i < numObstacles; i++) { 18 | int obstacleId = in.nextInt(); 19 | int x = in.nextInt(); 20 | int y = in.nextInt(); 21 | int radius = in.nextInt(); 22 | obstacles[obstacleId] = new Obst(obstacleId, x, y); 23 | } 24 | 25 | Obst barracks = null; 26 | int myQueenX = -1, myQueenY = -1; 27 | 28 | // game loop 29 | while (true) { 30 | int gold = in.nextInt(); 31 | int touching = in.nextInt(); 32 | 33 | for (int i = 0; i < numObstacles; i++) { 34 | int obstacleId = in.nextInt(); 35 | int goldRemaining = in.nextInt(); // -1 if unknown 36 | int maxMineSize = in.nextInt(); // -1 if unknown 37 | int structureType = in.nextInt(); // -1 = No structure, 0 = Resource Mine, 1 = Tower, 2 = Barracks 38 | int owner = in.nextInt(); // -1 = No structure, 0 = Friendly, 1 = Enemy 39 | int param1 = in.nextInt(); 40 | int param2 = in.nextInt(); 41 | 42 | obstacles[obstacleId].type = structureType; 43 | } 44 | int numUnits = in.nextInt(); 45 | for (int i = 0; i < numUnits; i++) { 46 | int x = in.nextInt(); 47 | int y = in.nextInt(); 48 | int owner = in.nextInt(); 49 | int type = in.nextInt(); 50 | int health = in.nextInt(); 51 | 52 | if (type == -1 && owner == 0){ 53 | myQueenX = x; 54 | myQueenY = y; 55 | } 56 | } 57 | 58 | if(barracks == null){ 59 | double minDist = Double.MAX_VALUE; 60 | for (Obst o: obstacles) { 61 | double dist = distance(myQueenX, o.x, myQueenY, o.y); 62 | if(dist < minDist){ 63 | minDist = dist; 64 | barracks = o; 65 | } 66 | } 67 | } 68 | 69 | String action = "WAIT"; 70 | if (barracks.type == -1){ 71 | action = String.format("BUILD %d BARRACKS-KNIGHT", barracks.id); 72 | } else { 73 | double minDist = Double.MAX_VALUE; 74 | Obst target = null; 75 | for (Obst o: obstacles) { 76 | double dist = distance(barracks.x, o.x, barracks.y, o.y); 77 | if(dist < minDist && o.type == -1){ 78 | minDist = dist; 79 | target = o; 80 | } 81 | } 82 | if (target != null){ 83 | action = String.format("BUILD %d TOWER", target.id); 84 | } 85 | } 86 | 87 | String train = "TRAIN"; 88 | if (gold >= 80){ 89 | if (barracks.type == 2){ 90 | train = train + " " + barracks.id; 91 | } 92 | } 93 | 94 | // First line: A valid queen action 95 | // Second line: A set of training instructions 96 | System.out.println(action); 97 | System.out.println(train); 98 | } 99 | } 100 | 101 | static double distance(int x1, int x2, int y1, int y2){ 102 | return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 103 | } 104 | } 105 | 106 | class Obst{ 107 | int id, x, y; 108 | int type = -1; 109 | 110 | public Obst(int id, int x, int y){ 111 | this.id = id; 112 | this.x = x; 113 | this.y = y; 114 | } 115 | } -------------------------------------------------------------------------------- /config/level2/statement_fr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
13 |
14 | 15 |
16 | 17 |

18 | Résumé des nouvelles règles 19 |

20 | 21 | Vous pouvez maintenant construire des tours pour vous défendre contre les armées adverses, et entraîner des géants pour détruire les tours adverses. 22 | Voir les détails ci-dessous. 23 | 24 |
25 | 26 | 27 | 28 |
29 |

30 |   31 | Objectif 32 |

33 |
34 | 35 | Construire des bâtiments et créer des armées pour détruire la Reine de votre adversaire. 36 |
37 |
38 | 39 | 40 | 41 |
42 |

43 |   44 | Règles 45 |

46 |
47 |

La carte

48 |

49 | Deux joueurs s'affrontent sur une carte rectangulaire de 1920 x 1000 50 | unités, parsemée de sites de construction circulaires. Les coordonnées 51 | 0,0 correspondent au pixel stué en haut à gauche. 52 |

53 | 54 |

La Reine 55 |

56 | Chaque joueur contrôle une Reine qui peut construire différents types de bâtiments sur des sites de construction. 57 | La Reine est le seul moyen pour un joueur d'interagir avec la carte. 58 | 59 |

60 |

61 | La Reine est représentée par un cercle de rayon de 62 | 30 63 | unités. Elle se déplace à l'aide de la commande MOVE de 60 unités au plus par tour. 64 |

65 |

66 | Chaque Reine démarre la partie avec 67 | 200 68 | points de vie (PV). A 0 PV, la Reine est détruite et le joueur correspondant perd la partie. Si les deux Reines sont détruites pendant le même tour de jeu, il y a match nul. 69 |

70 | 71 |

Bâtiments

72 |

73 | Les bâtiments ne coûtent rien à construire. 74 | La Reine peut construire un bâtiment sur un site de construction (à l'aide de la commande BUILD), si elle est en contact avec ce site. La variable touchedSite indique l'identifiant du site avec lequel la Reine est en contact (-1 sinon). 75 |

76 |

77 | Il y a deux types de bâtiments : 78 |

    79 |
  • 80 | BARRACKS-{type}: les casernes servent à entraîner des armées de différents types ({type} peut valoir KNIGHT, ARCHER, ou GIANT). 81 | 82 | Une caserne peut commencer à entraîner des armées un tour seulement après sa construction. 83 | Si la Reine rentre en contact avec une caserne ennemie, cette dernière est détruite. 84 |
  • 85 |
  • 86 | TOWER: les tours sont des structures de défense statique avec un certain rayon de tir. 87 | 88 | Chaque tour, elles ciblent l'armée ennemie la plus proche à portée. S'il n'y a pas d'armée ennemie à portée et que la Reine est à portée, une tour ciblera la Reine à la place. Les tours se détériorent à chaque tour jusqu'à être détruites. 89 | 90 | La Reine peut réparer une tour et augmenter son rayon d'action en utilisant la même commande que pour la construire: BUILD {id} TOWER. 91 |
  • 92 |
93 |

94 |

95 | La Reine peut remplacer une caserne alliée en construisant un autre bâtiment par dessus (sauf si la caserne entraîne des armées à ce moment). 96 |

97 | 98 |

L'or

99 |

100 | L'or sert à entraîner des armées. 101 |

102 |

103 | Chaque joueur démarre la partie avec 104 | 100 105 | d'or et gagne 106 | 10 107 | d'or à la fin de chaque tour. 108 |

109 | 110 |

Les armées

111 |

112 | Pour combattre la Reine de l'adversaire, un joueur doit entraîner des armées dans une caserne. 113 | Une fois entraînées (ce qui prend plusieurs tours), les armées ont un comportement très simple et ne peuvent pas être contrôlées. 114 | Chaque armée a un temps de vie limité, perdant 115 | 1 116 | PV par tour. 117 |

118 |

119 | Il y a trois types d'armées. 120 |

    121 |
  • 122 | Les chevaliers (KNIGHT) sont des unités rapides qui se déplacent vers la Reine ennemie et l'attaquent au corps à corps. 123 | Il faut 124 | 80 125 | d'or pour entraîner un groupe de 4 chevaliers à la caserne de chevaliers (BARRACKS-KNIGHT). 126 |
  • 127 |
  • 128 | Les archers (ARCHER) sont des unités lentes qui se déplacent vers l'armée ennemie la plus proche et l'attaquent à courte portée. 129 | Il faut 130 | 100 131 | d'or pour entraîner un groupe de 2 archers à la caserne d'archers (BARRACKS-ARCHER). 132 | Note: les archers n'attaquent pas la Reine ennemie ; ce sont des unités purement défensives. 133 |
  • 134 |
  • 135 | Les géants (GIANT) n'attaquent pas les armées ni la Reine. Ils n'attaquent que les tours, les détruisant petit à petit. 136 | Il faut 137 | 140 138 | d'or pour entraîner un seul géant à la caserne de géants (BARRACKS-GIANT). 139 |
140 |

141 | 142 |

L'entraînement des armées

143 |

144 | Un joueur peut entraîner des armées en utilisant la commande TRAIN et en indiquant les identifiants de casernes alliées dans lesquelles il souhaite créer des troupes. 145 | Une caserne qui entraîne des armées ne peut pas recommencer un cycle d'entraînement avant que les premières armées soient créées. 146 | 147 | Exemples: 148 |

149 |
    150 |
  • 151 | TRAIN 13 6 19 152 | Les 3 casernes alliées 13, 6 et 19 commencent à entraîner des armées. 153 |
  • 154 |
  • 155 | TRAIN 14 156 | La caserne alliée 14 commence à entraîner des armées. 157 |
  • 158 |
159 |

160 | Une fois que les commandes d'entraînement sont envoyées, le joueur paie le prix total en or, et chaque caserne commence à entraîner le nombre correspondant d'unités. 161 | Au dernier tour d'entraînement, les unités apparaissent autour de la caserne. Elles commencent à agir au tour suivant. 162 |

163 |

164 | L'entraînement des armées constitue une commande supplémentaire obligatoire chaque tour. 165 | Pour n'entraîner aucune armée, il suffit d'utiliser la commande TRAIN sans identifiant. 166 |

167 |
168 | 169 | 170 | 171 |
172 |
173 |
174 |
Conditions de victoire
175 |
176 |
    177 |
  • Détruire la Reine ennemie
  • 178 |
  • Après 200 tours, votre Reine a plus de PV que la Reine ennemie.
  • 179 |
180 |
181 |
182 |
183 | 184 | 185 |
186 |
187 |
188 |
Conditions de défaite
189 |
190 |
    191 |
  • Vous effectuez une action invalide ou votre programme ne répond pas dans les temps
  • 192 |
193 |
194 |
195 |
196 |
197 |
198 | 199 |
200 |

201 |   202 | Règles avancées 203 |

204 |
205 | Un énoncé plus complet sera disponible à partir de la ligue Bronze. 206 |
207 |
208 | 209 | 210 | 211 |
212 |

213 |   214 | Entrées du jeu 215 |

216 | 217 |
218 |
Entrées d'initialisation
219 |
220 | Ligne 1: un entier numSites, indiquant le nombre de sites de construction présents sur la carte.
221 | Ligne 2: Un entier touchedSite représentant l'identifiant du site de construction en contact avec la Reine s'il existe, -1 sinon.
222 | Les numSites lignes suivantes : 4 entiers représentant l'identifiant obstacleId, les coordonnées x et y, et le rayon radius
d'un site de construction. 223 |
224 |
225 | 226 | 227 |
228 |
Entrées pour un tour de jeu
229 |
230 | 231 | Ligne 1: Un entier gold représentant l'or disponible du joueur.
232 | Les numSites lignes suivantes : 7 entiers siteId ignore1 233 | ignore2 structureType owner param1 param2
234 |
    235 |
  • siteId: L'identifiant d'un site de construction
  • 236 |
  • ignore1: A ignorer pour cette ligue
  • 237 |
  • ignore2: A ignorer pour cette ligue
  • 238 |
  • structureType: Le type de bâtiment construit sur le site. 239 |
      240 |
    • -1: Pas de bâtiment construit
    • 241 |
    • 1: Une tour
    • 242 |
    • 2: Une caserne
    • 243 |
    244 |
  • 245 |
  • owner: Le propriétaire du bâtiment 246 |
      247 |
    • -1: Pas de bâtiment construit
    • 248 |
    • 0: Bâtiment allié
    • 249 |
    • 1: Bâtiment ennemi
    • 250 |
    251 |
  • 252 |
  • param1: 253 |
      254 |
    • Quand il n'y a pas de bâtiment construit : -1
    • 255 |
    • Si c'est une tour, son nombre de points de vie restants. 256 |
    • Si c'est une caserne, le nombre de tours restant avant que la caserne puisse à nouveau lancer un cycle d'entraînement d'armées, 0 si elle est disponible.
    • 257 |
    258 |
  • 259 |
  • param2 260 |
      261 |
    • Quand il n'y a pas de bâtiment construit : -1
    • 262 |
    • Si c'est une tour, son rayon de portée
    • 263 |
    • Si c'est une caserne, le type d'armée qu'elle produit 264 | 0 265 | pour une caserne de chevaliers, 266 | 1 267 | pour une caserne d'archers, 268 | 2 269 | pour une caserne de géants. 270 |
    • 271 |
    272 |
  • 273 |
274 | Ligne suivante : numUnits le nombre total d'unités actives, incluant les Reines
275 | Les numUnits lignes suivantes : 5 entiers x, y, owner, unitType, et health
276 |
    277 |
  • x et y: les coordonnées de l'unité.
  • 278 |
  • owner: 0 = alliée; 1 = ennemie.
  • 279 |
  • unitType: le type d'unité 280 | -1 281 | pour une Reine, 282 | 0 283 | pour un chevalier, 284 | 1 285 | pour un archer, et 286 | 2 287 | pour un géant. 288 |
  • 289 |
  • health : Le nombre de points de vie restants de l'unité
  • 290 |
291 |
292 |
293 | 294 | 295 |
296 |
Sortie pour un tour de jeu
297 | Première ligne : L'une des commandes suivantes 298 |
    299 |
  • WAIT Ne rien faire
  • 300 |
  • MOVE x y Déplacer la Reine vers les coordonnées indiquées (x et y étant des entiers)
  • 301 |
  • 302 | BUILD {siteId} TOWER 303 | Construire une tour sur le site de construction indiqué. 304 | Si la Reine est trop éloignée du site de construction, cette commande déplacera la Reine vers le site. 305 |
  • 306 |
  • 307 | BUILD {siteId} BARRACKS-{type} 308 | Construire une caserne sur le site de construction indiqué. 309 | Si la Reine est trop éloignée du site de construction, cette commande déplacera la Reine vers le site. Le type de caserne est soit 310 | KNIGHT (chevalier) 311 | soit 312 | ARCHER (archer) 313 | soit 314 | GIANT (géant). 315 |
  • 316 |
317 | Seconde ligne : TRAIN suvi de zéro, un ou plusieurs identifiants siteId de casernes alliées pour commencer à entraîner des armées. 318 |
319 | 320 | 321 |
322 |
Contraintes
323 |
324 | Temps de réponse pour le premier tour ≤ 1000ms
325 | Temps de réponse pour un tour ≤ 50ms
326 |
327 |
328 | 329 | 330 |
335 |
336 | 337 |
338 |

339 | Qu'est-ce qui m'attend dans les prochaines ligues ? 340 |

341 | Les nouvelles règles débloquées dans les prochaines ligues sont : 342 |
    343 |
  • Vous devrez construire des mines pour produire de l'or.
  • 344 |
345 |
346 |
347 | -------------------------------------------------------------------------------- /config/level2/stub.txt: -------------------------------------------------------------------------------- 1 | read numSites:int 2 | loop numSites read siteId:int x:int y:int radius:int 3 | gameloop 4 | read gold:int touchedSite:int 5 | loop numSites read siteId:int ignore1:int ignore2:int structureType:int owner:int param1:int param2:int 6 | read numUnits:int 7 | loop numUnits read x:int y:int owner:int unitType:int health:int 8 | write WAIT 9 | TRAIN 10 | 11 | INPUT 12 | touchedSite: -1 if none 13 | ignore1: used in future leagues 14 | ignore2: used in future leagues 15 | structureType: -1 = No structure, 1 = Tower, 2 = Barracks 16 | owner: -1 = No structure, 0 = Friendly, 1 = Enemy 17 | unitType: -1 = QUEEN, 0 = KNIGHT, 1 = ARCHER, 2 = GIANT 18 | 19 | OUTPUT 20 | First line: A valid queen action 21 | Second line: A set of training instructions -------------------------------------------------------------------------------- /config/level3/Boss.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | import java.math.*; 4 | 5 | /* 6 | Level 3 Boss: Duchess 7 | Expected Player Skills: Generating income via goldmines 8 | Boss strategy: Build a single barracks and train Knight minions whenever possible. 9 | If income is less than eight, build or upgrade a mine on the closest obstacle to the barracks. 10 | Otherwise, build a tower on the empty obstacle closest to the barracks. 11 | */ 12 | class Player { 13 | 14 | public static void main(String args[]) { 15 | Scanner in = new Scanner(System.in); 16 | int numObstacles = in.nextInt(); 17 | Obst[] obstacles = new Obst[numObstacles]; 18 | for (int i = 0; i < numObstacles; i++) { 19 | int obstacleId = in.nextInt(); 20 | int x = in.nextInt(); 21 | int y = in.nextInt(); 22 | int radius = in.nextInt(); 23 | obstacles[obstacleId] = new Obst(obstacleId, x, y); 24 | } 25 | 26 | Obst barracks = null; 27 | int myQueenX = -1, myQueenY = -1; 28 | 29 | // game loop 30 | while (true) { 31 | int gold = in.nextInt(); 32 | int touching = in.nextInt(); 33 | int income = 0; 34 | 35 | for (int i = 0; i < numObstacles; i++) { 36 | int obstacleId = in.nextInt(); 37 | int goldRemaining = in.nextInt(); // -1 if unknown 38 | int maxMineSize = in.nextInt(); // -1 if unknown 39 | int structureType = in.nextInt(); // -1 = No structure, 0 = Resource Mine, 1 = Tower, 2 = Barracks 40 | int owner = in.nextInt(); // -1 = No structure, 0 = Friendly, 1 = Enemy 41 | int param1 = in.nextInt(); 42 | int param2 = in.nextInt(); 43 | 44 | obstacles[obstacleId].type = structureType; 45 | obstacles[obstacleId].maxSize = maxMineSize; 46 | obstacles[obstacleId].remainingGold = goldRemaining; 47 | obstacles[obstacleId].param1 = param1; 48 | 49 | if (structureType == 0 && owner == 0){ 50 | income += param1; 51 | } 52 | } 53 | int numUnits = in.nextInt(); 54 | for (int i = 0; i < numUnits; i++) { 55 | int x = in.nextInt(); 56 | int y = in.nextInt(); 57 | int owner = in.nextInt(); 58 | int type = in.nextInt(); 59 | int health = in.nextInt(); 60 | 61 | if (type == -1 && owner == 0){ 62 | myQueenX = x; 63 | myQueenY = y; 64 | } 65 | } 66 | 67 | if(barracks == null){ 68 | double minDist = Double.MAX_VALUE; 69 | for (Obst o: obstacles) { 70 | double dist = distance(myQueenX, o.x, myQueenY, o.y); 71 | if(dist < minDist){ 72 | minDist = dist; 73 | barracks = o; 74 | } 75 | } 76 | } 77 | 78 | String action = "WAIT"; 79 | if (barracks.type == -1){ 80 | action = String.format("BUILD %d BARRACKS-KNIGHT", barracks.id); 81 | } else if (income < 8){ 82 | double minDist = Double.MAX_VALUE; 83 | Obst target = null; 84 | for (Obst o: obstacles) { 85 | double dist = distance(barracks.x, o.x, barracks.y, o.y); 86 | if(dist < minDist && o.remainingGold > 0 && (o.type == -1 || (o.type == 0 && o.param1 < o.maxSize))){ 87 | minDist = dist; 88 | target = o; 89 | } 90 | } 91 | if (target != null){ 92 | action = String.format("BUILD %d MINE", target.id); 93 | } 94 | } else { 95 | double minDist = Double.MAX_VALUE; 96 | Obst target = null; 97 | for (Obst o: obstacles) { 98 | double dist = distance(barracks.x, o.x, barracks.y, o.y); 99 | if(dist < minDist && o.type == -1){ 100 | minDist = dist; 101 | target = o; 102 | } 103 | } 104 | if (target != null){ 105 | action = String.format("BUILD %d TOWER", target.id); 106 | } 107 | } 108 | 109 | String train = "TRAIN"; 110 | if (gold >= 80){ 111 | if (barracks.type == 2){ 112 | train = train + " " + barracks.id; 113 | } 114 | } 115 | 116 | // First line: A valid queen action 117 | // Second line: A set of training instructions 118 | System.out.println(action); 119 | System.out.println(train); 120 | } 121 | } 122 | 123 | static double distance(int x1, int x2, int y1, int y2){ 124 | return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 125 | } 126 | } 127 | 128 | class Obst{ 129 | int id, x, y; 130 | int type = -1; 131 | int maxSize; 132 | int remainingGold; 133 | int param1; 134 | 135 | public Obst(int id, int x, int y){ 136 | this.id = id; 137 | this.x = x; 138 | this.y = y; 139 | } 140 | } -------------------------------------------------------------------------------- /config/level3/stub.txt: -------------------------------------------------------------------------------- 1 | read numSites:int 2 | loop numSites read siteId:int x:int y:int radius:int 3 | gameloop 4 | read gold:int touchedSite:int 5 | loop numSites read siteId:int goldRemaining:int maxMineSize:int structureType:int owner:int param1:int param2:int 6 | read numUnits:int 7 | loop numUnits read x:int y:int owner:int unitType:int health:int 8 | write WAIT 9 | TRAIN 10 | 11 | INPUT 12 | touchedSite: -1 if none 13 | goldRemaining: -1 if unknown 14 | maxMineSize: -1 if unknown 15 | structureType: -1 = No structure, 0 = Goldmine, 1 = Tower, 2 = Barracks 16 | owner: -1 = No structure, 0 = Friendly, 1 = Enemy 17 | unitType: -1 = QUEEN, 0 = KNIGHT, 1 = ARCHER, 2 = GIANT 18 | 19 | OUTPUT 20 | First line: A valid queen action 21 | Second line: A set of training instructions -------------------------------------------------------------------------------- /config/level4/Boss.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | import java.math.*; 4 | 5 | /* 6 | Default AI 7 | Strategy: Build a single barracks and train Knight minions whenever possible. 8 | If income is less than ten, build or upgrade a mine on the closest obstacle to the barracks. 9 | Otherwise, build a tower on the empty obstacle closest to the barracks. 10 | */ 11 | class Player { 12 | 13 | public static void main(String args[]) { 14 | Scanner in = new Scanner(System.in); 15 | int numObstacles = in.nextInt(); 16 | Obst[] obstacles = new Obst[numObstacles]; 17 | for (int i = 0; i < numObstacles; i++) { 18 | int obstacleId = in.nextInt(); 19 | int x = in.nextInt(); 20 | int y = in.nextInt(); 21 | int radius = in.nextInt(); 22 | obstacles[obstacleId] = new Obst(obstacleId, x, y); 23 | } 24 | 25 | Obst barracks = null; 26 | int myQueenX = -1, myQueenY = -1; 27 | 28 | // game loop 29 | while (true) { 30 | int gold = in.nextInt(); 31 | int touching = in.nextInt(); 32 | int income = 0; 33 | 34 | for (int i = 0; i < numObstacles; i++) { 35 | int obstacleId = in.nextInt(); 36 | int goldRemaining = in.nextInt(); // -1 if unknown 37 | int maxMineSize = in.nextInt(); // -1 if unknown 38 | int structureType = in.nextInt(); // -1 = No structure, 0 = Resource Mine, 1 = Tower, 2 = Barracks 39 | int owner = in.nextInt(); // -1 = No structure, 0 = Friendly, 1 = Enemy 40 | int param1 = in.nextInt(); 41 | int param2 = in.nextInt(); 42 | 43 | obstacles[obstacleId].type = structureType; 44 | obstacles[obstacleId].maxSize = maxMineSize; 45 | obstacles[obstacleId].remainingGold = goldRemaining; 46 | obstacles[obstacleId].param1 = param1; 47 | obstacles[obstacleId].owner = owner; 48 | 49 | if (structureType == 0 && owner == 0){ 50 | income += param1; 51 | } 52 | } 53 | int numUnits = in.nextInt(); 54 | for (int i = 0; i < numUnits; i++) { 55 | int x = in.nextInt(); 56 | int y = in.nextInt(); 57 | int owner = in.nextInt(); 58 | int type = in.nextInt(); 59 | int health = in.nextInt(); 60 | 61 | if (type == -1 && owner == 0){ 62 | myQueenX = x; 63 | myQueenY = y; 64 | } 65 | } 66 | 67 | if(barracks == null){ 68 | double minDist = Double.MAX_VALUE; 69 | for (Obst o: obstacles) { 70 | double dist = distance(myQueenX, o.x, myQueenY, o.y); 71 | if(dist < minDist){ 72 | minDist = dist; 73 | barracks = o; 74 | } 75 | } 76 | } 77 | 78 | String action = "WAIT"; 79 | if (barracks.type == -1){ 80 | action = String.format("BUILD %d BARRACKS-KNIGHT", barracks.id); 81 | } else if (income < 10){ 82 | double minDist = Double.MAX_VALUE; 83 | Obst target = null; 84 | for (Obst o: obstacles) { 85 | double dist = distance(barracks.x, o.x, barracks.y, o.y); 86 | if(dist < minDist && o.remainingGold > 0 && (o.type == -1 || (o.type == 0 && o.param1 < o.maxSize))){ 87 | minDist = dist; 88 | target = o; 89 | } 90 | } 91 | if (target != null){ 92 | action = String.format("BUILD %d MINE", target.id); 93 | } 94 | } else { 95 | double minDist = Double.MAX_VALUE; 96 | Obst target = null; 97 | for (Obst o: obstacles) { 98 | double dist = distance(barracks.x, o.x, barracks.y, o.y); 99 | if(dist < minDist && o.type == -1){ 100 | minDist = dist; 101 | target = o; 102 | } 103 | } 104 | if (target != null){ 105 | action = String.format("BUILD %d TOWER", target.id); 106 | } 107 | } 108 | 109 | String train = "TRAIN"; 110 | if (gold >= 80){ 111 | if (barracks.type == 2 && barracks.owner == 0){ 112 | train = train + " " + barracks.id; 113 | } 114 | } 115 | 116 | // First line: A valid queen action 117 | // Second line: A set of training instructions 118 | System.out.println(action); 119 | System.out.println(train); 120 | } 121 | } 122 | 123 | static double distance(int x1, int x2, int y1, int y2){ 124 | return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 125 | } 126 | } 127 | 128 | class Obst{ 129 | int id, x, y; 130 | int type = -1; 131 | int owner; 132 | int maxSize; 133 | int remainingGold; 134 | int param1; 135 | 136 | public Obst(int id, int x, int y){ 137 | this.id = id; 138 | this.x = x; 139 | this.y = y; 140 | } 141 | } -------------------------------------------------------------------------------- /config/level4/stub.txt: -------------------------------------------------------------------------------- 1 | read numSites:int 2 | loop numSites read siteId:int x:int y:int radius:int 3 | gameloop 4 | read gold:int touchedSite:int 5 | loop numSites read siteId:int goldRemaining:int maxMineSize:int structureType:int owner:int param1:int param2:int 6 | read numUnits:int 7 | loop numUnits read x:int y:int owner:int unitType:int health:int 8 | write WAIT 9 | TRAIN 10 | 11 | INPUT 12 | touchedSite: -1 if none 13 | goldRemaining: -1 if unknown 14 | maxMineSize: -1 if unknown 15 | structureType: -1 = No structure, 0 = Goldmine, 1 = Tower, 2 = Barracks 16 | owner: -1 = No structure, 0 = Friendly, 1 = Enemy 17 | unitType: -1 = QUEEN, 0 = KNIGHT, 1 = ARCHER, 2 = GIANT 18 | 19 | OUTPUT 20 | First line: A valid queen action 21 | Second line: A set of training instructions -------------------------------------------------------------------------------- /leaguePopups/bronze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/leaguePopups/bronze.png -------------------------------------------------------------------------------- /leaguePopups/bronze_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league

3 | Welcome to the big leagues.
4 | Queens may now start the game with less health. 5 |
6 |
See the updated statement for details.
7 |
-------------------------------------------------------------------------------- /leaguePopups/bronze_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

Vous êtes passé à la ligue supérieure!

3 | Bienvenue dans la cours des grands.
4 | Les Reines peuvent maintenant commencer la partie avec moins de vie. 5 |
6 |
No rules have been changed.
7 |
-------------------------------------------------------------------------------- /leaguePopups/wood1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/leaguePopups/wood1.png -------------------------------------------------------------------------------- /leaguePopups/wood1_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league

3 | You are now able to build 4 | MINE 5 | structures to generate gold.
6 |
7 |
See the updated statement for details.
8 |
-------------------------------------------------------------------------------- /leaguePopups/wood1_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

Vous êtes passé à la ligue supérieure!

3 | Bous pouvez maintenant construire des structures de type 4 | MINE pour générer de l'or.
5 |
6 |
Reportez-vous à l'énoncé pour plus de détails.
7 |
-------------------------------------------------------------------------------- /leaguePopups/wood2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/leaguePopups/wood2.png -------------------------------------------------------------------------------- /leaguePopups/wood2_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league

3 | You are now able to build 4 | TOWER 5 | structures to defend yourself against enemy creeps, and train 6 | GIANT 7 | creeps in order to destroy opposing towers. 8 |
9 |
See the updated statement for details.
10 |
-------------------------------------------------------------------------------- /leaguePopups/wood2_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

Vous êtes passé à la ligue supérieure!

3 | Vous pouvez maintenant construire des tours (TOWER) pour vous défendre contre les unités ennemies, 4 | entraînez des géants (GIANT) pour détruire les tours de l'adversaire. 5 |
6 |
Reportez-vous à l'énoncé pour plus de détails.
7 |
-------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.codingame.game 6 | code-royale 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 1.2.10 11 | 1.30 12 | 13 | 14 | 15 | 16 | com.codingame.gameengine 17 | core 18 | ${gameengine.version} 19 | 20 | 21 | 22 | com.codingame.gameengine 23 | module-entities 24 | ${gameengine.version} 25 | 26 | 27 | 28 | com.codingame.gameengine 29 | runner 30 | ${gameengine.version} 31 | 32 | 33 | 34 | org.reflections 35 | reflections 36 | 0.9.10 37 | 38 | 39 | org.jetbrains.kotlin 40 | kotlin-stdlib-jre8 41 | ${kotlin.version} 42 | 43 | 44 | org.jetbrains.kotlin 45 | kotlin-test 46 | ${kotlin.version} 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.jetbrains.kotlin 55 | kotlin-maven-plugin 56 | ${kotlin.version} 57 | 58 | 59 | compile 60 | compile 61 | 62 | compile 63 | 64 | 65 | 66 | src/main/java 67 | src/main/kotlin 68 | src/test/java 69 | src/test/kotlin 70 | 71 | 72 | 73 | 74 | test-compile 75 | test-compile 76 | 77 | test-compile 78 | 79 | 80 | 81 | 82 | 1.8 83 | enable 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 3.7.0 90 | 91 | 92 | compile 93 | compile 94 | 95 | compile 96 | 97 | 98 | 99 | testCompile 100 | test-compile 101 | 102 | testCompile 103 | 104 | 105 | 106 | 107 | 1.8 108 | 1.8 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/main/java/anims/Anim.java: -------------------------------------------------------------------------------- 1 | package anims; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Anim { 7 | private double t; 8 | private String id; 9 | private Map params; 10 | 11 | public Anim(String id, double t) { 12 | this.t = t; 13 | this.id = id; 14 | this.params = new HashMap<>(); 15 | } 16 | 17 | public Anim(String id) { 18 | this(id, 0); 19 | } 20 | 21 | public double getT() { 22 | return t; 23 | } 24 | 25 | public void setT(double t) { 26 | this.t = t; 27 | } 28 | 29 | public String getId() { 30 | return id; 31 | } 32 | public void setId(String id) { 33 | this.id = id; 34 | } 35 | 36 | public Map getParams() { 37 | return params; 38 | } 39 | 40 | public void setParams(Map params) { 41 | this.params = params; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/anims/AnimModule.java: -------------------------------------------------------------------------------- 1 | package anims; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.codingame.gameengine.core.AbstractPlayer; 7 | import com.codingame.gameengine.core.GameManager; 8 | import com.codingame.gameengine.core.Module; 9 | import com.codingame.gameengine.module.entities.GraphicEntityModule; 10 | import com.google.inject.Inject; 11 | 12 | public class AnimModule implements Module { 13 | 14 | GameManager gameManager; 15 | @Inject GraphicEntityModule entityModule; 16 | List animEvents; 17 | 18 | @Inject 19 | AnimModule(GameManager gameManager) { 20 | this.gameManager = gameManager; 21 | animEvents = new ArrayList<>(); 22 | gameManager.registerModule(this); 23 | } 24 | 25 | @Override 26 | public void onGameInit() { 27 | sendFrameData(); 28 | } 29 | 30 | @Override 31 | public void onAfterGameTurn() { 32 | sendFrameData(); 33 | } 34 | 35 | @Override 36 | public void onAfterOnEnd() { 37 | sendFrameData(); 38 | } 39 | 40 | public Anim createAnimationEvent(String id, double t) { 41 | Anim animEvent = new Anim(id, t); 42 | animEvents.add(animEvent); 43 | return animEvent; 44 | } 45 | 46 | private void sendFrameData() { 47 | gameManager.setViewData("anims", animEvents); 48 | animEvents.clear(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/tooltipModule/TooltipModule.java: -------------------------------------------------------------------------------- 1 | package tooltipModule; 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.codingame.gameengine.module.entities.Entity; 7 | import com.google.inject.Inject; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | public class TooltipModule implements Module { 13 | 14 | GameManager gameManager; 15 | Map> registrations; 16 | Map> newRegistrations; 17 | Map extra, newExtra; 18 | 19 | @Inject 20 | TooltipModule(GameManager gameManager) { 21 | this.gameManager = gameManager; 22 | gameManager.registerModule(this); 23 | registrations = new HashMap<>(); 24 | newRegistrations = new HashMap<>(); 25 | extra = new HashMap<>(); 26 | newExtra = new HashMap<>(); 27 | } 28 | 29 | @Override 30 | public void onGameInit() { 31 | sendFrameData(); 32 | } 33 | 34 | @Override 35 | public void onAfterGameTurn() { 36 | sendFrameData(); 37 | } 38 | 39 | @Override 40 | public void onAfterOnEnd() { 41 | sendFrameData(); 42 | } 43 | 44 | private void sendFrameData() { 45 | Object[] data = { newRegistrations, newExtra }; 46 | gameManager.setViewData("tooltips", data); 47 | newRegistrations.clear(); 48 | newExtra.clear(); 49 | } 50 | 51 | public void registerEntity(Entity entity) { 52 | registerEntity(entity, new HashMap<>()); 53 | } 54 | 55 | public void registerEntity(Entity entity, Map params) { 56 | int id = entity.getId(); 57 | if (!params.equals(registrations.get(id))) { 58 | newRegistrations.put(id, params); 59 | registrations.put(id, params); 60 | } 61 | } 62 | 63 | public void updateExtraTooltipText(Entity entity, String... lines) { 64 | int id = entity.getId(); 65 | if (!lines.equals(extra.get(id))) { 66 | newExtra.put(id, lines); 67 | extra.put(id, lines); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Characters.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import anims.Anim 4 | import anims.AnimModule 5 | import com.codingame.game.Constants.GIANT_BUST_RATE 6 | import com.codingame.game.Constants.KNIGHT_DAMAGE 7 | import com.codingame.game.Constants.QUEEN_HP 8 | import com.codingame.game.Constants.QUEEN_MASS 9 | import com.codingame.game.Constants.QUEEN_RADIUS 10 | import com.codingame.game.Constants.QUEEN_SPEED 11 | import com.codingame.game.Constants.ARCHER_DAMAGE 12 | import com.codingame.game.Constants.ARCHER_DAMAGE_TO_GIANTS 13 | import com.codingame.game.Constants.TOUCHING_DELTA 14 | import com.codingame.gameengine.core.GameManager 15 | import com.codingame.gameengine.module.entities.Curve 16 | import com.codingame.gameengine.module.entities.Entity 17 | import com.codingame.gameengine.module.entities.GraphicEntityModule 18 | import tooltipModule.TooltipModule 19 | import java.lang.UnsupportedOperationException 20 | import java.util.* 21 | 22 | lateinit var theEntityManager: GraphicEntityModule 23 | lateinit var theTooltipModule: TooltipModule 24 | lateinit var theGameManager: GameManager 25 | lateinit var theAnimModule: AnimModule 26 | lateinit var theRandom: Random 27 | 28 | val viewportX = 0..1920 29 | val viewportY = 0..1000 30 | 31 | var Anim.location: Vector2 32 | get() = throw UnsupportedOperationException() 33 | set(value) { 34 | params["x"] = value.x 35 | params["y"] = value.y 36 | } 37 | 38 | var ?> Entity.location: Vector2 39 | get() = Vector2(x - viewportX.first, y - viewportY.first) 40 | 41 | set(value) { 42 | x = (value.x + viewportX.first).toInt() 43 | y = (value.y + viewportY.first).toInt() 44 | } 45 | 46 | abstract class FieldObject { 47 | abstract var location: Vector2 48 | abstract var radius: Int 49 | abstract val mass: Int // 0 := immovable 50 | } 51 | 52 | abstract class Unit(val owner: Player) : FieldObject() { 53 | abstract fun damage(damageAmount: Int) 54 | 55 | protected val tokenCircle = theEntityManager.createSprite() 56 | .setImage(if (owner.isSecondPlayer) "Unite_Base_Bleu" else "Unite_Base_Rouge") 57 | .setAnchor(0.5) 58 | .setZIndex(40)!! // TODO: set to some kind of increasing ID 59 | 60 | protected val characterSprite = theEntityManager.createSprite() 61 | .setZIndex(41) 62 | .setScale(1.2) 63 | .setAnchor(0.5)!! 64 | 65 | protected val tokenGroup = theEntityManager.createGroup(tokenCircle, characterSprite) 66 | 67 | override var location: Vector2 = Vector2.Zero 68 | set(value) { 69 | if (value == Vector2.Zero) return 70 | field = value 71 | 72 | tokenGroup.location = value 73 | } 74 | 75 | abstract val maxHealth:Int 76 | open var health:Int = 0 77 | set(value) { 78 | field = value 79 | if (value < 0) field = 0 80 | theTooltipModule.updateExtraTooltipText(tokenCircle, "Health: $health") 81 | } 82 | 83 | fun commitState(time: Double) { 84 | theEntityManager.commitEntityState(time, tokenCircle, characterSprite, tokenGroup) 85 | } 86 | } 87 | 88 | class Queen(owner: Player) : Unit(owner) { 89 | override val mass = QUEEN_MASS 90 | override var radius = QUEEN_RADIUS 91 | override val maxHealth = Leagues.queenHp 92 | 93 | init { 94 | characterSprite.image = "Unite_Reine" 95 | theTooltipModule.registerEntity(tokenGroup, mapOf("type" to "Queen")) 96 | tokenCircle.baseWidth = radius*2 97 | tokenCircle.baseHeight = radius*2 98 | characterSprite.baseWidth = radius*2 99 | characterSprite.baseHeight = radius*2 100 | } 101 | 102 | fun moveTowards(target: Vector2) { 103 | location = location.towards(target, QUEEN_SPEED.toDouble()) 104 | } 105 | 106 | override fun damage(damageAmount: Int) { 107 | if (damageAmount <= 0) return 108 | owner.health -= damageAmount 109 | } 110 | 111 | } 112 | 113 | abstract class Creep( 114 | owner: Player, 115 | val creepType: CreepType 116 | ) : Unit(owner) { 117 | 118 | protected val speed: Int = creepType.speed 119 | val attackRange: Int = creepType.range 120 | override val mass: Int = creepType.mass 121 | override val maxHealth = creepType.hp 122 | final override var radius = creepType.radius 123 | 124 | open fun finalizeFrame() { } 125 | 126 | override fun damage(damageAmount: Int) { 127 | if (damageAmount <= 0) return // no accidental healing! 128 | 129 | health -= damageAmount 130 | theTooltipModule.updateExtraTooltipText(tokenCircle, "Health: $health") 131 | } 132 | 133 | abstract fun dealDamage() 134 | abstract fun move(frames: Double) 135 | 136 | final override var health: Int 137 | get() { return super.health } 138 | set(value) { 139 | super.health = value 140 | if (super.health == 0) { 141 | characterSprite.alpha = 0.0 142 | tokenCircle.alpha = 0.0 143 | } else { 144 | tokenCircle.alpha = 0.8 * health / maxHealth + 0.2 145 | } 146 | } 147 | 148 | init { 149 | health = creepType.hp 150 | 151 | tokenCircle.baseWidth = radius*2 152 | tokenCircle.baseHeight = radius*2 153 | characterSprite.image = creepType.assetName 154 | characterSprite.baseWidth = radius*2 155 | characterSprite.baseHeight = radius*2 156 | 157 | theTooltipModule.registerEntity(tokenGroup,mapOf("type" to creepType.toString())) 158 | } 159 | } 160 | 161 | class GiantCreep( 162 | owner: Player, 163 | creepType: CreepType, 164 | private val obstacles: List 165 | ) : Creep(owner, creepType) { 166 | override fun move(frames: Double) { 167 | obstacles 168 | .filter { it.structure != null && it.structure is Tower && (it.structure as Tower).owner == owner.enemyPlayer } 169 | .minBy { it.location.distanceTo(location) } 170 | ?.let { 171 | location = location.towards(it.location, speed.toDouble() * frames) 172 | } 173 | } 174 | 175 | override fun dealDamage() { 176 | obstacles 177 | .firstOrNull { 178 | val struc = it.structure 179 | struc is Tower 180 | && struc.owner == owner.enemyPlayer 181 | && it.location.distanceTo(location) < radius + it.radius + TOUCHING_DELTA 182 | }?.also { 183 | (it.structure as Tower).health -= GIANT_BUST_RATE 184 | val creepToTower = it.location - location 185 | characterSprite.location = creepToTower.resizedTo(radius.toDouble()) 186 | theEntityManager.commitEntityState(0.2, characterSprite) 187 | characterSprite.location = Vector2(0,0) 188 | theEntityManager.commitEntityState(1.0, characterSprite) 189 | } 190 | } 191 | } 192 | 193 | class KnightCreep(owner: Player, creepType: CreepType) 194 | : Creep(owner, creepType) { 195 | 196 | private var lastLocation: Vector2? = null 197 | private var attacksThisTurn: Boolean = false 198 | 199 | override fun finalizeFrame() { 200 | val last = lastLocation 201 | 202 | if (last != null) { 203 | val movementVector = when { 204 | last.distanceTo(location) > 30 && !attacksThisTurn -> location - last 205 | else -> owner.enemyPlayer.queenUnit.location - location 206 | } 207 | characterSprite.rotation = movementVector.angle 208 | } 209 | 210 | lastLocation = location 211 | } 212 | 213 | override fun move(frames: Double) { 214 | val enemyQueen = owner.enemyPlayer.queenUnit 215 | // move toward enemy queen, if not yet in range 216 | if (location.distanceTo(enemyQueen.location) > radius + enemyQueen.radius + attackRange) 217 | location = location.towards((enemyQueen.location + (location - enemyQueen.location).resizedTo(3.0)), speed.toDouble() * frames) 218 | } 219 | 220 | override fun dealDamage() { 221 | attacksThisTurn = false 222 | val enemyQueen = owner.enemyPlayer.queenUnit 223 | if (location.distanceTo(enemyQueen.location) < radius + enemyQueen.radius + attackRange + TOUCHING_DELTA) { 224 | attacksThisTurn = true 225 | characterSprite.setAnchorX(0.5, Curve.IMMEDIATE) 226 | theEntityManager.commitEntityState(0.4, characterSprite) 227 | characterSprite.anchorX = 0.2 228 | theEntityManager.commitEntityState(0.7, characterSprite) 229 | characterSprite.anchorX = 0.5 230 | theEntityManager.commitEntityState(1.0, characterSprite) 231 | owner.enemyPlayer.health -= KNIGHT_DAMAGE 232 | } 233 | } 234 | } 235 | 236 | class ArcherCreep(owner: Player, creepType: CreepType) 237 | : Creep(owner, creepType){ 238 | 239 | private var lastLocation: Vector2? = null 240 | 241 | var attackTarget: Creep? = null 242 | val color = if (owner.isSecondPlayer) "Bleu" else "Rouge" 243 | private val projectile = theEntityManager.createSprite()!! 244 | .setZIndex(60) 245 | .setImage("Fleche_$color") 246 | .setVisible(false) 247 | .setAnchorX(1.0).setAnchorY(0.5) 248 | 249 | override fun finalizeFrame() { 250 | val target = findTarget() ?: owner.enemyPlayer.queenUnit 251 | 252 | val last = lastLocation 253 | 254 | if (last != null) { 255 | val movementVector = when { 256 | last.distanceTo(location) > 30 -> location - last 257 | else -> target.location - location 258 | } 259 | characterSprite.rotation = Math.atan2(movementVector.y, movementVector.x) 260 | } 261 | 262 | lastLocation = location 263 | 264 | val localAttackTarget = attackTarget 265 | if (localAttackTarget != null) { 266 | characterSprite.anchorX = 0.8 267 | theEntityManager.commitEntityState(0.3, characterSprite) 268 | characterSprite.anchorX = 0.5 269 | theEntityManager.commitEntityState(0.4, characterSprite) 270 | 271 | projectile.setRotation((localAttackTarget.location - location).angle, Curve.IMMEDIATE) 272 | projectile.isVisible = true 273 | projectile.setX(location.x.toInt() + viewportX.first, Curve.NONE) 274 | projectile.setY(location.y.toInt() + viewportY.first, Curve.NONE) 275 | theEntityManager.commitEntityState(0.4, projectile) 276 | projectile.setX(localAttackTarget.location.x.toInt() + viewportX.first, Curve.EASE_IN_AND_OUT) 277 | projectile.setY(localAttackTarget.location.y.toInt() + viewportY.first, Curve.EASE_IN_AND_OUT) 278 | theEntityManager.commitEntityState(0.99, projectile) 279 | projectile.isVisible = false 280 | theEntityManager.commitEntityState(1.0, projectile) 281 | } 282 | } 283 | 284 | override fun move(frames: Double) { 285 | val target = findTarget() ?: owner.queenUnit 286 | // move toward target, if not yet in range 287 | if (location.distanceTo(target.location) > radius + target.radius + attackRange) 288 | location = location.towards((target.location + (location - target.location).resizedTo(3.0)), speed.toDouble() * frames) 289 | } 290 | 291 | override fun dealDamage() { 292 | attackTarget = null 293 | val target = findTarget() ?: return 294 | if (location.distanceTo(target.location) < radius + target.radius + attackRange + TOUCHING_DELTA) { 295 | target.damage(if (target is GiantCreep) ARCHER_DAMAGE_TO_GIANTS else ARCHER_DAMAGE) 296 | attackTarget = target 297 | } 298 | } 299 | 300 | private fun findTarget(): Creep? { 301 | return owner.enemyPlayer.activeCreeps 302 | .minBy { it.location.distanceTo(location) } 303 | } 304 | } 305 | 306 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.Constants.OBSTACLE_PAIRS 4 | 5 | object Constants { 6 | const val STARTING_GOLD = 100 7 | 8 | const val QUEEN_SPEED = 60 9 | const val TOWER_HP_INITIAL = 200 10 | const val TOWER_HP_INCREMENT = 100 11 | const val TOWER_HP_MAXIMUM = 800 12 | const val TOWER_CREEP_DAMAGE_MIN = 3 13 | const val TOWER_CREEP_DAMAGE_CLIMB_DISTANCE = 200 14 | const val TOWER_QUEEN_DAMAGE_MIN = 1 15 | const val TOWER_QUEEN_DAMAGE_CLIMB_DISTANCE = 200 16 | const val TOWER_MELT_RATE = 4 17 | const val TOWER_COVERAGE_PER_HP = 1000 18 | 19 | const val GIANT_BUST_RATE = 80 20 | 21 | const val OBSTACLE_GAP = 90 22 | val OBSTACLE_RADIUS_RANGE = 60..90 23 | val OBSTACLE_GOLD_RANGE = 200..250 24 | val OBSTACLE_MINE_BASESIZE_RANGE = 1..3 25 | const val OBSTACLE_GOLD_INCREASE = 50 26 | const val OBSTACLE_GOLD_INCREASE_DISTANCE_1 = 500 27 | const val OBSTACLE_GOLD_INCREASE_DISTANCE_2 = 200 28 | val OBSTACLE_PAIRS = 6..12 29 | 30 | const val KNIGHT_DAMAGE = 1 31 | const val ARCHER_DAMAGE = 2 32 | const val ARCHER_DAMAGE_TO_GIANTS = 10 33 | 34 | const val QUEEN_RADIUS = 30 35 | const val QUEEN_MASS = 10000 36 | val QUEEN_HP = 5..20 37 | const val QUEEN_HP_MULT = 5 // i.e. 25..100 by 5 38 | const val QUEEN_VISION = 300 39 | 40 | val WORLD_WIDTH = viewportX.last - viewportX.first 41 | val WORLD_HEIGHT = viewportY.last - viewportY.first 42 | 43 | const val TOUCHING_DELTA = 5 44 | const val WOOD_FIXED_INCOME = 10 45 | } 46 | 47 | object Leagues { 48 | var towers = true 49 | var giants = true 50 | var mines = true 51 | var fixedIncome:Int? = null 52 | var obstacles:Int = OBSTACLE_PAIRS.last 53 | var queenHp: Int = 100 54 | } 55 | 56 | enum class CreepType(val count: Int, val cost: Int, val speed: Int, val range: Int, val radius: Int, 57 | val mass: Int, val hp: Int, val buildTime: Int, val assetName: String) { 58 | KNIGHT( 4, 80, 100, 0, 20, 400, 30, 5, "Unite_Fantassin"), 59 | ARCHER( 2, 100, 75 , 200, 25, 900, 45, 8, "Unite_Archer"), 60 | GIANT( 1, 140, 50 , 0, 40, 2000, 200, 10, "Unite_Siege") 61 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/MapBuilding.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.Constants.WORLD_HEIGHT 4 | import com.codingame.game.Constants.WORLD_WIDTH 5 | 6 | val background = theEntityManager.createSprite() 7 | .setImage("Background.jpg") 8 | .setBaseWidth(Constants.WORLD_WIDTH).setBaseHeight(Constants.WORLD_HEIGHT) 9 | .setX(viewportX.first).setY(viewportY.first) 10 | .setZIndex(0) 11 | 12 | val hudBackground = theEntityManager.createSprite() 13 | .setImage("Hud.png") 14 | .setBaseWidth(1920) 15 | .setX(0).setY(1080) 16 | .setAnchorY(1.0) 17 | .setZIndex(4000) 18 | 19 | fun IntRange.sample(): Int = theRandom.nextInt(last-first+1) + first 20 | 21 | fun buildMap(): List { 22 | fun buildObstacles(): List? { 23 | nextObstacleId = 0 24 | 25 | val obstaclePairs = (1..Leagues.obstacles).map { 26 | val rate = Constants.OBSTACLE_MINE_BASESIZE_RANGE.sample() 27 | val gold = Constants.OBSTACLE_GOLD_RANGE.sample() 28 | val radius = Constants.OBSTACLE_RADIUS_RANGE.sample() 29 | val l1 = Vector2.random(theRandom, WORLD_WIDTH, WORLD_HEIGHT) 30 | val l2 = Vector2(WORLD_WIDTH, WORLD_HEIGHT) - l1 31 | Pair(Obstacle(rate, gold, radius, l1), Obstacle(rate, gold, radius, l2)) 32 | } 33 | val obstacles = obstaclePairs.flatMap { listOf(it.first, it.second) } 34 | 35 | if ((1..100).all { 36 | obstaclePairs.forEach { (o1, o2) -> 37 | val mid = (o1.location + Vector2(Constants.WORLD_WIDTH -o2.location.x, Constants.WORLD_HEIGHT -o2.location.y)) / 2.0 38 | o1.location = mid 39 | o2.location = Vector2(Constants.WORLD_WIDTH -mid.x, Constants.WORLD_HEIGHT -mid.y) 40 | } 41 | collisionCheck(obstacles, Constants.OBSTACLE_GAP.toDouble()) 42 | }) { 43 | return obstacles 44 | } 45 | 46 | return obstacles 47 | } 48 | 49 | var obstacles: List? 50 | do { obstacles = buildObstacles(); } while (obstacles == null) 51 | 52 | val mapCenter = Vector2(viewportX.length / 2, viewportY.length / 2) 53 | obstacles.forEach { 54 | it.location = it.location.snapToIntegers() 55 | if (it.location.distanceTo(mapCenter) < Constants.OBSTACLE_GOLD_INCREASE_DISTANCE_1) { it.maxMineSize++; it.gold += Constants.OBSTACLE_GOLD_INCREASE } 56 | if (it.location.distanceTo(mapCenter) < Constants.OBSTACLE_GOLD_INCREASE_DISTANCE_2) { it.maxMineSize++; it.gold += Constants.OBSTACLE_GOLD_INCREASE } 57 | it.updateEntities() 58 | } 59 | PlayerHUD.obstacles = obstacles 60 | return obstacles 61 | } 62 | 63 | fun fixCollisions(entities: List, maxIterations: Int = 999) { 64 | repeat(maxIterations) { if (!collisionCheck(entities)) return } 65 | } 66 | 67 | /** 68 | * @return false if everything is ok; true if there was a correction 69 | */ 70 | fun collisionCheck(entities: List, acceptableGap: Double = 0.0): Boolean { 71 | return entities.flatMap { u1 -> 72 | val rad = u1.radius.toDouble() 73 | val clampDist = if (u1.mass == 0) Constants.OBSTACLE_GAP + rad else rad 74 | u1.location = u1.location.clampWithin(clampDist, WORLD_WIDTH - clampDist, clampDist, WORLD_HEIGHT - clampDist) 75 | 76 | (entities-u1).map { u2 -> 77 | val overlap = u1.radius + u2.radius + acceptableGap - u1.location.distanceTo(u2.location).toDouble // TODO: Fix this? 78 | if (overlap <= 1e-6) { 79 | false 80 | } 81 | else { 82 | val (d1, d2) = when { 83 | u1.mass == 0 && u2.mass == 0 -> Pair(0.5, 0.5) 84 | u1.mass == 0 -> Pair(0.0, 1.0) 85 | u2.mass == 0 -> Pair(1.0, 0.0) 86 | else -> Pair(u2.mass.toDouble() / (u1.mass + u2.mass), u1.mass.toDouble() / (u1.mass + u2.mass)) 87 | } 88 | 89 | val u1tou2 = u2.location - u1.location 90 | val gap = if (u1.mass == 0 && u2.mass == 0) 20.0 else 1.0 91 | 92 | u1.location -= u1tou2.resizedTo(d1 * overlap + if (u1.mass == 0 && u2.mass > 0) 0.0 else gap) 93 | u2.location += u1tou2.resizedTo(d2 * overlap + if (u2.mass == 0 && u1.mass > 0) 0.0 else gap) 94 | true 95 | } 96 | } 97 | }.toList().any { it } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Player.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.Constants.QUEEN_HP 4 | import com.codingame.game.Constants.QUEEN_VISION 5 | import com.codingame.game.Constants.STARTING_GOLD 6 | import com.codingame.gameengine.core.AbstractPlayer 7 | import kotlin.math.roundToInt 8 | 9 | class Player : AbstractPlayer() { 10 | override fun getExpectedOutputLines(): Int = 2 11 | lateinit var queenUnit: Queen 12 | lateinit var enemyPlayer: Player 13 | var isSecondPlayer: Boolean = false 14 | 15 | private fun fixOwner(player: Player?) = when (player) { null -> -1; this -> 0; else -> 1 } 16 | 17 | fun printObstacleInit(obstacle: Obstacle) { 18 | val (x,y) = obstacle.location 19 | val toks = listOf(obstacle.obstacleId, x.roundToInt(), y.roundToInt(), obstacle.radius) 20 | sendInputLine(toks.joinToString(" ")) 21 | } 22 | 23 | fun printObstaclePerTurn(obstacle: Obstacle) { 24 | val struc = obstacle.structure 25 | val visible = (struc != null && struc.owner == this) || obstacle.location.distanceTo(queenUnit.location) < QUEEN_VISION 26 | 27 | val toks = listOf( 28 | obstacle.obstacleId, 29 | if (visible) obstacle.gold else -1, 30 | if (visible) obstacle.maxMineSize else -1) + when (struc) { 31 | is Mine -> listOf(0, fixOwner(struc.owner), if (visible) struc.incomeRate else -1, -1) 32 | is Tower -> listOf(1, fixOwner(struc.owner), struc.health, struc.attackRadius) 33 | is Barracks -> listOf(2, fixOwner(struc.owner), if(!struc.isTraining) 0 else struc.progressMax - struc.progress, struc.creepType.ordinal) 34 | else -> listOf(-1, -1, -1, -1) 35 | } 36 | sendInputLine(toks.joinToString(" ")) 37 | } 38 | 39 | val activeCreeps = mutableListOf() 40 | 41 | fun allUnits() = activeCreeps + queenUnit 42 | 43 | var health by nonNegative(100).andAlso { if (score >= 0) score = it } 44 | var gold = STARTING_GOLD 45 | var goldPerTurn = 0 46 | 47 | fun checkQueenHealth() { 48 | queenUnit.health = health 49 | if (health == 0) deactivate("Dead queen") 50 | hud.update() 51 | } 52 | 53 | val hud by lazy { PlayerHUD(this, isSecondPlayer) } 54 | 55 | fun kill(reason: String) { 56 | score = -1 57 | deactivate(reason) 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/PlayerHUD.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.Constants.QUEEN_HP 4 | import com.codingame.game.Constants.QUEEN_HP_MULT 5 | 6 | class PlayerHUD(private val player: Player, isSecondPlayer: Boolean) { 7 | private val left = if (isSecondPlayer) 1920/2 else 0 8 | private val right = if (isSecondPlayer) 1920 else 1920/2 9 | private val top = viewportY.last 10 | private val bottom = 1080 11 | 12 | private val healthBarWidth = 420 13 | 14 | private val avatar = theEntityManager.createSprite() 15 | .setImage(player.avatarToken) 16 | .setY(bottom).setAnchorY(1.0) 17 | .apply { if (isSecondPlayer) { x = 1920; anchorX = 1.0 } else { x = 0 } } 18 | .setBaseWidth(140) 19 | .setBaseHeight(140) 20 | .setZIndex(4003)!! 21 | 22 | init { 23 | val maskCircle = theEntityManager.createCircle() 24 | .setRadius(64) 25 | .setX( if (isSecondPlayer) 1920-69 else 69) 26 | .setY(bottom - 68) 27 | 28 | avatar.setMask(maskCircle) 29 | } 30 | 31 | private val healthBarFillMask = theEntityManager.createRectangle()!! 32 | .setLineAlpha(0.0) 33 | .setY(top + 39) 34 | .setX(if (isSecondPlayer) 1920 - 139 - healthBarWidth else 143) 35 | .setWidth(healthBarWidth).setHeight(28) 36 | .setFillColor(player.colorToken) 37 | .setFillAlpha(0.0) 38 | .setLineWidth(0) 39 | .setZIndex(4002) 40 | 41 | private val healthBarFill = theEntityManager.createSprite() 42 | .setImage(if (isSecondPlayer) "Life-Bleu" else "Life-Rouge") 43 | .setX(if (isSecondPlayer) 1920 - 139 - healthBarWidth else 143) 44 | .setY(top + 39) 45 | .setZIndex(4002) 46 | .setMask(healthBarFillMask) 47 | 48 | private val playerName = theEntityManager.createText(player.nicknameToken)!! 49 | .setY(bottom - 45).setAnchorY(1.0) 50 | .apply { if (isSecondPlayer) { x = 1770; anchorX = 1.0 } else { x = 150 }} 51 | .setFillColor(0xffffff) 52 | .setScale(1.8) 53 | .setFontFamily("Arial Black") 54 | .setZIndex(4003) 55 | 56 | private val healthText = theEntityManager.createText(player.health.toString())!! 57 | .setX(healthBarFillMask.x + healthBarFillMask.width - 10).setY(healthBarFillMask.y + healthBarFillMask.height/2) 58 | .setAnchorX(1.0).setAnchorY(0.5) 59 | .setScale(1.3) 60 | .setFontFamily("Arial Black") 61 | .setFillColor(0xffffff) 62 | .setZIndex(4003) 63 | 64 | private val moneyText = theEntityManager.createText("0") 65 | .setY(bottom - 40).setAnchorY(0.5) 66 | .setX(if (isSecondPlayer) 1020 else 700) 67 | .setFillColor(0xffffff) 68 | .setScale(1.8) 69 | .setFontFamily("Arial Black") 70 | .setZIndex(4002)!! 71 | 72 | private val moneyIncText = theEntityManager.createText("") 73 | .setY(bottom - 40).setAnchorY(0.5) 74 | .setX(if (isSecondPlayer) 1220 else 910).setAnchorX(1.0) 75 | .setFillColor(0xffffff) 76 | .setScale(1.2) 77 | .setFontFamily("Arial Black") 78 | .setZIndex(4002)!! 79 | 80 | fun update() { 81 | healthBarFillMask.width = healthBarWidth * player.health / (QUEEN_HP.last * QUEEN_HP_MULT) 82 | healthText.text = player.health.toString() 83 | moneyText.text = player.gold.toString() 84 | moneyIncText.text = when (player.goldPerTurn) { 85 | 0 -> "" 86 | else -> "+${player.goldPerTurn}" 87 | } 88 | theEntityManager.commitEntityState(0.0, moneyText, moneyIncText) 89 | } 90 | 91 | companion object { 92 | lateinit var obstacles: List 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Referee.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import anims.AnimModule 4 | import com.codingame.game.Constants.OBSTACLE_PAIRS 5 | import com.codingame.game.Constants.QUEEN_HP 6 | import com.codingame.game.Constants.QUEEN_HP_MULT 7 | import com.codingame.game.Constants.QUEEN_RADIUS 8 | import com.codingame.game.Constants.TOUCHING_DELTA 9 | import com.codingame.game.Constants.TOWER_HP_INCREMENT 10 | import com.codingame.game.Constants.TOWER_HP_INITIAL 11 | import com.codingame.game.Constants.TOWER_HP_MAXIMUM 12 | import com.codingame.game.Constants.WOOD_FIXED_INCOME 13 | import com.codingame.game.Constants.WORLD_HEIGHT 14 | import com.codingame.game.Constants.WORLD_WIDTH 15 | import com.codingame.gameengine.core.AbstractPlayer 16 | import com.codingame.gameengine.core.AbstractReferee 17 | import com.codingame.gameengine.core.GameManager 18 | import com.codingame.gameengine.module.entities.GraphicEntityModule 19 | import com.google.inject.Inject 20 | import tooltipModule.TooltipModule 21 | import java.util.* 22 | import kotlin.math.roundToInt 23 | 24 | @Suppress("unused") // injected by magic 25 | class Referee : AbstractReferee() { 26 | @Inject private lateinit var gameManager: GameManager 27 | @Inject private lateinit var entityManager: GraphicEntityModule 28 | @Inject private lateinit var animModule: AnimModule 29 | @Inject private lateinit var tooltipModule: TooltipModule 30 | 31 | private var obstacles: List = listOf() 32 | 33 | //private fun allEntities(): List = gameManager.players.flatMap { it.allUnits() } + obstacles // Bug in collisions in favor to red queen against blue queen 34 | private fun allEntities(): List = gameManager.players.flatMap { it.activeCreeps } + gameManager.players.map { it.queenUnit } + obstacles 35 | 36 | override fun init(params: Properties): Properties { 37 | 38 | theEntityManager = entityManager 39 | theTooltipModule = tooltipModule 40 | theGameManager = gameManager 41 | theAnimModule = animModule 42 | theGameManager.maxTurns = 200 43 | 44 | theRandom = (params["seed"] as? String)?.let { Random(it.toLong()) } ?: Random() 45 | 46 | // when (3) { 47 | when (gameManager.leagueLevel) { 48 | 1 -> { 49 | Leagues.mines = false; Leagues.fixedIncome = WOOD_FIXED_INCOME; Leagues.towers = false; Leagues.giants = false 50 | Leagues.obstacles = OBSTACLE_PAIRS.sample() 51 | } 52 | 2 -> { 53 | Leagues.mines = false; Leagues.fixedIncome = WOOD_FIXED_INCOME 54 | Leagues.obstacles = OBSTACLE_PAIRS.sample() 55 | } 56 | 3 -> { } 57 | else -> { 58 | Leagues.queenHp = QUEEN_HP.sample() * QUEEN_HP_MULT 59 | } 60 | } 61 | 62 | gameManager.frameDuration = 750 63 | 64 | gameManager.players[0].enemyPlayer = gameManager.players[1] 65 | gameManager.players[1].enemyPlayer = gameManager.players[0] 66 | gameManager.players[1].isSecondPlayer = true 67 | gameManager.players.forEach { it.health = Leagues.queenHp } 68 | 69 | obstacles = buildMap() 70 | 71 | for ((activePlayer, invert) in gameManager.activePlayers.zip(listOf(false, true))) { 72 | val spawnDistance = 200 73 | val corner = if (invert) 74 | Vector2(WORLD_WIDTH - spawnDistance, WORLD_HEIGHT - spawnDistance) 75 | else 76 | Vector2(spawnDistance, spawnDistance) 77 | 78 | activePlayer.queenUnit = Queen(activePlayer).also { it.location = corner } 79 | } 80 | 81 | fixCollisions(allEntities()) 82 | 83 | gameManager.activePlayers.forEach { it.hud.update() } 84 | 85 | gameManager.activePlayers.forEach { player -> 86 | player.sendInputLine(obstacles.size.toString()) 87 | obstacles.forEach { player.printObstacleInit(it) } 88 | } 89 | 90 | // Params contains all the game parameters that has been to generate this game 91 | // For instance, it can be a seed number, the size of a grid/map, ... 92 | return params 93 | } 94 | 95 | override fun gameTurn(turn: Int) { 96 | fun sendGameStates() { 97 | for (activePlayer in gameManager.activePlayers) { 98 | val touchedObstacle = obstacles.singleOrNull { it.location.distanceTo(activePlayer.queenUnit.location) < it.radius + QUEEN_RADIUS + TOUCHING_DELTA } 99 | ?.obstacleId ?: -1 100 | activePlayer.sendInputLine("${activePlayer.gold} $touchedObstacle") 101 | obstacles.forEach { activePlayer.printObstaclePerTurn(it) } 102 | 103 | val units = gameManager.activePlayers.flatMap { it.activeCreeps + it.queenUnit } 104 | activePlayer.sendInputLine(units.size.toString()) 105 | units.forEach { 106 | val toks = listOf(it.location.x.roundToInt(), it.location.y.roundToInt(), 107 | if (it.owner == activePlayer) 0 else 1) + 108 | when(it) { 109 | is Queen -> listOf(-1, it.owner.health) 110 | is Creep -> listOf(it.creepType.ordinal, it.health) 111 | else -> throw IllegalArgumentException("Unrecognized entity type: $it") 112 | } 113 | 114 | activePlayer.sendInputLine(toks.joinToString(" ")) 115 | } 116 | activePlayer.execute() 117 | } 118 | } 119 | 120 | fun processPlayerActions() { 121 | val obstaclesAttemptedToBuildUpon = mutableListOf() 122 | val scheduledBuildings = mutableListOf kotlin.Unit>>() 123 | class PlayerInputException(message: String): Exception(message) 124 | class PlayerInputWarning(message: String): Exception(message) 125 | 126 | fun scheduleBuilding(player: Player, obs: Obstacle, strucType: String) { 127 | val struc = obs.structure 128 | if (struc?.owner == player.enemyPlayer) throw PlayerInputWarning("Cannot build: owned by enemy player") 129 | if (struc is Barracks && struc.owner == player && struc.isTraining) 130 | throw PlayerInputWarning("Cannot rebuild: training is in progress") 131 | 132 | obstaclesAttemptedToBuildUpon += obs 133 | val toks = strucType.split('-').iterator() 134 | 135 | scheduledBuildings += player to { 136 | if (!toks.hasNext()) throw PlayerInputException("Structure type must be specified") 137 | val firstToken = toks.next() 138 | when { 139 | firstToken == "MINE" && Leagues.mines -> 140 | if (struc is Mine) { 141 | struc.incomeRate++ 142 | if (struc.incomeRate > obs.maxMineSize) struc.incomeRate = obs.maxMineSize 143 | } else { 144 | obs.setMine(player) 145 | } 146 | firstToken == "TOWER" && Leagues.towers -> { 147 | if (struc is Tower) { 148 | struc.health += TOWER_HP_INCREMENT 149 | if (struc.health > TOWER_HP_MAXIMUM) struc.health = TOWER_HP_MAXIMUM 150 | } else { 151 | obs.setTower(player, TOWER_HP_INITIAL) 152 | } 153 | } 154 | firstToken == "BARRACKS" -> { 155 | if (!toks.hasNext()) throw PlayerInputException("BARRACKS type must be specified") 156 | val creepInputType = toks.next() 157 | val creepType = try { 158 | CreepType.valueOf(creepInputType) 159 | .also { if (!Leagues.giants && it == CreepType.GIANT) throw Exception("GIANTS")} 160 | } catch (e:Exception) { 161 | throw PlayerInputException("Invalid BARRACKS type: $creepInputType") 162 | } 163 | obs.setBarracks(player, creepType) 164 | } 165 | else -> throw PlayerInputException("Invalid structure type: $firstToken") 166 | } 167 | } 168 | } 169 | 170 | playerLoop@ for (player in gameManager.activePlayers) { 171 | val queen = player.queenUnit 172 | try { 173 | try { 174 | val toks = player.outputs[1].split(" ") 175 | if (toks[0] != "TRAIN") throw PlayerInputException("Expected TRAIN on the second line") 176 | 177 | // Process building creeps 178 | val buildingBarracks = toks.drop(1) 179 | .map { obsIdStr -> obsIdStr.toIntOrNull() ?: throw PlayerInputException("Couldn't process siteId: $obsIdStr") } 180 | .map { obsId -> obstacles.find { it.obstacleId == obsId } ?: throw PlayerInputException("No site with id = $obsId") } 181 | .map { obs -> 182 | val struc = obs.structure as? Barracks ?: throw PlayerInputWarning("Cannot spawn from ${obs.obstacleId}: not a barracks") 183 | if (struc.owner != player) throw PlayerInputWarning("Cannot spawn from ${obs.obstacleId}: not owned") 184 | if (struc.isTraining) throw PlayerInputWarning("Barracks ${obs.obstacleId} is training") 185 | struc 186 | } 187 | 188 | if (buildingBarracks.size > buildingBarracks.toSet().size) 189 | throw PlayerInputWarning("Training from some barracks more than once") 190 | 191 | val sum = buildingBarracks.sumBy { it.creepType.cost } 192 | if (sum > player.gold) throw PlayerInputWarning("Training too many creeps ($sum total gold requested)") 193 | 194 | player.gold -= sum 195 | buildingBarracks.forEach { barracks -> 196 | barracks.progress = 0 197 | barracks.isTraining = true 198 | barracks.onComplete = { 199 | repeat(barracks.creepType.count) { iter -> 200 | player.activeCreeps += when (barracks.creepType) { 201 | CreepType.KNIGHT -> 202 | KnightCreep(barracks.owner, barracks.creepType) 203 | CreepType.ARCHER -> 204 | ArcherCreep(barracks.owner, barracks.creepType) 205 | CreepType.GIANT -> 206 | GiantCreep(barracks.owner, barracks.creepType, obstacles) 207 | }.also { 208 | val c = if (barracks.owner.isSecondPlayer) -1 else 1 209 | it.location = barracks.obstacle.location + Vector2(c * iter, c * iter) 210 | //it.location = barracks.obstacle.location + Vector2(iter, iter) // Fix units start point outside barracks 211 | it.finalizeFrame() 212 | it.location = it.location.towards(barracks.owner.enemyPlayer.queenUnit.location, 30.0) 213 | it.finalizeFrame() 214 | it.commitState(0.0) 215 | } 216 | } 217 | fixCollisions(allEntities()) 218 | } 219 | } 220 | } 221 | catch (e: PlayerInputWarning) { 222 | gameManager.addToGameSummary("${player.nicknameToken}: [WARNING] ${e.message}") 223 | } 224 | 225 | // Process queen command 226 | try { 227 | val line = player.outputs[0].trim() 228 | val toks = line.split(" ").iterator() 229 | val command = toks.next() 230 | 231 | when (command) { 232 | "WAIT" -> { 233 | } 234 | "MOVE" -> { 235 | try { 236 | val x = toks.next().toInt() 237 | val y = toks.next().toInt() 238 | queen.moveTowards(Vector2(x, y)) 239 | } catch (e: Exception) { 240 | throw PlayerInputException("In MOVE command, x and y must be integers") 241 | } 242 | } 243 | "BUILD" -> { 244 | val obsId = try { toks.next().toInt() } catch (e:Exception) { throw PlayerInputException("Could not parse siteId")} 245 | val obs = obstacles.find { it.obstacleId == obsId } ?: throw PlayerInputException("Site id $obsId does not exist") 246 | if (!toks.hasNext()) { 247 | throw PlayerInputException("Missing structure type in $command command") 248 | } 249 | val strucType = toks.next() 250 | 251 | val dist = obs.location.distanceTo(queen.location) 252 | if (dist < queen.radius + obs.radius + TOUCHING_DELTA) { 253 | scheduleBuilding(player, obs, strucType) 254 | } else { 255 | queen.moveTowards(obs.location) 256 | } 257 | } 258 | else -> throw PlayerInputException("Didn't understand command: $command") 259 | } 260 | if (toks.hasNext()) throw PlayerInputException("Too many tokens after $command command") 261 | 262 | } catch (e: PlayerInputWarning) { 263 | gameManager.addToGameSummary("${player.nicknameToken}: [WARNING] ${e.message}") 264 | } 265 | } catch (e: AbstractPlayer.TimeoutException) { 266 | e.printStackTrace() 267 | player.kill("Timeout!") 268 | gameManager.addToGameSummary("${player.nicknameToken} failed to provide ${player.expectedOutputLines} lines of output in time.") 269 | } catch (e: PlayerInputException) { 270 | System.err.println("WARNING: Terminating ${player.nicknameToken}, because of:") 271 | e.printStackTrace() 272 | player.kill("${e.message}") 273 | gameManager.addToGameSummary("${player.nicknameToken}: ${e.message}") 274 | } catch (e: Exception) { 275 | e.printStackTrace() 276 | player.kill("${e.message}") 277 | gameManager.addToGameSummary("${player.nicknameToken}: ${e.message}") 278 | } 279 | } 280 | 281 | // If they're both building onto the same one, then actually build only one: depending on parity of the turn number 282 | if (obstaclesAttemptedToBuildUpon.size == 2 && obstaclesAttemptedToBuildUpon[0] == obstaclesAttemptedToBuildUpon[1]) { 283 | scheduledBuildings.removeAt(turn % 2) 284 | } 285 | 286 | // Execute builds that remain 287 | scheduledBuildings.forEach { (player: Player, callback: () -> kotlin.Unit) -> 288 | try { callback.invoke() } 289 | catch (e: PlayerInputException) { 290 | System.err.println("WARNING: Deactivating ${player.nicknameToken} because of:") 291 | e.printStackTrace() 292 | player.kill("${e.message}") 293 | gameManager.addToGameSummary("${player.nicknameToken}: ${e.message}") 294 | } 295 | } 296 | } 297 | 298 | fun processCreeps() { 299 | val allCreeps = gameManager.activePlayers.flatMap { it.activeCreeps }.sortedBy { it.creepType }.toList() 300 | repeat(5) { 301 | allCreeps.forEach { it.move(1.0 / 5) } 302 | fixCollisions(allEntities(), 1) 303 | } 304 | allCreeps.forEach { it.dealDamage() } 305 | 306 | // Tear down enemy mines 307 | allCreeps.forEach { creep -> 308 | val closestObstacle = obstacles.minBy { it.location.distanceTo(creep.location) }!! 309 | if (closestObstacle.location.distanceTo(creep.location) >= closestObstacle.radius + creep.radius + TOUCHING_DELTA) return@forEach 310 | val struc = closestObstacle.structure 311 | if (struc is Mine && struc.owner != creep.owner) closestObstacle.structure = null 312 | } 313 | 314 | allCreeps.forEach { it.damage(1) } 315 | allCreeps.forEach { it.finalizeFrame() } 316 | 317 | // Queens tear down enemy structures (not TOWERs) 318 | gameManager.activePlayers.forEach { 319 | val queen = it.queenUnit 320 | val closestObstacle = obstacles.minBy { it.location.distanceTo(queen.location) }!! 321 | if (closestObstacle.location.distanceTo(queen.location) >= closestObstacle.radius + queen.radius + TOUCHING_DELTA) return@forEach 322 | val struc = closestObstacle.structure 323 | if ((struc is Mine || struc is Barracks) && struc.owner != queen.owner) closestObstacle.structure = null 324 | } 325 | } 326 | 327 | gameManager.activePlayers.forEach { it.goldPerTurn = 0 } 328 | 329 | sendGameStates() 330 | processPlayerActions() 331 | processCreeps() 332 | 333 | // Process structures 334 | obstacles.forEach { it.act() } 335 | 336 | Leagues.fixedIncome?.also { income -> 337 | gameManager.activePlayers.forEach { 338 | it.goldPerTurn = income 339 | it.gold += income 340 | } 341 | } 342 | 343 | 344 | // Remove dead creeps 345 | gameManager.activePlayers.forEach { player -> 346 | player.activeCreeps.filter { it.health == 0 }.forEach { 347 | player.activeCreeps.remove(it) 348 | animModule.createAnimationEvent("death", 1.0) 349 | .location = it.location 350 | } 351 | } 352 | 353 | // Check end game 354 | gameManager.activePlayers.forEach { it.checkQueenHealth() } 355 | if (gameManager.activePlayers.size < 2) { 356 | gameManager.endGame() 357 | } 358 | 359 | // Snap entities to integer coordinates 360 | allEntities().forEach { it.location = it.location.snapToIntegers() } 361 | } 362 | } 363 | 364 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Structures.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.Constants.OBSTACLE_GOLD_INCREASE 4 | import com.codingame.game.Constants.OBSTACLE_GOLD_RANGE 5 | import com.codingame.game.Constants.TOWER_COVERAGE_PER_HP 6 | import com.codingame.game.Constants.TOWER_CREEP_DAMAGE_CLIMB_DISTANCE 7 | import com.codingame.game.Constants.TOWER_CREEP_DAMAGE_MIN 8 | import com.codingame.game.Constants.TOWER_MELT_RATE 9 | import com.codingame.game.Constants.TOWER_QUEEN_DAMAGE_CLIMB_DISTANCE 10 | import com.codingame.game.Constants.TOWER_QUEEN_DAMAGE_MIN 11 | import com.codingame.gameengine.module.entities.Curve 12 | import java.util.Random 13 | import kotlin.Unit 14 | import kotlin.math.min 15 | import kotlin.math.sqrt 16 | 17 | var nextObstacleId = 0 18 | val rando = Random() 19 | 20 | class Obstacle(var maxMineSize: Int, initialGold: Int, initialRadius: Int, initialLocation: Vector2): FieldObject() { 21 | val obstacleId = nextObstacleId++ 22 | override val mass = 0 23 | var gold by nonNegative(initialGold) 24 | 25 | private val obstacleImage = theEntityManager.createSprite() 26 | .setImage("LC_${rando.nextInt(10) + 1}") 27 | .setZIndex(20) 28 | .setAnchor(0.5) 29 | 30 | override var radius: Int = 0 31 | set(value) { 32 | field = value 33 | obstacleImage.setScale(value * 2 / 220.0) 34 | } 35 | 36 | override var location: Vector2 = initialLocation 37 | set(value) { 38 | field = value 39 | obstacleImage.location = location 40 | } 41 | 42 | init { 43 | radius = initialRadius 44 | location = initialLocation 45 | val params = hashMapOf("id" to obstacleId, "type" to "Site") 46 | theTooltipModule.registerEntity(obstacleImage, params as Map?) 47 | } 48 | 49 | val area = Math.PI * radius * radius 50 | 51 | var structure: Structure? = null 52 | set(value) { 53 | if (value != null && structure != value) 54 | theAnimModule.createAnimationEvent("construction", 0.0).location = location // includes replacing 55 | if (value == null && structure != null) 56 | theAnimModule.createAnimationEvent("destruction", 0.0).location = location 57 | 58 | structure?.hideEntities() 59 | field = value 60 | value?.updateEntities() 61 | if (value == null) { 62 | obstacleImage.alpha = 1.0 63 | } else { 64 | obstacleImage.alpha = 0.0 65 | obstacleImage.image = "LieuDetruit" 66 | } 67 | obstacleImage.alpha = if (value == null) 1.0 else 0.0 68 | theEntityManager.commitEntityState(0.0, obstacleImage) 69 | } 70 | 71 | fun updateEntities() { 72 | structure?.updateEntities() 73 | val struc = structure 74 | val lines = listOf("Radius: $radius") + 75 | (if (Leagues.mines) listOf("Remaining gold: $gold") else listOf()) + 76 | struc?.extraTooltipLines().orEmpty() 77 | 78 | theTooltipModule.updateExtraTooltipText(obstacleImage, *lines.toTypedArray()) 79 | } 80 | 81 | fun destroy() { 82 | obstacleImage.isVisible = false 83 | } 84 | 85 | fun act() { 86 | structure?.also { if (it.act()) structure = null } 87 | updateEntities() 88 | } 89 | 90 | fun setMine(owner: Player) { 91 | structure = Mine(this, owner, 1) 92 | } 93 | 94 | fun setTower(owner: Player, health: Int) { 95 | structure = Tower(this, owner, 0, health) 96 | } 97 | 98 | fun setBarracks(owner: Player, creepType: CreepType) { 99 | structure = Barracks(this, owner, creepType) 100 | } 101 | } 102 | 103 | interface Structure { 104 | val owner: Player 105 | val obstacle: Obstacle 106 | fun updateEntities() 107 | fun hideEntities() 108 | fun extraTooltipLines(): List 109 | fun act(): Boolean // return true if the Structure should be destroyed 110 | } 111 | 112 | class Mine(override val obstacle: Obstacle, override val owner: Player, incomeRate: Int) : Structure { 113 | 114 | override fun extraTooltipLines(): List = listOf( 115 | "MINE (+$incomeRate)" 116 | ) 117 | 118 | private val mineImage = theEntityManager.createSprite() 119 | .setImage("Mine") 120 | .setZIndex(40) 121 | .setAnchor(0.5) 122 | .also { it.location = obstacle.location } 123 | .setScale(obstacle.radius * 2 / 220.0) 124 | 125 | private val pickaxeSprite = theEntityManager.createSprite() 126 | .setImage(if (owner.isSecondPlayer) "Mine_Bleu" else "Mine_Rouge") 127 | .setZIndex(41) 128 | .also { it.location = obstacle.location + Vector2(0, -40) } 129 | .setAnchor(0.5)!! 130 | 131 | private val text = theEntityManager.createText("+$incomeRate") 132 | .setFillColor(0xffffff)!! 133 | .setZIndex(42) 134 | .setFontFamily("Arial Black") 135 | .setAnchorY(0.5) 136 | .also { it.location = obstacle.location + Vector2(0,-40) } 137 | 138 | private val mineralBarOutline = theEntityManager.createRectangle() 139 | .also { it.location = obstacle.location + Vector2(-40, -90) } 140 | .setHeight(15) 141 | .setWidth(80) 142 | .setLineColor(0) 143 | .setLineWidth(1) 144 | .setFillAlpha(0.0) 145 | .setZIndex(401)!! 146 | 147 | private val mineralBarFill = theEntityManager.createRectangle() 148 | .also { it.location = obstacle.location + Vector2(-40, -90) } 149 | .setHeight(15) 150 | .setWidth(80) 151 | .setFillColor(0xffbf00) 152 | .setLineAlpha(0.0)!! 153 | .setZIndex(400) 154 | 155 | var incomeRate = incomeRate 156 | set(value) { 157 | field = value 158 | text.text = "+$incomeRate" 159 | } 160 | 161 | override fun hideEntities() { 162 | text.isVisible = false 163 | pickaxeSprite.isVisible = false 164 | mineImage.isVisible = false 165 | mineralBarOutline.isVisible = false 166 | mineralBarFill.isVisible = false 167 | theEntityManager.commitEntityState(0.51, text, pickaxeSprite, mineImage, mineralBarOutline, mineralBarFill) 168 | } 169 | 170 | override fun updateEntities() { 171 | text.isVisible = true 172 | pickaxeSprite.isVisible = true 173 | mineImage.isVisible = true 174 | mineralBarOutline.isVisible = true 175 | mineralBarFill.isVisible = true 176 | mineralBarFill.width = 80 * obstacle.gold / (OBSTACLE_GOLD_RANGE.last + 2 * OBSTACLE_GOLD_INCREASE) 177 | theEntityManager.commitEntityState(0.5, text, pickaxeSprite, mineImage, mineralBarOutline, mineralBarFill) 178 | } 179 | 180 | override fun act(): Boolean { 181 | val cash = min(incomeRate, obstacle.gold) 182 | 183 | owner.goldPerTurn += cash 184 | owner.gold += cash 185 | obstacle.gold -= cash 186 | if (obstacle.gold <= 0) { 187 | hideEntities() 188 | return true 189 | } 190 | 191 | return false 192 | } 193 | } 194 | 195 | class Tower(override val obstacle: Obstacle, override val owner: Player, var attackRadius: Int, var health: Int) : Structure { 196 | override fun extraTooltipLines(): List = listOf( 197 | "TOWER", 198 | "Range: $attackRadius", 199 | "Health: $health" 200 | ) 201 | 202 | private val towerRangeCircle = theEntityManager.createCircle() 203 | .setFillAlpha(0.0) 204 | .setAlpha(0.2) 205 | .setLineWidth(10) 206 | .setZIndex(10) 207 | .also { it.location = obstacle.location } 208 | .setRadius(obstacle.radius) 209 | .also { theEntityManager.commitEntityState(0.0, it) } 210 | 211 | private val sprite = theEntityManager.createSpriteAnimation() 212 | .setImages(*{ 213 | val color = if (owner.isSecondPlayer) "B" else "R" 214 | (1..15).map { 215 | "T$color${it.toString().padStart(2, '0')}" 216 | } 217 | }().toTypedArray()) 218 | .setZIndex(40) 219 | .also { it.location = obstacle.location } 220 | .setAnchorX(0.5).setAnchorY(1 - (220.0 / 238.0 * 0.5)) 221 | .setStarted(true) 222 | .setLoop(true) 223 | .setScale(obstacle.radius * 2 / 220.0) 224 | 225 | private val projectile = theEntityManager.createSprite()!! 226 | .setImage(if (owner.isSecondPlayer) "Eclair_Bleu" else "Eclair_Rouge") 227 | .setZIndex(50) 228 | .setVisible(false) 229 | .setAnchorX(0.5) 230 | .setAnchorY(0.5) 231 | 232 | var attackTarget: FieldObject? = null 233 | 234 | override fun hideEntities() { 235 | towerRangeCircle.radius = obstacle.radius 236 | towerRangeCircle.isVisible = false 237 | sprite.isVisible = false 238 | theEntityManager.commitEntityState(0.51, towerRangeCircle, sprite) 239 | } 240 | 241 | override fun updateEntities() 242 | { 243 | towerRangeCircle.isVisible = true 244 | towerRangeCircle.lineColor = if (owner.isSecondPlayer) 0x8844ff else 0xff4444 245 | sprite.isVisible = true 246 | theEntityManager.commitEntityState(0.5, towerRangeCircle, sprite) 247 | towerRangeCircle.radius = attackRadius 248 | theEntityManager.commitEntityState(1.0, towerRangeCircle) 249 | 250 | val localAttackTarget = attackTarget 251 | if (localAttackTarget != null) { 252 | projectile.isVisible = true 253 | val projectileSource = obstacle.location - Vector2(0.0, obstacle.radius * 0.6) 254 | val obsToTarget = localAttackTarget.location - projectileSource 255 | projectile.location = (projectileSource + localAttackTarget.location) / 2.0 256 | projectile.scaleX = obsToTarget.length.toDouble / 200.0 257 | projectile.scaleY = 1.0 258 | projectile.setRotation (obsToTarget.angle, Curve.IMMEDIATE) 259 | theEntityManager.commitEntityState(0.0, projectile) 260 | projectile.setRotation ((-obsToTarget).angle, Curve.IMMEDIATE) 261 | projectile.scaleY = 2.0 262 | theEntityManager.commitEntityState(0.2, projectile) 263 | projectile.setRotation (obsToTarget.angle, Curve.IMMEDIATE) 264 | theEntityManager.commitEntityState(0.4, projectile) 265 | projectile.setRotation ((-obsToTarget).angle, Curve.IMMEDIATE) 266 | projectile.scaleY = 1.0 267 | theEntityManager.commitEntityState(0.6, projectile) 268 | projectile.setRotation (obsToTarget.angle, Curve.IMMEDIATE) 269 | theEntityManager.commitEntityState(0.8, projectile) 270 | projectile.setRotation ((-obsToTarget).angle, Curve.IMMEDIATE) 271 | theEntityManager.commitEntityState(0.99, projectile) 272 | projectile.isVisible = false 273 | theEntityManager.commitEntityState(1.0, projectile) 274 | 275 | } 276 | } 277 | 278 | private fun damageCreep(target: Creep) { 279 | val shotDistance = target.location.distanceTo(obstacle.location).toDouble - obstacle.radius 280 | val differenceFromMax = attackRadius - shotDistance 281 | val damage = TOWER_CREEP_DAMAGE_MIN + (differenceFromMax / TOWER_CREEP_DAMAGE_CLIMB_DISTANCE).toInt() 282 | target.damage(damage) 283 | } 284 | 285 | private fun damageQueen(target: Queen) { 286 | val shotDistance = target.location.distanceTo(obstacle.location).toDouble - obstacle.radius 287 | val differenceFromMax = attackRadius - shotDistance 288 | val damage = TOWER_QUEEN_DAMAGE_MIN + (differenceFromMax / TOWER_QUEEN_DAMAGE_CLIMB_DISTANCE).toInt() 289 | target.damage(damage) 290 | } 291 | 292 | override fun act(): Boolean { 293 | val closestEnemy = owner.enemyPlayer.activeCreeps.minBy { it.location.distanceTo(obstacle.location) } 294 | val enemyQueen = owner.enemyPlayer.queenUnit 295 | 296 | attackTarget = when { 297 | closestEnemy != null && closestEnemy.location.distanceTo(obstacle.location) < attackRadius -> 298 | closestEnemy.also { damageCreep(it) } 299 | enemyQueen.location.distanceTo(obstacle.location) < attackRadius -> 300 | enemyQueen.also { damageQueen(it) } 301 | else -> null 302 | } 303 | 304 | health -= TOWER_MELT_RATE 305 | attackRadius = sqrt((health * TOWER_COVERAGE_PER_HP + obstacle.area) / Math.PI).toInt() 306 | 307 | if (health <= 0) { 308 | hideEntities() 309 | return true 310 | } 311 | 312 | return false 313 | } 314 | 315 | } 316 | 317 | class Barracks(override val obstacle: Obstacle, override val owner: Player, var creepType: CreepType) : Structure { 318 | 319 | override fun extraTooltipLines(): List { 320 | val retVal = mutableListOf( 321 | "BARRACKS ($creepType)" 322 | ) 323 | return retVal 324 | } 325 | 326 | private val progressFillMaxWidth =(obstacle.radius * 1.05).toInt() 327 | 328 | private val progressFillMask = theEntityManager.createRectangle() 329 | .also { it.location = obstacle.location + Vector2(-0.51, 0.72) * obstacle.radius.toDouble() } 330 | .setHeight(8) 331 | .setWidth(progressFillMaxWidth) 332 | 333 | var progressMax = creepType.buildTime 334 | var progress = 0 335 | var isTraining = false 336 | 337 | var onComplete: () -> Unit = { } 338 | 339 | private val barracksImage = theEntityManager.createSprite() 340 | .setAnchor(0.5) 341 | .setImage(if (owner.isSecondPlayer) "Caserne_Bleu" else "Caserne_Rouge") 342 | .setZIndex(40) 343 | .also { it.location = obstacle.location } 344 | .setBaseHeight(obstacle.radius * 2).setBaseWidth(obstacle.radius * 2) 345 | 346 | private val progressFill = theEntityManager.createSprite() 347 | .setAnchor(0.5) 348 | .setImage(if (owner.isSecondPlayer) "Caserne_Bleu_Jauge" else "Caserne_Rouge_Jauge") 349 | .setZIndex(400) 350 | .also { it.location = obstacle.location.plus(Vector2(0, obstacle.radius * 53/68)) } 351 | .setBaseWidth(obstacle.radius * 2 * 70 / 138) 352 | .setBaseHeight((obstacle.radius * 2 * 70 / 138 * 10.0/110.0).toInt()) 353 | .setMask(progressFillMask) 354 | 355 | private val creepToken = theEntityManager.createSprite() 356 | .setAnchor(0.5) 357 | .setImage(if (owner.isSecondPlayer) "Unite_Base_Bleu" else "Unite_Base_Rouge") 358 | .setZIndex(41) 359 | .also { it.location = obstacle.location + Vector2(-0.1, -0.45) * obstacle.radius.toDouble() } 360 | .setBaseHeight((barracksImage.baseHeight * 0.32).toInt()).setBaseWidth((barracksImage.baseWidth * 0.32).toInt()) 361 | 362 | private val creepSprite = theEntityManager.createSprite() 363 | .setAnchor(0.5) 364 | .setZIndex(42) 365 | .also { it.location = creepToken.location } 366 | .setBaseHeight(creepToken.baseHeight).setBaseWidth(creepToken.baseWidth) 367 | 368 | override fun updateEntities() { 369 | barracksImage.isVisible = true 370 | creepToken.isVisible = true 371 | creepSprite.isVisible = true 372 | creepSprite.image = creepType.assetName 373 | theEntityManager.commitEntityState(0.5, barracksImage, creepToken, creepSprite) 374 | 375 | progressFill.isVisible = isTraining 376 | theEntityManager.commitEntityState(0.0, progressFill, progressFillMask) 377 | progressFillMask.width = (progressFillMaxWidth * progress / (progressMax-1)) 378 | theEntityManager.commitEntityState(1.0, progressFillMask) 379 | } 380 | 381 | override fun hideEntities() { 382 | barracksImage.isVisible = false 383 | creepToken.isVisible = false 384 | creepSprite.isVisible = false 385 | progressFill.isVisible = false 386 | theEntityManager.commitEntityState(0.51, barracksImage, creepToken, creepSprite, progressFill, progressFillMask) 387 | } 388 | 389 | override fun act(): Boolean { 390 | if (isTraining) { 391 | progress++ 392 | if (progress == progressMax) { 393 | progress = 0 394 | isTraining = false 395 | onComplete() 396 | } 397 | } 398 | updateEntities() 399 | return false 400 | } 401 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Utility.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import kotlin.Unit 4 | import kotlin.reflect.KProperty 5 | 6 | interface PropertyDelegate { 7 | operator fun getValue(source: S, property: KProperty<*>): T 8 | operator fun setValue(source: S, property: KProperty<*>, value: T): Unit 9 | } 10 | 11 | class ClampingPropertyDelegate>(initialValue: T, private val minValue: T? = null, private val maxValue: T? = null) : PropertyDelegate{ 12 | var field: T = initialValue 13 | 14 | override operator fun getValue(source: S, property: KProperty<*>): T = field 15 | 16 | override operator fun setValue(source: S, property: KProperty<*>, value: T) { 17 | field = when { 18 | minValue != null && value < minValue -> minValue 19 | maxValue != null && value > maxValue -> maxValue 20 | else -> value 21 | } 22 | } 23 | } 24 | 25 | fun nonNegative(initialValue: Int) = ClampingPropertyDelegate(initialValue, minValue = 0) 26 | 27 | fun PropertyDelegate.andAlso(cont: (T) -> Unit): PropertyDelegate { 28 | val that = this 29 | return object : PropertyDelegate { 30 | override fun getValue(source: S, property: KProperty<*>): T { 31 | return that.getValue(source, property) 32 | } 33 | override fun setValue(source: S, property: KProperty<*>, value: T) { 34 | that.setValue(source, property, value) 35 | cont(getValue(source, property)) 36 | } 37 | } 38 | } 39 | 40 | val IntRange.length: Int; get() = last - first -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Vector2.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import java.lang.IllegalArgumentException 4 | import java.util.* 5 | import kotlin.math.* 6 | 7 | data class Distance(private val squareDistance: Double): Comparable { 8 | override fun compareTo(other: Distance) = squareDistance.compareTo(other.squareDistance) 9 | operator fun compareTo(compareDist: Double) = squareDistance.compareTo(compareDist * compareDist) 10 | operator fun compareTo(compareDist: Int) = squareDistance.compareTo(compareDist * compareDist) 11 | val toDouble by lazy { sqrt(squareDistance) } 12 | } 13 | 14 | @Suppress("MemberVisibilityCanBePrivate", "unused") // It's a utility, ok 15 | data class Vector2(val x: Double, val y: Double) { 16 | private val lengthSquared by lazy { x*x + y*y } 17 | val length by lazy { Distance(lengthSquared) } 18 | val isNearZero by lazy { Math.abs(x) < 1e-12 && Math.abs(y) < 1e-12 } 19 | val normalized: Vector2 by lazy { 20 | val len = length 21 | when { 22 | len < 1e-6 -> Vector2(1,0) 23 | else -> Vector2(x / len.toDouble, y / len.toDouble) 24 | } 25 | } 26 | val angle by lazy { Math.atan2(y, x) } 27 | 28 | constructor(x: Int, y: Int): this(x.toDouble(), y.toDouble()) 29 | 30 | operator fun minus(other: Vector2) = Vector2(x - other.x, y - other.y) 31 | operator fun plus(other: Vector2) = Vector2(x + other.x, y + other.y) 32 | operator fun times(other: Double) = Vector2(x * other, y * other) 33 | operator fun div(other: Double) = when(other) { 34 | 0.0 -> throw IllegalArgumentException("Division by zero") 35 | else -> Vector2(x / other, y / other) 36 | } 37 | operator fun unaryMinus() = Vector2(-x, -y) 38 | fun resizedTo(newLength: Double) = normalized * newLength 39 | fun distanceTo(other: Vector2) = (this - other).length 40 | fun dot(other: Vector2) = x * other.x + y * other.y 41 | fun compareDirection(other: Vector2) = normalized.dot(other.normalized) /* 1 == same direction, -1 == opposite direction */ 42 | fun projectInDirectionOf(other: Vector2) = when { 43 | other.isNearZero -> throw IllegalArgumentException("cannot project in direction of zero") 44 | else -> other * (this.dot(other) / other.dot(other)) 45 | } 46 | fun rejectInDirectionOf(other: Vector2) = this - projectInDirectionOf(other) 47 | fun towards(other: Vector2, maxDistance: Double) = 48 | if (distanceTo(other) < maxDistance) other 49 | else this + (other - this).resizedTo(maxDistance) 50 | fun clampWithin(minX: Double, maxX: Double, minY: Double, maxY: Double): Vector2 { 51 | val nx = when { x < minX -> minX; x > maxX -> maxX; else -> x } 52 | val ny = when { y < minY -> minY; y > maxY -> maxY; else -> y } 53 | return Vector2(nx, ny) 54 | } 55 | override fun toString(): String = "(${Math.round(x)}, ${Math.round(y)})" 56 | fun snapToIntegers(): Vector2 = Vector2(x.roundToInt(), y.roundToInt()) 57 | 58 | companion object { 59 | val Zero = Vector2(0, 0) 60 | fun random(theRandom: Random, maxX: Int, maxY: Int) = Vector2(theRandom.nextInt(maxX), theRandom.nextInt(maxY)) 61 | fun randomCircle(theRandom: Random, maxRadius: Int): Vector2 { 62 | val ang = theRandom.nextDouble() * PI * 2 63 | val radius = theRandom.nextDouble() * maxRadius 64 | return Vector2(cos(ang) * radius, sin(ang) * radius) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/view/anims/AnimData.js: -------------------------------------------------------------------------------- 1 | export const FRAMES = { 2 | death: ["Mort0001", "Mort0002", "Mort0003", "Mort0004", "Mort0005", "Mort0006", "Mort0007", "Mort0008", "Mort0009", "Mort0010", "Mort0011", "Mort0012", "Mort0013", "Mort0014", "Mort0015", "Mort0016", "Mort0017", "Mort0018", "Mort0019", "Mort0020", "Mort0021", "Mort0022", "Mort0023", "Mort0024", "Mort0025", "Mort0026", "Mort0027", "Mort0028", "Mort0029", "Mort0030", "Mort0031", "Mort0032", "Mort0033", "Mort0034", "Mort0035", "Mort0036", "Mort0037", "Mort0038", "Mort0039", "Mort0040", "Mort0041", "Mort0042", "Mort0043", "Mort0044", "Mort0045", "Mort0046", "Mort0047", "Mort0048", "Mort0049", "Mort0050"], 3 | construction: ["construction0002", "construction0003", "construction0004", "construction0005", "construction0006", "construction0007", "construction0008", "construction0009", "construction0010", "construction0011", "construction0012", "construction0013", "construction0014", "construction0015", "construction0016", "construction0017", "construction0018", "construction0019", "construction0020", "construction0021", "construction0022", "construction0023", "construction0024", "construction0025", "construction0026", "construction0027", "construction0028", "construction0029", "construction0030", "construction0031", "construction0032", "construction0033", "construction0034", "construction0035", "construction0036", "construction0037", "construction0038", "construction0039", "construction0040", "construction0041", "construction0042", "construction0043", "construction0044", "construction0045", "construction0046", "construction0047", "construction0048", "construction0049", "construction0050", "construction0051", "construction0052", "construction0053", "construction0054"], 4 | destruction: ["destruction0001", "destruction0002", "destruction0003", "destruction0004", "destruction0005", "destruction0006", "destruction0007", "destruction0008", "destruction0009", "destruction0010", "destruction0011", "destruction0012", "destruction0013", "destruction0014", "destruction0015", "destruction0016", "destruction0017", "destruction0018", "destruction0019", "destruction0020", "destruction0021", "destruction0022", "destruction0023", "destruction0024", "destruction0025", "destruction0026", "destruction0027", "destruction0028", "destruction0029", "destruction0030", "destruction0031", "destruction0032", "destruction0033", "destruction0034", "destruction0035", "destruction0036", "destruction0037", "destruction0038", "destruction0039", "destruction0040", "destruction0041", "destruction0042", "destruction0043"] 5 | }; 6 | 7 | export const ANCHORS = { 8 | death: {x: 0.5, y: 0.75}, 9 | construction: {x: 0.5, y: 0.5}, 10 | destruction: {x: 0.5, y: 0.5} 11 | }; -------------------------------------------------------------------------------- /src/main/resources/view/anims/AnimModule.js: -------------------------------------------------------------------------------- 1 | 2 | import * as utils from '../core/utils.js'; 3 | import {WIDTH, HEIGHT} from '../core/constants.js'; 4 | import {FRAMES, ANCHORS} from './AnimData.js'; 5 | import {api as entityModule} from '../entity-module/GraphicEntityModule.js'; 6 | 7 | const DURATIONS = { 8 | death: 1, 9 | destruction: 1, 10 | construction: 1 11 | } 12 | 13 | const REPEAT = { 14 | death: 1, 15 | destruction: 1, 16 | construction: 1 17 | } 18 | 19 | export class AnimModule { 20 | constructor(assets) { 21 | this.loadState = 1; 22 | this.animData = []; 23 | this.anims = {}; 24 | this.frames = 0; 25 | this.currentData = {number: 0}; 26 | this.progress = 0; 27 | } 28 | 29 | static get name() { 30 | return 'anims'; 31 | } 32 | 33 | updateScene(previousData, currentData, progress) { 34 | this.currentData = currentData; 35 | this.progress = progress; 36 | 37 | for (let data of this.animData) { 38 | let visible = false; 39 | let start = data.started.frame + data.started.t; 40 | let end = start + data.duration; 41 | let now = currentData.number + progress; 42 | 43 | if (start <= now && end > now) { 44 | visible = true; 45 | } 46 | 47 | if (!visible && data.sprite) { 48 | data.sprite.visible = false; 49 | data.sprite.busyp = false; 50 | data.sprite = null; 51 | } else if (visible && !data.sprite) { 52 | data.sprite = this.getAnimFromPool(data.id); 53 | data.sprite.visible = true; 54 | } 55 | 56 | if (visible) { 57 | if (this.loadState > 0) { 58 | const repeats = REPEAT[data.id] || 1; 59 | 60 | let image = FRAMES[data.id][0]; 61 | if (repeats > 1) { 62 | const animationProgress = utils.unlerpUnclamped(start, end, now) * repeats; 63 | if (animationProgress >= 0) { 64 | const animationIndex = Math.floor(FRAMES[data.id].length * animationProgress); 65 | image = FRAMES[data.id][animationIndex % FRAMES[data.id].length]; 66 | } 67 | } else { 68 | const animationProgress = utils.unlerp(start, end, now); 69 | const animationIndex = Math.floor(FRAMES[data.id].length * animationProgress); 70 | image = (FRAMES[data.id][animationIndex] || FRAMES[data.id][FRAMES[data.id].length - 1]); 71 | } 72 | 73 | data.sprite.texture = PIXI.Texture.fromFrame(image); 74 | } 75 | if (data.params.x && data.params.y) { 76 | data.sprite.position.x = +data.params.x 77 | data.sprite.position.y = +data.params.y 78 | } 79 | } 80 | } 81 | } 82 | 83 | handleFrameData(frameInfo, anims) { 84 | const number = (frameInfo.number == 0) ? 0 : ++this.frames; 85 | 86 | for (let a of anims) { 87 | a.started = {frame: number, t: a.t} 88 | a.duration = DURATIONS[a.id] || 1; 89 | a.duration *= REPEAT[a.id] || 1; 90 | this.animData.push(a); 91 | } 92 | return {number}; 93 | } 94 | 95 | getAnimFromPool(id) { 96 | for (let a of this.anims[id]) { 97 | if (!a.busyp) { 98 | a.busyp = true; 99 | return a; 100 | } 101 | } 102 | 103 | let a = this.createAnim(id); 104 | this.anims[id].push(a); 105 | a.busyp = true; 106 | return a; 107 | }; 108 | 109 | createAnim(id) { 110 | const sprite = new PIXI.Sprite(PIXI.Texture.EMPTY); 111 | if (ANCHORS[id]) { 112 | sprite.anchor.copy(ANCHORS[id]); 113 | } 114 | this.container.addChild(sprite); 115 | return sprite; 116 | } 117 | 118 | reinitScene(container) { 119 | this.container = container; 120 | for (let a of this.animData) { 121 | a.sprite = null; 122 | } 123 | for (let key in FRAMES) { 124 | this.anims[key] = []; 125 | } 126 | } 127 | 128 | animateScene(delta) { 129 | } 130 | 131 | handleGlobalData(players, globalData) { 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/Background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/Background.jpg -------------------------------------------------------------------------------- /src/main/resources/view/assets/Hud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/Hud.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/construction.json: -------------------------------------------------------------------------------- 1 | {"frames":{"construction0002":{"frame":{"x":0,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0003":{"frame":{"x":505,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0004":{"frame":{"x":505,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0005":{"frame":{"x":505,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0006":{"frame":{"x":0,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0007":{"frame":{"x":505,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0008":{"frame":{"x":1010,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0009":{"frame":{"x":1010,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0010":{"frame":{"x":1010,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0011":{"frame":{"x":0,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0012":{"frame":{"x":505,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0013":{"frame":{"x":1010,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0014":{"frame":{"x":1515,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0015":{"frame":{"x":1515,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0016":{"frame":{"x":1515,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0017":{"frame":{"x":1515,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0018":{"frame":{"x":0,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0019":{"frame":{"x":505,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0020":{"frame":{"x":1010,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0021":{"frame":{"x":1515,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0022":{"frame":{"x":2020,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0023":{"frame":{"x":2020,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0024":{"frame":{"x":2020,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0025":{"frame":{"x":2020,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0026":{"frame":{"x":2020,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0027":{"frame":{"x":0,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0028":{"frame":{"x":0,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0029":{"frame":{"x":1010,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0030":{"frame":{"x":1515,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0031":{"frame":{"x":2020,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0032":{"frame":{"x":2525,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0033":{"frame":{"x":2525,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0034":{"frame":{"x":2525,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0035":{"frame":{"x":2525,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0036":{"frame":{"x":2525,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0037":{"frame":{"x":2525,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0038":{"frame":{"x":0,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0039":{"frame":{"x":505,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0040":{"frame":{"x":1010,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0041":{"frame":{"x":1515,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0042":{"frame":{"x":2020,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0043":{"frame":{"x":2525,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0044":{"frame":{"x":3030,"y":0,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0045":{"frame":{"x":3030,"y":505,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0046":{"frame":{"x":3030,"y":1010,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0047":{"frame":{"x":3030,"y":1515,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0048":{"frame":{"x":3030,"y":2020,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0049":{"frame":{"x":3030,"y":2525,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0050":{"frame":{"x":3030,"y":3030,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0051":{"frame":{"x":0,"y":3535,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0052":{"frame":{"x":505,"y":3535,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0053":{"frame":{"x":1010,"y":3535,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}},"construction0054":{"frame":{"x":1515,"y":3535,"w":504,"h":504},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":504},"sourceSize":{"w":504,"h":504}}},"meta":{"app":"https://www.leshylabs.com/apps/sstool/","version":"Leshy SpriteSheet Tool v0.8.4","image":"construction.png","size":{"w":3534,"h":4039},"scale":1}} -------------------------------------------------------------------------------- /src/main/resources/view/assets/construction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/construction.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/death.json: -------------------------------------------------------------------------------- 1 | {"frames":{"Mort0001":{"frame":{"x":85,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0002":{"frame":{"x":510,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0003":{"frame":{"x":170,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0004":{"frame":{"x":0,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0005":{"frame":{"x":85,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0006":{"frame":{"x":170,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0007":{"frame":{"x":255,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0008":{"frame":{"x":255,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0009":{"frame":{"x":340,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0010":{"frame":{"x":340,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0011":{"frame":{"x":0,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0012":{"frame":{"x":85,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0013":{"frame":{"x":170,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0014":{"frame":{"x":255,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0015":{"frame":{"x":340,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0016":{"frame":{"x":425,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0017":{"frame":{"x":425,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0018":{"frame":{"x":425,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0019":{"frame":{"x":0,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0020":{"frame":{"x":85,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0021":{"frame":{"x":170,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0022":{"frame":{"x":255,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0023":{"frame":{"x":340,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0024":{"frame":{"x":425,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0025":{"frame":{"x":510,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0026":{"frame":{"x":0,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0027":{"frame":{"x":510,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0028":{"frame":{"x":510,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0029":{"frame":{"x":595,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0030":{"frame":{"x":595,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0031":{"frame":{"x":595,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0032":{"frame":{"x":595,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0033":{"frame":{"x":0,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0034":{"frame":{"x":85,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0035":{"frame":{"x":170,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0036":{"frame":{"x":255,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0037":{"frame":{"x":340,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0038":{"frame":{"x":425,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0039":{"frame":{"x":510,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0040":{"frame":{"x":595,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0041":{"frame":{"x":680,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0042":{"frame":{"x":680,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0043":{"frame":{"x":680,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0044":{"frame":{"x":680,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0045":{"frame":{"x":680,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0046":{"frame":{"x":765,"y":0,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0047":{"frame":{"x":765,"y":145,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0048":{"frame":{"x":765,"y":290,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0049":{"frame":{"x":765,"y":435,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}},"Mort0050":{"frame":{"x":765,"y":580,"w":84,"h":144},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":84,"h":144},"sourceSize":{"w":84,"h":144}}},"meta":{"app":"https://www.leshylabs.com/apps/sstool/","version":"Leshy SpriteSheet Tool v0.8.4","image":"death.png","size":{"w":849,"h":724},"scale":1}} -------------------------------------------------------------------------------- /src/main/resources/view/assets/death.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/death.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/destruction.json: -------------------------------------------------------------------------------- 1 | {"frames":{"destruction0001":{"frame":{"x":504,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0002":{"frame":{"x":504,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0003":{"frame":{"x":0,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0004":{"frame":{"x":504,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0005":{"frame":{"x":1008,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0006":{"frame":{"x":1008,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0007":{"frame":{"x":0,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0008":{"frame":{"x":504,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0009":{"frame":{"x":1008,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0010":{"frame":{"x":1512,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0011":{"frame":{"x":1512,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0012":{"frame":{"x":1512,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0013":{"frame":{"x":0,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0014":{"frame":{"x":504,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0015":{"frame":{"x":1008,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0016":{"frame":{"x":1512,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0017":{"frame":{"x":2016,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0018":{"frame":{"x":2016,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0019":{"frame":{"x":2016,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0020":{"frame":{"x":2016,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0021":{"frame":{"x":0,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0022":{"frame":{"x":0,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0023":{"frame":{"x":1008,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0024":{"frame":{"x":1512,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0025":{"frame":{"x":2016,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0026":{"frame":{"x":2520,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0027":{"frame":{"x":2520,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0028":{"frame":{"x":2520,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0029":{"frame":{"x":2520,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0030":{"frame":{"x":2520,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0031":{"frame":{"x":0,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0032":{"frame":{"x":504,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0033":{"frame":{"x":1008,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0034":{"frame":{"x":1512,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0035":{"frame":{"x":2016,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0036":{"frame":{"x":2520,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0037":{"frame":{"x":3024,"y":0,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0038":{"frame":{"x":3024,"y":505,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0039":{"frame":{"x":3024,"y":1010,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0040":{"frame":{"x":3024,"y":1515,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0041":{"frame":{"x":3024,"y":2020,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0042":{"frame":{"x":3024,"y":2525,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}},"destruction0043":{"frame":{"x":0,"y":3030,"w":504,"h":505},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":504,"h":505},"sourceSize":{"w":504,"h":505}}},"meta":{"app":"https://www.leshylabs.com/apps/sstool/","version":"Leshy SpriteSheet Tool v0.8.4","image":"destruction.png","size":{"w":3528,"h":3535},"scale":1}} -------------------------------------------------------------------------------- /src/main/resources/view/assets/destruction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/destruction.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/game.json: -------------------------------------------------------------------------------- 1 | {"frames":{"Unite_Reine":{"frame":{"x":1544,"y":900,"w":300,"h":300},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":300,"h":300},"sourceSize":{"w":300,"h":300}},"Unite_Siege":{"frame":{"x":1544,"y":600,"w":300,"h":300},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":300,"h":300},"sourceSize":{"w":300,"h":300}},"Caserne_Rouge":{"frame":{"x":440,"y":978,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"Caserne_Rouge_Jauge":{"frame":{"x":660,"y":758,"w":110,"h":10},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":110,"h":10},"sourceSize":{"w":110,"h":10}},"Eclair_Rouge":{"frame":{"x":660,"y":768,"w":200,"h":30},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":200,"h":30},"sourceSize":{"w":200,"h":30}},"Life-Rouge":{"frame":{"x":776,"y":1538,"w":416,"h":28},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":416,"h":28},"sourceSize":{"w":416,"h":28}},"Mine_Rouge":{"frame":{"x":300,"y":458,"w":97,"h":45},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":97,"h":45},"sourceSize":{"w":97,"h":45}},"Tour_Rouge":{"frame":{"x":1540,"y":1576,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"TR01":{"frame":{"x":884,"y":238,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR02":{"frame":{"x":1324,"y":714,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR03":{"frame":{"x":1104,"y":238,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR04":{"frame":{"x":664,"y":220,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR05":{"frame":{"x":0,"y":978,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR06":{"frame":{"x":444,"y":220,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR07":{"frame":{"x":1104,"y":0,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR08":{"frame":{"x":1104,"y":952,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR09":{"frame":{"x":884,"y":0,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR10":{"frame":{"x":1104,"y":714,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR11":{"frame":{"x":0,"y":220,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR12":{"frame":{"x":1324,"y":238,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR13":{"frame":{"x":880,"y":1576,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR14":{"frame":{"x":0,"y":1576,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TR15":{"frame":{"x":1544,"y":1200,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"Unite_Base_Rouge":{"frame":{"x":0,"y":458,"w":300,"h":300},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":300,"h":300},"sourceSize":{"w":300,"h":300}},"Caserne_Bleu":{"frame":{"x":880,"y":978,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"Caserne_Bleu_Jauge":{"frame":{"x":360,"y":1516,"w":110,"h":10},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":110,"h":10},"sourceSize":{"w":110,"h":10}},"Eclair_Bleu":{"frame":{"x":397,"y":458,"w":200,"h":30},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":200,"h":30},"sourceSize":{"w":200,"h":30}},"Life-Bleu":{"frame":{"x":360,"y":1538,"w":416,"h":28},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":416,"h":28},"sourceSize":{"w":416,"h":28}},"Mine_Bleu":{"frame":{"x":660,"y":1454,"w":97,"h":45},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":97,"h":45},"sourceSize":{"w":97,"h":45}},"TB01":{"frame":{"x":1324,"y":952,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB02":{"frame":{"x":1100,"y":1216,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB03":{"frame":{"x":664,"y":458,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB04":{"frame":{"x":220,"y":1576,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB05":{"frame":{"x":1104,"y":476,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB06":{"frame":{"x":660,"y":1216,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB07":{"frame":{"x":660,"y":1576,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB08":{"frame":{"x":884,"y":714,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB09":{"frame":{"x":884,"y":476,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB10":{"frame":{"x":220,"y":978,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB11":{"frame":{"x":300,"y":503,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB12":{"frame":{"x":1324,"y":0,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB13":{"frame":{"x":1324,"y":476,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB14":{"frame":{"x":880,"y":1216,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"TB15":{"frame":{"x":440,"y":1576,"w":220,"h":238},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":238},"sourceSize":{"w":220,"h":238}},"Tour_Bleu":{"frame":{"x":444,"y":0,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"Unite_Base_Bleu":{"frame":{"x":360,"y":1216,"w":300,"h":300},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":300,"h":300},"sourceSize":{"w":300,"h":300}},"LC_1":{"frame":{"x":0,"y":0,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_2":{"frame":{"x":220,"y":0,"w":224,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":224,"h":220},"sourceSize":{"w":224,"h":220}},"LC_3":{"frame":{"x":440,"y":758,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_4":{"frame":{"x":660,"y":978,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_5":{"frame":{"x":0,"y":758,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_6":{"frame":{"x":220,"y":220,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_7":{"frame":{"x":664,"y":0,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_8":{"frame":{"x":1100,"y":1576,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_9":{"frame":{"x":1544,"y":380,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LC_10":{"frame":{"x":1320,"y":1576,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"LieuDetruit":{"frame":{"x":1324,"y":1190,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"Mine":{"frame":{"x":220,"y":758,"w":220,"h":220},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":220,"h":220},"sourceSize":{"w":220,"h":220}},"Fleche_Bleu":{"frame":{"x":660,"y":798,"w":27,"h":12},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":27,"h":12},"sourceSize":{"w":27,"h":12}},"Fleche_Rouge":{"frame":{"x":360,"y":1526,"w":27,"h":12},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":27,"h":12},"sourceSize":{"w":27,"h":12}},"Unite_Archer":{"frame":{"x":0,"y":1216,"w":360,"h":360},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":360,"h":360},"sourceSize":{"w":360,"h":360}},"Unite_Fantassin":{"frame":{"x":1544,"y":0,"w":380,"h":380},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":380,"h":380},"sourceSize":{"w":380,"h":380}}},"meta":{"app":"https://www.leshylabs.com/apps/sstool/","version":"Leshy SpriteSheet Tool v0.8.4","image":"game.png","size":{"w":1924,"h":1814},"scale":1}} -------------------------------------------------------------------------------- /src/main/resources/view/assets/game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/game.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-royale/349924a140258107a5f6f75d5d6bde717e12ae3b/src/main/resources/view/assets/logo.png -------------------------------------------------------------------------------- /src/main/resources/view/config.js: -------------------------------------------------------------------------------- 1 | // import { EntityManager } from './EntityManager.js'; 2 | // 3 | // // You can define resources used by your games (like images, fonts, ...) 4 | // export const assets = { 5 | // images: { 6 | // // ball: '/assets/ball.png', 7 | // // background: '/assets/background.jpg' 8 | // } 9 | // }; 10 | // 11 | // export const players = [{ 12 | // name: 'Player 1', 13 | // avatar: 'https://static.codingame.com/servlet/fileservlet?id=1719285195844&format=viewer_avatar' 14 | // }, 15 | // { 16 | // name: 'Player 2', 17 | // avatar: 'https://static.codingame.com/servlet/fileservlet?id=1717001354716&format=viewer_avatar' 18 | // } 19 | // ]; 20 | // 21 | // // List of viewer modules that you want to use in your game 22 | // export const modules = [ 23 | // {name: 'entitymanager', class: EntityManager} 24 | // ]; 25 | 26 | 27 | import { GraphicEntityModule } from './entity-module/GraphicEntityModule.js'; 28 | import {TooltipModule} from './tooltips/TooltipModule.js'; 29 | import { AnimModule } from './anims/AnimModule.js'; 30 | 31 | // List of viewer modules that you want to use in your game 32 | export const modules = [ 33 | GraphicEntityModule, 34 | AnimModule, 35 | TooltipModule 36 | ]; 37 | -------------------------------------------------------------------------------- /src/main/resources/view/tooltips/TooltipModule.js: -------------------------------------------------------------------------------- 1 | import { ErrorLog } from '../core/ErrorLog.js'; 2 | import { WIDTH, HEIGHT } from '../core/constants.js'; 3 | import * as utils from '../core/utils.js'; 4 | import { api as entityModule } from '../entity-module/GraphicEntityModule.js'; 5 | 6 | 7 | function getMouseOverFunc(id, tooltip) { 8 | return function () { 9 | tooltip.inside[id] = true; 10 | }; 11 | } 12 | 13 | function getMouseOutFunc(id, tooltip) { 14 | return function () { 15 | delete tooltip.inside[id]; 16 | }; 17 | } 18 | 19 | function getEntityState(entity, frame, progress) { 20 | const subStates = entity.states[frame]; 21 | if (subStates && subStates.length) { 22 | return subStates[subStates.length - 1]; 23 | } 24 | return null; 25 | } 26 | 27 | function getMouseMoveFunc(tooltip, container, module) { 28 | return function (ev) { 29 | if (tooltip) { 30 | var pos = ev.data.getLocalPosition(container); 31 | tooltip.x = pos.x; 32 | tooltip.y = pos.y; 33 | var point = { 34 | x: pos.x * entityModule.coeff, 35 | y: pos.y * entityModule.coeff 36 | }; 37 | var showing = null; 38 | var ids = Object.keys(tooltip.inside).map(n => +n); 39 | var entity; 40 | 41 | for (let id of ids) { 42 | if (tooltip.inside[id]) { 43 | entity = entityModule.entities.get(id); 44 | if (!entity) { 45 | delete tooltip.inside[id]; 46 | } else { 47 | showing = id; 48 | } 49 | } 50 | } 51 | 52 | let state = null; 53 | if (showing !== null) { 54 | state = getEntityState(entity, module.currentFrame); 55 | } 56 | 57 | if (state !== null) { 58 | const params = module.registered[showing]; 59 | 60 | tooltip.label.text = 61 | (params.hasOwnProperty('id') ? 'id: ' + params.id + '\n' : '') 62 | + 'type: ' + params.type 63 | + '\nx: ' + state.x 64 | + '\ny: ' + (state.y - hudHeight) 65 | ; 66 | 67 | tooltip.visible = true; 68 | const extras = module.extra[showing]; 69 | 70 | if (extras) { 71 | const frames = Object.keys(extras).map(a => +a).sort((b,c) => b - c); 72 | let index = 0; 73 | while (index < frames.length - 1 && frames[index] < module.currentFrame) { 74 | index++; 75 | } 76 | const extra = extras[frames[index]]; 77 | if (extra) { 78 | tooltip.label.text += "\n" + extra.join("\n") 79 | } 80 | } 81 | } else { 82 | tooltip.visible = point.y <= 1080 && point.y >= hudHeight && point.x >= 0 && point.x < WIDTH; 83 | tooltip.label.text = 'x: ' + Math.round(point.x) + '\ny: ' + Math.round(point.y - hudHeight); 84 | } 85 | 86 | tooltip.background.width = tooltip.label.width + 20; 87 | tooltip.background.height = tooltip.label.height + 20; 88 | // scope.drawer.asyncRenderingTime = Drawer.RenderTimeout; 89 | tooltip.pivot.x = -30; 90 | tooltip.pivot.y = -50; 91 | 92 | if (tooltip.y - tooltip.pivot.y + tooltip.height > HEIGHT) { 93 | tooltip.pivot.y = 10 + tooltip.height; 94 | } 95 | 96 | if (tooltip.x - tooltip.pivot.x + tooltip.width > WIDTH) { 97 | tooltip.pivot.x = tooltip.width; 98 | } 99 | 100 | } 101 | }; 102 | } 103 | 104 | var hudHeight = 0; 105 | 106 | export class TooltipModule { 107 | constructor(assets) { 108 | this.registered = {}; 109 | this.extra = {}; 110 | this.lastProgress = 1; 111 | this.lastFrame = 0; 112 | this.toDestroy = []; 113 | } 114 | 115 | static get name() { 116 | return 'tooltips'; 117 | } 118 | 119 | updateScene(previousData, currentData, progress) { 120 | this.currentFrame = currentData.number; 121 | this.currentProgress = progress; 122 | } 123 | 124 | handleFrameData(frameInfo, [registrations, extra]) { 125 | 126 | // Quick check 127 | Object.keys(registrations).forEach( 128 | k => { 129 | if (this.registered[k]) { 130 | ErrorLog.push(new NotYetImplemented('Registering the same entity multiple times', state.image)); 131 | } 132 | } 133 | ); 134 | 135 | Object.assign(this.registered, registrations); 136 | 137 | Object.keys(extra).forEach( 138 | k => { 139 | if (!this.extra[k]) { 140 | this.extra[k] = {}; 141 | } 142 | this.extra[k][frameInfo.number] = extra[k]; 143 | } 144 | ); 145 | return { number: frameInfo.number }; 146 | } 147 | 148 | reinitScene(container, canvasData) { 149 | this.toDestroy.forEach(texture => { 150 | try { 151 | texture.destroy(true); 152 | } catch (ouch) { 153 | // ignore, carry on 154 | } 155 | }); 156 | 157 | this.tooltip = this.initTooltip(); 158 | entityModule.entities.forEach(entity => { 159 | if (this.registered[entity.id]) { 160 | entity.container.interactive = true; 161 | entity.container.mouseover = getMouseOverFunc(entity.id, this.tooltip); 162 | entity.container.mouseout = getMouseOutFunc(entity.id, this.tooltip); 163 | } 164 | }); 165 | this.container = container; 166 | container.interactive = true; 167 | container.mousemove = getMouseMoveFunc(this.tooltip, container, this); 168 | container.addChild(this.tooltip); 169 | } 170 | 171 | generateText(text, size, color, align) { 172 | var textEl = new PIXI.Text(text, { 173 | fontSize: Math.round(size / 1.2) + 'px', 174 | fontFamily: 'Lato', 175 | fontWeight: 'bold', 176 | fill: color 177 | }); 178 | 179 | textEl.lineHeight = Math.round(size / 1.2); 180 | if (align === 'right') { 181 | textEl.anchor.x = 1; 182 | } else if (align === 'center') { 183 | textEl.anchor.x = 0.5; 184 | } 185 | this.toDestroy.push(textEl); 186 | return textEl; 187 | }; 188 | 189 | initTooltip() { 190 | var tooltip = new PIXI.Container(); 191 | var background = tooltip.background = new PIXI.Graphics(); 192 | var label = tooltip.label = this.generateText('', 36, 0xFFFFFF, 'left'); 193 | 194 | background.beginFill(0x0, 0.7); 195 | background.drawRect(0, 0, 200, 185); 196 | background.endFill(); 197 | background.x = -10; 198 | background.y = -10; 199 | 200 | tooltip.visible = false; 201 | tooltip.inside = {}; 202 | 203 | tooltip.addChild(background); 204 | tooltip.addChild(label); 205 | 206 | tooltip.interactiveChildren = false; 207 | return tooltip; 208 | }; 209 | 210 | animateScene(delta) { 211 | 212 | } 213 | 214 | handleGlobalData(players, globalData) { 215 | 216 | } 217 | } 218 | 219 | class NotYetImplemented extends Error { 220 | constructor(feature) { 221 | super('Not yet implemented: "' + feature); 222 | this.feature = feature; 223 | this.name = 'NotYetImplemented'; 224 | } 225 | } -------------------------------------------------------------------------------- /src/test/java/Main.java: -------------------------------------------------------------------------------- 1 | import com.codingame.game.ThibaudPlayer; 2 | import com.codingame.gameengine.runner.GameRunner; 3 | 4 | public class Main { 5 | public static void main(String[] args) { 6 | 7 | GameRunner gameRunner = new GameRunner(); 8 | 9 | // Adds as many player as you need to test your game 10 | gameRunner.addAgent(Level1Player.class); 11 | gameRunner.addAgent(ThibaudPlayer.class); 12 | 13 | // gameRunner.addCommandLinePlayer("python3 /home/user/player.py"); 14 | 15 | gameRunner.start(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/WaitBot.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | import java.math.*; 4 | 5 | /** 6 | * Auto-generated code below aims at helping you parse 7 | * the standard input according to the problem statement. 8 | **/ 9 | public class WaitBot { 10 | 11 | public static void main(String args[]) { 12 | Scanner in = new Scanner(System.in); 13 | int numObstacles = in.nextInt(); 14 | for (int i = 0; i < numObstacles; i++) { 15 | int obstacleId = in.nextInt(); 16 | int x = in.nextInt(); 17 | int y = in.nextInt(); 18 | int radius = in.nextInt(); 19 | } 20 | 21 | // game loop 22 | while (true) { 23 | int gold = in.nextInt(); 24 | int touchingObstacle = in.nextInt(); 25 | for (int i = 0; i < numObstacles; i++) { 26 | int obstacleId = in.nextInt(); 27 | int goldRemaining = in.nextInt(); // -1 if unknown 28 | int maxMineSize = in.nextInt(); // -1 if unknown 29 | int structureType = in.nextInt(); // -1 = No structure, 0 = Resource Mine, 1 = Tower, 2 = Barracks 30 | int owner = in.nextInt(); // -1 = No structure, 0 = Friendly, 1 = Enemy 31 | int param1 = in.nextInt(); 32 | int param2 = in.nextInt(); 33 | } 34 | int numUnits = in.nextInt(); 35 | for (int i = 0; i < numUnits; i++) { 36 | int x = in.nextInt(); 37 | int y = in.nextInt(); 38 | int owner = in.nextInt(); 39 | int type = in.nextInt(); 40 | int health = in.nextInt(); 41 | } 42 | 43 | // Write an action using System.out.println() 44 | // To debug: System.err.println("Debug messages..."); 45 | 46 | 47 | // First line: A valid queen action 48 | // Second line: A set of training instructions 49 | System.out.println("WAIT"); 50 | System.out.println("TRAIN"); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/AllArcherPlayer.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.BasePlayer 2 | import com.codingame.game.Constants.QUEEN_RADIUS 3 | import com.codingame.game.Constants.TOUCHING_DELTA 4 | import com.codingame.game.ObstacleInput 5 | import java.io.InputStream 6 | import java.io.PrintStream 7 | 8 | class AllArcherPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BasePlayer(stdin, stdout, stderr) { 9 | 10 | private fun myBarracks(): List = obstacles.filter { it.owner == 0 && it.structureType == 2 } 11 | 12 | init { 13 | 14 | while (true) { 15 | val (queenLoc, _, _, _, _, _, obstacles, _, _) = readInputs() 16 | 17 | // strategy: 18 | // build all mines 19 | // build barracks, knight, anytime our income exceeds our ability to spam units 20 | // spam knight units forever 21 | 22 | fun getQueenAction(): String { 23 | 24 | // if touching a mine that isn't at max capacity, keep growing it 25 | val growingMine = obstacles 26 | .filter { it.owner == 0 && it.structureType == 0 && it.incomeRateOrHealthOrCooldown < it.maxMineSize } 27 | .firstOrNull { it.location.distanceTo(queenLoc).toDouble - it.radius - QUEEN_RADIUS < TOUCHING_DELTA } 28 | 29 | if (growingMine != null) { 30 | stderr.println("Max: ${growingMine.maxMineSize}") 31 | return "BUILD ${growingMine.obstacleId} MINE" 32 | } 33 | 34 | val queenTarget = obstacles 35 | .filter { it.owner == -1 } 36 | .minBy { it.location.distanceTo(queenLoc) } ?: return "WAIT" 37 | 38 | val income = obstacles 39 | .filter { it.owner == 0 && it.structureType == 0 } 40 | .sumBy { it.incomeRateOrHealthOrCooldown } 41 | 42 | val maxUnitSpend = (myBarracks().size + 0.5) * 16 // = 80/5 43 | val needsBarracks = income >= maxUnitSpend 44 | 45 | return "BUILD ${queenTarget.obstacleId} ${if(needsBarracks) "BARRACKS-ARCHER" else "MINE"}" 46 | } 47 | 48 | try { 49 | stdout.println(getQueenAction()) 50 | stdout.println("TRAIN${myBarracks().joinToString("") { " " + it.obstacleId }}") 51 | } catch (ex: Exception) { 52 | ex.printStackTrace(stderr) 53 | throw ex 54 | } 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/AllGiantsPlayer.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.BasePlayer 2 | import com.codingame.game.Constants.QUEEN_RADIUS 3 | import com.codingame.game.Constants.TOUCHING_DELTA 4 | import com.codingame.game.ObstacleInput 5 | import java.io.InputStream 6 | import java.io.PrintStream 7 | 8 | class AllGiantsPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream) : BasePlayer(stdin, stdout, stderr) { 9 | 10 | private fun myBarracks(): List = obstacles.filter { it.owner == 0 && it.structureType == 2 } 11 | 12 | init { 13 | 14 | while (true) { 15 | val (queenLoc, _, _, _, _, _, obstacles, _, _) = readInputs() 16 | 17 | // strategy: 18 | // build all mines 19 | // build barracks, melee, anytime our income exceeds our ability to spam units 20 | // spam melee units forever 21 | 22 | fun getQueenAction(): String { 23 | 24 | // if touching a mine that isn't at max capacity, keep growing it 25 | val growingMine = obstacles 26 | .filter { it.owner == 0 && it.structureType == 0 && it.incomeRateOrHealthOrCooldown < it.maxMineSize } 27 | .firstOrNull { it.location.distanceTo(queenLoc).toDouble - it.radius - QUEEN_RADIUS < TOUCHING_DELTA } 28 | 29 | if (growingMine != null) { 30 | stderr.println("Max: ${growingMine.maxMineSize}") 31 | return "BUILD ${growingMine.obstacleId} MINE" 32 | } 33 | 34 | val queenTarget = obstacles 35 | .filter { it.owner == -1 } 36 | .minBy { it.location.distanceTo(queenLoc) } ?: return "WAIT" 37 | 38 | val income = obstacles 39 | .filter { it.owner == 0 && it.structureType == 0 } 40 | .sumBy { it.incomeRateOrHealthOrCooldown } 41 | 42 | val maxUnitSpend = (myBarracks().size + 0.5) * 16 // = 80/5 43 | val needsBarracks = income >= maxUnitSpend 44 | 45 | return "BUILD ${queenTarget.obstacleId} ${if (needsBarracks) "BARRACKS-GIANT" else "MINE"}" 46 | } 47 | 48 | try { 49 | stdout.println(getQueenAction()) 50 | stdout.println("TRAIN${myBarracks().joinToString("") { " " + it.obstacleId }}") 51 | } catch (ex: Exception) { 52 | ex.printStackTrace(stderr) 53 | throw ex 54 | } 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/AllKnightPlayer.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.BasePlayer 2 | import com.codingame.game.Constants.QUEEN_RADIUS 3 | import com.codingame.game.Constants.TOUCHING_DELTA 4 | import com.codingame.game.ObstacleInput 5 | import java.io.InputStream 6 | import java.io.PrintStream 7 | 8 | class AllKnightPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BasePlayer(stdin, stdout, stderr) { 9 | 10 | private fun myBarracks(): List = obstacles.filter { it.owner == 0 && it.structureType == 2 } 11 | 12 | init { 13 | 14 | while (true) { 15 | val (queenLoc, _, _, _, _, _, obstacles, _, _) = readInputs() 16 | 17 | // strategy: 18 | // build all mines 19 | // build barracks, knight, anytime our income exceeds our ability to spam units 20 | // spam knight units forever 21 | 22 | fun getQueenAction(): String { 23 | 24 | // if touching a mine that isn't at max capacity, keep growing it 25 | val growingMine = obstacles 26 | .filter { it.owner == 0 && it.structureType == 0 && it.incomeRateOrHealthOrCooldown < it.maxMineSize } 27 | .firstOrNull { it.location.distanceTo(queenLoc).toDouble - it.radius - QUEEN_RADIUS < TOUCHING_DELTA } 28 | 29 | if (growingMine != null) { 30 | stderr.println("Max: ${growingMine.maxMineSize}") 31 | return "BUILD ${growingMine.obstacleId} MINE" 32 | } 33 | 34 | val queenTarget = obstacles 35 | .filter { it.owner == -1 } 36 | .minBy { it.location.distanceTo(queenLoc) } ?: return "WAIT" 37 | 38 | val income = obstacles 39 | .filter { it.owner == 0 && it.structureType == 0 } 40 | .sumBy { it.incomeRateOrHealthOrCooldown } 41 | 42 | val maxUnitSpend = (myBarracks().size + 0.5) * 16 // = 80/5 43 | val needsBarracks = income >= maxUnitSpend 44 | 45 | return "BUILD ${queenTarget.obstacleId} ${if(needsBarracks) "BARRACKS-KNIGHT" else "MINE"}" 46 | } 47 | 48 | try { 49 | stdout.println(getQueenAction()) 50 | stdout.println("TRAIN${myBarracks().joinToString("") { " " + it.obstacleId }}") 51 | } catch (ex: Exception) { 52 | ex.printStackTrace(stderr) 53 | throw ex 54 | } 55 | 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/AllTowersPlayer.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.BasePlayer 2 | import com.codingame.game.Constants 3 | import com.codingame.game.Constants.QUEEN_RADIUS 4 | import java.io.InputStream 5 | import java.io.PrintStream 6 | 7 | class AllTowersPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BasePlayer(stdin, stdout, stderr) { 8 | init { 9 | while (true) { 10 | val (queenLoc, _, _, _, _, _, obstacles, _, _) = readInputs() 11 | 12 | fun getQueenAction(): String { 13 | // if touching a tower that isn't at max health, keep growing it 14 | val growingTower = obstacles 15 | .filter { it.owner == 0 && it.structureType == 1 && it.incomeRateOrHealthOrCooldown < 400 } 16 | .firstOrNull { it.location.distanceTo(queenLoc).toDouble - it.radius - QUEEN_RADIUS < 5 } 17 | 18 | if (growingTower != null) return "BUILD ${growingTower.obstacleId} TOWER" 19 | 20 | val queenTarget = obstacles 21 | .filter { it.owner == -1 } 22 | .minBy { it.location.distanceTo(queenLoc).toDouble - it.radius } ?: return "WAIT" 23 | 24 | return "BUILD ${queenTarget.obstacleId} TOWER" 25 | } 26 | 27 | stdout.println(getQueenAction()) 28 | stdout.println("TRAIN") 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/BasePlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import java.io.InputStream 4 | import java.io.PrintStream 5 | import java.util.* 6 | 7 | @Suppress("unused") 8 | abstract class BasePlayer(stdin: InputStream, val stdout: PrintStream, val stderr: PrintStream) { 9 | private val scanner = Scanner(stdin) 10 | 11 | var obstacles = listOf() 12 | 13 | private fun readObstacleInit() = ObstacleInput( 14 | scanner.nextInt(), 15 | Vector2(scanner.nextInt(), scanner.nextInt()), 16 | scanner.nextInt() 17 | )//.also { stderr.println("Read obstacle: $it")} 18 | 19 | private fun readObstaclePerTurn() = ObstaclePerTurnInput( 20 | scanner.nextInt(), scanner.nextInt(), scanner.nextInt(), scanner.nextInt(), 21 | scanner.nextInt(), scanner.nextInt(), scanner.nextInt() 22 | ) 23 | 24 | private fun readUnit() = UnitInput( 25 | Vector2(scanner.nextInt(), scanner.nextInt()), scanner.nextInt() == 0, { 26 | val type = scanner.nextInt() 27 | when (type) { 28 | -1 -> null 29 | else -> CreepType.values()[type] 30 | } 31 | }(), scanner.nextInt() 32 | )//.also { stderr.println("Read creep: $it")} 33 | 34 | init { 35 | obstacles = (0 until scanner.nextInt()).map { readObstacleInit() } 36 | } 37 | 38 | protected fun readInputs(): AllInputs { 39 | val gold = scanner.nextInt() 40 | val touchedObstacleId = scanner.nextInt() 41 | val obstacles = (0 until obstacles.size).map { applyObstacleUpdate(readObstaclePerTurn()) } 42 | val units = (0 until scanner.nextInt()).map { readUnit() } 43 | return AllInputs( 44 | units.single { it.isFriendly && it.creepType == null }.let { it.location }, 45 | units.single { it.isFriendly && it.creepType == null }.let { it.health }, 46 | gold, 47 | touchedObstacleId, 48 | units.single { !it.isFriendly && it.creepType == null }.let { it.location }, 49 | units.single { !it.isFriendly && it.creepType == null }.let { it.health }, 50 | obstacles, 51 | units.filter { it.isFriendly && it.creepType != null }, 52 | units.filter { !it.isFriendly && it.creepType != null } 53 | ) 54 | }//.also { stderr.println("Read inputs: $it")} 55 | 56 | private fun applyObstacleUpdate(update: ObstaclePerTurnInput): ObstacleInput { 57 | val matchingObstacle = obstacles.find { it.obstacleId == update.obstacleId }!! 58 | matchingObstacle.applyUpdate(update) 59 | return matchingObstacle 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/CSJPlayer.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.* 2 | import com.codingame.game.BasePlayer 3 | import com.codingame.game.Constants.QUEEN_RADIUS 4 | import java.io.InputStream 5 | import java.io.PrintStream 6 | import java.util.* 7 | 8 | class CSJPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BasePlayer(stdin, stdout, stderr) { 9 | var turn = 0 10 | val theRandom = Random() 11 | 12 | init { 13 | 14 | while (true) { 15 | turn++ 16 | 17 | val (queenLoc, _, gold, touch, enemyQueenLoc, _, obstacles, _, enemyCreeps) = readInputs() 18 | // stderr.println("Touching: $touch") 19 | 20 | // strategy: 21 | // build mines 22 | // when they build their first barracks, build our first tower, and stay near it 23 | // fan out and add more mines 24 | // try to keep as much production as we have income (rotate creep types) 25 | // schedule barracks to fire at the same time 26 | 27 | val totalIncome = obstacles 28 | .filter { it.owner == 0 && it.structureType == 0 } 29 | .sumBy { it.incomeRateOrHealthOrCooldown } 30 | 31 | val totalProduction = obstacles 32 | .filter { it.owner == 0 && it.structureType == 2 } 33 | .sumBy { CreepType.values()[it.attackRadiusOrCreepType].let { it.cost / it.buildTime } } 34 | 35 | val danger = enemyCreeps.any { it.location.distanceTo(queenLoc) < 300 } 36 | 37 | fun getQueenAction(): String { 38 | // if touching a tower that isn't at max health, keep growing it 39 | val growingTower = obstacles 40 | .filter { it.owner == 0 && it.structureType == 1 && it.incomeRateOrHealthOrCooldown < 400 } 41 | .firstOrNull { it.location.distanceTo(queenLoc).toDouble - it.radius - QUEEN_RADIUS < 5 } 42 | if (growingTower != null) return "BUILD ${growingTower.obstacleId} TOWER" 43 | 44 | // if touching a mine that isn't at max capacity, keep growing it 45 | val growingMine = obstacles 46 | .filter { it.owner == 0 && it.structureType == 0 && it.incomeRateOrHealthOrCooldown < it.maxMineSize } 47 | .firstOrNull { it.location.distanceTo(queenLoc).toDouble - it.radius - QUEEN_RADIUS < 5 } 48 | if (growingMine != null) { 49 | return "BUILD ${growingMine.obstacleId} MINE" 50 | } 51 | 52 | val queenTarget = obstacles 53 | .filter { it.owner == -1 || (it.owner == 1 && it.structureType != 1) } 54 | .filter { target -> !obstacles.any { 55 | it.owner == 1 && it.structureType == 1 && 56 | it.location.distanceTo(target.location).toDouble - it.attackRadiusOrCreepType - target.radius < -30 }} 57 | .minBy { it.location.distanceTo(queenLoc).toDouble - it.radius } 58 | 59 | if (queenTarget == null) { 60 | // bear toward closest friendly tower 61 | val closestTower = obstacles 62 | .filter { it.owner == 0 && it.structureType == 1 } 63 | .minBy { it.location.distanceTo(queenLoc).toDouble - it.radius } 64 | 65 | return closestTower?.let { "BUILD ${it.obstacleId} TOWER" } ?: "WAIT" 66 | } 67 | 68 | val dist = queenTarget.location.distanceTo(queenLoc).toDouble - QUEEN_RADIUS - queenTarget.radius 69 | 70 | if (dist < 5) { 71 | // Touching an obstacle; do something here 72 | if (danger) return "BUILD ${queenTarget.obstacleId} TOWER" 73 | if (totalIncome * 1.5 <= totalProduction) 74 | return if (queenTarget.gold > 0) "BUILD ${queenTarget.obstacleId} MINE" else "BUILD ${queenTarget.obstacleId} TOWER" 75 | 76 | // count enemy towers; make sure we have a giant if they have more than 3 77 | val ourKnights = obstacles.count { it.owner == 0 && it.structureType == 2 && it.attackRadiusOrCreepType == 0 } 78 | val ourArchers = obstacles.count { it.owner == 0 && it.structureType == 2 && it.attackRadiusOrCreepType == 1 } 79 | val ourGiants = obstacles.count { it.owner == 0 && it.structureType == 2 && it.attackRadiusOrCreepType == 2 } 80 | val theirTowers = obstacles.count { it.owner == 1 && it.structureType == 1 } 81 | 82 | val barracksType = when { 83 | theirTowers >= 2 && ourGiants == 0 -> CreepType.GIANT 84 | ourKnights > ourArchers -> CreepType.ARCHER 85 | else -> CreepType.KNIGHT 86 | } 87 | return "BUILD ${queenTarget.obstacleId} BARRACKS-$barracksType" 88 | } 89 | 90 | return queenTarget.let { "BUILD ${it.obstacleId} TOWER"} 91 | } 92 | 93 | fun getTrainOrders(): List { 94 | val myBarracks = obstacles.filter { it.owner == 0 && it.structureType == 2 } 95 | 96 | if (myBarracks.isEmpty()) return listOf() 97 | if (myBarracks.any { it.incomeRateOrHealthOrCooldown > 0 }) return listOf() 98 | val totalCost = myBarracks.sumBy { CreepType.values()[it.attackRadiusOrCreepType].cost } 99 | if (gold < totalCost) return listOf() 100 | return myBarracks 101 | } 102 | 103 | try { 104 | stdout.println(getQueenAction()) 105 | stdout.println("TRAIN${getTrainOrders().joinToString("") { " " + it.obstacleId }}") 106 | } catch (ex: Exception) { 107 | ex.printStackTrace(stderr) 108 | throw ex 109 | } 110 | 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/Level1Player.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.BasePlayer 2 | import com.codingame.game.ObstacleInput 3 | import java.io.InputStream 4 | import java.io.PrintStream 5 | 6 | class Level1Player(stdin: InputStream, stdout: PrintStream, stderr: PrintStream) : BasePlayer(stdin, stdout, stderr) { 7 | 8 | private fun myBarracks(): List = obstacles.filter { it.owner == 0 && it.structureType == 2 } 9 | 10 | init { 11 | 12 | while (true) { 13 | val (queenLoc, _, _, touch, _, _, obstacles, _, _) = readInputs() 14 | // stderr.println("Touching: $touch") 15 | fun getQueenAction(): String { 16 | 17 | val queenTarget = obstacles 18 | .filter { it.owner == -1 } 19 | .minBy { it.location.distanceTo(queenLoc) } ?: return "WAIT" 20 | 21 | val needsKnight = obstacles.count { it.structureType == 2 && it.owner == 0 && it.attackRadiusOrCreepType == 0 } < 2 22 | val needsArcher = !obstacles.any { it.structureType == 2 && it.owner == 0 && it.attackRadiusOrCreepType == 1 } 23 | // val needsGiant = !obstacles.any { it.structureType == 2 && it.owner == 0 && it.attackRadiusOrCreepType == 2 } 24 | 25 | return when { 26 | needsKnight -> "BUILD ${queenTarget.obstacleId} BARRACKS-KNIGHT" 27 | needsArcher -> "BUILD ${queenTarget.obstacleId} BARRACKS-ARCHER" 28 | // needsGiant -> "BUILD ${queenTarget.obstacleId} BARRACKS-GIANT" 29 | // else -> "BUILD ${queenTarget.obstacleId} TOWER" 30 | else -> "WAIT" 31 | } 32 | } 33 | 34 | try { 35 | stdout.println(getQueenAction()) 36 | stdout.println("TRAIN${myBarracks().joinToString("") { " " + it.obstacleId }}") 37 | } catch (ex: Exception) { 38 | ex.printStackTrace(stderr) 39 | throw ex 40 | } 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/Level2Player.kt: -------------------------------------------------------------------------------- 1 | import com.codingame.game.BasePlayer 2 | import com.codingame.game.ObstacleInput 3 | import java.io.InputStream 4 | import java.io.PrintStream 5 | 6 | class Level2Player(stdin: InputStream, stdout: PrintStream, stderr: PrintStream) : BasePlayer(stdin, stdout, stderr) { 7 | 8 | private fun myBarracks(): List = obstacles.filter { it.owner == 0 && it.structureType == 2 } 9 | 10 | init { 11 | 12 | while (true) { 13 | val (queenLoc, _, _, _, _, _, obstacles, _, _) = readInputs() 14 | 15 | fun getQueenAction(): String { 16 | val queenTarget = obstacles 17 | .filter { it.owner == -1 || (it.owner == 0 && it.structureType == 1 && it.incomeRateOrHealthOrCooldown < 400) } 18 | .minBy { it.location.distanceTo(queenLoc) } ?: return "WAIT" 19 | 20 | val needsKnight = !obstacles.any { it.structureType == 2 && it.owner == 0 && it.attackRadiusOrCreepType == 0 } 21 | val needsArcher = !obstacles.any { it.structureType == 2 && it.owner == 0 && it.attackRadiusOrCreepType == 1 } 22 | val needsGiant = !obstacles.any { it.structureType == 2 && it.owner == 0 && it.attackRadiusOrCreepType == 2 } 23 | val needsTower = obstacles.count { it.structureType == 1 && it.owner == 0 } < 3 24 | 25 | return when { 26 | needsKnight -> "BUILD ${queenTarget.obstacleId} BARRACKS-KNIGHT" 27 | needsArcher -> "BUILD ${queenTarget.obstacleId} BARRACKS-ARCHER" 28 | needsGiant -> "BUILD ${queenTarget.obstacleId} BARRACKS-GIANT" 29 | needsTower -> "BUILD ${queenTarget.obstacleId} TOWER" 30 | else -> "WAIT" 31 | } 32 | } 33 | 34 | try { 35 | stdout.println(getQueenAction()) 36 | stdout.println("TRAIN${myBarracks().joinToString("") { " " + it.obstacleId }}") 37 | } catch (ex: Exception) { 38 | ex.printStackTrace(stderr) 39 | throw ex 40 | } 41 | 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/ThibaudPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import java.io.InputStream 4 | import java.io.PrintStream 5 | 6 | class ThibaudPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BasePlayer(stdin, stdout, stderr) { 7 | 8 | init { 9 | while (true) { 10 | readInputs() 11 | stdout.println("BUILD 0 BARRACKS-KNIGHT") 12 | stdout.println("TRAIN 0") 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/Types.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | data class UnitInput( 4 | val location: Vector2, 5 | val isFriendly: Boolean, 6 | val creepType: CreepType?, 7 | val health: Int 8 | ) 9 | 10 | data class ObstaclePerTurnInput( 11 | val obstacleId: Int, 12 | val gold: Int, 13 | val maxMineSize: Int, 14 | val structureType: Int, 15 | val owner: Int, 16 | val incomeRateOrHealthOrCooldown: Int, 17 | val attackRadiusOrCreepType: Int 18 | ) 19 | 20 | data class ObstacleInput( 21 | val obstacleId: Int, 22 | val location: Vector2, 23 | val radius: Int, 24 | var gold: Int = -1, 25 | var maxMineSize: Int = -1, 26 | var structureType: Int = -1, // -1 = None, 0 = Mine, 1 = Tower, 2 = Barracks 27 | var owner: Int = -1, // 0 = Us, 1 = Enemy 28 | var incomeRateOrHealthOrCooldown: Int = -1, // mine / tower / barracks 29 | var attackRadiusOrCreepType: Int = -1 // tower / barracks 30 | ) { 31 | fun applyUpdate(update: ObstaclePerTurnInput) { 32 | structureType = update.structureType 33 | gold = update.gold 34 | maxMineSize = update.maxMineSize 35 | owner = update.owner 36 | incomeRateOrHealthOrCooldown = update.incomeRateOrHealthOrCooldown 37 | attackRadiusOrCreepType = update.attackRadiusOrCreepType 38 | } 39 | } 40 | 41 | data class AllInputs( 42 | val queenLoc: Vector2, 43 | val health: Int, 44 | val gold: Int, 45 | val touchedObstacleId: Int, 46 | val enemyQueenLoc: Vector2, 47 | val enemyHealth: Int, 48 | val obstacles: List, 49 | val friendlyCreeps: List, 50 | val enemyCreeps: List 51 | ) -------------------------------------------------------------------------------- /src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | log4j.rootLogger=DEBUG, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | log4j.appender.A1=org.apache.log4j.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | log4j.appender.A1.layout=org.apache.log4j.PatternLayout 9 | log4j.appender.A1.layout.ConversionPattern=%m%n 10 | --------------------------------------------------------------------------------