├── .gitignore ├── GameParameters.md ├── README.md ├── config ├── Boss.py3 ├── config.ini ├── starterAI │ ├── Starter.cpp │ ├── Starter.cs │ ├── Starter.java │ ├── Starter.js │ └── Starter.py ├── statement_en.html.tpl ├── statement_fr.html.tpl └── stub.txt ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── codingame │ │ ├── game │ │ ├── CommandManager.java │ │ ├── GameException.java │ │ ├── InvalidInputException.java │ │ ├── Player.java │ │ └── Referee.java │ │ ├── utg2019 │ │ ├── Agent.java │ │ ├── Cell.java │ │ ├── Config.java │ │ ├── Coord.java │ │ ├── Game.java │ │ ├── Grid.java │ │ ├── Item.java │ │ ├── ItemCoord.java │ │ ├── PathNode.java │ │ └── action │ │ │ ├── Action.java │ │ │ ├── ActionException.java │ │ │ ├── DigAction.java │ │ │ ├── MoveAction.java │ │ │ └── RequestAction.java │ │ ├── utils │ │ ├── Padding.java │ │ └── Vector.java │ │ └── view │ │ ├── AgentData.java │ │ ├── CellData.java │ │ ├── EventData.java │ │ ├── FrameViewData.java │ │ ├── GlobalViewData.java │ │ ├── Point.java │ │ ├── ViewModule.java │ │ └── endscreen │ │ └── EndScreenModule.java └── resources │ └── view │ ├── assets │ ├── Background.jpg │ ├── Hud_left.png │ ├── Hud_top.png │ ├── elems.json │ ├── elems.png │ ├── logo.png │ ├── sprites.json │ └── spritesheet.png │ ├── config.js │ ├── demo.js │ ├── endscreen │ └── EndScreenModule.js │ ├── graphics │ ├── Deserializer.js │ ├── MessageBoxes.js │ ├── ViewModule.js │ ├── assetConstants.js │ └── gameConstants.js │ └── tooltip │ └── TooltipModule.js └── test ├── java └── UTG2019Main.java └── resources └── log4j2.properties /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /.project 3 | /.classpath 4 | /bin/ 5 | .factorypath 6 | /.settings/ 7 | statement_en.html 8 | statement_fr.html -------------------------------------------------------------------------------- /GameParameters.md: -------------------------------------------------------------------------------- 1 | __ADJACENCY__ 2 | 3 | Sets whether to allow diagonals in pathfinding and interactions. 4 | 5 | **Values:** One of two strings: `EIGHT_ADJACENCY` or `FOUR_ADJENCENCY` 6 | 7 | *Default: FOUR_ADJACENCY* 8 | 9 | 10 | 11 | __AGENTS_MOVE_DISTANCE__ 12 | 13 | Sets the max distance in cells a robot can move in a turn. 14 | 15 | *Default: 4* 16 | 17 | 18 | 19 | __AGENTS_PER_PLAYER__ 20 | 21 | Sets the amount of agents each player controls. 22 | 23 | *Default: 5* 24 | 25 | 26 | 27 | __AGENT_INTERACT_RADIUS__ 28 | 29 | Sets how far in cells a robot can reach. 30 | 31 | *Default: 1* 32 | 33 | 34 | 35 | __AGENT_RESPAWN_TIME__ 36 | 37 | Sets amount of turns until a destroyed robot respawns. 38 | 39 | *Default: 999* 40 | 41 | 42 | 43 | __MAP_CLUSTER_SIZE__ 44 | 45 | Sets the size of the area around a gold vein in which to generate additional veins. 46 | 47 | *Default: 5* 48 | 49 | 50 | 51 | __MAP_GOLD_COEFF_X__ 52 | 53 | Probability modifier for the X coordinate of gold veins. 54 | 55 | 1 = normal probability. 56 | 57 | 0 = always maximum. 58 | 59 | *Default: 0.55* 60 | 61 | 62 | 63 | __MAP_HEIGHT__ 64 | 65 | Sets the height in cells of the map. 66 | 67 | *Default: 15* 68 | 69 | 70 | 71 | __MAP_WIDTH__ 72 | 73 | Sets the width in cells of the map. 74 | 75 | *Default: 30* 76 | 77 | 78 | 79 | __MAP_CLUSTER_DISTRIBUTION_MAX__ 80 | 81 | Maximum percentage of cells that should be the center of a cluster of veins. 82 | 83 | *Default: 0.064* 84 | 85 | 86 | 87 | __MAP_CLUSTER_DISTRIBUTION_MIN__ 88 | 89 | Minimum percentage of cells that should be the center of a cluster of veins. 90 | 91 | *Default: 0.032* 92 | 93 | 94 | 95 | __MAP_GOLD_IN_CELL_MAX__ 96 | 97 | Maximum amount of gold per vein. 98 | 99 | *Default: 3* 100 | 101 | 102 | 103 | __MAP_GOLD_IN_CELL_MIN__ 104 | 105 | Minimum amount of gold per vein. 106 | 107 | *Default: 1* 108 | 109 | 110 | 111 | __RADAR_COOLDOWN__ 112 | 113 | Amount of turns until a radar can be requested again. 114 | 115 | *Default: 5* 116 | 117 | 118 | 119 | __RADAR_RANGE__ 120 | 121 | The range in which gold is visible around a radar. 122 | 123 | *Default: 4* 124 | 125 | 126 | __EUCLIDEAN_RADAR__ 127 | 128 | Whether the radar should calculate range using the euclidean distance. 129 | 130 | *Default: false* 131 | 132 | __AGENTS_START_PACKED__ 133 | 134 | Whether pairs of agents should start on the same cell. Otherwise, they spawn next to one another. 135 | 136 | *Default: true* 137 | 138 | 139 | __ROBOTS_CAN_OCCUPY_SAME_CELL__ 140 | 141 | Sets whether robots may occupy the same cell after a `MOVE`. 142 | 143 | **Values:** `true` or `false` 144 | 145 | *Default: true* 146 | 147 | 148 | 149 | __TRAP_CHAIN_REACTION__ 150 | 151 | Sets whether traps trigger neighbouring traps. 152 | 153 | **Values:** `true` or `false` 154 | 155 | *Default: true* 156 | 157 | 158 | 159 | __TRAP_COOLDOWN__ 160 | 161 | Amount of turns until a trap can be requested again. 162 | 163 | *Default: 5* 164 | 165 | 166 | __TRAP_RANGE__ 167 | 168 | How far in cells a triggered trap reaches. 169 | 170 | *Default: 1* 171 | 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnleashTheGeek 2 | 3 | The source code used to configure and run the CodinGame contest Unleash The Geek https://www.codingame.com/contests/unleash-the-geek-amadeus 4 | -------------------------------------------------------------------------------- /config/Boss.py3: -------------------------------------------------------------------------------- 1 | import sys 2 | import math 3 | 4 | TYPE_NONE = -1 5 | TYPE_FRIEND = 0 6 | TYPE_FOE = 1 7 | TYPE_RADAR = 2 8 | TYPE_TRAP = 3 9 | TYPE_GOLD = 4 10 | 11 | class Agent: 12 | def __init__(self, x, y, item): 13 | self.x = x 14 | self.y = y 15 | self.action = 'WAIT' 16 | self.tx = -1 17 | self.ty = -1 18 | self.item = item 19 | self.gogo = False 20 | 21 | 22 | def thinkHard(self, r, cell, main): 23 | if self.item == TYPE_GOLD: 24 | self.tx = 0 25 | self.ty = self.y 26 | elif cell != None: 27 | self.tx = cell[0] 28 | self.ty = cell[1] 29 | elif self.item == TYPE_RADAR: 30 | self.tx = (self.y * 57649 * r + 5535853) % width 31 | self.ty = (self.y + r) % height 32 | elif main: 33 | self.gogo = True 34 | self.tx = 0 35 | self.ty = self.y 36 | else: 37 | self.tx = (self.y * 5649 * r + 5535853) % width 38 | self.ty = (self.x * 8485849 * r + 8466231) % height 39 | 40 | 41 | def think(self, r, cell = None, main = False): 42 | if self.tx != -1: 43 | if self.x == self.tx and self.y == self.ty: 44 | if self.gogo: 45 | self.action = 'REQUEST RADAR' 46 | else: 47 | self.action = f'DIG {self.tx} {self.ty}' 48 | self.tx = -1 49 | self.gogo = False 50 | else: 51 | self.action = f'MOVE {self.tx} {self.ty}' 52 | else: 53 | self.thinkHard(r, cell, main) 54 | self.action = f'MOVE {self.tx} {self.ty}' 55 | 56 | 57 | width, height = [int(i) for i in input().split()] 58 | 59 | my_gold = 0 60 | 61 | 62 | def findGoldCell(golds): 63 | for y in range(height): 64 | for x in range(width): 65 | if golds[y][x] > 0: 66 | return (x, y) 67 | 68 | 69 | agents = [ Agent(0, 0, 0) for i in range(5) ] 70 | r = 0 71 | 72 | while True: 73 | my_gold, enemy_gold = [int(i) for i in input().split()] 74 | 75 | golds = [ [ None ] * width for i in range(height) ] 76 | for i in range(height): 77 | inputs = input().split() 78 | for j in range(width): 79 | gold = inputs[2*j] 80 | hole = int(inputs[2*j+1]) 81 | 82 | golds[i][j] = int(gold) if gold != '?' else 0 83 | 84 | robot = None 85 | my_other_robots = [] 86 | 87 | k = 0 88 | 89 | entity_count, radar_cooldown, trap_cooldown = [int(i) for i in input().split()] 90 | for i in range(entity_count): 91 | id, type, x, y, item = [int(j) for j in input().split()] 92 | if type == TYPE_FRIEND: 93 | agents[k].x = x 94 | agents[k].y = y 95 | agents[k].item = item 96 | if my_gold == 0 and robot == None and x != -1: 97 | robot = agents[k] 98 | else: 99 | my_other_robots.append(agents[k]) 100 | k += 1 101 | 102 | if robot: 103 | robot.think(r, findGoldCell(golds), True) 104 | print(robot.action) 105 | 106 | r += 1 107 | 108 | for robot in my_other_robots: 109 | if my_gold == 0: 110 | robot.think(r) 111 | print(robot.action) 112 | else: 113 | print(f'MOVE {(robot.y * 15233 + 545647) % width} {(robot.x * 689241 - 75132) % height}') 114 | -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | min_players=2 2 | max_players=2 3 | type=multi -------------------------------------------------------------------------------- /config/starterAI/Starter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | //********************************* UTILS ********************************************** 9 | 10 | //----------------------------------Point---------------------------------------------------------- 11 | struct Point { 12 | int x{-1}, y{-1}; 13 | 14 | int distance(const Point& oth) { return abs(x - oth.x) + abs(y - oth.y); } 15 | ostream& dump(ostream& ioOut) const { 16 | ioOut << x << " " << y; 17 | return ioOut; 18 | } 19 | }; 20 | ostream& operator<<(ostream& ioOut, const Point& obj) { return obj.dump(ioOut); } 21 | 22 | //********************************* GAME STATE ********************************************** 23 | 24 | //----------------------------------Constants---------------------------------------------------------- 25 | enum class Type : int { NONE = 0, ROBOT, RADAR, TRAP, ORE, HOLE }; 26 | enum class ActionType : int { WAIT = 0, MOVE, DIG, REQUEST }; 27 | 28 | static constexpr int MAX_PLAYERS = 2; 29 | static constexpr int MAX_WIDTH = 30; 30 | static constexpr int MAX_HEIGHT = 15; 31 | static constexpr int MAX_ROBOTS = 5; 32 | 33 | //----------------------------------Cell---------------------------------------------------------- 34 | struct Cell : Point { 35 | bool hole{false}; 36 | bool oreVisible{false}; 37 | int ore{0}; 38 | 39 | void update(Point p, int _ore, bool _oreVisible, int _hole) { 40 | hole = _hole; 41 | ore = _ore; 42 | oreVisible = _oreVisible; 43 | x = p.x; 44 | y = p.y; 45 | } 46 | }; 47 | 48 | //----------------------------------Entity---------------------------------------------------------- 49 | struct Entity : Point { 50 | int id{0}; 51 | Type type{Type::NONE}; 52 | Type item{Type::NONE}; 53 | int owner{0}; 54 | 55 | Entity() = default; 56 | Entity(int _id, Type _type, Point p, Type _item, int _owner) : Point{p}, id{_id}, type{_type}, item{_item}, owner{_owner} {} 57 | void update(int _id, Type _type, Point p, Type _item, int _owner) { 58 | x = p.x; 59 | y = p.y; 60 | id = _id; 61 | type = _type; 62 | owner = _owner; 63 | item = _item; 64 | } 65 | }; 66 | 67 | //----------------------------------Robot---------------------------------------------------------- 68 | struct Robot : Entity { 69 | bool isDead() const { return x == -1 && y == -1; } 70 | }; 71 | 72 | //----------------------------------Player---------------------------------------------------------- 73 | struct Player { 74 | array robots; 75 | int ore{0}; 76 | int cooldownRadar{0}, cooldownTrap{0}; 77 | int owner{0}; 78 | 79 | void updateRobot(int id, Point p, Type item, int owner) { 80 | int idxOffset{0}; 81 | if (id >= MAX_ROBOTS) { idxOffset = MAX_ROBOTS; } 82 | robots.at(id - idxOffset).update(id, Type::ROBOT, p, item, owner); 83 | owner = owner; 84 | } 85 | void updateOre(int _owner, int _ore) { 86 | ore = _ore; 87 | owner = _owner; 88 | } 89 | void updateCooldown(int radar, int trap) { 90 | cooldownRadar = radar; 91 | cooldownTrap = trap; 92 | } 93 | }; 94 | 95 | //----------------------------------Game---------------------------------------------------------- 96 | struct Game { 97 | array, MAX_WIDTH> grid; 98 | array players; 99 | vector radars; 100 | vector traps; 101 | 102 | Game() { reset(); } 103 | Cell& get(int x, int y) { return grid.at(x).at(y); } 104 | Cell& get(Point p) { return grid.at(p.x).at(p.y); } 105 | void reset() { 106 | radars.reserve(20); 107 | radars.clear(); 108 | traps.reserve(30); 109 | traps.clear(); 110 | } 111 | void updateOre(int owner, int ore) { players.at(owner).updateOre(owner, ore); } 112 | void updateCooldown(int owner, int radar, int trap) { players.at(owner).updateCooldown(radar, trap); } 113 | void updateCell(int x, int y, const string& ore, int hole) { 114 | int oreAmount{0}; 115 | bool oreVisible{false}; 116 | Point p{x, y}; 117 | if (ore != "?") { 118 | oreAmount = stoi(ore); 119 | oreVisible = true; 120 | } 121 | get(p).update(p, oreAmount, oreVisible, hole); 122 | } 123 | void updateEntity(int id, int type, int x, int y, int _item) { 124 | // item 125 | Type item{Type::NONE}; 126 | switch (_item) { //-1 for NONE, 2 for RADAR, 3 for TRAP, 4 ORE 127 | case -1: item = Type::NONE; break; 128 | case 2: item = Type::RADAR; break; 129 | case 3: item = Type::TRAP; break; 130 | case 4: item = Type::ORE; break; 131 | default: assert(false); 132 | } 133 | Point p{x, y}; 134 | switch (type) { // 0 for your robot, 1 for other robot, 2 for radar, 3 for trap 135 | case 0: 136 | case 1: players.at(type).updateRobot(id, p, item, type); break; 137 | case 2: radars.emplace_back(id, Type::RADAR, p, item, 0); break; 138 | case 3: traps.emplace_back(id, Type::TRAP, p, item, 0); break; 139 | default: assert(false); 140 | } 141 | } 142 | }; 143 | 144 | //********************************* GAME SIMULATION ************************************************** 145 | 146 | //----------------------------------Action---------------------------------------------------------- 147 | struct Action { 148 | static const array LABELS_ACTIONS; 149 | 150 | Point dest; 151 | ActionType type{ActionType::WAIT}; 152 | Type item{Type::NONE}; 153 | string message; 154 | 155 | void wait(string _message = "") { 156 | dest = Point{0, 0}; 157 | type = ActionType::WAIT; 158 | item = Type::NONE; 159 | message = _message; 160 | } 161 | void move(Point _dest, string _message = "") { 162 | dest = _dest; 163 | type = ActionType::MOVE; 164 | item = Type::NONE; 165 | message = _message; 166 | } 167 | void dig(Point _dest, string _message = "") { 168 | dest = _dest; 169 | type = ActionType::DIG; 170 | item = Type::NONE; 171 | message = _message; 172 | } 173 | void request(Type _item, string _message = "") { 174 | dest = Point{0, 0}; 175 | type = ActionType::REQUEST; 176 | item = _item; 177 | message = _message; 178 | } 179 | ostream& dump(ostream& ioOut) const { 180 | ioOut << LABELS_ACTIONS.at((int)(type)); 181 | if (type == ActionType::MOVE || type == ActionType::DIG) { ioOut << " " << dest; } 182 | if (type == ActionType::REQUEST && item == Type::RADAR) { ioOut << " RADAR"; } 183 | if (type == ActionType::REQUEST && item == Type::TRAP) { ioOut << " TRAP"; } 184 | if (message != "") { ioOut << " " << message; } 185 | return ioOut; 186 | } 187 | }; 188 | const array Action::LABELS_ACTIONS{"WAIT", "MOVE", "DIG", "REQUEST"}; 189 | ostream& operator<<(ostream& ioOut, const Action& obj) { return obj.dump(ioOut); } 190 | 191 | //********************************* AI ***************************************************************** 192 | 193 | array getActions(Game& game) { 194 | array actions; 195 | 196 | // smart code here 197 | if (game.players.at(0).ore <= 0) { 198 | cerr << "time to collect stuf!\n"; 199 | } 200 | actions.at(0).wait(); 201 | actions.at(1).wait("I'm Robot1"); 202 | Player& me{game.players.at(0)}; 203 | Robot& robot1{me.robots.at(1)}; 204 | Robot& robot2{me.robots.at(2)}; 205 | if (robot2.distance(robot1) > 3) { actions.at(2).wait("Where is Robot1?"); } 206 | else { actions.at(2).wait("I'm robot2"); } 207 | actions.at(3).wait("No beer here!"); 208 | actions.at(4).wait(); 209 | // end smart code 210 | 211 | return actions; 212 | } 213 | 214 | //********************************* MAIN ***************************************************************** 215 | 216 | int main() { 217 | Game game; 218 | 219 | // global inputs 220 | int width; 221 | int height; // size of the map 222 | cin >> width >> height; 223 | cin.ignore(); 224 | 225 | // game loop 226 | while (1) { 227 | game.reset(); 228 | // first loop local inputs 229 | int myOre; 230 | int enemyOre; 231 | cin >> myOre >> enemyOre; 232 | cin.ignore(); 233 | game.updateOre(0, myOre); 234 | game.updateOre(1, enemyOre); 235 | 236 | // other loop local inputs 237 | for (int i = 0; i < height; i++) { 238 | for (int j = 0; j < width; j++) { 239 | string ore; // amount of ore or "?" if unknown 240 | int hole; // 1 if cell has a hole 241 | cin >> ore >> hole; 242 | cin.ignore(); 243 | game.updateCell(j, i, ore, hole); 244 | } 245 | } 246 | int entityCount; // number of visible entities 247 | int radarCooldown; // turns left until a new radar can be requested 248 | int trapCooldown; // turns left until a new trap can be requested 249 | cin >> entityCount >> radarCooldown >> trapCooldown; 250 | cin.ignore(); 251 | game.updateCooldown(0, radarCooldown, trapCooldown); 252 | for (int i = 0; i < entityCount; i++) { 253 | int id; // unique id of the entity 254 | int type; // 0 for your robot, 1 for other robot, 2 for radar, 3 for trap 255 | int x; 256 | int y; // position of the entity 257 | int item; // if this entity is a robot, the item it is carrying (-1 for NONE, 2 for RADAR, 3 for TRAP, 4 for ORE) 258 | cin >> id >> type >> x >> y >> item; 259 | cin.ignore(); 260 | game.updateEntity(id, type, x, y, item); 261 | } 262 | 263 | // AI ------------------------------------------------------------------ 264 | auto actions{getActions(game)}; 265 | // AI ------------------------------------------------------------------ 266 | 267 | for (const Action& action : actions) { 268 | // Write an action using cout. DON'T FORGET THE "<< endl" 269 | // To debug: cerr << "Debug messages..." << endl; 270 | 271 | cout << action << "\n"; // WAIT|MOVE x y|REQUEST item 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /config/starterAI/Starter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.IO; 4 | using System.Text; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | 9 | enum EntityType 10 | { 11 | NONE = -1, MY_ROBOT = 0, OPPONENT_ROBOT = 1, RADAR = 2, TRAP = 3, ORE = 4 12 | } 13 | 14 | class Cell 15 | { 16 | public int Ore { get; set; } 17 | public bool Hole { get; set; } 18 | public bool Known { get; set; } 19 | 20 | public void Update(string ore, int hole) 21 | { 22 | Hole = hole == 1; 23 | Known = !"?".Equals(ore); 24 | if (Known) 25 | { 26 | Ore = int.Parse(ore); 27 | } 28 | } 29 | } 30 | 31 | class Game 32 | { 33 | // Given at startup 34 | public readonly int Width; 35 | public readonly int Height; 36 | 37 | // Updated each turn 38 | public List MyRobots { get; set; } 39 | public List OpponentRobots { get; set; } 40 | public Cell[,] Cells { get; set; } 41 | public int RadarCooldown { get; set; } 42 | public int TrapCooldown { get; set; } 43 | public int MyScore { get; set; } 44 | public int OpponentScore { get; set; } 45 | public List Radars { get; set; } 46 | public List Traps { get; set; } 47 | 48 | public Game(int width, int height) 49 | { 50 | Width = width; 51 | Height = height; 52 | MyRobots = new List(); 53 | OpponentRobots = new List(); 54 | Cells = new Cell[width, height]; 55 | Radars = new List(); 56 | Traps = new List(); 57 | 58 | for (int x = 0; x < width; ++x) 59 | { 60 | for (int y = 0; y < height; ++y) 61 | { 62 | Cells[x, y] = new Cell(); 63 | } 64 | } 65 | } 66 | } 67 | 68 | class Coord 69 | { 70 | public static readonly Coord NONE = new Coord(-1, -1); 71 | 72 | public int X { get; } 73 | public int Y { get; } 74 | 75 | public Coord(int x, int y) 76 | { 77 | X = x; 78 | Y = y; 79 | } 80 | 81 | // Manhattan distance (for 4 directions maps) 82 | // see: https://en.wikipedia.org/wiki/Taxicab_geometry 83 | public int Distance(Coord other) 84 | { 85 | return Math.Abs(X - other.X) + Math.Abs(Y - other.Y); 86 | } 87 | 88 | public override bool Equals(object obj) 89 | { 90 | if (obj == null) return false; 91 | if (this.GetType() != obj.GetType()) return false; 92 | Coord other = (Coord)obj; 93 | return X == other.X && Y == other.Y; 94 | } 95 | 96 | public override int GetHashCode() 97 | { 98 | return 31 * (31 + X) + Y; 99 | } 100 | } 101 | 102 | class Entity 103 | { 104 | public int Id { get; set; } 105 | public Coord Pos { get; set; } 106 | public EntityType Item { get; set; } 107 | 108 | public Entity(int id, Coord pos, EntityType item) 109 | { 110 | Id = id; 111 | Pos = pos; 112 | Item = item; 113 | } 114 | } 115 | 116 | class Robot : Entity 117 | { 118 | public Robot(int id, Coord pos, EntityType item) : base(id, pos, item) 119 | { } 120 | 121 | bool IsDead() 122 | { 123 | return Pos.Equals(Coord.NONE); 124 | } 125 | 126 | public static string Wait(string message = "") 127 | { 128 | return $"WAIT {message}"; 129 | } 130 | 131 | public static string Move(Coord pos, string message = "") 132 | { 133 | return $"MOVE {pos.X} {pos.Y} {message}"; 134 | } 135 | 136 | public static string Dig(Coord pos, string message = "") 137 | { 138 | return $"DIG {pos.X} {pos.Y} {message}"; 139 | } 140 | 141 | public static string Request(EntityType item, string message = "") 142 | { 143 | return $"REQUEST {(int)item} {message}"; 144 | } 145 | } 146 | 147 | /** 148 | * Deliver more ore to hq (left side of the map) than your opponent. Use radars to find ore but beware of traps! 149 | **/ 150 | class Player 151 | { 152 | static void Main(string[] args) 153 | { 154 | new Player(); 155 | } 156 | 157 | Game game; 158 | 159 | public Player() 160 | { 161 | string[] inputs; 162 | inputs = Console.ReadLine().Split(' '); 163 | int width = int.Parse(inputs[0]); 164 | int height = int.Parse(inputs[1]); // size of the map 165 | 166 | game = new Game(width, height); 167 | 168 | // game loop 169 | while (true) 170 | { 171 | inputs = Console.ReadLine().Split(' '); 172 | game.MyScore = int.Parse(inputs[0]); // Amount of ore delivered 173 | game.OpponentScore = int.Parse(inputs[1]); 174 | for (int i = 0; i < height; i++) 175 | { 176 | inputs = Console.ReadLine().Split(' '); 177 | for (int j = 0; j < width; j++) 178 | { 179 | string ore = inputs[2 * j];// amount of ore or "?" if unknown 180 | int hole = int.Parse(inputs[2 * j + 1]);// 1 if cell has a hole 181 | game.Cells[j, i].Update(ore, hole); 182 | } 183 | } 184 | inputs = Console.ReadLine().Split(' '); 185 | int entityCount = int.Parse(inputs[0]); // number of entities visible to you 186 | int radarCooldown = int.Parse(inputs[1]); // turns left until a new radar can be requested 187 | int trapCooldown = int.Parse(inputs[2]); // turns left until a new trap can be requested 188 | game.Radars.Clear(); 189 | game.Traps.Clear(); 190 | for (int i = 0; i < entityCount; i++) 191 | { 192 | inputs = Console.ReadLine().Split(' '); 193 | int id = int.Parse(inputs[0]); // unique id of the entity 194 | EntityType type = (EntityType)int.Parse(inputs[1]); // 0 for your robot, 1 for other robot, 2 for radar, 3 for trap 195 | int x = int.Parse(inputs[2]); 196 | int y = int.Parse(inputs[3]); // position of the entity 197 | EntityType item = (EntityType)int.Parse(inputs[4]); // if this entity is a robot, the item it is carrying (-1 for NONE, 2 for RADAR, 3 for TRAP, 4 for ORE) 198 | Coord coord = new Coord(x, y); 199 | switch (type) 200 | { 201 | case EntityType.MY_ROBOT: 202 | game.MyRobots.Add(new Robot(id, coord, item)); 203 | break; 204 | case EntityType.OPPONENT_ROBOT: 205 | game.OpponentRobots.Add(new Robot(id, coord, item)); 206 | break; 207 | case EntityType.RADAR: 208 | game.Radars.Add(new Entity(id, coord, item)); 209 | break; 210 | case EntityType.TRAP: 211 | game.Traps.Add(new Entity(id, coord, item)); 212 | break; 213 | } 214 | } 215 | for (int i = 0; i < 5; i++) 216 | { 217 | // To debug: Console.Error.WriteLine("Debug messages..."); 218 | 219 | Robot robot = game.MyRobots[i]; 220 | string action = Robot.Wait("C# Starter"); 221 | 222 | // Implement action selection logic here. 223 | 224 | // WAIT|MOVE x y|REQUEST item|DIG x y 225 | Console.WriteLine(action); 226 | 227 | } 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /config/starterAI/Starter.java: -------------------------------------------------------------------------------- 1 | import static java.lang.Math.*; 2 | 3 | import java.io.*; 4 | import java.nio.*; 5 | import java.util.*; 6 | 7 | 8 | class Coord { 9 | final int x; 10 | final int y; 11 | 12 | Coord(int x, int y) { 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | Coord(Scanner in) { 18 | this(in.nextInt(), in.nextInt()); 19 | } 20 | 21 | Coord add(Coord other) { 22 | return new Coord(x + other.x, y + other.y); 23 | } 24 | 25 | // Manhattan distance (for 4 directions maps) 26 | // see: https://en.wikipedia.org/wiki/Taxicab_geometry 27 | int distance(Coord other) { 28 | return abs(x - other.x) + abs(y - other.y); 29 | } 30 | 31 | public int hashCode() { 32 | final int PRIME = 31; 33 | int result = 1; 34 | result = PRIME * result + x; 35 | result = PRIME * result + y; 36 | return result; 37 | } 38 | 39 | public boolean equals(Object obj) { 40 | if (this == obj) 41 | return true; 42 | if (obj == null) 43 | return false; 44 | if (getClass() != obj.getClass()) 45 | return false; 46 | Coord other = (Coord) obj; 47 | return (x == other.x) && (y == other.y); 48 | } 49 | 50 | public String toString() { 51 | return x + " " + y; 52 | } 53 | } 54 | 55 | 56 | class Cell { 57 | boolean known; 58 | int ore; 59 | boolean hole; 60 | 61 | Cell(boolean known, int ore, boolean hole) { 62 | this.known = known; 63 | this.ore = ore; 64 | this.hole = hole; 65 | } 66 | 67 | Cell(Scanner in) { 68 | String oreStr = in.next(); 69 | if (oreStr.charAt(0) == '?') { 70 | known = false; 71 | ore = 0; 72 | } else { 73 | known = true; 74 | ore = Integer.parseInt(oreStr); 75 | } 76 | String holeStr = in.next(); 77 | hole = (holeStr.charAt(0) != '0'); 78 | } 79 | } 80 | 81 | 82 | class Action { 83 | final String command; 84 | final Coord pos; 85 | final EntityType item; 86 | String message; 87 | 88 | private Action(String command, Coord pos, EntityType item) { 89 | this.command = command; 90 | this.pos = pos; 91 | this.item = item; 92 | } 93 | 94 | static Action none() { 95 | return new Action("WAIT", null, null); 96 | } 97 | 98 | static Action move(Coord pos) { 99 | return new Action("MOVE", pos, null); 100 | } 101 | 102 | static Action dig(Coord pos) { 103 | return new Action("DIG", pos, null); 104 | } 105 | 106 | static Action request(EntityType item) { 107 | return new Action("REQUEST", null, item); 108 | } 109 | 110 | public String toString() { 111 | StringBuilder builder = new StringBuilder(command); 112 | if (pos != null) { 113 | builder.append(' ').append(pos); 114 | } 115 | if (item != null) { 116 | builder.append(' ').append(item); 117 | } 118 | if (message != null) { 119 | builder.append(' ').append(message); 120 | } 121 | return builder.toString(); 122 | } 123 | } 124 | 125 | 126 | enum EntityType { 127 | NOTHING, ALLY_ROBOT, ENEMY_ROBOT, RADAR, TRAP, AMADEUSIUM; 128 | 129 | static EntityType valueOf(int id) { 130 | return values()[id + 1]; 131 | } 132 | } 133 | 134 | 135 | class Entity { 136 | private static final Coord DEAD_POS = new Coord(-1, -1); 137 | 138 | // Updated every turn 139 | final int id; 140 | final EntityType type; 141 | final Coord pos; 142 | final EntityType item; 143 | 144 | // Computed for my robots 145 | Action action; 146 | 147 | Entity(Scanner in) { 148 | id = in.nextInt(); 149 | type = EntityType.valueOf(in.nextInt()); 150 | pos = new Coord(in); 151 | item = EntityType.valueOf(in.nextInt()); 152 | } 153 | 154 | boolean isAlive() { 155 | return !DEAD_POS.equals(pos); 156 | } 157 | } 158 | 159 | 160 | class Team { 161 | int score; 162 | Collection robots; 163 | 164 | void readScore(Scanner in) { 165 | score = in.nextInt(); 166 | robots = new ArrayList<>(); 167 | } 168 | } 169 | 170 | 171 | class Board { 172 | // Given at startup 173 | final int width; 174 | final int height; 175 | 176 | // Updated each turn 177 | final Team myTeam = new Team(); 178 | final Team opponentTeam = new Team(); 179 | private Cell[][] cells; 180 | int myRadarCooldown; 181 | int myTrapCooldown; 182 | Map entitiesById; 183 | Collection myRadarPos; 184 | Collection myTrapPos; 185 | 186 | Board(Scanner in) { 187 | width = in.nextInt(); 188 | height = in.nextInt(); 189 | } 190 | 191 | void update(Scanner in) { 192 | // Read new data 193 | myTeam.readScore(in); 194 | opponentTeam.readScore(in); 195 | cells = new Cell[height][width]; 196 | for (int y = 0; y < height; y++) { 197 | for (int x = 0; x < width; x++) { 198 | cells[y][x] = new Cell(in); 199 | } 200 | } 201 | int entityCount = in.nextInt(); 202 | myRadarCooldown = in.nextInt(); 203 | myTrapCooldown = in.nextInt(); 204 | entitiesById = new HashMap<>(); 205 | myRadarPos = new ArrayList<>(); 206 | myTrapPos = new ArrayList<>(); 207 | for (int i = 0; i < entityCount; i++) { 208 | Entity entity = new Entity(in); 209 | entitiesById.put(entity.id, entity); 210 | if (entity.type == EntityType.ALLY_ROBOT) { 211 | myTeam.robots.add(entity); 212 | } else if (entity.type == EntityType.ENEMY_ROBOT) { 213 | opponentTeam.robots.add(entity); 214 | } else if (entity.type == EntityType.RADAR) { 215 | myRadarPos.add(entity.pos); 216 | } else if (entity.type == EntityType.TRAP) { 217 | myTrapPos.add(entity.pos); 218 | } 219 | } 220 | } 221 | 222 | boolean cellExist(Coord pos) { 223 | return (pos.x >= 0) && (pos.y >= 0) && (pos.x < width) && (pos.y < height); 224 | } 225 | 226 | Cell getCell(Coord pos) { 227 | return cells[pos.y][pos.x]; 228 | } 229 | } 230 | 231 | 232 | class Player { 233 | 234 | public static void main(String args[]) { 235 | new Player().run(); 236 | } 237 | 238 | final Scanner in = new Scanner(System.in); 239 | 240 | void run() { 241 | // Parse initial conditions 242 | Board board = new Board(in); 243 | 244 | while (true) { 245 | // Parse current state of the game 246 | board.update(in); 247 | 248 | // Insert your strategy here 249 | for (Entity robot : board.myTeam.robots) { 250 | robot.action = Action.none(); 251 | robot.action.message = "Java Starter"; 252 | } 253 | 254 | // Send your actions for this turn 255 | for (Entity robot : board.myTeam.robots) { 256 | System.out.println(robot.action); 257 | } 258 | } 259 | } 260 | } -------------------------------------------------------------------------------- /config/starterAI/Starter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Deliver more ore to hq (left side of the map) than your opponent. Use radars to find ore but beware of traps! 3 | **/ 4 | let inputs = readline().split(' '); 5 | const MAP_WIDTH = parseInt(inputs[0]); 6 | const MAP_HEIGHT = parseInt(inputs[1]); // size of the map 7 | 8 | const NONE = -1; 9 | const ROBOT_ALLY = 0; 10 | const ROBOT_ENEMY = 1; 11 | const HOLE = 1; 12 | const RADAR = 2; 13 | const TRAP = 3; 14 | const ORE = 4; 15 | 16 | class Pos { 17 | constructor(x, y) { 18 | this.x = x; 19 | this.y = y; 20 | } 21 | 22 | distance(pos) { 23 | return Math.abs(this.x - pos.x) + Math.abs(this.y - pos.y); 24 | } 25 | 26 | } 27 | 28 | class Entity extends Pos { 29 | constructor(x, y, type, id) { 30 | super(x, y); 31 | this.id = id; 32 | this.type = type; 33 | } 34 | } 35 | 36 | class Robot extends Entity { 37 | constructor(x, y, type, id, item) { 38 | super(x, y, type, id); 39 | this.item = item; 40 | } 41 | 42 | isDead() { 43 | return this.x === -1 && this.y === -1; 44 | } 45 | 46 | move(x, y, message = "") { 47 | console.log(`MOVE ${x} ${y} ${message}`); 48 | } 49 | 50 | wait(message = "") { 51 | console.log(`WAIT ${message}`); 52 | } 53 | 54 | dig(x, y, message = "") { 55 | console.log(`DIG ${x} ${y} ${message}`); 56 | } 57 | 58 | request(item, message = "") { 59 | if(item === RADAR){ 60 | console.log(`REQUEST RADAR ${message}`); 61 | } 62 | else if(item === TRAP){ 63 | console.log(`REQUEST TRAP ${message}`); 64 | } 65 | else{ 66 | throw Error(`unrecognized item: ${item}`); 67 | } 68 | 69 | } 70 | 71 | } 72 | 73 | class Cell extends Pos { 74 | constructor(ore, hole, x, y) { 75 | super(x, y); 76 | this.update(ore, hole); 77 | } 78 | 79 | hasHole() { 80 | return this.hole === HOLE; 81 | } 82 | 83 | update(ore, hole) { 84 | this.ore = ore; 85 | this.hole = hole; 86 | } 87 | } 88 | 89 | class Grid { 90 | constructor() { 91 | this.cells = []; 92 | } 93 | 94 | init() { 95 | for (let y = 0; y < MAP_HEIGHT; y++) { 96 | for (let x = 0; x < MAP_WIDTH; x++) { 97 | let index = x + MAP_WIDTH * y; 98 | this.cells[index] = new Cell(0, 0, x, y); 99 | } 100 | } 101 | } 102 | 103 | getCell(x, y) { 104 | if (x < MAP_WIDTH && y < MAP_HEIGHT && x >= 0 && y >= 0) { 105 | return this.cells[x + MAP_WIDTH * y]; 106 | } 107 | return null; 108 | } 109 | 110 | } 111 | 112 | class Game { 113 | constructor() { 114 | this.grid = new Grid(); 115 | this.grid.init(); 116 | this.myScore = 0; 117 | this.enemyScore = 0; 118 | this.radarCooldown = 0; 119 | this.trapCooldown = 0; 120 | 121 | this.reset(); 122 | } 123 | 124 | reset() { 125 | this.radars = []; 126 | this.traps = []; 127 | this.myRobots = []; 128 | this.enemyRobots = []; 129 | } 130 | 131 | } 132 | 133 | let game = new Game(); 134 | 135 | // game loop 136 | while (true) { 137 | let inputsScore = readline().split(' '); 138 | game.myScore = parseInt(inputsScore[0]); // Players score 139 | game.enemyScore = parseInt(inputsScore[1]); 140 | for (let i = 0; i < MAP_HEIGHT; i++) { 141 | let inputs = readline().split(' '); 142 | for (let j = 0; j < MAP_WIDTH; j++) { 143 | const ore = inputs[2 * j];// amount of ore or "?" if unknown 144 | const hole = parseInt(inputs[2 * j + 1]);// 1 if cell has a hole 145 | game.grid.getCell(j, i).update(ore, hole); 146 | } 147 | } 148 | 149 | let inputsStatus = readline().split(' '); 150 | const entityCount = parseInt(inputsStatus[0]); // number of visible entities 151 | game.radarCooldown = parseInt(inputsStatus[1]); // turns left until a new radar can be requested 152 | game.trapCooldown = parseInt(inputsStatus[2]); // turns left until a new trap can be requested 153 | 154 | game.reset(); 155 | 156 | for (let i = 0; i < entityCount; i++) { 157 | let inputsEntities = readline().split(' '); 158 | const id = parseInt(inputsEntities[0]); // unique id of the entity 159 | const type = parseInt(inputsEntities[1]); // 0 for your robot, 1 for other robot, 2 for radar, 3 for trap 160 | const x = parseInt(inputsEntities[2]); 161 | const y = parseInt(inputsEntities[3]); // position of the entity 162 | const item = parseInt(inputsEntities[4]); // if this entity is a robot, the item it is carrying (-1 for NONE, 2 for RADAR, 3 for TRAP, 4 for ORE) 163 | if (type === ROBOT_ALLY) { 164 | game.myRobots.push(new Robot(x, y, type, id, item)); 165 | } else if (type === ROBOT_ENEMY) { 166 | game.enemyRobots.push(new Robot(x, y, type, id, item)); 167 | } else if (type === RADAR) { 168 | game.radars.push(new Entity(x, y, type, id)); 169 | } else if (type === TRAP) { 170 | game.traps.push(new Entity(x, y, type, id)); 171 | } 172 | } 173 | 174 | for (let i = 0; i < game.myRobots.length; i++) { 175 | game.myRobots[i].wait(`Starter AI ${i}`); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /config/starterAI/Starter.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import math 3 | 4 | # Deliver more amadeusium to hq (left side of the map) than your opponent. Use radars to find amadeusium but beware of traps! 5 | 6 | # height: size of the map 7 | width, height = [int(i) for i in input().split()] 8 | 9 | NONE = -1 10 | ROBOT_ALLY = 0 11 | ROBOT_ENEMY = 1 12 | HOLE = 1 13 | RADAR = 2 14 | TRAP = 3 15 | AMADEUSIUM = 4 16 | 17 | 18 | class Pos: 19 | def __init__(self, x, y): 20 | self.x = x 21 | self.y = y 22 | 23 | def distance(self, pos): 24 | return abs(self.x - pos.x) + abs(self.y - pos.y) 25 | 26 | 27 | class Entity(Pos): 28 | def __init__(self, x, y, type, id): 29 | super().__init__(x, y) 30 | self.type = type 31 | self.id = id 32 | 33 | 34 | class Robot(Entity): 35 | def __init__(self, x, y, type, id, item): 36 | super().__init__(x, y, type, id) 37 | self.item = item 38 | 39 | def is_dead(self): 40 | return self.x == -1 and self.y == -1 41 | 42 | @staticmethod 43 | def move(x, y, message=""): 44 | print(f"MOVE {x} {y} {message}") 45 | 46 | @staticmethod 47 | def wait(message=""): 48 | print(f"WAIT {message}") 49 | 50 | @staticmethod 51 | def dig(x, y, message=""): 52 | print(f"DIG {x} {y} {message}") 53 | 54 | @staticmethod 55 | def request(requested_item, message=""): 56 | if requested_item == RADAR: 57 | print(f"REQUEST RADAR {message}") 58 | elif requested_item == TRAP: 59 | print(f"REQUEST TRAP {message}") 60 | else: 61 | raise Exception(f"Unknown item {requested_item}") 62 | 63 | 64 | class Cell(Pos): 65 | def __init__(self, x, y, amadeusium, hole): 66 | super().__init__(x, y) 67 | self.amadeusium = amadeusium 68 | self.hole = hole 69 | 70 | def has_hole(self): 71 | return self.hole == HOLE 72 | 73 | def update(self, amadeusium, hole): 74 | self.amadeusium = amadeusium 75 | self.hole = hole 76 | 77 | 78 | class Grid: 79 | def __init__(self): 80 | self.cells = [] 81 | for y in range(height): 82 | for x in range(width): 83 | self.cells.append(Cell(x, y, 0, 0)) 84 | 85 | def get_cell(self, x, y): 86 | if width > x >= 0 and height > y >= 0: 87 | return self.cells[x + width * y] 88 | return None 89 | 90 | 91 | class Game: 92 | def __init__(self): 93 | self.grid = Grid() 94 | self.my_score = 0 95 | self.enemy_score = 0 96 | self.radar_cooldown = 0 97 | self.trap_cooldown = 0 98 | self.radars = [] 99 | self.traps = [] 100 | self.my_robots = [] 101 | self.enemy_robots = [] 102 | 103 | def reset(self): 104 | self.radars = [] 105 | self.traps = [] 106 | self.my_robots = [] 107 | self.enemy_robots = [] 108 | 109 | 110 | game = Game() 111 | 112 | # game loop 113 | while True: 114 | # my_score: Players score 115 | game.my_score, game.enemy_score = [int(i) for i in input().split()] 116 | for i in range(height): 117 | inputs = input().split() 118 | for j in range(width): 119 | # amadeusium: amount of amadeusium or "?" if unknown 120 | # hole: 1 if cell has a hole 121 | amadeusium = inputs[2 * j] 122 | hole = int(inputs[2 * j + 1]) 123 | game.grid.get_cell(j, i).update(amadeusium, hole) 124 | # entity_count: number of entities visible to you 125 | # radar_cooldown: turns left until a new radar can be requested 126 | # trap_cooldown: turns left until a new trap can be requested 127 | entity_count, game.radar_cooldown, game.trap_cooldown = [int(i) for i in input().split()] 128 | 129 | game.reset() 130 | 131 | for i in range(entity_count): 132 | # id: unique id of the entity 133 | # type: 0 for your robot, 1 for other robot, 2 for radar, 3 for trap 134 | # y: position of the entity 135 | # item: if this entity is a robot, the item it is carrying (-1 for NONE, 2 for RADAR, 3 for TRAP, 4 for AMADEUSIUM) 136 | id, type, x, y, item = [int(j) for j in input().split()] 137 | 138 | if type == ROBOT_ALLY: 139 | game.my_robots.append(Robot(x, y, type, id, item)) 140 | elif type == ROBOT_ENEMY: 141 | game.enemy_robots.append(Robot(x, y, type, id, item)) 142 | elif type == TRAP: 143 | game.traps.append(Entity(x, y, type, id)) 144 | elif type == RADAR: 145 | game.radars.append(Entity(x, y, type, id)) 146 | 147 | for i in range(len(game.my_robots)): 148 | # Write an action using print 149 | # To debug: print("Debug messages...", file=sys.stderr) 150 | 151 | # WAIT| 152 | # MOVE x y|REQUEST item 153 | game.my_robots[i].wait(f"Starter AI {i}") 154 | -------------------------------------------------------------------------------- /config/statement_en.html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
12 |
13 | 14 |
15 |

16 | This is a league based challenge. 17 |

18 | 19 | For this challenge, multiple leagues for the same game are available. Once you have proven your skills against the 20 | first Boss, you will access a higher league and extra opponents will be available. 21 | 22 |
23 | 24 |
25 |

26 |   27 | Goal 28 |

29 |
30 |

Amadeusium is a rare and valuable crystal ore which is only found on inhospitable planets. 31 | As one of two competing mining companies, you must control the robots on-site to unearth as much ore as you can. 32 |

33 | Deliver more Amadeusium than your opponent! 34 |
35 |
36 | 37 |
38 |

39 |   40 | Rules 41 |

42 | 43 |
44 |

Both players control a team of several robots. The teams start out at the same points on the 45 | map, at the headquarters. The robots can use radars from the headquarters to 46 | detect and mine Amadeusium veins. They may also trap certain areas of the map with EMP traps. 47 | These can be triggered by robots which are then rendered inoperable. 48 |

49 | 50 |
51 |

The map

52 |
53 | 54 |

The game is played on a grid 30 cells wide by 15 cells high. The coordinates 55 | x=0, y=0 56 | corresponds to the top left cell.

57 | 58 |

The first column of cells is considered to be part of the headquarters. This is where 59 | Amadeusium ore must be returned to once mined and where objects are requested.

60 |

The cells that contain Amadeusium ore are called vein cells. Veins are not 61 | visible to the players unless they are within the range of the player's radar. There are no vein cells in the 62 | headquarters.

63 | 64 |

Robots can drill a hole on any cell (except the headquarters'). Holes are visible to both 65 | players and do not impede movement.

66 | 67 | 68 | 69 |
70 |

Robots

71 |
72 | 73 |

Each robot can hold 1 item in its inventory.

74 | 75 |

A robot may:

76 | 77 |
    78 |
  • 79 | REQUEST an item from the headquarters. 80 |
  • 81 | 82 |
  • 83 | MOVE towards a given cell. 84 |
  • 85 |
  • 86 | DIG on a cell. This will, in order: 87 | 88 |
      89 |
    1. Create a hole on this cell if there isn't one already.
    2. 90 | 91 |
    3. Bury any item the robot is holding into the hole.
    4. 92 | 93 |
    5. If digging on a vein cell and ore was not buried on step 2, place one unit of ore into 94 | the robot's inventory.
    6. 95 |
    96 | 97 |
  • 98 |
  • 99 | WAIT to do nothing. 100 |
  • 101 |
102 | 103 |

Details:

104 |
    105 |
  • Robots may only dig on the cell they occupy or neighbouring cells. Cells have 4 neighbours: 106 | up, 107 | left, right, and down.
  • 108 | 109 |
  • Robots on any cell part of the headquarters will automatically deliver any ore it is 110 | holding.
  • 111 | 112 |
  • Robots can occupy the same cell.
  • 113 | 114 |
  • Robots cannot leave the grid.
  • 115 | 116 |
  • Robots' inventories are not visible to the opponent.
  • 117 | 118 |
119 | 120 |

Items

121 |
122 | 123 |

Amadeusium Ore is considered an item and should be delivered to the headquarters to 124 | score 1 point.

125 | 126 |

At the headquarters, robots may request one of two possible items: a 127 | RADAR or a 128 | TRAP.

129 | 130 |

If an item is taken from the headquarters, that item will no longer be 131 | available for the robots of the same team for 5 turns.

132 | 133 |

A trap buried inside a hole will go off if any robot uses the DIG 134 | command on 135 | the cell it is buried in. The EMP pulse destroys any robots on the cell or on the 4 neighbouring 136 | cells. Any other 137 | trap caught in the pulse will also go off, causing a chain reaction.

138 | 139 | 140 |

A radar buried inside a hole will grant the ability to see the amount of 141 | buried ore 142 | in veins within a range of 4 cells, for the team which buried it. 143 | 144 | If an opponent robot uses the DIG on the cell the radar is buried in, the radar is destroyed. 145 |

146 | 147 |
148 |

Action order for one turn

149 | 150 |
    151 |
  1. If DIG commands would trigger Traps, they go off.
  2. 152 | 153 |
  3. 154 | The other DIG commands are resolved. 155 |
  4. 156 | 157 |
  5. 158 | REQUEST commands are resolved. 159 |
  6. 160 | 161 |
  7. Request timers are decremented.
  8. 162 | 163 |
  9. 164 | MOVE and WAIT commands are resolved. 165 |
  10. 166 | 167 |
  11. Ore is delivered to the headquarters.
  12. 168 | 169 |
170 |
171 | 172 |
173 |
174 |
175 |
Victory Conditions
176 |
177 |
    178 |
  • After 200 rounds, your team has delivered the most Amadeusium ore.
  • 179 |
  • You have delivered more ore than your opponent and they have no more active robots.
  • 180 |
181 |
182 |
183 |
184 | 185 |
186 |
187 |
188 |
Defeat Conditions
189 |
190 |
    191 |
  • Your program does not provide one valid command per robot in time, including destroyed robots.
  • 192 |
193 |
194 |
195 |
196 |
197 | 198 | 199 | 200 |
201 |

202 |   203 | Technical Details 204 |

205 |
206 |
    207 |
  • Robots can insert ore into a cell, the cell becomes a vein.
  • 208 | 209 |
  • Each robot, radar and trap has a unique id.
  • 210 | 211 |
  • Receiving an item from the headquarters will destroy any item a robot may already be 212 | holding.
  • 213 | 214 |
  • When several robots of the same team request an item, robots with no item will be given priority for the 215 | request.
  • 216 | 217 |
  • Traps have no effect on buried radars and ore.
  • 218 | 219 |
  • If a robot holding an item is destroyed, the item is lost.
  • 220 |
221 |

You can check out the source code of this game on this GitHub repo.

223 |
224 |
225 | 226 | 227 |
228 |

229 |   230 | Game Input 231 |

232 | 233 |
234 |
Initialization Input
235 |
236 | Line 1: two integers 237 | width and 238 | height for the size of the map. 239 | The leftmost row are cells with access to the headquarters. 240 |
241 |
242 |
243 | 244 |
245 |
Input for One Game Turn
246 |
247 | First line: Two integers:
248 |
    249 |
  • 250 | myScore for the amount of ore you delivered to the elevator. 251 |
  • 252 |
  • 253 | enemyScore for the amount ore your opponent delivered to the elevator. 254 |
  • 255 |
256 | Next height lines: each line has width * 2 257 | variables: ore and hole.
258 | ore is:
    259 |
  • 260 | ? character if this cell is not within range of a radar you control. 261 |
  • 262 |
  • A positive integer otherwise, for the amount of ore this cell contains.
  • 263 |
264 | hole is:
    265 |
  • 266 | 1 if this cell has a hole on it. 267 |
  • 268 |
  • 269 | 0 otherwise. 270 |
  • 271 |
272 |

273 | Next line: Four integers 274 |
    275 |
  • 276 | entityCount for the amount of robots, radars and traps currently visible to you. 277 |
  • 278 |
  • 279 | radarCooldown for the number of turns until a new RADAR can be requested. 280 |
  • 281 |
  • 282 | trapCooldown for the number of turns until a new TRAP can be requested. 283 |
  • 284 |
285 | Next 286 | entityCount lines: 5 integers to describe each entity 287 |
    288 |
  • 289 | id: entity's unique id. 290 |
  • 291 |
  • 292 | type: 293 |
      294 |
    • 295 | 0: one of your robots 296 |
    • 297 |
    • 298 | 1: one of your opponent's robots 299 |
    • 300 |
    • 301 | 2: one of your buried radars 302 |
    • 303 |
    • 304 | 3: one of your buried traps 305 |
    • 306 |
    307 |
  • 308 |
  • 309 | x & 310 | y: the entity's position.
    If this entity is a destroyed robot, x y will equal 311 | -1 -1 312 |
  • 313 |
  • 314 | item: if this entity is a robot, the item this robot is carrying: 315 |
      316 |
    • 317 | -1for nothing 318 |
    • 319 |
    • 320 | 2for a radar 321 |
    • 322 |
    • 323 | 3for a trap 324 |
    • 325 |
    • 326 | 4for a unit of Amadeusium ore 327 |
    • 328 |
    329 |
  • 330 |
331 | 332 |
333 |
334 | 335 |
336 |
Output
337 |
338 | 339 | 5 lines, 340 | one for each robot, in the same order in which they were given, containing one of the 341 | following actions: 342 |
    343 |
  • 344 | WAIT: the robot does nothing. 345 |
  • 346 |
  • 347 | MOVE x y: the robot moves 348 | 4 cells towards the given cell. 349 |
  • 350 |
  • 351 | DIG x y: the robot attempts to bury the item it is carrying in 352 | the target cell, retrieve ore from the cell, or both. If the cell is not adjacent, the robot will execute 353 | a MOVE command towards the target instead. 354 |
  • 355 |
  • 356 | REQUEST followed by RADAR or TRAP: the robot attempts to 357 | take an item from the headquarters. If the robot is not on a headquarters cell, the robot 358 | will execute the MOVE 0 y command instead, where y is the ordinate of the robot. 359 |
  • 360 |
361 | You may append text to a command to have it displayed in the viewer above your robot. 362 |

Examples:
    363 |
  • 364 | MOVE 8 4 365 |
  • 366 |
  • 367 | WAIT nothing to do... 368 |
  • 369 |
370 | 371 | You must provide a command to all robots each turn, even if they are destroyed. Destroyed robots will ignore the 372 | command.
373 |
374 |
375 |
Constraints
376 |
377 | Response time per turn ≤ 378 | 50ms 379 |
Response time for the first turn ≤ 380 | 1000ms 381 |
382 |
383 |
384 | 385 |
386 |
388 |
389 |

Getting Started

390 |
391 | Why not jump straight into battle with one these Starter AIs, 392 | provided by the Unleash The Geek Team: 393 | 420 | 421 |

422 | You can modify them to suit your own coding style or start completely 423 | from scratch. 424 |

425 | Other starters may become available during the event here. 427 | 428 | 429 |
430 |
431 |
432 |
433 |
-------------------------------------------------------------------------------- /config/statement_fr.html.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
12 |
13 | 14 |
15 |

16 | Ce challenge se déroule en ligues. 17 |

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

26 |   27 | Objectif 28 |

29 |
30 |

L'Amadeusium est un cristal rare et précieux, que l'on trouve sous la surface de certaines 31 | planètes inhospitalières. Vous devez contrôler les robots de votre société minière, présents à la surface d'une 32 | de ces planètes pour extraire le plus possible de cristal. Cependant, vous n'êtes pas les seuls sur le coup ! 33 |

34 | Livrez plus de cristaux d'Amadeusium que la société minière adverse ! 35 |
36 |
37 | 38 |
39 |

40 |   41 | Règles 42 |

43 | 44 |
45 |

Chaque joueur contrôle une équipe de robots. 46 | Les deux équipes démarrent au même endroit sur la zone de jeu, au quartier général. Les robots 47 | peuvent faire chacune des actions suivantes : se déplacer, forer la surface, miner un filon d'Amadeusium, mettre 48 | en place un radar pour détecter des filons, mettre en place un piège électromagnétique 49 | (EMP) pour saboter les robots adverses. 50 |

51 | 52 |
53 |

La grille de jeu

54 |
55 | 56 |

Le jeu est joué sur une grille de 30 cases en largeur par 15 en hauteur. Les 57 | coordonnées 58 | x=0, y=0 59 | correspondent à la case en haut à gauche.

60 | 61 |

La première colonne à gauche de la grille de jeu correspond au quartier général. C'est là que 62 | le cristal d'Amadeusium doit être amené une fois extrait et que les objets sont demandés.

63 | 64 |

Certaines cases cachent des filons d'Amadeusium. Par défaut, les filons ne sont pas visibles 65 | des joueurs. On ne peut pas trouver de filon au quartier général.

66 | 67 |

Les robots peuvent forer la surface et creuser un trou sur n'importe quelle 68 | case de la grille de jeu (hors quartier général). Les trous sont visibles des 69 | 2 joueurs et n'empêchent pas le mouvement des robots.

70 | 71 |
72 |

Les robots

73 |
74 | 75 |

Chaque robot ne peut transporter qu' 1 seul objet dans son inventaire.

76 | 77 |

Un robot peut faire les actions suivantes :

78 | 79 |
    80 |
  • 81 | demander un objet au quartier général avec la commande REQUEST. 82 |
  • 83 | 84 |
  • 85 | se déplacer avec la commande MOVE. 86 |
  • 87 |
  • 88 | utiliser la commande DIG pour creuser un trou et interagir avec. Voici, dans 89 | l'ordre, ses effets : 90 | 91 |
      92 |
    1. Si la case ne contient pas déjà un trou, un nouveau trou est creusé.
    2. 93 | 94 |
    3. Si le robot contient un objet, l'objet est enterré dans le trou (et retiré de l'inventaire du robot). 95 |
    4. 96 | 97 |
    5. Si la case contient un filon d'Amadeusium (et qu'un cristal n'a pas été enterré à l'étape 2), un cristal 98 | est extrait du filon et ajouté à l'inventaire du robot.
    6. 99 |
    100 | 101 |
  • 102 |
  • 103 | ne rien faire avec la commande WAIT. 104 |
  • 105 |
106 | 107 |

Précisions :

108 |
    109 |
  • Chaque robot ne peut creuser que sur la case qu'il occupe ou les cases adjacentes à sa position. Chaque case 110 | a 4 111 | cases adjacentes : en haut, à droite, en bas et à gauche.
  • 112 | 113 |
  • Si un robot possède un cristal dans son inventaire en arrivant au quartier général, le cristal est 114 | automatiquement livré et le joueur marque 1 point.
  • 115 | 116 |
  • Plusieurs robots peuvent occuper une même case.
  • 117 | 118 |
  • Les robots ne peuvent pas quitter la grille de jeu.
  • 119 | 120 |
  • Les inventaires des robots sont invisibles des joueurs adverses.
  • 121 | 122 |
123 | 124 |

Les objets

125 |
126 | 127 |

Un cristal d'Amadeusium est considéré comme un objet et doit être livré au quartier général 128 | pour marquer 1 point.

129 | 130 |

Au quartier général, un robot peut demander l'un des 2 objets suivants : un 131 | radar avec la commande 132 | RADAR ou un piège EMP avec la commande 133 | TRAP.

134 | 135 |

Si un objet est livré suite à une demande d'un robot, cet objet sera de nouveau disponible pour la même équipe 136 | de robot après 5 tours.

137 | 138 |

Un piège EMP enterrée dans un trou ne se déclenche que si un robot utilise la 139 | commande DIG sur ce même trou. L'impulsion électromagnétique qui s'en suit détruit tous les 140 | robots sur la case correspondante et les 4 cases adjacentes. N'importe quel autre 141 | piège EMP se déclenche à son tour si il se trouve sur une des cases touchées par l'impulsion, 142 | entraînant une réaction en chaîne.

143 | 144 | 145 |

Un radar enterré dans un trou permet de détecter les filons d'Amadeusium et 146 | de connaître la quantité de cristaux disponibles dans chaque, dans un rayon de 4 cases, pour 147 | l'équipe qui l'a enterré. 148 | 149 | Si un robot adverse utilise la commande DIG sur le trou contenant l'un de vos radars, le radar 150 | est détruit. 151 |

152 | 153 |
154 |

Ordre des actions pour un tour de jeu

155 | 156 |
    157 |
  1. Si des commandes DIG déclenchent des piège EMP, ils sont détonés.
  2. 158 | 159 |
  3. 160 | Les autres commandes DIG sont résolues. 161 |
  4. 162 | 163 |
  5. 164 | Les commandes REQUEST sont résolues. 165 |
  6. 166 | 167 |
  7. 168 | Les temps de recharge des objets sont incrémentés.
  8. 169 | 170 |
  9. 171 | Les commandes MOVE et WAIT sont résolues. 172 |
  10. 173 | 174 |
  11. Les cristaux d'Amadeusium sont livrés au quartier général.
  12. 175 | 176 |
  13. 177 | Il n'est pas nécessaire de détecter un filon d'Amadeusium pour pouvoir extraire un cristal.
  14. 178 | 179 |
180 |
181 | 182 |
183 |
184 |
185 |
Conditions de victoire
186 |
187 |
    188 |
  • Après 200 tours, votre équipe de robot a marqué le plus de points.
  • 189 |
  • Vous avez marqué plus de points que votre adversaire, et tous ses robots ont été détruits.
  • 190 |
191 |
192 |
193 |
194 | 195 |
196 |
197 |
198 |
Conditions de défaite
199 |
200 |
    201 |
  • Votre programme ne retourne pas de commande valide dans le temps imparti pour chaque robot de votre 202 | équipe, y compris ceux qui sont détruits.
  • 203 |
204 |
205 |
206 |
207 |
208 | 209 | 210 | 211 |
212 |

213 |   214 | Détails techniques 215 |

216 |
217 |
    218 |
  • Un robot peut enterrer un cristal dans un trou. Si la case ne contenait pas déjà un filon, alors un nouveau 219 | filon est créé, détectable par les radars.
  • 220 | 221 |
  • Chaque robot, radar, et piège EMP possède un unique identifiant.
  • 222 | 223 |
  • Recevoir un objet du quartier général détruira n'importe quel objet que le robot a déjà dans son inventaire. 224 |
  • 225 | 226 |
  • Quand plusieurs robots de la même équipe demandent le même objet, les robots sans objets auront la priorité. 227 |
  • 228 | 229 |
  • Les pièges EMP n'ont pas d'effet sur les radars et les filons.
  • 230 | 231 |
  • Si un robot transportant un objet est détruit, l'objet est détruit.
  • 232 |
233 |

Vous pouvez voir le code source de ce jeu sur ce repo GitHub.

235 |
236 |
237 | 238 | 239 |
240 |

241 |   242 | Données d'entrée 243 |

244 | 245 |
246 |
Entrée pour le premier tour
247 |
248 | Ligne 1: deux entiers 249 | width et 250 | height pour la taille de la grille. 251 | La première colonne à gauche de la grille de jeu correspond au quartier général. 252 |
253 |
254 |
255 | 256 |
257 |
Entrée pour un tour de jeu
258 |
259 | Première ligne : Deux entiers 260 |
    261 |
  • 262 | myScore pour le score du joueur. 263 |
  • 264 |
  • 265 | enemyScore pour le score de son adversaire. 266 |
  • 267 |
268 | Prochaines height lignes : width * 2 269 | variables ore et hole.
270 | ore vaut :
    271 |
  • 272 | ? si la case n'est pas à portée d'un radar que le joueur contrôle. 273 |
  • 274 |
  • Un entier positif, pour la quantité de cristaux dans le filon. 0, si la case ne cache pas 275 | de filon.
  • 276 |
277 | hole vaut :
    278 |
  • 279 | 1 s'il y a un trou sur la case. 280 |
  • 281 |
  • 282 | 0 sinon. 283 |
  • 284 |
285 |

286 | Prochaine ligne : 4 entiers
287 |
    288 |
  • entityCount pour la quantité d'entités : robots, radars et pièges EMP visibles au joueur. 289 |
  • 290 |
  • 291 | radarCooldown pour le nombre de tours jusqu'à ce qu'un nouveau radar soit disponible au quartier 292 | général. 293 |
  • 294 |
  • 295 | trapCooldown pour le nombre de tours jusqu'à ce qu'un nouveau piège EMP soit disponible au 296 | quartier général. 297 |
  • 298 |
299 |

300 | Prochaines 301 | entityCount lignes : 5 entiers pour décrire chaque entité 302 |
    303 |
  • 304 | id: son unique identifiant. 305 |
  • 306 |
  • 307 | type: son type. 308 |
      309 |
    • 310 | 0 pour l'un de vos robots 311 |
    • 312 |
    • 313 | 1 pour un robot adverse 314 |
    • 315 |
    • 316 | 2 pour un de vos radars enterrés 317 |
    • 318 |
    • 319 | 3 pour une de vos pièges enterrées 320 |
    • 321 |
    322 |
  • 323 |
  • 324 | x & 325 | y: les coordonnées de l'entité.
    Si l'entité est un robot détruit, ses coordonnées x 326 | yvalent 327 | -1 -1 328 |
  • 329 |
  • 330 | item: si l'entité est un robot, l'objet présent dans son inventaire. 331 |
      332 |
    • 333 | -1 si l'inventaire est vide 334 |
    • 335 |
    • 336 | 2 pour un radar 337 |
    • 338 |
    • 339 | 3 pour un piège EMP 340 |
    • 341 |
    • 342 | 4 pour un cristal d'Amadeusium 343 |
    • 344 |
    345 |
  • 346 |
347 | 348 |
349 |
350 | 351 |
352 |
Sortie pour un tour de jeu
353 |
354 | 355 | 5 lignes, 356 | pour chaque robot de l'équipe, suivant l'ordre de leurs identifiants, une des commandes suivantes : 357 |
    358 |
  • 359 | WAIT: le robot ne fait rien. 360 |
  • 361 |
  • 362 | MOVE x y: le robot se déplace de 4 cases vers la case (x, y). 363 |
  • 364 |
  • 365 | DIG x y: le robot tente d'enterrer un objet s'il en transporte, et d'extraire un cristal 366 | d'un possible filon. Si la case n'est pas adjacente, le robot effectuera une commande MOVE 367 | pour se rapprocher de la destination à la place. 368 |
  • 369 |
  • 370 | REQUEST suivi de RADAR ou TRAP: le robot tente de 371 | récupérer un objet du quartier général. Si le robot n'est pas sur une case du quartier général, il 372 | effectuera la commande MOVE 0 y où y est l'ordonnée du robot. 373 |
  • 374 |
375 | Il est possible d'ajouter un message à chaque commande (séparé d'un espace) pour qu'il soit affiché au-dessus du 376 | robot. 377 |

Exemples:
    378 |
  • 379 | MOVE 8 4 380 |
  • 381 |
  • 382 | DIG 7 10 let's go mining 383 |
  • 384 |
385 | 386 | Il est nécessaire d'envoyer une commande pour les robots détruits. Elle sera ignorée.
387 |
388 |
389 |
Contraintes
390 |
391 | Temps de réponse pour un tour ≤ 392 | 50ms 393 |
Temps de réponse pour le premier tour ≤ 394 | 1000ms 395 |
396 |
397 | 398 |
399 |
401 |
402 |

Pour Démarrer

403 |
404 | Pourquoi ne pas se lancer dans la bataille avec l'un ces nos IA Starters, fourni par l'équipe Unleash 405 | The Geek : 406 | 433 |

434 | Vous pouvez les modifier selon votre style, ou les prendre comme exemple pour tout coder à partir de 435 | zero. 436 |

437 | 438 | D'autres starters pourront être rendus disponibles pendant le contest ici. 440 | 441 |
442 |
443 |
444 |
445 |
446 |
-------------------------------------------------------------------------------- /config/stub.txt: -------------------------------------------------------------------------------- 1 | read width:int height:int 2 | gameloop 3 | read myScore:int opponentScore:int 4 | loop height 5 | loopline width ore:word(2) hole:int 6 | read entityCount:int radarCooldown:int trapCooldown:int 7 | loop entityCount 8 | read id:int type:int x:int y:int item:int 9 | loop 5 10 | write WAIT 11 | 12 | INPUT 13 | height: size of the map 14 | myScore: Amount of ore delivered 15 | ore: amount of ore or "?" if unknown 16 | hole: 1 if cell has a hole 17 | entityCount: number of entities visible to you 18 | id: unique id of the entity 19 | type: 0 for your robot, 1 for other robot, 2 for radar, 3 for trap 20 | y: position of the entity 21 | radarCooldown: turns left until a new radar can be requested 22 | trapCooldown: turns left until a new trap can be requested 23 | item: if this entity is a robot, the item it is carrying (-1 for NONE, 2 for RADAR, 3 for TRAP, 4 for ORE) 24 | 25 | OUTPUT 26 | WAIT|MOVE x y|DIG x y|REQUEST item 27 | 28 | STATEMENT 29 | Deliver more ore to hq (left side of the map) than your opponent. Use radars to find ore but beware of traps! 30 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.codingame.game 6 | unleash-the-geek-2019-gold 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 3.8.2 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | com.codingame.gameengine 18 | core 19 | ${gamengine.version} 20 | 21 | 22 | 23 | com.codingame.gameengine 24 | runner 25 | ${gamengine.version} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/CommandManager.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.List; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import com.codingame.gameengine.core.MultiplayerGameManager; 8 | import com.codingame.utg2019.Agent; 9 | import com.codingame.utg2019.Coord; 10 | import com.codingame.utg2019.Item; 11 | import com.codingame.utg2019.action.Action; 12 | import com.codingame.utg2019.action.DigAction; 13 | import com.codingame.utg2019.action.MoveAction; 14 | import com.codingame.utg2019.action.RequestAction; 15 | import com.google.inject.Inject; 16 | import com.google.inject.Singleton; 17 | 18 | @Singleton 19 | public class CommandManager { 20 | 21 | @Inject private MultiplayerGameManager gameManager; 22 | 23 | static final Pattern PLAYER_MOVE_PATTERN = Pattern.compile( 24 | "^MOVE\\s+(?-?\\d+)\\s+(?-?\\d+)" 25 | + "(?:\\s+(?.+))?" 26 | + "\\s*$", 27 | Pattern.CASE_INSENSITIVE 28 | ); 29 | static final Pattern PLAYER_INTERACT_PATTERN = Pattern.compile( 30 | "^(INTERACT|DIG)\\s+(?-?\\d+)\\s+(?-?\\d+)" 31 | + "(?:\\s+(?.+))?" 32 | + "\\s*$", 33 | Pattern.CASE_INSENSITIVE 34 | ); 35 | static final Pattern PLAYER_REQUEST_PATTERN = Pattern.compile( 36 | "^REQUEST\\s+(?(?:TRAP|RADAR))" 37 | + "(?:\\s+(?.+))?" 38 | + "\\s*$", 39 | Pattern.CASE_INSENSITIVE 40 | ); 41 | static final Pattern PLAYER_WAIT_PATTERN = Pattern.compile( 42 | "^WAIT" 43 | + "(?:\\s+(?.+))?" 44 | + "\\s*$", 45 | Pattern.CASE_INSENSITIVE 46 | ); 47 | 48 | static String EXPECTED = "DIG | REQUEST | MOVE | WAIT"; 49 | 50 | public void handleCommands(Player player, List lines) { 51 | int i = 0; 52 | for (String line : lines) { 53 | Agent agent = player.getAgents().get(i++); 54 | if (agent.isDead()) { 55 | continue; 56 | } 57 | 58 | try { 59 | 60 | Matcher match = PLAYER_WAIT_PATTERN.matcher(line); 61 | if (match.matches()) { 62 | //Message 63 | matchMessage(agent, match); 64 | continue; 65 | } 66 | 67 | match = PLAYER_MOVE_PATTERN.matcher(line); 68 | if (match.matches()) { 69 | int x = Integer.valueOf(match.group("x")); 70 | int y = Integer.valueOf(match.group("y")); 71 | 72 | Action intent = new MoveAction(new Coord(x, y)); 73 | agent.setIntent(intent); 74 | 75 | //Message 76 | matchMessage(agent, match); 77 | continue; 78 | } 79 | 80 | match = PLAYER_INTERACT_PATTERN.matcher(line); 81 | if (match.matches()) { 82 | int x = Integer.valueOf(match.group("x")); 83 | int y = Integer.valueOf(match.group("y")); 84 | 85 | Action intent = new DigAction(new Coord(x, y)); 86 | agent.setIntent(intent); 87 | 88 | //Message 89 | matchMessage(agent, match); 90 | continue; 91 | } 92 | 93 | match = PLAYER_REQUEST_PATTERN.matcher(line); 94 | if (match.matches()) { 95 | Item item = Item.valueOf(match.group("item").toUpperCase()); 96 | 97 | Action intent = new RequestAction(item); 98 | agent.setIntent(intent); 99 | 100 | //Message 101 | matchMessage(agent, match); 102 | continue; 103 | } 104 | 105 | throw new InvalidInputException(EXPECTED, line); 106 | 107 | } catch (InvalidInputException e) { 108 | deactivatePlayer(player, e.getMessage()); 109 | gameManager.addToGameSummary("Bad command: " + e.getMessage()); 110 | return; 111 | } catch (Exception e) { 112 | 113 | deactivatePlayer(player, new InvalidInputException(e.toString(), EXPECTED, line).getMessage()); 114 | gameManager.addToGameSummary("Bad command: " + e.getMessage()); 115 | return; 116 | } 117 | 118 | } 119 | } 120 | 121 | private void deactivatePlayer(Player player, String message) { 122 | player.deactivate(escapeHTMLEntities(message)); 123 | } 124 | 125 | private String escapeHTMLEntities(String message) { 126 | return message 127 | .replace("<", "<") 128 | .replace(">", ">"); 129 | } 130 | 131 | private void matchMessage(Agent agent, Matcher match) { 132 | String message = match.group("message"); 133 | if (message != null) { 134 | String characterFilter = "[^\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}\\p{Cf}\\p{Cs}\\s]"; 135 | String messageWithoutEmojis = message.replaceAll(characterFilter, ""); 136 | agent.setMessage(messageWithoutEmojis); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/GameException.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | @SuppressWarnings("serial") 4 | public class GameException extends Exception { 5 | 6 | public GameException(String string) { 7 | super(string); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/InvalidInputException.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | @SuppressWarnings("serial") 4 | public class InvalidInputException extends Exception { 5 | 6 | public InvalidInputException(String expected, String got) { 7 | super("Invalid Input: Expected " + expected + " but got '" + got + "'"); 8 | } 9 | 10 | public InvalidInputException(String error, String expected, String got) { 11 | super(error + ": Expected " + expected + " but got '" + got + "'"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Player.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.ArrayList; 4 | import java.util.EnumMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | 9 | import com.codingame.gameengine.core.AbstractMultiplayerPlayer; 10 | import com.codingame.utg2019.Agent; 11 | import com.codingame.utg2019.Config; 12 | import com.codingame.utg2019.Item; 13 | 14 | public class Player extends AbstractMultiplayerPlayer { 15 | 16 | private List agents = new ArrayList<>(); 17 | private int ore; 18 | private Map cooldowns; 19 | private Map cooldownTimes; 20 | 21 | public Player() { 22 | agents = new ArrayList<>(Config.AGENTS_PER_PLAYER); 23 | cooldowns = new EnumMap<>(Item.class); 24 | cooldowns.put(Item.RADAR, 0); 25 | cooldowns.put(Item.TRAP, 0); 26 | cooldownTimes = new EnumMap<>(Item.class); 27 | cooldownTimes.put(Item.RADAR, Config.RADAR_COOLDOWN); 28 | cooldownTimes.put(Item.TRAP, Config.TRAP_COOLDOWN); 29 | } 30 | 31 | public void addAgent(Agent agent) { 32 | agents.add(agent); 33 | 34 | } 35 | 36 | public void decrementCooldowns() { 37 | cooldowns.forEach((k, v) -> { 38 | cooldowns.compute(k, (item, cooldown) -> { 39 | return (cooldown > 0) ? cooldown - 1 : cooldown; 40 | }); 41 | }); 42 | } 43 | 44 | public List getAgents() { 45 | return agents; 46 | } 47 | 48 | public int getCooldown(Item item) { 49 | return Objects.requireNonNull( 50 | cooldowns.get(item) 51 | ); 52 | } 53 | 54 | @Override 55 | public int getExpectedOutputLines() { 56 | return agents.size(); 57 | } 58 | 59 | public int getOre() { 60 | return ore; 61 | } 62 | 63 | public void removeAgent(Agent agent) { 64 | agents.remove(agent); 65 | } 66 | 67 | public void reset() { 68 | agents.forEach(a -> a.reset()); 69 | } 70 | 71 | public void scoreOre() { 72 | ore++; 73 | setScore(ore); 74 | } 75 | 76 | public void startCooldown(Item item) { 77 | cooldowns.put(item, cooldownTimes.get(item)); 78 | } 79 | 80 | public void setCooldown(Item item, int cooldown) { 81 | cooldowns.put(item, cooldown); 82 | 83 | } 84 | 85 | public void setOre(int score) { 86 | ore = score; 87 | setScore(score); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/game/Referee.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game; 2 | 3 | import java.util.Properties; 4 | import java.util.stream.Collectors; 5 | import java.util.stream.Stream; 6 | 7 | import com.codingame.gameengine.core.AbstractPlayer.TimeoutException; 8 | import com.codingame.gameengine.core.AbstractReferee; 9 | import com.codingame.gameengine.core.MultiplayerGameManager; 10 | import com.codingame.utg2019.Config; 11 | import com.codingame.utg2019.Game; 12 | import com.codingame.view.FrameViewData; 13 | import com.codingame.view.GlobalViewData; 14 | import com.codingame.view.ViewModule; 15 | import com.codingame.view.endscreen.EndScreenModule; 16 | import com.google.inject.Inject; 17 | import com.google.inject.Singleton; 18 | 19 | @Singleton 20 | public class Referee extends AbstractReferee { 21 | 22 | @Inject private MultiplayerGameManager gameManager; 23 | @Inject private ViewModule viewModule; 24 | @Inject private CommandManager commandManager; 25 | @Inject private EndScreenModule endScreenModule; 26 | @Inject private Game game; 27 | 28 | long seed; 29 | 30 | @Override 31 | public void init() { 32 | 33 | viewModule.setReferee(this); 34 | this.seed = gameManager.getSeed(); 35 | 36 | computeConfiguration(gameManager.getGameParameters()); 37 | 38 | try { 39 | game.init(seed); 40 | String state = gameManager.getGameParameters().getProperty("state"); 41 | game.initGameState(state); 42 | sendGlobalInfo(); 43 | 44 | gameManager.setFrameDuration(1000); 45 | gameManager.setMaxTurns(200); 46 | gameManager.setTurnMaxTime(50); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | System.err.println("Referee failed to initialize"); 50 | abort(); 51 | } 52 | } 53 | 54 | private void computeConfiguration(Properties gameParameters) { 55 | Config.take(gameParameters); 56 | // Config.give(gameParameters); 57 | } 58 | 59 | private void abort() { 60 | System.err.println("Unexpected game end"); 61 | gameManager.endGame(); 62 | } 63 | 64 | private void sendGlobalInfo() { 65 | for (Player player : gameManager.getActivePlayers()) { 66 | for (String line : game.getGlobalInfoFor(player)) { 67 | player.sendInputLine(line); 68 | } 69 | } 70 | } 71 | 72 | @Override 73 | public void gameTurn(int turn) { 74 | game.resetGameTurnData(); 75 | 76 | // Give input to players 77 | for (Player player : gameManager.getActivePlayers()) { 78 | for (String line : game.getCurrentFrameInfoFor(player)) { 79 | player.sendInputLine(line); 80 | } 81 | player.execute(); 82 | } 83 | // Get output from players 84 | handlePlayerCommands(); 85 | 86 | game.performGameUpdate(turn); 87 | 88 | if (game.gameOver(turn) || gameManager.getActivePlayers().size() < 2) { 89 | gameManager.endGame(); 90 | } 91 | } 92 | 93 | private void handlePlayerCommands() { 94 | for (Player player : gameManager.getActivePlayers()) { 95 | try { 96 | commandManager.handleCommands(player, player.getOutputs()); 97 | } catch (TimeoutException e) { 98 | player.deactivate("Timeout!"); 99 | gameManager.addToGameSummary(player.getNicknameToken() + " has not provided " + player.getExpectedOutputLines() + " lines in time"); 100 | } 101 | } 102 | 103 | } 104 | 105 | static public String join(Object... args) { 106 | return Stream.of(args).map(String::valueOf).collect(Collectors.joining(" ")); 107 | } 108 | 109 | @Override 110 | public void onEnd() { 111 | gameManager.getPlayers().forEach(player -> player.setScore(player.isActive() ? player.getOre() : -1)); 112 | int reference = gameManager.getPlayer(0).getScore(); 113 | boolean tie = gameManager.getPlayers() 114 | .stream() 115 | .skip(1) 116 | .mapToInt(Player::getScore) 117 | .allMatch(i -> i == reference); 118 | 119 | endScreenModule.setScores( 120 | gameManager.getPlayers() 121 | .stream() 122 | .mapToInt(Player::getScore) 123 | .toArray(), 124 | tie 125 | ); 126 | 127 | gameManager.putMetadata("robotsDestroyed", String.valueOf(game.getRobotsDestroyed())); 128 | gameManager.putMetadata("trapsPlaced", String.valueOf(game.getTrapsPlaced())); 129 | gameManager.putMetadata("oreDelivered", String.valueOf(game.getOreDelivered())); 130 | } 131 | 132 | public FrameViewData getCurrentFrameData() { 133 | return game.getCurrentFrameData(); 134 | } 135 | 136 | public GlobalViewData getGlobalData() { 137 | return game.getGlobalViewData(); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Agent.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | import com.codingame.game.Player; 4 | import com.codingame.utg2019.action.Action; 5 | 6 | public class Agent { 7 | private Player owner; 8 | private int id; 9 | private boolean dead; 10 | private int respawnIn; 11 | private String message; 12 | 13 | private Coord position; 14 | private Item inventory; 15 | private Action intent; 16 | private Coord initialPosition; 17 | 18 | public Agent(int id, Player owner, Coord pos) { 19 | this.owner = owner; 20 | this.position = pos; 21 | this.initialPosition = pos; 22 | this.inventory = Item.NOTHING; 23 | this.id = id; 24 | intent = Action.NO_ACTION; 25 | } 26 | 27 | public void setMessage(String message) { 28 | this.message = message; 29 | if (message != null && message.length() > 48) { 30 | this.message = message.substring(0, 46) + "..."; 31 | } 32 | 33 | } 34 | 35 | public void setPosition(Coord coord) { 36 | this.position = coord; 37 | } 38 | 39 | public Coord getPosition() { 40 | return position; 41 | } 42 | 43 | public void setIntent(Action intent) { 44 | this.intent = intent; 45 | } 46 | 47 | public void reset() { 48 | intent = Action.NO_ACTION; 49 | message = null; 50 | } 51 | 52 | public Action getIntent() { 53 | return intent; 54 | } 55 | 56 | public int getId() { 57 | return id; 58 | } 59 | 60 | public Player getOwner() { 61 | return owner; 62 | } 63 | 64 | public void receiveOre() { 65 | inventory = Item.ORE; 66 | } 67 | 68 | public void factoryReset() { 69 | reset(); 70 | position = initialPosition; 71 | inventory = Item.NOTHING; 72 | dead = false; 73 | } 74 | 75 | public Item getInventory() { 76 | return inventory; 77 | } 78 | 79 | public void setInventory(Item collected) { 80 | inventory = collected; 81 | } 82 | 83 | public boolean isDead() { 84 | return dead; 85 | } 86 | 87 | public void die() { 88 | dead = true; 89 | respawnIn = Config.AGENT_RESPAWN_TIME; 90 | intent = Action.NO_ACTION; 91 | } 92 | 93 | public void decrementRespawnTimer() { 94 | respawnIn--; 95 | } 96 | 97 | // Robots could respawn at some point during development 98 | public boolean shouldRespawn() { 99 | return dead && respawnIn <= 0; 100 | } 101 | 102 | public String getMessage() { 103 | return message; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Cell.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | public class Cell { 4 | public static final Cell NO_CELL = new Cell() { 5 | @Override 6 | public boolean isValid() { 7 | return false; 8 | } 9 | 10 | @Override 11 | public int getOre() { 12 | return 0; 13 | } 14 | }; 15 | 16 | private boolean accessToHQ; 17 | private boolean hole; 18 | private int ore; 19 | 20 | public boolean isValid() { 21 | return true; 22 | } 23 | 24 | public int getOre() { 25 | return ore; 26 | } 27 | 28 | public Cell() { 29 | 30 | } 31 | 32 | public void setAccessToHQ(boolean acecssToHQ) { 33 | this.accessToHQ = acecssToHQ; 34 | 35 | } 36 | 37 | public void setOre(int ore) { 38 | this.ore = ore; 39 | } 40 | 41 | public void setHole(boolean hole) { 42 | this.hole = hole; 43 | 44 | } 45 | 46 | public void reduceOre(int amount) { 47 | ore = Math.max(0, ore - amount); 48 | } 49 | 50 | public void incrementOre() { 51 | ore++; 52 | } 53 | 54 | public boolean hasAccessToHQ() { 55 | return accessToHQ; 56 | } 57 | 58 | public boolean isHole() { 59 | return hole; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Config.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | import java.util.Properties; 4 | 5 | public class Config { 6 | public static enum Adjacency { 7 | FOUR_ADJACENCY(new Coord(-1, 0), new Coord(1, 0), new Coord(0, -1), new Coord(0, 1)), EIGHT_ADJACENCY( 8 | new Coord(-1, 0), new Coord(1, 0), new Coord(0, -1), new Coord(0, 1), new Coord(-1, -1), 9 | new Coord(1, -1), new Coord(-1, 1), new Coord(1, 1) 10 | ); 11 | 12 | public final Coord[] deltas; 13 | 14 | private Adjacency(Coord... deltas) { 15 | this.deltas = deltas; 16 | } 17 | } 18 | 19 | public static final Adjacency FOUR_ADJACENCY = Adjacency.FOUR_ADJACENCY; 20 | public static final Adjacency EIGHT_ADJACENCY = Adjacency.EIGHT_ADJACENCY; 21 | 22 | public static final int TYPE_NONE = -1; 23 | public static final int TYPE_MY_AGENT = 0; 24 | public static final int TYPE_FOE_AGENT = 1; 25 | public static final int TYPE_RADAR = 2; 26 | public static final int TYPE_TRAP = 3; 27 | public static final int TYPE_ORE = 4; 28 | 29 | public static Adjacency ADJACENCY = FOUR_ADJACENCY; 30 | public static int AGENTS_MOVE_DISTANCE = 4; 31 | public static int AGENTS_PER_PLAYER = 5; 32 | public static int AGENT_INTERACT_RADIUS = 1; 33 | public static int AGENT_RESPAWN_TIME = 999; 34 | public static int MAP_CLUSTER_SIZE = 5; 35 | public static double MAP_ORE_COEFF_X = 0.55; 36 | public static int MAP_HEIGHT = 15; 37 | public static int MAP_WIDTH = 30; 38 | public static double MAP_CLUSTER_DISTRIBUTION_MAX = 0.064; 39 | public static double MAP_CLUSTER_DISTRIBUTION_MIN = 0.032; 40 | public static int MAP_ORE_IN_CELL_MAX = 3; 41 | public static int MAP_ORE_IN_CELL_MIN = 1; 42 | public static int RADAR_COOLDOWN = 5; 43 | public static int RADAR_RANGE = 4; 44 | public static boolean ROBOTS_CAN_OCCUPY_SAME_CELL = true; 45 | public static boolean TRAP_CHAIN_REACTION = true; 46 | public static boolean TRAP_FRIENDLY_FIRE = true; 47 | public static int TRAP_COOLDOWN = 5; 48 | public static int TRAP_RANGE = 1; 49 | public static boolean EUCLIDEAN_RADAR = false; 50 | public static boolean AGENTS_START_PACKED = true; 51 | 52 | public static void take(Properties params) { 53 | ADJACENCY = getFromParams(params, "ADJACENCY", ADJACENCY); 54 | EUCLIDEAN_RADAR = getFromParams(params, "EUCLIDEAN_RADAR", EUCLIDEAN_RADAR); 55 | AGENTS_MOVE_DISTANCE = getFromParams(params, "AGENTS_MOVE_DISTANCE", AGENTS_MOVE_DISTANCE); 56 | AGENTS_PER_PLAYER = getFromParams(params, "AGENTS_PER_PLAYER", AGENTS_PER_PLAYER); 57 | AGENT_INTERACT_RADIUS = getFromParams(params, "AGENT_INTERACT_RADIUS", AGENT_INTERACT_RADIUS); 58 | AGENT_RESPAWN_TIME = getFromParams(params, "AGENT_RESPAWN_TIME", AGENT_RESPAWN_TIME); 59 | MAP_CLUSTER_SIZE = getFromParams(params, "MAP_CLUSTER_SIZE", MAP_CLUSTER_SIZE); 60 | MAP_ORE_COEFF_X = getFromParams(params, "MAP_ORE_COEFF_X", MAP_ORE_COEFF_X); 61 | MAP_HEIGHT = getFromParams(params, "MAP_HEIGHT", MAP_HEIGHT); 62 | MAP_WIDTH = getFromParams(params, "MAP_WIDTH", MAP_WIDTH); 63 | MAP_ORE_IN_CELL_MAX = getFromParams(params, "MAP_ORE_IN_CELL_MAX", MAP_ORE_IN_CELL_MAX); 64 | MAP_CLUSTER_DISTRIBUTION_MIN = getFromParams(params, "MAP_CLUSTER_DISTRIBUTION_MIN", MAP_CLUSTER_DISTRIBUTION_MIN); 65 | MAP_CLUSTER_DISTRIBUTION_MAX = getFromParams(params, "MAP_CLUSTER_DISTRIBUTION_MAX", MAP_CLUSTER_DISTRIBUTION_MAX); 66 | MAP_ORE_IN_CELL_MIN = getFromParams(params, "MAP_ORE_IN_CELL_MIN", MAP_ORE_IN_CELL_MIN); 67 | RADAR_COOLDOWN = getFromParams(params, "RADAR_COOLDOWN", RADAR_COOLDOWN); 68 | RADAR_RANGE = getFromParams(params, "RADAR_RANGE", RADAR_RANGE); 69 | TRAP_RANGE = getFromParams(params, "TRAP_RANGE", TRAP_RANGE); 70 | ROBOTS_CAN_OCCUPY_SAME_CELL = getFromParams(params, "ROBOTS_CAN_OCCUPY_SAME_CELL", ROBOTS_CAN_OCCUPY_SAME_CELL); 71 | TRAP_CHAIN_REACTION = getFromParams(params, "TRAP_CHAIN_REACTION", TRAP_CHAIN_REACTION); 72 | TRAP_COOLDOWN = getFromParams(params, "TRAP_COOLDOWN", TRAP_COOLDOWN); 73 | } 74 | 75 | private static double getFromParams(Properties params, String name, double defaultValue) { 76 | String inputValue = params.getProperty(name); 77 | if (inputValue != null) { 78 | try { 79 | return Double.parseDouble(inputValue); 80 | } catch (NumberFormatException e) { 81 | // Do naught 82 | } 83 | } 84 | return defaultValue; 85 | } 86 | 87 | private static Adjacency getFromParams(Properties params, String name, Adjacency defaultValue) { 88 | String inputValue = params.getProperty(name); 89 | if (inputValue != null) { 90 | try { 91 | return Adjacency.valueOf(inputValue); 92 | } catch (IllegalArgumentException e) { 93 | // Do naught 94 | } 95 | } 96 | return defaultValue; 97 | 98 | } 99 | 100 | private static int getFromParams(Properties params, String name, int defaultValue) { 101 | String inputValue = params.getProperty(name); 102 | if (inputValue != null) { 103 | try { 104 | return Integer.parseInt(inputValue); 105 | } catch (NumberFormatException e) { 106 | // Do naught 107 | } 108 | } 109 | return defaultValue; 110 | } 111 | 112 | private static boolean getFromParams(Properties params, String name, boolean defaultValue) { 113 | String inputValue = params.getProperty(name); 114 | if (inputValue != null) { 115 | try { 116 | return new Boolean(inputValue); 117 | } catch (NumberFormatException e) { 118 | // Do naught 119 | } 120 | } 121 | return defaultValue; 122 | } 123 | 124 | public static void give(Properties params) { 125 | params.setProperty("ADJACENCY", String.valueOf(ADJACENCY)); 126 | params.setProperty("AGENTS_MOVE_DISTANCE", String.valueOf(AGENTS_MOVE_DISTANCE)); 127 | params.setProperty("AGENTS_PER_PLAYER", String.valueOf(AGENTS_PER_PLAYER)); 128 | params.setProperty("AGENT_INTERACT_RADIUS", String.valueOf(AGENT_INTERACT_RADIUS)); 129 | params.setProperty("AGENT_RESPAWN_TIME", String.valueOf(AGENT_RESPAWN_TIME)); 130 | params.setProperty("MAP_CLUSTER_SIZE", String.valueOf(MAP_CLUSTER_SIZE)); 131 | params.setProperty("MAP_ORE_COEFF_X", String.valueOf(MAP_ORE_COEFF_X)); 132 | params.setProperty("MAP_HEIGHT", String.valueOf(MAP_HEIGHT)); 133 | params.setProperty("MAP_WIDTH", String.valueOf(MAP_WIDTH)); 134 | params.setProperty("MAP_CLUSTER_DISTRIBUTION_MAX", String.valueOf(MAP_CLUSTER_DISTRIBUTION_MAX)); 135 | params.setProperty("MAP_ORE_IN_CELL_MAX", String.valueOf(MAP_ORE_IN_CELL_MAX)); 136 | params.setProperty("MAP_CLUSTER_DISTRIBUTION_MIN", String.valueOf(MAP_CLUSTER_DISTRIBUTION_MIN)); 137 | params.setProperty("MAP_ORE_IN_CELL_MIN", String.valueOf(MAP_ORE_IN_CELL_MIN)); 138 | params.setProperty("RADAR_COOLDOWN", String.valueOf(RADAR_COOLDOWN)); 139 | params.setProperty("RADAR_RANGE", String.valueOf(RADAR_RANGE)); 140 | params.setProperty("TRAP_RANGE", String.valueOf(TRAP_RANGE)); 141 | params.setProperty("ROBOTS_CAN_OCCUPY_SAME_CELL", String.valueOf(ROBOTS_CAN_OCCUPY_SAME_CELL)); 142 | params.setProperty("TRAP_CHAIN_REACTION", String.valueOf(TRAP_CHAIN_REACTION)); 143 | params.setProperty("TRAP_COOLDOWN", String.valueOf(TRAP_COOLDOWN)); 144 | params.setProperty("EUCLIDEAN_RADAR", String.valueOf(EUCLIDEAN_RADAR)); 145 | params.setProperty("AGENTS_START_PACKED", String.valueOf(AGENTS_START_PACKED)); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Coord.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | public class Coord { 4 | protected final int x; 5 | protected final int y; 6 | 7 | public Coord(int x, int y) { 8 | this.x = x; 9 | this.y = y; 10 | } 11 | 12 | public double euclideanTo(int x, int y) { 13 | return Math.sqrt(sqrEuclideanTo(x, y)); 14 | } 15 | 16 | private double sqrEuclideanTo(int x, int y) { 17 | return Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2); 18 | } 19 | 20 | @Override 21 | public int hashCode() { 22 | final int prime = 31; 23 | int result = 1; 24 | result = prime * result + x; 25 | result = prime * result + y; 26 | return result; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object obj) { 31 | if (this == obj) return true; 32 | if (obj == null) return false; 33 | Coord other = (Coord) obj; 34 | if (x != other.x) return false; 35 | if (y != other.y) return false; 36 | return true; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "(" + x + ", " + y + ")"; 42 | } 43 | 44 | public int getX() { 45 | return x; 46 | } 47 | 48 | public int getY() { 49 | return y; 50 | } 51 | 52 | public int manhattanTo(Coord other) { 53 | return manhattanTo(other.x, other.y); 54 | } 55 | 56 | public int gameDistanceTo(int x, int y) { 57 | return Config.ADJACENCY == Config.FOUR_ADJACENCY ? manhattanTo(x, y) : chebyshevTo(x, y); 58 | } 59 | 60 | public int chebyshevTo(int x, int y) { 61 | return Math.max(Math.abs(x - this.x), Math.abs(y - this.y)); 62 | } 63 | 64 | public int manhattanTo(int x, int y) { 65 | return Math.abs(x - this.x) + Math.abs(y - this.y); 66 | } 67 | 68 | public int gameDistanceTo(Coord coord) { 69 | return gameDistanceTo(coord.x, coord.y); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Game.java: -------------------------------------------------------------------------------- 1 | 2 | package com.codingame.utg2019; 3 | 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Queue; 13 | import java.util.Random; 14 | import java.util.Set; 15 | import java.util.function.Function; 16 | import java.util.function.Predicate; 17 | import java.util.function.Supplier; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.Stream; 20 | 21 | import com.codingame.game.Player; 22 | import com.codingame.game.Referee; 23 | import com.codingame.gameengine.core.MultiplayerGameManager; 24 | import com.codingame.gameengine.core.Tooltip; 25 | import com.codingame.utg2019.action.Action; 26 | import com.codingame.utg2019.action.MoveAction; 27 | import com.codingame.utils.Padding; 28 | import com.codingame.view.AgentData; 29 | import com.codingame.view.CellData; 30 | import com.codingame.view.EventData; 31 | import com.codingame.view.FrameViewData; 32 | import com.codingame.view.GlobalViewData; 33 | import com.google.inject.Inject; 34 | import com.google.inject.Singleton; 35 | 36 | @Singleton 37 | public class Game { 38 | @Inject private MultiplayerGameManager gameManager; 39 | 40 | private Random random; 41 | private Supplier> allAgents; 42 | private List deadAgents; 43 | private Grid grid; 44 | private List viewerEvents; 45 | 46 | private int trapsPlaced = 0; 47 | private int robotsDestroyed = 0; 48 | private int oreDelivered = 0; 49 | 50 | public int getTrapsPlaced() { 51 | return trapsPlaced; 52 | } 53 | 54 | public int getRobotsDestroyed() { 55 | return robotsDestroyed; 56 | } 57 | 58 | public int getOreDelivered() { 59 | return oreDelivered; 60 | } 61 | 62 | private void convertIntents() { 63 | allAgents.get().forEach(agent -> { 64 | Action intent = agent.getIntent(); 65 | if (intent.isDig()) { 66 | Cell cell = grid.get(intent.getTarget()); 67 | if (!cell.isValid() || cell.hasAccessToHQ()) { 68 | agent.setIntent(new MoveAction(intent.getTarget())); 69 | } else if (agent.getPosition().gameDistanceTo(intent.getTarget()) > Config.AGENT_INTERACT_RADIUS) { 70 | List closest = grid.getClosestTarget(agent.getPosition(), grid.getNeighbours(intent.getTarget())); 71 | 72 | Coord target = closest.get(randomInt(closest.size())); 73 | agent.setIntent(new MoveAction(target)); 74 | } 75 | } else if (intent.isRequest()) { 76 | if (!grid.get(agent.getPosition()).hasAccessToHQ()) { 77 | List closest = grid.getClosestTarget(agent.getPosition(), grid.getHQAccesses()); 78 | Coord target = closest.get(randomInt(closest.size())); 79 | agent.setIntent(new MoveAction(target)); 80 | gameManager.addToGameSummary(agent.getId() + " wanted to request but instead moves to " + target); 81 | } 82 | } 83 | 84 | if (intent.isMove()) { 85 | if (agent.getPosition().getX() == intent.getTarget().getX() && 86 | agent.getPosition().getY() == intent.getTarget().getY()) { 87 | agent.setIntent(Action.NO_ACTION); 88 | gameManager.addToGameSummary(agent.getId() + " stays in place"); 89 | } 90 | } 91 | }); 92 | } 93 | 94 | private void decrementCooldowns() { 95 | deadAgents.forEach(da -> da.decrementRespawnTimer()); 96 | gameManager.getPlayers() 97 | .forEach(Player::decrementCooldowns); 98 | } 99 | 100 | private void destroyAgent(Agent agent) { 101 | agent.die(); 102 | deadAgents.add(agent); 103 | 104 | EventData event = new EventData(); 105 | event.type = EventData.AGENT_DEATH; 106 | event.agent = agent.getId(); 107 | viewerEvents.add(event); 108 | robotsDestroyed++; 109 | gameManager.addTooltip(new Tooltip(agent.getOwner().getIndex(), String.format("Robot %d destroyed!", agent.getId()))); 110 | } 111 | 112 | private Grid generateMap() { 113 | Grid grid = newMap(); 114 | 115 | // Set up ore 116 | int cellCount = Config.MAP_WIDTH * Config.MAP_HEIGHT; 117 | int clustersMin = Math.max(1, (int) (cellCount * Config.MAP_CLUSTER_DISTRIBUTION_MIN)); 118 | int clustersMax = Math.max(clustersMin, (int) (cellCount * Config.MAP_CLUSTER_DISTRIBUTION_MAX)); 119 | int oreClusterCount = randomInt(clustersMin, clustersMax + 1); 120 | 121 | Padding padding = new Padding() 122 | .setLeft(3) 123 | .setRight(2) 124 | .setTop(2) 125 | .setBottom(2); 126 | 127 | int tries = 0; 128 | while (oreClusterCount > 0 && tries < 1000) { 129 | 130 | double factor = Math.pow(random.nextFloat(), Config.MAP_ORE_COEFF_X); 131 | int x = interpolate(padding.left, Config.MAP_WIDTH - padding.right, factor); 132 | int y = randomInt(padding.top, Config.MAP_HEIGHT - padding.bottom); 133 | 134 | if (grid.get(x, y).getOre() == 0) { 135 | Coord clusterCenter = new Coord(x, y); 136 | for (int i = 0; i < Config.MAP_CLUSTER_SIZE; ++i) { 137 | for (int j = 0; j < Config.MAP_CLUSTER_SIZE; ++j) { 138 | x = clusterCenter.getX() + (i - Config.MAP_CLUSTER_SIZE / 2); 139 | y = clusterCenter.getY() + (j - Config.MAP_CLUSTER_SIZE / 2); 140 | 141 | int chances = clusterCenter.manhattanTo(x, y) * 2 + 1; 142 | int hit = randomInt(chances); 143 | if (hit == 0) { 144 | // Place ore in cell 145 | int amount = randomInt(Config.MAP_ORE_IN_CELL_MIN, Config.MAP_ORE_IN_CELL_MAX + 1); 146 | grid.get(x, y).setOre(amount); 147 | } 148 | } 149 | } 150 | oreClusterCount--; 151 | } 152 | tries++; 153 | } 154 | 155 | return grid; 156 | } 157 | 158 | private Grid newMap() { 159 | Grid grid = new Grid(Config.MAP_WIDTH, Config.MAP_HEIGHT, random, gameManager.getPlayerCount()); 160 | 161 | //First column has access to hq 162 | for (int y = 0; y < Config.MAP_HEIGHT; ++y) { 163 | grid.get(0, y).setAccessToHQ(true); 164 | } 165 | return grid; 166 | } 167 | 168 | private List getAllAgentsOfIndex(List> teams, int idx) { 169 | return teams 170 | .stream() 171 | .filter(team -> team.size() > idx) 172 | .map(team -> team.get(idx)) 173 | .collect(Collectors.toList()); 174 | } 175 | 176 | public FrameViewData getCurrentFrameData() { 177 | FrameViewData data = new FrameViewData(); 178 | // Entities 179 | data.agents = allAgents.get() 180 | .map(Game::agentToAgentFrameData) 181 | .collect(Collectors.toList()); 182 | data.events = viewerEvents; 183 | data.scores = gameManager.getPlayers() 184 | .stream() 185 | .map(Player::getScore) 186 | .collect(Collectors.toList()); 187 | return data; 188 | } 189 | 190 | static private AgentData agentToAgentFrameData(Agent agent) { 191 | AgentData data = new AgentData(); 192 | data.id = agent.getId(); 193 | data.x = agent.getPosition().getX(); 194 | data.y = agent.getPosition().getY(); 195 | if (agent.getIntent().getTarget() != null) { 196 | data.tx = agent.getIntent().getTarget().getX(); 197 | data.ty = agent.getIntent().getTarget().getY(); 198 | } 199 | data.message = agent.getMessage(); 200 | data.dead = agent.isDead(); 201 | 202 | return data; 203 | } 204 | 205 | public List getCurrentFrameInfoFor(Player player) { 206 | List lines = new ArrayList<>(); 207 | Player opponent; 208 | 209 | if (player != gameManager.getPlayer(0)) { 210 | opponent = gameManager.getPlayer(0); 211 | } else { 212 | opponent = gameManager.getPlayer(1); 213 | } 214 | 215 | lines.add(Referee.join(player.getOre(), opponent.getOre())); 216 | 217 | // map 218 | for (int y = 0; y < grid.height; ++y) { 219 | List row = new ArrayList<>(grid.width); 220 | for (int x = 0; x < grid.width; ++x) { 221 | Cell c = grid.get(x, y); 222 | 223 | String oreValue; 224 | if (grid.isOreVisibleTo(x, y, player)) { 225 | 226 | oreValue = String.valueOf(c.getOre()); 227 | } else { 228 | oreValue = "?"; 229 | } 230 | 231 | row.add(Referee.join(oreValue, c.isHole() ? 1 : 0)); 232 | } 233 | // 234 | lines.add(row.stream().collect(Collectors.joining(" "))); 235 | } 236 | 237 | List entities = new ArrayList<>(); 238 | allAgents.get().forEach( 239 | agent -> entities.add( 240 | // 241 | agentToString(agent, player) 242 | ) 243 | ); 244 | 245 | entities.addAll(itemsToStrings(player, Item.RADAR)); 246 | entities.addAll(itemsToStrings(player, Item.TRAP)); 247 | 248 | // 249 | lines.add(Referee.join(entities.size(), player.getCooldown(Item.RADAR), player.getCooldown(Item.TRAP))); 250 | lines.addAll(entities); 251 | 252 | return lines; 253 | } 254 | 255 | private List itemsToStrings(Player player, Item item) { 256 | return grid.getItems(item, player) 257 | .stream() 258 | .map( 259 | pos -> Referee.join(pos.getId(), item.getTypeId(), pos.getX(), pos.getY(), item.getTypeId()) 260 | ).collect(Collectors.toList()); 261 | } 262 | 263 | private List> getFilteredTeams(Predicate filter) { 264 | return getTeamList() 265 | .stream() 266 | .map(team -> { 267 | return team 268 | .stream() 269 | .filter(filter) 270 | .collect(Collectors.toList()); 271 | }) 272 | .collect(Collectors.toList()); 273 | } 274 | 275 | public List getGlobalInfoFor(Player player) { 276 | List lines = new ArrayList<>(); 277 | // 278 | lines.add(Referee.join(Config.MAP_WIDTH, Config.MAP_HEIGHT)); 279 | return lines; 280 | } 281 | 282 | public GlobalViewData getGlobalViewData() { 283 | GlobalViewData data = new GlobalViewData(); 284 | data.width = Config.MAP_WIDTH; 285 | data.height = Config.MAP_HEIGHT; 286 | data.agentsPerPlayer = Config.AGENTS_PER_PLAYER; 287 | data.ore = grid.cells.entrySet() 288 | .stream() 289 | .filter(e -> e.getValue().getOre() > 0) 290 | .map(e -> { 291 | Coord coord = e.getKey(); 292 | Cell cell = e.getValue(); 293 | return new CellData(coord, cell.getOre()); 294 | }) 295 | .collect(Collectors.toList()); 296 | 297 | return data; 298 | } 299 | 300 | public Grid getGrid() { 301 | return grid; 302 | } 303 | 304 | private int getLargetSize(List> lists) { 305 | return lists 306 | .stream() 307 | .map(List::size) 308 | .max(Comparator.naturalOrder()) 309 | .get(); 310 | } 311 | 312 | private List> getMoversByTeam() { 313 | return getFilteredTeams(agent -> agent.getIntent().isMove()); 314 | } 315 | 316 | private List> getTeamList() { 317 | return gameManager.getActivePlayers() 318 | .stream() 319 | .map(Player::getAgents) 320 | .collect(Collectors.toList()); 321 | } 322 | 323 | public void init(long seed) { 324 | 325 | viewerEvents = new ArrayList<>(); 326 | deadAgents = new ArrayList<>(Config.AGENTS_PER_PLAYER * gameManager.getPlayerCount()); 327 | allAgents = () -> gameManager.getPlayers().stream().flatMap(p -> p.getAgents().stream()); 328 | 329 | random = new Random(seed); 330 | } 331 | 332 | public void initGameState(String state) { 333 | if (state == null) { 334 | grid = generateMap(); 335 | initPlayers(); 336 | } else { 337 | // The code below was exclusively for debugging the game's engine. 338 | int[] agentCount = new int[2]; 339 | List commands = Stream.of(state.split("\n|;")) 340 | .map(String::trim) 341 | .filter(str -> !str.isEmpty()) 342 | .collect(Collectors.toList()); 343 | commands.forEach(command -> { 344 | String[] tokens = command.split(" "); 345 | if (tokens[0].startsWith("ROBOT")) { 346 | int playerIdx = Integer.parseInt(tokens[0].substring(5, 6)); 347 | agentCount[playerIdx]++; 348 | } 349 | }); 350 | 351 | int agentsPerPlayer = Math.max(Math.max(agentCount[0], agentCount[1]), 1); 352 | Config.AGENTS_PER_PLAYER = agentsPerPlayer; 353 | grid = newMap(); 354 | commands.forEach(command -> { 355 | String[] tokens = command.split(" "); 356 | 357 | if (tokens[0].startsWith("ROBOT")) { 358 | int x = Integer.valueOf(tokens[1]); 359 | int y = Integer.valueOf(tokens[2]); 360 | Coord pos = new Coord(x, y); 361 | int playerIdx = Integer.parseInt(tokens[0].substring(5, 6)); 362 | Item carry = Item.valueOf(tokens[3]); 363 | Player player = gameManager.getPlayers().get(playerIdx); 364 | Agent agent = new Agent(agentsPerPlayer * player.getIndex() + player.getAgents().size(), player, pos); 365 | agent.setInventory(carry); 366 | player.addAgent(agent); 367 | if (carry != Item.NOTHING) { 368 | EventData event = new EventData(); 369 | event.type = EventData.REQUEST; 370 | event.item = carry.getTypeId(); 371 | event.agent = agent.getId(); 372 | viewerEvents.add(event); 373 | } 374 | 375 | } 376 | }); 377 | 378 | for (int i = 0; i < agentsPerPlayer; ++i) { 379 | for (Player player : gameManager.getPlayers()) { 380 | if (player.getAgents().size() <= i) { 381 | Coord pos = new Coord(0, 0); 382 | Agent agent = new Agent(Config.AGENTS_PER_PLAYER * player.getIndex() + i, player, pos); 383 | player.addAgent(agent); 384 | destroyAgent(agent); 385 | } 386 | } 387 | } 388 | 389 | commands.forEach(command -> { 390 | String[] tokens = command.split(" "); 391 | 392 | if ("COOLDOWN".equals(tokens[0])) { 393 | int playerIdx = Integer.parseInt(tokens[1]); 394 | Item item = Item.valueOf(tokens[2]); 395 | int cooldown = Integer.parseInt(tokens[3]); 396 | gameManager.getPlayer(playerIdx).setCooldown(item, cooldown); 397 | return; 398 | } else if ("SCORE".equals(tokens[0])) { 399 | int playerIdx = Integer.parseInt(tokens[1]); 400 | int score = Integer.parseInt(tokens[2]); 401 | gameManager.getPlayer(playerIdx).setOre(score); 402 | return; 403 | } 404 | 405 | int x = Integer.valueOf(tokens[1]); 406 | int y = Integer.valueOf(tokens[2]); 407 | Coord pos = new Coord(x, y); 408 | if (!tokens[0].startsWith("ROBOT")) { 409 | Item item = Item.valueOf(tokens[0]); 410 | if (item == Item.ORE) { 411 | grid.get(x, y).setOre(Integer.parseInt(tokens[3])); 412 | } else { 413 | Cell cell = grid.get(x, y); 414 | cell.setHole(true); 415 | 416 | int playerIdx = 0; 417 | if (item != Item.NOTHING) { 418 | playerIdx = Integer.parseInt(tokens[3]); 419 | grid.insertItem(pos, item, gameManager.getPlayer(playerIdx)); 420 | } 421 | 422 | EventData event = new EventData(); 423 | event.type = EventData.BURY; 424 | event.x = pos.getX(); 425 | event.y = pos.getY(); 426 | event.agent = gameManager.getPlayer(playerIdx).getAgents().get(0).getId(); 427 | event.item = item.getTypeId(); 428 | viewerEvents.add(event); 429 | } 430 | } 431 | }); 432 | 433 | } 434 | 435 | } 436 | 437 | public void initPlayers() { 438 | int spaces = Config.MAP_HEIGHT / 2; 439 | 440 | LinkedList available = new LinkedList<>(); 441 | for (int i = 0; i < spaces; ++i) { 442 | available.add(i); 443 | } 444 | Collections.shuffle(available, random); 445 | for (int i = 0; i < Config.AGENTS_PER_PLAYER; ++i) { 446 | Integer y = null; 447 | if (!available.isEmpty()) { 448 | y = available.poll(); 449 | } else { 450 | y = randomInt(spaces); 451 | } 452 | 453 | for (Player player : gameManager.getPlayers()) { 454 | Coord pos = new Coord(0, y * 2 + (Config.AGENTS_START_PACKED ? 0 : player.getIndex())); 455 | Agent agent = new Agent(Config.AGENTS_PER_PLAYER * player.getIndex() + i, player, pos); 456 | player.addAgent(agent); 457 | } 458 | } 459 | } 460 | 461 | private int interpolate(int low, int high, double factor) { 462 | return (int) (low + factor * (high - low)); 463 | } 464 | 465 | public void performGameUpdate(int turn) { 466 | convertIntents(); 467 | 468 | resolveTraps(); 469 | resolveDigs(); 470 | resolveRequests(); 471 | 472 | decrementCooldowns(); 473 | 474 | resolveMoves(); 475 | resolveDelivers(); 476 | respawnDeadAgents(); 477 | } 478 | 479 | /** 480 | * Get a random value within [0;high[ 481 | */ 482 | public int randomInt(int high) { 483 | return randomInt(0, high); 484 | } 485 | 486 | /** 487 | * Get a random value within [low;high[ 488 | */ 489 | private int randomInt(int low, int high) { 490 | return low + random.nextInt(high - low); 491 | } 492 | 493 | /** 494 | * Called before player outputs are handled 495 | */ 496 | public void resetGameTurnData() { 497 | for (Player p : gameManager.getPlayers()) { 498 | p.reset(); 499 | } 500 | viewerEvents.clear(); 501 | } 502 | 503 | private void resolveDelivers() { 504 | allAgents.get() 505 | .filter(agent -> !agent.isDead() && grid.get(agent.getPosition()).hasAccessToHQ()) 506 | .forEach(agent -> { 507 | if (agent.getInventory() == Item.ORE) { 508 | agent.getOwner().scoreOre(); 509 | agent.setInventory(Item.NOTHING); 510 | 511 | EventData event = new EventData(); 512 | event.type = EventData.GIVE_ORE; 513 | event.agent = agent.getId(); 514 | viewerEvents.add(event); 515 | oreDelivered++; 516 | } 517 | }); 518 | } 519 | 520 | private void resolveDigs() { 521 | Map>> digRequests = allAgents 522 | .get() 523 | .map(Agent::getIntent) 524 | .filter(Action::isDig) 525 | .map(Action::getTarget) 526 | .distinct() 527 | .collect( 528 | Collectors.toMap( 529 | Function.identity(), 530 | c -> getFilteredTeams(agent -> { 531 | return agent.getIntent().isDig() && agent.getIntent().getTarget().equals(c); 532 | }) 533 | ) 534 | ); 535 | 536 | digRequests.forEach((pos, teams) -> { 537 | Cell cell = grid.get(pos); 538 | int maxAgentsInTeam = getLargetSize(teams); 539 | 540 | Map> buried = new HashMap<>(); 541 | 542 | //Radar destruction 543 | teams 544 | .stream() 545 | .filter(team -> !team.isEmpty()) 546 | .map(team -> team.get(0)) 547 | .forEach(agent -> { 548 | boolean destroyed = grid.destroyRadar(pos, agent.getOwner()); 549 | if (destroyed) { 550 | EventData event = new EventData(); 551 | event.type = EventData.RADAR_DESTRUCTION; 552 | event.x = pos.getX(); 553 | event.y = pos.getY(); 554 | event.agent = agent.getId(); 555 | viewerEvents.add(event); 556 | } 557 | }); 558 | 559 | //Ore collection 560 | for (int idx = 0; idx < maxAgentsInTeam; ++idx) { 561 | List currentDiggers = getAllAgentsOfIndex(teams, idx); 562 | List oreCollectors = new ArrayList<>(); 563 | 564 | currentDiggers 565 | .stream() 566 | .forEach(agent -> { 567 | EventData event = new EventData(); 568 | event.type = EventData.BURY; 569 | event.x = pos.getX(); 570 | event.y = pos.getY(); 571 | event.agent = agent.getId(); 572 | event.item = agent.getInventory().getTypeId(); 573 | viewerEvents.add(event); 574 | 575 | if (agent.getInventory() != Item.ORE) { 576 | oreCollectors.add(agent); 577 | } 578 | if (agent.getInventory() != Item.NOTHING) { 579 | buried.putIfAbsent(agent.getOwner().getIndex(), new ArrayList<>()); 580 | buried.get(agent.getOwner().getIndex()).add(agent.getInventory()); 581 | agent.setInventory(Item.NOTHING); 582 | } 583 | if (event.item == Item.TRAP.getTypeId()) { 584 | trapsPlaced++; 585 | } 586 | }); 587 | 588 | // Drill hole 589 | if (!cell.isHole()) { 590 | cell.setHole(true); 591 | } 592 | if (cell.getOre() > 0) { 593 | cell.reduceOre(oreCollectors.size()); 594 | oreCollectors 595 | .stream() 596 | .forEach(agent -> { 597 | agent.receiveOre(); 598 | 599 | EventData event = new EventData(); 600 | event.type = EventData.GET_ORE; 601 | event.x = pos.getX(); 602 | event.y = pos.getY(); 603 | event.agent = agent.getId(); 604 | viewerEvents.add(event); 605 | }); 606 | } 607 | } 608 | 609 | // Item insertion 610 | buried.forEach((playerIndex, itemSet) -> { 611 | for (Item item : itemSet) { 612 | if (item == Item.ORE) { 613 | cell.incrementOre(); 614 | } else { 615 | grid.insertItem(pos, item, gameManager.getPlayer(playerIndex)); 616 | } 617 | } 618 | }); 619 | }); 620 | 621 | } 622 | 623 | private void resolveMoves() { 624 | List> movers = getMoversByTeam(); 625 | 626 | for (List team : movers) { 627 | for (Agent agent : team) { 628 | MoveAction action = (MoveAction) agent.getIntent(); 629 | 630 | List obstacles = Config.ROBOTS_CAN_OCCUPY_SAME_CELL 631 | ? Collections.emptyList() 632 | : team.stream() 633 | .map(Agent::getPosition) 634 | .collect(Collectors.toList()); 635 | 636 | List path = grid.findPath( 637 | agent.getPosition(), 638 | action.getTarget(), 639 | obstacles 640 | ); 641 | action.setPath(path); 642 | if (!path.isEmpty()) { 643 | agent.setPosition(path.get(path.size() - 1)); 644 | } 645 | } 646 | } 647 | } 648 | 649 | private static int compareByInventorySpace(Agent a, Agent b) { 650 | boolean hasItemA = a.getInventory() != Item.NOTHING; 651 | boolean hasItemB = b.getInventory() != Item.NOTHING; 652 | if (hasItemA && !hasItemB) 653 | return 1; 654 | if (!hasItemA && hasItemB) 655 | return -1; 656 | return 0; 657 | } 658 | 659 | private void resolveRequests() { 660 | allAgents.get() 661 | .filter(agent -> agent.getIntent().isRequest()) 662 | .sorted(Game::compareByInventorySpace) 663 | .forEach(agent -> { 664 | Item item = agent.getIntent().getItem(); 665 | gameManager.addToGameSummary(agent.getId() + " requested a " + item.name()); 666 | if (agent.getOwner().getCooldown(item) == 0) { 667 | agent.getOwner().startCooldown(item); 668 | agent.setInventory(item); 669 | gameManager.addToGameSummary("and was given one\n"); 670 | 671 | EventData event = new EventData(); 672 | event.type = EventData.REQUEST; 673 | event.item = item.getTypeId(); 674 | event.agent = agent.getId(); 675 | viewerEvents.add(event); 676 | } else { 677 | gameManager.addToGameSummary("but cooldown is " + agent.getOwner().getCooldown(item) + "\n"); 678 | } 679 | }); 680 | 681 | } 682 | 683 | private void resolveTraps() { 684 | Set triggeredTraps = allAgents 685 | .get() 686 | .map(Agent::getIntent) 687 | .filter(intent -> intent.isDig()) 688 | .filter(intent -> grid.hasTrap(intent.getTarget())) 689 | .map(Action::getTarget) 690 | .collect(Collectors.toSet()); 691 | 692 | Set deathZone; 693 | 694 | if (Config.TRAP_CHAIN_REACTION) { 695 | deathZone = new HashSet<>(triggeredTraps); 696 | Queue exploding = new LinkedList(triggeredTraps); 697 | while (!exploding.isEmpty()) { 698 | Coord trap = exploding.poll(); 699 | 700 | grid.getCellsInRange(trap, Config.TRAP_RANGE) 701 | .stream() 702 | .forEach(c -> { 703 | deathZone.add(c); 704 | if (grid.hasTrap(c) && !triggeredTraps.contains(c)) { 705 | exploding.add(c); 706 | triggeredTraps.add(c); 707 | } 708 | }); 709 | } 710 | } else { 711 | deathZone = triggeredTraps 712 | .stream() 713 | .flatMap(coord -> { 714 | return grid.getCellsInRange(coord, Config.TRAP_RANGE).stream(); 715 | }) 716 | .collect(Collectors.toSet()); 717 | } 718 | deathZone 719 | .stream() 720 | .forEach(coord -> grid.removeTrap(coord)); 721 | 722 | allAgents 723 | .get() 724 | .filter(agent -> !agent.isDead() && deathZone.contains(agent.getPosition())) 725 | .collect(Collectors.toList()) 726 | .forEach(this::destroyAgent); 727 | 728 | for (Coord trigger : triggeredTraps) { 729 | EventData event = new EventData(); 730 | event.type = EventData.EXPLOSION; 731 | event.x = trigger.getX(); 732 | event.y = trigger.getY(); 733 | viewerEvents.add(event); 734 | } 735 | } 736 | 737 | private void respawnDeadAgents() { 738 | deadAgents 739 | .stream() 740 | .filter(Agent::shouldRespawn) 741 | .collect(Collectors.toList()) 742 | .stream() 743 | .forEach(da -> { 744 | deadAgents.remove(da); 745 | da.factoryReset(); 746 | 747 | EventData event = new EventData(); 748 | event.type = EventData.RESPAWN; 749 | event.agent = da.getId(); 750 | viewerEvents.add(event); 751 | }); 752 | 753 | } 754 | 755 | private String agentToString(Agent agent, Player player) { 756 | return Referee.join( 757 | agent.getId(), 758 | player == agent.getOwner() ? Config.TYPE_MY_AGENT : Config.TYPE_FOE_AGENT, 759 | agent.isDead() ? -1 : agent.getPosition().getX(), 760 | agent.isDead() ? -1 : agent.getPosition().getY(), 761 | player == agent.getOwner() ? agent.getInventory().getTypeId() : Item.NOTHING.getTypeId() 762 | ); 763 | } 764 | 765 | public boolean gameOver(int turn) { 766 | if (getRemainingOre() == 0) { 767 | return true; 768 | } 769 | // Player with most ore is only player with live bots 770 | Player playerWithMostOre = getPlayerWithMostOre(); 771 | if (playerWithMostOre != null) { 772 | return gameManager.getPlayers().stream() 773 | .filter(player -> player != playerWithMostOre) 774 | .flatMap(player -> player.getAgents().stream()) 775 | .allMatch(Agent::isDead); 776 | } 777 | 778 | // No bots left 779 | return gameManager.getPlayers().stream() 780 | .flatMap(player -> player.getAgents().stream()) 781 | .allMatch(Agent::isDead); 782 | } 783 | 784 | private Player getPlayerWithMostOre() { 785 | int most = 0; 786 | Player result = null; 787 | for (Player player : gameManager.getPlayers()) { 788 | if (result == null || player.getOre() > most) { 789 | most = player.getOre(); 790 | result = player; 791 | } else if (player.getOre() == most) { 792 | return null; 793 | } 794 | } 795 | return result; 796 | } 797 | 798 | private int getRemainingOre() { 799 | int remainingOre = 0; 800 | 801 | for (Cell c : grid.cells.values()) { 802 | remainingOre += c.getOre(); 803 | } 804 | for (Player player : gameManager.getPlayers()) { 805 | for (Agent agent : player.getAgents()) { 806 | if (agent.getInventory() == Item.ORE) { 807 | remainingOre += 1; 808 | } 809 | } 810 | } 811 | ; 812 | return remainingOre; 813 | } 814 | } 815 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Grid.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.PriorityQueue; 13 | import java.util.Queue; 14 | import java.util.Random; 15 | import java.util.Set; 16 | 17 | import com.codingame.game.Player; 18 | 19 | public class Grid { 20 | int width, height; 21 | Map cells; 22 | Map>> items; 23 | List> traps, radars; 24 | 25 | Random random; 26 | 27 | public Grid(int width, int height, Random random, int playerCount) { 28 | this.random = random; 29 | this.width = width; 30 | this.height = height; 31 | cells = new HashMap<>(); 32 | items = new HashMap<>(); 33 | traps = new ArrayList<>(playerCount); 34 | radars = new ArrayList<>(playerCount); 35 | items.put(Item.RADAR, radars); 36 | items.put(Item.TRAP, traps); 37 | 38 | for (int i = 0; i < playerCount; ++i) { 39 | radars.add(new HashSet<>()); 40 | traps.add(new HashSet<>()); 41 | } 42 | 43 | for (int y = 0; y < height; ++y) { 44 | for (int x = 0; x < width; ++x) { 45 | Coord coord = new Coord(x, y); 46 | Cell cell = new Cell(); 47 | cells.put(coord, cell); 48 | } 49 | } 50 | } 51 | 52 | public Cell get(int x, int y) { 53 | return cells.getOrDefault(new Coord(x, y), Cell.NO_CELL); 54 | } 55 | 56 | public List unrollPath(PathNode current) { 57 | List path = new ArrayList<>(); 58 | while (!current.isStart()) { 59 | path.add(0, current.coord); 60 | current = current.previous; 61 | } 62 | return path; 63 | } 64 | 65 | // This greedy best first search was originally designed with a different control scheme in mind for the robots 66 | public List findPath(Coord start, Coord target, List restricted) { 67 | PriorityQueue queue = new PriorityQueue<>(byDistanceTo(target)); 68 | Set computed = new HashSet<>(); 69 | 70 | List closest = new ArrayList<>(); 71 | int closestBy = 0; 72 | 73 | queue.add(new PathNode(start)); 74 | computed.add(start); 75 | 76 | while (!queue.isEmpty()) { 77 | PathNode current = queue.poll(); 78 | if (current.coord.equals(target)) { 79 | return unrollPath(current); 80 | } else { 81 | int distance = current.coord.gameDistanceTo(target); 82 | if (closest.isEmpty() || closestBy > distance) { 83 | closest.clear(); 84 | closest.add(current); 85 | closestBy = distance; 86 | } else if (!closest.isEmpty() && closestBy == distance) { 87 | closest.add(current); 88 | } 89 | } 90 | if (current.steps < Config.AGENTS_MOVE_DISTANCE) { 91 | List neighbours = getNeighbours(current.coord); 92 | for (Coord neigh : neighbours) { 93 | if (!restricted.contains(neigh) && !computed.contains(neigh)) { 94 | queue.add(new PathNode(neigh, current)); 95 | computed.add(neigh); 96 | } 97 | } 98 | } 99 | } 100 | if (closest.isEmpty()) { 101 | return Collections.emptyList(); 102 | } 103 | return unrollPath(closest.get(random.nextInt(closest.size()))); 104 | } 105 | 106 | public List getNeighbours(Coord pos) { 107 | List neighs = new ArrayList<>(); 108 | for (Coord delta : Config.ADJACENCY.deltas) { 109 | Coord n = new Coord(pos.getX() + delta.getX(), pos.getY() + delta.getY()); 110 | if (get(n) != Cell.NO_CELL) { 111 | neighs.add(n); 112 | } 113 | } 114 | return neighs; 115 | } 116 | 117 | Cell get(Coord n) { 118 | return get(n.getX(), n.getY()); 119 | } 120 | 121 | private Comparator byDistanceTo(Coord target) { 122 | return Comparator.comparing(node -> node.coord.gameDistanceTo(target)); 123 | 124 | } 125 | 126 | public List getClosestTarget(Coord from, List targets) { 127 | List closest = new ArrayList<>(); 128 | int closestBy = 0; 129 | for (Coord neigh : targets) { 130 | int distance = from.gameDistanceTo(neigh); 131 | if (closest.isEmpty() || closestBy > distance) { 132 | closest.clear(); 133 | closest.add(neigh); 134 | closestBy = distance; 135 | } else if (!closest.isEmpty() && closestBy == distance) { 136 | closest.add(neigh); 137 | } 138 | } 139 | return closest; 140 | } 141 | 142 | public boolean hasTrap(Coord pos) { 143 | return traps 144 | .stream() 145 | .anyMatch(list -> list.contains(pos)); 146 | } 147 | 148 | public void removeTrap(Coord pos) { 149 | traps.forEach(list -> list.remove(pos)); 150 | } 151 | 152 | public boolean destroyRadar(Coord pos, Player destroyer) { 153 | boolean destroyed = false; 154 | for (int i = 0; i < radars.size(); ++i) { 155 | if (i != destroyer.getIndex()) { 156 | destroyed |= radars.get(i).remove(pos); 157 | } 158 | } 159 | return destroyed; 160 | } 161 | 162 | public void insertItem(Coord pos, Item item, Player itemOwner) { 163 | Set itemSet = getItems(item, itemOwner); 164 | itemSet.add(new ItemCoord(pos.getX(), pos.getY())); 165 | } 166 | 167 | public List getHQAccesses() { 168 | List coords = new ArrayList<>(Config.MAP_HEIGHT); 169 | for (int y = 0; y < Config.MAP_HEIGHT; ++y) { 170 | coords.add(new Coord(0, y)); 171 | } 172 | return coords; 173 | } 174 | 175 | public boolean isOreVisibleTo(int x, int y, Player player) { 176 | return radars.get(player.getIndex()) 177 | .stream() 178 | .anyMatch(pos -> { 179 | if (Config.EUCLIDEAN_RADAR) { 180 | return pos.euclideanTo(x, y) <= Config.RADAR_RANGE; 181 | } else { 182 | return pos.gameDistanceTo(x, y) <= Config.RADAR_RANGE; 183 | } 184 | }); 185 | } 186 | 187 | public Set getItems(Item item, Player player) { 188 | return items 189 | .get(item) 190 | .get(player.getIndex()); 191 | } 192 | 193 | public Collection getCellsInRange(Coord coord, int range) { 194 | Set result = new HashSet<>(); 195 | Queue queue = new LinkedList<>(); 196 | queue.add(new PathNode(coord)); 197 | while (!queue.isEmpty()) { 198 | PathNode e = queue.poll(); 199 | result.add(e.coord); 200 | if (e.steps < range) { 201 | getNeighbours(e.coord) 202 | .stream() 203 | .forEach(neigh -> { 204 | if (!result.contains(neigh)) { 205 | queue.add(new PathNode(neigh, e)); 206 | } 207 | }); 208 | } 209 | } 210 | 211 | return result; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/Item.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | public enum Item { 4 | TRAP(Config.TYPE_TRAP), RADAR(Config.TYPE_RADAR), ORE(Config.TYPE_ORE), NOTHING(Config.TYPE_NONE); 5 | 6 | private int typeId; 7 | 8 | private Item(int typeId) { 9 | this.typeId = typeId; 10 | } 11 | public int getTypeId() { 12 | return typeId; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/ItemCoord.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | public class ItemCoord extends Coord { 4 | static int INSTANCE_COUNT = 0; 5 | 6 | private int id; 7 | 8 | public ItemCoord(int x, int y) { 9 | super(x, y); 10 | this.id = INSTANCE_COUNT++ + Config.AGENTS_PER_PLAYER * 2; 11 | } 12 | 13 | public int getId() { 14 | return id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/PathNode.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019; 2 | 3 | public class PathNode { 4 | Coord coord; 5 | PathNode previous; 6 | int steps; 7 | 8 | public PathNode(Coord coord, PathNode previous) { 9 | this.coord = coord; 10 | this.previous = previous; 11 | this.steps = previous.steps + 1; 12 | } 13 | 14 | public PathNode(Coord coord) { 15 | this.coord = coord; 16 | } 17 | 18 | public boolean isStart() { 19 | return previous == null; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/action/Action.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019.action; 2 | 3 | import com.codingame.utg2019.Coord; 4 | import com.codingame.utg2019.Item; 5 | 6 | public interface Action { 7 | Action NO_ACTION = new Action() { 8 | 9 | @Override 10 | public boolean isMove() { 11 | return false; 12 | } 13 | 14 | @Override 15 | public boolean isDig() { 16 | return false; 17 | } 18 | 19 | @Override 20 | public Coord getTarget() { 21 | return null; 22 | } 23 | 24 | @Override 25 | public boolean isRequest() { 26 | return false; 27 | } 28 | 29 | @Override 30 | public Item getItem() { 31 | return Item.NOTHING; 32 | } 33 | 34 | }; 35 | 36 | public Coord getTarget(); 37 | 38 | public boolean isMove(); 39 | 40 | public boolean isDig(); 41 | 42 | public boolean isRequest(); 43 | 44 | public Item getItem(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/action/ActionException.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019.action; 2 | 3 | @SuppressWarnings("serial") 4 | public class ActionException extends Exception { 5 | 6 | public ActionException(String message) { 7 | super(message); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/action/DigAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019.action; 2 | 3 | import com.codingame.utg2019.Coord; 4 | import com.codingame.utg2019.Item; 5 | 6 | public class DigAction implements Action { 7 | 8 | private Coord target; 9 | 10 | public Coord getTarget() { 11 | return target; 12 | } 13 | 14 | public DigAction(Coord target) { 15 | this.target = target; 16 | } 17 | 18 | @Override 19 | public boolean isMove() { 20 | return false; 21 | } 22 | 23 | @Override 24 | public boolean isDig() { 25 | return true; 26 | } 27 | 28 | @Override 29 | public boolean isRequest() { 30 | return false; 31 | } 32 | 33 | @Override 34 | public Item getItem() { 35 | return Item.NOTHING; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/action/MoveAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019.action; 2 | 3 | import java.util.List; 4 | 5 | import com.codingame.utg2019.Coord; 6 | import com.codingame.utg2019.Item; 7 | 8 | public class MoveAction implements Action { 9 | 10 | private Coord destination; 11 | private List path; 12 | 13 | public Coord getTarget() { 14 | return destination; 15 | } 16 | 17 | public MoveAction(Coord destination) { 18 | this.destination = destination; 19 | } 20 | 21 | public void setPath(List path) { 22 | this.path = path; 23 | } 24 | 25 | @Override 26 | public boolean isMove() { 27 | return true; 28 | } 29 | 30 | @Override 31 | public boolean isDig() { 32 | return false; 33 | } 34 | 35 | @Override 36 | public boolean isRequest() { 37 | return false; 38 | } 39 | 40 | @Override 41 | public Item getItem() { 42 | return Item.NOTHING; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utg2019/action/RequestAction.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utg2019.action; 2 | 3 | import java.util.List; 4 | 5 | import com.codingame.utg2019.Coord; 6 | import com.codingame.utg2019.Item; 7 | 8 | public class RequestAction implements Action { 9 | 10 | private Item item; 11 | 12 | public RequestAction(Item item) { 13 | this.item = item; 14 | } 15 | 16 | @Override 17 | public boolean isMove() { 18 | return false; 19 | } 20 | 21 | @Override 22 | public boolean isDig() { 23 | return false; 24 | } 25 | 26 | @Override 27 | public boolean isRequest() { 28 | return true; 29 | } 30 | 31 | @Override 32 | public Coord getTarget() { 33 | return null; 34 | } 35 | @Override 36 | public Item getItem() { 37 | return item; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utils/Padding.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utils; 2 | 3 | public class Padding { 4 | public int left, right, top, bottom; 5 | 6 | public Padding() { 7 | } 8 | 9 | public Padding setLeft(int left) { 10 | this.left = left; 11 | return this; 12 | } 13 | 14 | public Padding setRight(int right) { 15 | this.right = right; 16 | return this; 17 | } 18 | 19 | public Padding setTop(int top) { 20 | this.top = top; 21 | return this; 22 | } 23 | 24 | public Padding setBottom(int bottom) { 25 | this.bottom = bottom; 26 | return this; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/utils/Vector.java: -------------------------------------------------------------------------------- 1 | package com.codingame.utils; 2 | 3 | public class Vector { 4 | private final double x, y; 5 | 6 | @Override 7 | public int hashCode() { 8 | final int prime = 31; 9 | int result = 1; 10 | long temp; 11 | temp = Double.doubleToLongBits(x); 12 | result = prime * result + (int) (temp ^ (temp >>> 32)); 13 | temp = Double.doubleToLongBits(y); 14 | result = prime * result + (int) (temp ^ (temp >>> 32)); 15 | return result; 16 | } 17 | 18 | @Override 19 | public boolean equals(Object obj) { 20 | if (this == obj) return true; 21 | if (obj == null) return false; 22 | if (getClass() != obj.getClass()) return false; 23 | Vector other = (Vector) obj; 24 | if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) return false; 25 | if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) return false; 26 | return true; 27 | } 28 | 29 | public Vector(double x, double y) { 30 | this.x = x; 31 | this.y = y; 32 | } 33 | 34 | public Vector(Vector a, Vector b) { 35 | this.x = b.x - a.x; 36 | this.y = b.y - a.y; 37 | } 38 | 39 | public Vector(double angle) { 40 | this.x = Math.cos(angle); 41 | this.y = Math.sin(angle); 42 | } 43 | 44 | public Vector rotate(double angle) { 45 | double nx = (x * Math.cos(angle)) - (y * Math.sin(angle)); 46 | double ny = (x * Math.sin(angle)) + (y * Math.cos(angle)); 47 | 48 | return new Vector(nx, ny); 49 | }; 50 | 51 | public boolean equals(Vector v) { 52 | return v.getX() == x && v.getY() == y; 53 | } 54 | 55 | public Vector round() { 56 | return new Vector((int) Math.round(this.x), (int) Math.round(this.y)); 57 | } 58 | 59 | public Vector truncate() { 60 | return new Vector((int) this.x, (int) this.y); 61 | } 62 | 63 | public double getX() { 64 | return x; 65 | } 66 | 67 | public double getY() { 68 | return y; 69 | } 70 | 71 | public double distance(Vector v) { 72 | return Math.sqrt((v.x - x) * (v.x - x) + (v.y - y) * (v.y - y)); 73 | } 74 | 75 | public boolean inRange(Vector v, double range) { 76 | return (v.x - x) * (v.x - x) + (v.y - y) * (v.y - y) <= range * range; 77 | } 78 | 79 | public Vector add(Vector v) { 80 | return new Vector(x + v.x, y + v.y); 81 | } 82 | 83 | public Vector mult(double factor) { 84 | return new Vector(x * factor, y * factor); 85 | } 86 | 87 | public Vector sub(Vector v) { 88 | return new Vector(this.x - v.x, this.y - v.y); 89 | } 90 | 91 | public double length() { 92 | return Math.sqrt(x * x + y * y); 93 | } 94 | 95 | public double lengthSquared() { 96 | return x * x + y * y; 97 | } 98 | 99 | public Vector normalize() { 100 | double length = length(); 101 | if (length == 0) 102 | return new Vector(0, 0); 103 | return new Vector(x / length, y / length); 104 | } 105 | 106 | public double dot(Vector v) { 107 | return x * v.x + y * v.y; 108 | } 109 | 110 | public double angle() { 111 | return Math.atan2(y, x); 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return "[" + x + ", " + y + "]"; 117 | } 118 | 119 | public String toIntString() { 120 | return (int) x + " " + (int) y; 121 | } 122 | 123 | public Vector project(Vector force) { 124 | Vector normalize = this.normalize(); 125 | return normalize.mult(normalize.dot(force)); 126 | } 127 | 128 | public final Vector cross(double s) { 129 | return new Vector(-s * y, s * x); 130 | } 131 | 132 | public Vector hsymmetric(double center) { 133 | return new Vector(2 * center - this.x, this.y); 134 | } 135 | 136 | public Vector vsymmetric(double center) { 137 | return new Vector(this.x, 2 * center - this.y); 138 | } 139 | 140 | public Vector vsymmetric() { 141 | return new Vector(this.x, -this.y); 142 | } 143 | 144 | public Vector hsymmetric() { 145 | return new Vector(-this.x, this.y); 146 | } 147 | 148 | public Vector symmetric() { 149 | return symmetric(new Vector(0, 0)); 150 | } 151 | 152 | public Vector symmetric(Vector center) { 153 | return new Vector(center.x * 2 - this.x, center.y * 2 - this.y); 154 | } 155 | 156 | public boolean withinBounds(double minx, double miny, double maxx, double maxy) { 157 | return x >= minx && x < maxx && y >= miny && y < maxy; 158 | } 159 | 160 | public boolean isZero() { 161 | return x == 0 && y == 0; 162 | } 163 | 164 | public Vector symmetricTruncate(Vector origin) { 165 | return sub(origin).truncate().add(origin); 166 | } 167 | 168 | } -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/AgentData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | public class AgentData { 4 | public int id; 5 | public int x, y; 6 | public Integer tx, ty; 7 | public int item; 8 | public boolean dead; 9 | public String message; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/CellData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import com.codingame.utg2019.Coord; 4 | 5 | public class CellData { 6 | int x; 7 | int y; 8 | int ore; 9 | 10 | public CellData(Coord coord, int ore) { 11 | x = coord.getX(); 12 | y = coord.getY(); 13 | this.ore = ore; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/EventData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | public class EventData { 4 | public static final int REQUEST = 0; 5 | public static final int RADAR_DESTRUCTION = 1; 6 | public static final int BURY = 2; 7 | public static final int GET_ORE = 3; 8 | public static final int GIVE_ORE = 4; 9 | public static final int EXPLOSION = 5; 10 | public static final int AGENT_DEATH = 6; 11 | public static final int RESPAWN = 7; 12 | 13 | public Integer type, item, agent, x, y; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/FrameViewData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.List; 4 | 5 | public class FrameViewData { 6 | 7 | public List agents; 8 | public List events; 9 | public List scores; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/GlobalViewData.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class GlobalViewData { 7 | public int width; 8 | public int height; 9 | public int agentsPerPlayer; 10 | public List ore; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/Point.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | public class Point { 4 | int x, y; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/ViewModule.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import com.codingame.game.Referee; 8 | import com.codingame.gameengine.core.AbstractPlayer; 9 | import com.codingame.gameengine.core.GameManager; 10 | import com.codingame.gameengine.core.Module; 11 | import com.codingame.utg2019.Coord; 12 | import com.google.inject.Inject; 13 | 14 | public class ViewModule implements Module { 15 | 16 | private GameManager gameManager; 17 | private Referee referee; 18 | 19 | @Inject 20 | ViewModule(GameManager gameManager) { 21 | this.gameManager = gameManager; 22 | gameManager.registerModule(this); 23 | } 24 | 25 | public void setReferee(Referee referee) { 26 | this.referee = referee; 27 | } 28 | 29 | @Override 30 | public final void onGameInit() { 31 | sendGlobalData(); 32 | sendFrameData(); 33 | } 34 | 35 | private void sendFrameData() { 36 | FrameViewData data = referee.getCurrentFrameData(); 37 | gameManager.setViewData("graphics", serialize(data)); 38 | } 39 | 40 | private void sendGlobalData() { 41 | GlobalViewData data = referee.getGlobalData(); 42 | gameManager.setViewGlobalData("graphics", serialize(data)); 43 | 44 | } 45 | 46 | @Override 47 | public final void onAfterGameTurn() { 48 | sendFrameData(); 49 | } 50 | 51 | @Override 52 | public final void onAfterOnEnd() { 53 | sendFrameData(); 54 | } 55 | 56 | private String serialize(GlobalViewData data) { 57 | List lines = new ArrayList<>(); 58 | lines.add(Referee.join(data.width, data.height, data.agentsPerPlayer, data.ore.size())); 59 | 60 | data.ore.stream() 61 | .forEach(ore -> { 62 | lines.add(Referee.join(ore.x, ore.y, ore.ore)); 63 | }); 64 | 65 | return lines.stream().collect(Collectors.joining("\n")); 66 | } 67 | 68 | private String serialize(FrameViewData data) { 69 | List lines = new ArrayList<>(); 70 | lines.add(Referee.join(serialize(data.scores), data.events.size())); 71 | data.agents.stream() 72 | .sorted((a, b) -> a.id - b.id) 73 | .forEach(agent -> { 74 | lines.add(Referee.join(agent.id, agent.x, agent.y, agent.item, agent.dead ? 1 : 0)); 75 | lines.add(agent.message == null ? "" : agent.message); 76 | if (agent.tx != null) { 77 | lines.add(Referee.join(agent.tx, agent.ty)); 78 | } 79 | }); 80 | data.events.stream() 81 | .forEach(event -> { 82 | lines 83 | .add( 84 | Referee.join( 85 | event.type, 86 | coalesce(event.item, "_"), 87 | coalesce(event.agent, "_"), 88 | coalesce(event.x, "_"), 89 | coalesce(event.y, "_") 90 | ) 91 | ); 92 | }); 93 | return lines.stream().collect(Collectors.joining("\n")); 94 | } 95 | 96 | private String coalesce(Integer item, String string) { 97 | return item == null ? string : String.valueOf(item); 98 | } 99 | 100 | private String serialize(List scores) { 101 | return scores.stream() 102 | .map(String::valueOf) 103 | .collect(Collectors.joining(" ")); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/codingame/view/endscreen/EndScreenModule.java: -------------------------------------------------------------------------------- 1 | package com.codingame.view.endscreen; 2 | 3 | import com.codingame.gameengine.core.AbstractPlayer; 4 | import com.codingame.gameengine.core.GameManager; 5 | import com.codingame.gameengine.core.Module; 6 | import com.google.inject.Inject; 7 | 8 | public class EndScreenModule implements Module { 9 | 10 | private GameManager gameManager; 11 | private int[] scores; 12 | private boolean tie; 13 | 14 | @Inject 15 | EndScreenModule(GameManager gameManager) { 16 | this.gameManager = gameManager; 17 | gameManager.registerModule(this); 18 | } 19 | 20 | public void setScores(int[] scores, boolean tie) { 21 | this.scores = scores; 22 | this.tie = tie; 23 | 24 | } 25 | 26 | @Override 27 | public final void onGameInit() { 28 | } 29 | 30 | @Override 31 | public final void onAfterGameTurn() { 32 | } 33 | 34 | @Override 35 | public final void onAfterOnEnd() { 36 | gameManager.setViewData("endScreen", new Object[] { scores, tie }); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/Background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/assets/Background.jpg -------------------------------------------------------------------------------- /src/main/resources/view/assets/Hud_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/assets/Hud_left.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/Hud_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/assets/Hud_top.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/elems.json: -------------------------------------------------------------------------------- 1 | {"frames":{"Bras_Robot":{"frame":{"x":0,"y":276,"w":124,"h":67},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":124,"h":67},"sourceSize":{"w":124,"h":67}},"Cristal":{"frame":{"x":186,"y":276,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Cristal_2":{"frame":{"x":183,"y":215,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Cristal_2_shadow":{"frame":{"x":0,"y":127,"w":88,"h":87},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":88,"h":87},"sourceSize":{"w":88,"h":87}},"Cristal_3":{"frame":{"x":61,"y":215,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Cristal_3_shadow":{"frame":{"x":0,"y":0,"w":88,"h":86},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":88,"h":86},"sourceSize":{"w":88,"h":86}},"Hud_dessus_left":{"frame":{"x":150,"y":0,"w":106,"h":106},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":106,"h":106},"sourceSize":{"w":106,"h":106}},"Hud_dessus_right":{"frame":{"x":150,"y":107,"w":106,"h":106},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":106,"h":106},"sourceSize":{"w":106,"h":106}},"HUD_Piege_Off":{"frame":{"x":89,"y":167,"w":39,"h":39},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":39,"h":39},"sourceSize":{"w":39,"h":39}},"HUD_Piege_Ok":{"frame":{"x":89,"y":127,"w":39,"h":39},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":39,"h":39},"sourceSize":{"w":39,"h":39}},"HUD_Radar_Off":{"frame":{"x":0,"y":87,"w":39,"h":39},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":39,"h":39},"sourceSize":{"w":39,"h":39}},"HUD_Radar_Ok":{"frame":{"x":40,"y":87,"w":39,"h":39},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":39,"h":39},"sourceSize":{"w":39,"h":39}},"Piege":{"frame":{"x":0,"y":215,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Piege_2":{"frame":{"x":89,"y":61,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Radar":{"frame":{"x":89,"y":0,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Radar_2":{"frame":{"x":125,"y":276,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}},"Terre":{"frame":{"x":122,"y":215,"w":60,"h":60},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":60,"h":60},"sourceSize":{"w":60,"h":60}}},"meta":{"app":"https://www.leshylabs.com/apps/sstool/","version":"Leshy SpriteSheet Tool v0.8.4","image":"elems.png","size":{"w":256,"h":343},"scale":1}} -------------------------------------------------------------------------------- /src/main/resources/view/assets/elems.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/assets/elems.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/assets/logo.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/spritesheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/assets/spritesheet.png -------------------------------------------------------------------------------- /src/main/resources/view/config.js: -------------------------------------------------------------------------------- 1 | import { ViewModule, api } from './graphics/ViewModule.js' 2 | import { TooltipModule } from './tooltip/TooltipModule.js' 3 | import { EndScreenModule } from './endscreen/EndScreenModule.js' 4 | 5 | // List of viewer modules that you want to use in your game 6 | export const modules = [ 7 | ViewModule, 8 | TooltipModule, 9 | EndScreenModule 10 | ] 11 | 12 | export const playerColors = [ 13 | '#22a1e4', // curious blue 14 | '#ff1d5c' // radical red 15 | ] 16 | 17 | export const gameName = 'UTG2019' 18 | 19 | export const options = [{ 20 | title: 'DESTINATIONS', 21 | get: function () { 22 | return api.options.destinations 23 | }, 24 | set: function (value) { 25 | api.options.destinations = value 26 | }, 27 | values: { 28 | 'ON HOVER': false, 29 | 'ALWAYS': true 30 | } 31 | }, { 32 | title: 'RADAR RANGE', 33 | get: () => api.options.radarRange, 34 | set: (value) => { 35 | api.options.radarRange = value 36 | api.updateAllRadarOverlay() 37 | }, 38 | values: { 39 | 'OFF': -1, 40 | 'BLUE': 0, 41 | 'RED': 1 42 | } 43 | }, { 44 | title: 'MY MESSAGES', 45 | get: function () { 46 | return api.options.showMyMessages 47 | }, 48 | set: function (value) { 49 | api.options.showMyMessages = value 50 | }, 51 | enabled: function () { 52 | return api.options.meInGame 53 | }, 54 | values: { 55 | 'ON': true, 56 | 'OFF': false 57 | } 58 | }, { 59 | title: 'OTHERS\' MESSAGES', 60 | get: function () { 61 | return api.options.showOthersMessages 62 | }, 63 | set: function (value) { 64 | api.options.showOthersMessages = value 65 | }, 66 | 67 | values: { 68 | 'ON': true, 69 | 'OFF': false 70 | } 71 | }] 72 | -------------------------------------------------------------------------------- /src/main/resources/view/endscreen/EndScreenModule.js: -------------------------------------------------------------------------------- 1 | import { WIDTH, HEIGHT } from '../core/constants.js' 2 | import { lerp, unlerp } from '../core/utils.js' 3 | import { api as ViewModule } from '../graphics/ViewModule.js' 4 | /* global PIXI */ 5 | 6 | export class EndScreenModule { 7 | constructor (assets) { 8 | this.states = [] 9 | this.globalData = {} 10 | this.atEnd = false 11 | } 12 | 13 | static get name () { 14 | return 'endScreen' 15 | } 16 | 17 | updateScene (previousData, currentData, progress) { 18 | if (currentData.data && progress === 1) { 19 | this.atEnd = true 20 | } else { 21 | this.atEnd = false 22 | } 23 | } 24 | 25 | handleFrameData (frameInfo, data) { 26 | const state = { 27 | number: frameInfo.number, 28 | data 29 | } 30 | if (data) { 31 | this.scores = data[0] 32 | this.tie = data[1] 33 | } 34 | this.states.push(state) 35 | return state 36 | } 37 | 38 | reinitScene (container, canvasData) { 39 | this.toDestroy = [] 40 | this.container = container 41 | this.endLayer = this.createEndScene(this) 42 | if (this.atEnd) { 43 | this.initEndScene() 44 | } 45 | this.container.addChild(this.endLayer) 46 | } 47 | 48 | animateScene (delta) { 49 | let step = Math.min(32, delta) 50 | 51 | if (!ViewModule.options.debugMode && this.atEnd) { 52 | if (!this.animationEnded) { 53 | this.renderEndScene(step) 54 | } 55 | } else { 56 | if (this.endTime > 0) { 57 | this.destroyEndScene() 58 | } 59 | this.endTime = 0 60 | } 61 | } 62 | 63 | destroyEndScene () { 64 | this.animationEnded = false 65 | this.endLayer.visible = false 66 | } 67 | 68 | initEndScene () { 69 | this.animationEnded = false 70 | this.endLayer.visible = true 71 | } 72 | 73 | renderEndScene (step) { 74 | var endOfEnd = 10000 75 | if (this.endTime === 0) { 76 | this.initEndScene() 77 | } 78 | 79 | var backS = 0 80 | var backD = 400 81 | var backP = unlerp(backS, backS + backD, this.endTime) 82 | this.endLayer.backgroundRanking.alpha = backP * 0.9 83 | 84 | var logoS = 400 85 | var logoD = 600 86 | var logoP = unlerp(logoS, logoS + logoD, this.endTime) 87 | this.endLayer.titleRanking.scale.x = this.endLayer.titleRanking.scale.y = 0.001 + lerp(10, 0.8, logoP) 88 | this.endLayer.titleRanking.visible = !!logoP 89 | 90 | var rankS = 1000 91 | var rankD = 300 92 | for (let i = 0; i < this.finishers.length; ++i) { 93 | var p = unlerp(rankS + rankD * i, rankS + rankD * i + rankD, this.endTime) 94 | this.finishers[i].alpha = p 95 | } 96 | 97 | this.endTime += step 98 | 99 | if (this.endTime >= endOfEnd) { 100 | this.animationEnded = true 101 | } 102 | } 103 | 104 | handleGlobalData (players, globalData) { 105 | this.globalData = { 106 | players: players, 107 | playerCount: players.length 108 | } 109 | } 110 | 111 | generateText (text, size, align, color, forceLato) { 112 | var textEl 113 | if (!forceLato) { 114 | textEl = new PIXI.extras.BitmapText(text, { 115 | font: size + 'px 04b', 116 | tint: color 117 | }) 118 | textEl.lineHeight = size 119 | } else { 120 | textEl = new PIXI.Text(text, { 121 | fontSize: Math.round(size / 1.2) + 'px', 122 | fontFamily: 'Lato', 123 | fontWeight: 'bold', 124 | fill: color 125 | }) 126 | textEl.lineHeight = Math.round(size / 1.2) 127 | this.toDestroy.push(textEl) 128 | } 129 | if (align === 'right') { 130 | textEl.anchor.x = 1 131 | } else if (align === 'center') { 132 | textEl.anchor.x = 0.5 133 | } 134 | return textEl 135 | } 136 | 137 | createFinisher (finisher, tie) { 138 | var layer = new PIXI.Container() 139 | 140 | /** ************************************* */ 141 | var avatarContainer = new PIXI.Container() 142 | avatarContainer.y = 0 143 | avatarContainer.x = 0 144 | 145 | var backgroundAvatar = new PIXI.Graphics() 146 | backgroundAvatar.beginFill(0xffffff) 147 | backgroundAvatar.alpha = 0.1 148 | backgroundAvatar.drawRect(0, 0, 240, 120) 149 | avatarContainer.addChild(backgroundAvatar) 150 | 151 | var avatar = new PIXI.Sprite(finisher.player.avatar) 152 | avatar.width = avatar.height = 120 153 | 154 | var rank = this.generateText(finisher.rank.toString(), 76, 'center', finisher.player.color, true) 155 | rank.anchor.y = 0.5 156 | rank.position.x = 160 157 | rank.position.y = 56 158 | avatarContainer.addChild(rank) 159 | 160 | var rankLetter = this.generateText(finisher.rank === 1 ? 'ST' : 'ND'.toString(), 34, 'left', finisher.player.color, true) 161 | rankLetter.position.x = 184 162 | rankLetter.position.y = 32 163 | avatarContainer.addChild(rankLetter) 164 | 165 | var hudAvatar = new PIXI.Container() 166 | hudAvatar.addChild(avatar) 167 | 168 | avatarContainer.addChild(hudAvatar) 169 | 170 | /** ************************************* */ 171 | 172 | var name = this.generateText(finisher.player.name.toUpperCase(), 76, 'left', finisher.player.color, true) 173 | const scoreText = ((finisher.score >= 0) ? finisher.score.toString() + ' Amadeusium' : '-') 174 | var scoreLabel = this.generateText(scoreText, 64, 'left', finisher.player.color, true) 175 | 176 | name.x = 330 177 | // if (tie) { 178 | name.y = -4 179 | // } else { 180 | // name.y = 25 181 | // } 182 | scoreLabel.x = 330 183 | scoreLabel.y = 70 184 | 185 | layer.addChild(avatarContainer) 186 | layer.addChild(name) 187 | // if (tie) { 188 | layer.addChild(scoreLabel) 189 | // } 190 | return layer 191 | } 192 | 193 | createEndScene () { 194 | let tie = this.tie 195 | var layer = new PIXI.Container() 196 | 197 | var background = new PIXI.Graphics() 198 | background.beginFill(0, 0.85) 199 | background.drawRect(0, 0, WIDTH, HEIGHT) 200 | background.endFill() 201 | 202 | layer.backgroundRanking = background 203 | 204 | var titleRanking = PIXI.Sprite.fromFrame('logo.png') 205 | titleRanking.anchor.x = titleRanking.anchor.y = 0.5 206 | layer.titleRanking = titleRanking 207 | 208 | titleRanking.position.x = WIDTH / 2 209 | 210 | titleRanking.position.y = 275 211 | 212 | var podium = [] 213 | for (var i = 0; i < this.globalData.playerCount; ++i) { 214 | podium.push({ 215 | score: this.scores[i], 216 | player: this.globalData.players[i], 217 | rank: 0 218 | }) 219 | } 220 | podium.sort(function (a, b) { 221 | return b.score - a.score 222 | }) 223 | this.finishers = [] 224 | var finishers = new PIXI.Container() 225 | var curRank = 1 226 | var elem 227 | for (i = 0; i < podium.length; ++i) { 228 | if (i > 0 && podium[i - 1].score !== podium[i].score) { 229 | curRank++ 230 | } 231 | 232 | podium[i].rank = curRank 233 | elem = this.createFinisher(podium[i], tie) 234 | finishers.addChild(elem) 235 | this.finishers.push(elem) 236 | } 237 | 238 | for (i = 0; i < this.finishers.length; ++i) { 239 | this.finishers[i].position.x = (WIDTH - this.finishers[0].width) / 2 240 | this.finishers[i].position.y = 75 + i * 200 241 | } 242 | finishers.y = 400 243 | 244 | layer.addChild(background) 245 | layer.addChild(titleRanking) 246 | layer.addChild(finishers) 247 | 248 | layer.visible = false 249 | return layer 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/main/resources/view/graphics/Deserializer.js: -------------------------------------------------------------------------------- 1 | 2 | function parseIntList (values) { 3 | return values.map(v => +v) 4 | } 5 | function parseIntMap (values) { 6 | let ints = {} 7 | for (let i = 0; i < values.length; i += 2) { 8 | let id = values[i] 9 | let int = values[i + 1] 10 | ints[id] = +int 11 | } 12 | return ints 13 | } 14 | 15 | function parseNullableIntList (values) { 16 | return values.map(v => v === '_' ? null : +v) 17 | } 18 | 19 | function parseFromTokens (tokens, callback) { 20 | return (values) => { 21 | const res = [] 22 | for (let i = 0; i < values.length; i += tokens) { 23 | res.push(callback(values.slice(i, i + tokens))) 24 | } 25 | return res 26 | } 27 | } 28 | 29 | export function parseData (raw, globalData) { 30 | let idx = 0 31 | const lines = raw.split('\n').map(line => line === '' ? [] : line.split(' ')) 32 | const [score0, score1, eventCount] = parseIntList(lines[idx++]) 33 | const agents = [] 34 | const events = [] 35 | const scores = [score0, score1] 36 | for (let k = 0; k < globalData.agentsPerPlayer * globalData.playerCount; ++k) { 37 | const [id, x, y, item, dead] = parseIntList(lines[idx++]) 38 | const message = lines[idx++].join(' ').trim() 39 | let tx = null 40 | let ty = null 41 | if (idx < lines.length && lines[idx].length === 2) { 42 | [tx, ty] = parseIntList(lines[idx++]) 43 | } 44 | agents.push({ id, x, y, tx, ty, item, dead: dead === 1, message }) 45 | } 46 | for (let k = 0; k < eventCount; ++k) { 47 | const [type, item, agent, x, y] = parseNullableIntList(lines[idx++]) 48 | events.push({ type, item, agent, x, y }) 49 | } 50 | 51 | return { 52 | agents, scores, events 53 | } 54 | } 55 | 56 | export function parseGlobalData (raw) { 57 | let idx = 0 58 | const lines = raw.split('\n').map(line => line === '' ? [] : line.split(' ')) 59 | const [width, height, agentsPerPlayer, veinCount] = parseIntList(lines[idx++]) 60 | const ore = [] 61 | for (let k = 0; k < veinCount; k++) { 62 | const [x, y, amount] = parseIntList(lines[idx++]) 63 | ore.push({ x, y, ore: amount }) 64 | } 65 | return { width, height, agentsPerPlayer, ore } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/resources/view/graphics/MessageBoxes.js: -------------------------------------------------------------------------------- 1 | import { flagForDestructionOnReinit } from '../core/rendering.js' 2 | 3 | /* global PIXI */ 4 | 5 | export function wordWrap (text) { 6 | var wordWrapWidth = this._style.wordWrapWidth 7 | var self = this 8 | text = text.replace(/\w+/g, function (text) { 9 | if (self.context.measureText(text).width > wordWrapWidth) { 10 | var list = [] 11 | while (text.length > 0) { 12 | var length = 1 13 | while (length <= text.length && self.context.measureText(text.slice(0, length)).width < wordWrapWidth) { 14 | length++ 15 | } 16 | list.push(text.slice(0, length - 1)) 17 | text = text.slice(length - 1) 18 | } 19 | return list.join('\n') 20 | } 21 | return text 22 | }) 23 | return this._wordWrap(text) 24 | } 25 | 26 | export function renderMessageContainer (step) { 27 | var drawer = this.drawer 28 | var stepFactor = Math.pow(0.993, step) 29 | var targetMessageAlpha = 0 30 | targetMessageAlpha = (!drawer.hideMessages && ((drawer.showMyMessages() && drawer.playerInfo[this.player].isMe) || (drawer.showOthersMessages() && !drawer.playerInfo[this.player].isMe))) ? 1 31 | : 0 32 | this.alpha = this.alpha * stepFactor + targetMessageAlpha * (1 - stepFactor) 33 | }; 34 | 35 | export const messageBox = { 36 | width: 200, 37 | offset: { 38 | x: 0, 39 | y: -50 40 | } 41 | } 42 | 43 | export function initMessages (layer) { 44 | this.messages = [] 45 | 46 | let scope = { 47 | hudColor: 0x0, 48 | messageBox: messageBox 49 | } 50 | 51 | for (let i = 0; i < this.globalData.playerCount; ++i) { 52 | this.messages[i] = [] 53 | const messageGroup = new PIXI.Container() 54 | const options = this.api.options 55 | const playerInfo = this.globalData.players 56 | Object.defineProperty(messageGroup, 'visible', { get: () => { 57 | return (options.showMyMessages && playerInfo[i].isMe) || (options.showOthersMessages && !playerInfo[i].isMe) 58 | } }) 59 | 60 | for (let k = 0; k < this.globalData.agentsPerPlayer; ++k) { 61 | var messageContainer = new PIXI.Container() 62 | var messageBackground = new PIXI.Graphics() 63 | messageBackground.beginFill(scope.hudColor, /* scope.hudAlpha */0) 64 | messageBackground.drawRect(-scope.messageBox.width / 2, -40, scope.messageBox.width, 80) 65 | messageBackground.endFill() 66 | messageContainer.addChild(messageBackground) 67 | var textContainer = new PIXI.Container() 68 | messageContainer.addChild(textContainer) 69 | 70 | var messageText = new PIXI.Text('', { 71 | fontFamily: 'Lato', 72 | fontWeight: 700, 73 | fontSize: 36, 74 | fill: this.globalData.players[i].color, 75 | align: 'center', 76 | strokeThickness: 4, 77 | lineHeight: 32, 78 | wordWrap: true, 79 | wordWrapWidth: 180 80 | }) 81 | messageText._wordWrap = messageText.wordWrap 82 | messageText.wordWrap = wordWrap 83 | 84 | messageText.anchor.x = 0.5 85 | messageText.anchor.y = 0.5 86 | messageText.x = 3 87 | messageText.y = -6 88 | 89 | flagForDestructionOnReinit(messageText) 90 | 91 | messageContainer.player = i 92 | 93 | messageContainer.messageText = messageText 94 | messageContainer.messageBackground = messageBackground 95 | 96 | messageContainer.addChild(messageText) 97 | messageContainer.scale.x = scope.messageBox.width / messageContainer.width 98 | messageContainer.scale.y = messageContainer.scale.x 99 | // messageContainer.render = renderMessageContainer; 100 | messageContainer.render = () => {} 101 | messageContainer.drawer = this 102 | // scope.renderables.push(messageContainer); 103 | this.messages[i].push(messageContainer) 104 | 105 | messageGroup.addChild(messageContainer) 106 | } 107 | layer.addChild(messageGroup) 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /src/main/resources/view/graphics/assetConstants.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodinGameCommunity/UnleashTheGeek/b8b955e71ce73ba41854db4ccbce7ae97283570d/src/main/resources/view/graphics/assetConstants.js -------------------------------------------------------------------------------- /src/main/resources/view/graphics/gameConstants.js: -------------------------------------------------------------------------------- 1 | export const EV_REQUEST = 0 2 | export const EV_RADAR_DESTRUCTION = 1 3 | export const EV_BURY = 2 4 | export const EV_GET_ORE = 3 5 | export const EV_GIVE_ORE = 4 6 | export const EV_EXPLOSION = 5 7 | export const EV_AGENT_DEATH = 6 8 | export const EV_RESPAWN = 7 9 | export const EV_DRILL = 8 10 | 11 | export const ITEM_NONE = -1 12 | export const ITEM_RADAR = 2 13 | export const ITEM_TRAP = 3 14 | export const ITEM_ORE = 4 15 | 16 | export const RADAR_RANGE = 4 17 | export const ITEM_COOLDOWN = 5 18 | 19 | export const REQUESTABLES = [ITEM_RADAR, ITEM_TRAP] 20 | -------------------------------------------------------------------------------- /src/main/resources/view/tooltip/TooltipModule.js: -------------------------------------------------------------------------------- 1 | import { WIDTH, HEIGHT } from '../core/constants.js' 2 | import { api as ViewModule } from '../graphics/ViewModule.js' 3 | import * as game from '../graphics/gameConstants.js' 4 | 5 | /* global PIXI */ 6 | 7 | ViewModule.getMouseOverFunc = function (id, tooltip) { 8 | return function () { 9 | tooltip.inside[id] = true 10 | } 11 | } 12 | 13 | ViewModule.getMouseOutFunc = function (id, tooltip) { 14 | return function () { 15 | delete tooltip.inside[id] 16 | } 17 | } 18 | 19 | const ITEM_NAMES = { 20 | 2: 'radar', 21 | 3: 'trap', 22 | 4: 'ore' 23 | } 24 | 25 | const ITEMS = { 26 | RADAR: 2, 27 | TRAP: 3 28 | } 29 | 30 | const COLOUR_NAMES = ['blue', 'red'] 31 | 32 | function getMouseMoveFunc (tooltip, container, module) { 33 | return function (ev) { 34 | if (tooltip) { 35 | var pos = ev.data.getLocalPosition(container) 36 | tooltip.x = pos.x 37 | tooltip.y = pos.y 38 | var point = ViewModule.unconvertPosition(pos) 39 | 40 | point.x = Math.round(point.x) 41 | point.y = Math.round(point.y) 42 | 43 | const showing = [] 44 | const ids = Object.keys(tooltip.inside).map(n => +n) 45 | const tooltipBlocks = [] 46 | 47 | for (let id of ids) { 48 | if (!ViewModule.robots) { 49 | break 50 | } else if (tooltip.inside[id]) { 51 | const entity = ViewModule.robots[id] 52 | if (!entity) { 53 | delete tooltip.inside[id] 54 | } else { 55 | showing.push(id) 56 | } 57 | } 58 | } 59 | 60 | if (showing.length) { 61 | for (let show of showing) { 62 | const entity = ViewModule.robots[show] 63 | 64 | if (entity) { 65 | const data = (ViewModule.progress === 1 ? ViewModule.currentData : ViewModule.previousData) || ViewModule.currentData 66 | 67 | let robot = ViewModule.currentData.agents[show] 68 | let displayedRobot = data.agents[show] 69 | let tooltipBlock = `Robot ${show} (${COLOUR_NAMES[ViewModule.playerIndexFromAgentId(robot.id)]})` 70 | if (robot != null) { 71 | let dig = robot.events.find(e => e.type === game.EV_BURY) 72 | if (dig) { 73 | tooltipBlock += `\naction: DIG ${dig.x} ${dig.y}` 74 | } else { 75 | let req = robot.events.find(e => e.type === game.EV_REQUEST) 76 | if (req) { 77 | tooltipBlock += `\naction: REQUEST ${req.item === ITEMS.RADAR ? 'RADAR' : 'TRAP'}` 78 | } 79 | } 80 | if (displayedRobot.item && displayedRobot.item.type !== game.ITEM_NONE) { 81 | tooltipBlock += `\nitem: ${ITEM_NAMES[displayedRobot.item.type]}` 82 | } 83 | } 84 | 85 | tooltipBlocks.push(tooltipBlock) 86 | } 87 | } 88 | } 89 | 90 | if (point.y >= 0 && point.x >= 0 && point.x < ViewModule.globalData.width && point.y < ViewModule.globalData.height) { 91 | const x = point.x 92 | const y = point.y 93 | 94 | let tooltipBlock = 'x: ' + x + '\ny: ' + y 95 | 96 | let data = (ViewModule.progress === 1 ? ViewModule.currentData : ViewModule.previousData) || ViewModule.currentData 97 | 98 | if (x > 0) { 99 | tooltipBlock += '\nore: ' + data.map[y][x].ore 100 | } 101 | 102 | for (let itemName in ITEMS) { 103 | let itemId = ITEMS[itemName] 104 | let hasItem = [] 105 | for (let player = 0; player < 2; ++player) { 106 | hasItem.push(data.items[player][itemId][x + ' ' + y]) 107 | } 108 | 109 | if (hasItem.some(v => v != null)) { 110 | tooltipBlock += `\n${itemName}` 111 | if (hasItem.every(v => v != null)) { 112 | tooltipBlock += ' (both players)' 113 | } else { 114 | let playerIdx = hasItem.map(v => v != null).indexOf(true) 115 | 116 | let playerName = COLOUR_NAMES[playerIdx] 117 | tooltipBlock += ` (${playerName} player)` 118 | } 119 | } 120 | } 121 | tooltipBlocks.push(tooltipBlock) 122 | } 123 | 124 | if (tooltipBlocks.length) { 125 | tooltip.label.text = tooltipBlocks.join('\n──────────\n') 126 | tooltip.visible = true 127 | } else { 128 | tooltip.visible = false 129 | } 130 | 131 | tooltip.background.width = tooltip.label.width + 20 132 | tooltip.background.height = tooltip.label.height + 20 133 | 134 | tooltip.pivot.x = -30 135 | tooltip.pivot.y = -50 136 | 137 | if (tooltip.y - tooltip.pivot.y + tooltip.height > HEIGHT) { 138 | tooltip.pivot.y = 10 + tooltip.height 139 | tooltip.y -= tooltip.y - tooltip.pivot.y + tooltip.height - HEIGHT 140 | } 141 | 142 | if (tooltip.x - tooltip.pivot.x + tooltip.width > WIDTH) { 143 | tooltip.pivot.x = tooltip.width 144 | } 145 | } 146 | } 147 | }; 148 | 149 | export class TooltipModule { 150 | constructor (assets) { 151 | this.interactive = {} 152 | this.previousFrame = { 153 | registrations: {}, 154 | extra: {} 155 | } 156 | this.lastProgress = 1 157 | this.lastFrame = 0 158 | } 159 | 160 | static get name () { 161 | return 'tooltips' 162 | } 163 | 164 | updateScene (previousData, currentData, progress) { 165 | this.currentFrame = currentData 166 | this.currentProgress = progress 167 | } 168 | 169 | handleFrameData (frameInfo) { 170 | return {} 171 | } 172 | 173 | reinitScene (container, canvasData) { 174 | this.tooltip = this.initTooltip() 175 | ViewModule.tooltip = this.tooltip 176 | this.container = container 177 | container.interactive = true 178 | container.mousemove = getMouseMoveFunc(this.tooltip, container, this) 179 | container.addChild(this.tooltip) 180 | } 181 | 182 | generateText (text, size, color, align) { 183 | var textEl = new PIXI.Text(text, { 184 | fontSize: Math.round(size / 1.2) + 'px', 185 | fontFamily: 'Lato', 186 | fontWeight: 'bold', 187 | fill: color 188 | }) 189 | 190 | textEl.lineHeight = Math.round(size / 1.2) 191 | if (align === 'right') { 192 | textEl.anchor.x = 1 193 | } else if (align === 'center') { 194 | textEl.anchor.x = 0.5 195 | } 196 | 197 | return textEl 198 | }; 199 | 200 | initTooltip () { 201 | var tooltip = new PIXI.Container() 202 | var background = tooltip.background = new PIXI.Graphics() 203 | var label = tooltip.label = this.generateText('', 36, 0xFFFFFF, 'left') 204 | 205 | background.beginFill(0x0, 0.7) 206 | background.drawRect(0, 0, 200, 185) 207 | background.endFill() 208 | background.x = -10 209 | background.y = -10 210 | 211 | tooltip.visible = false 212 | tooltip.inside = {} 213 | 214 | tooltip.addChild(background) 215 | tooltip.addChild(label) 216 | 217 | tooltip.interactiveChildren = false 218 | return tooltip 219 | }; 220 | 221 | animateScene (delta) { 222 | 223 | } 224 | 225 | handleGlobalData (players, globalData) { 226 | 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/test/java/UTG2019Main.java: -------------------------------------------------------------------------------- 1 | import java.util.Properties; 2 | 3 | import com.codingame.gameengine.runner.MultiplayerGameRunner; 4 | 5 | public class UTG2019Main { 6 | public static void main(String[] args) { 7 | 8 | MultiplayerGameRunner gameRunner = new MultiplayerGameRunner(); 9 | 10 | // Set seed here (leave empty for random) 11 | // gameRunner.setSeed(7659744061232896144l); 12 | 13 | // Select agents here 14 | gameRunner.addAgent("python3 config/Boss.py3", "Player 1"); 15 | gameRunner.addAgent("python3 config/Boss.py3", "Player 2"); 16 | 17 | Properties params = new Properties(); 18 | // Set params here 19 | // params.setProperty("state", "ROBOT0 0 0 TRAP; ROBOT1 5 1 TRAP; GOLD 1 0 10;GOLD 2 1 10"); 20 | gameRunner.setGameParameters(params); 21 | 22 | 23 | gameRunner.start(8888); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/resources/log4j2.properties: -------------------------------------------------------------------------------- 1 | # Configuration 2 | name = PropertiesConfig 3 | status = WARN 4 | 5 | appender.console.type = Console 6 | appender.console.name = CONSOLE 7 | appender.console.layout.type = PatternLayout 8 | appender.console.layout.pattern = [%d{HH:mm:ss}] %-5p : %c{1} - %m%n 9 | 10 | rootLogger.level = warn 11 | rootLogger.appenderRef.console.ref = CONSOLE 12 | --------------------------------------------------------------------------------