├── .gitignore ├── .settings └── .gitignore ├── COMMUNITY.md ├── README.md ├── USE.md ├── code-a-la-mode.iml ├── config ├── Boss.kt ├── config.ini ├── level1 │ └── menu1.jpg ├── level2 │ ├── menu2.jpg │ ├── welcome_en.html │ └── welcome_fr.html ├── level3 │ ├── menu3.jpg │ ├── welcome_en.html │ └── welcome_fr.html ├── level4 │ ├── menu4.jpg │ ├── welcome_en.html │ └── welcome_fr.html ├── statement_en.html.tpl ├── statement_fr.html.tpl └── stub.txt ├── pom.xml └── src ├── main ├── java │ ├── nicknameHandlerModule │ │ └── TextLimitModule.java │ └── tooltipModule │ │ └── TooltipModule.java ├── kotlin │ └── com │ │ └── codingame │ │ └── game │ │ ├── BoardBuilding.kt │ │ ├── Player.kt │ │ ├── Referee.kt │ │ ├── Util.kt │ │ ├── model │ │ ├── BoardFeatures.kt │ │ ├── ChoppingBoard.kt │ │ ├── Constants.kt │ │ ├── Containers.kt │ │ ├── Crates.kt │ │ ├── Customers.kt │ │ ├── Dishes.kt │ │ ├── Items.kt │ │ ├── ItemsAndEquipment.kt │ │ ├── LogicException.kt │ │ └── Oven.kt │ │ └── view │ │ ├── BoardView.kt │ │ ├── GameView.kt │ │ ├── QueueView.kt │ │ └── ScoresView.kt └── resources │ └── view │ ├── assets │ ├── Player_blue_v3.json │ ├── Player_blue_v3.png │ ├── Player_green_v3.json │ ├── Player_green_v3.png │ ├── Player_red_v3.json │ ├── Player_red_v3.png │ ├── background03.jpg │ ├── blueberries.png │ ├── board02.png │ ├── bowl.png │ ├── croissant.png │ ├── dough.png │ ├── hot_left.png │ ├── hot_right.png │ ├── hot_top.png │ ├── ice-cream.png │ ├── logo.png │ ├── microwave.png │ ├── oven_left.png │ ├── oven_right.png │ ├── oven_top.png │ ├── paton_bb.png │ ├── paton_cut_big.png │ ├── plate.png │ ├── smoke.png │ ├── strawberries-cut.png │ ├── strawberry.png │ ├── tart_big_bb.png │ └── window.png │ ├── config.js │ ├── demo.js │ └── modules │ ├── TextLimitModule.js │ └── TooltipModule.js └── test ├── java └── com │ └── codingame │ └── game │ └── sample │ └── Main.java ├── kotlin └── com │ └── codingame │ └── game │ └── sample │ ├── BaseCALMPlayer.kt │ ├── HugPlayer.kt │ ├── InstaFoodPlayer.kt │ ├── MovePlayer.kt │ ├── NaiveAllItemsPlayer.kt │ ├── TartMakerOnTable.kt │ └── VanillaIceCreamPlayer.kt ├── resources └── log4j2.properties └── starterkit ├── starterAI.cs ├── starterAI.go ├── starterAI.java ├── starterAI.js ├── starterAI.kt ├── starterAI.ml ├── starterAI.py ├── starterAI.rb └── starterAI.vb /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | /target/ 3 | /.project 4 | /.classpath 5 | /.idea 6 | statement_en.html 7 | statement_fr.html 8 | .factorypath 9 | /.settings/ -------------------------------------------------------------------------------- /.settings/.gitignore: -------------------------------------------------------------------------------- 1 | /org.eclipse.jdt.core.prefs 2 | /org.eclipse.m2e.core.prefs 3 | -------------------------------------------------------------------------------- /COMMUNITY.md: -------------------------------------------------------------------------------- 1 | #List of streams made by the community 2 | 3 | - [From Wood to Bronze](https://www.twitch.tv/videos/392302771) [C#] by Illedan 4 | - [Creating an AI bot](https://www.youtube.com/watch?v=RvqcuKLqQyI) [C++] by Errichto 5 | - [csj walks down history lane](https://www.twitch.tv/videos/392808982) [Kotlin] by csj 6 | - [Reaching Silver League](https://www.twitch.tv/videos/393965040) [Python3] by Icebox -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Just run `com.codingame.game.Main.java` and then send your browser to http://localhost:8888 -------------------------------------------------------------------------------- /USE.md: -------------------------------------------------------------------------------- 1 | # All Possible USE cases 2 | 3 | _Invalid action_ resolves as a **WAIT** action. 4 | 5 | **Dishwasher** 6 | 7 | | Carried Item | => | Carried Item | 8 | |:---:|:---:|:---:| 9 | | none | => | DISH | 10 | | full DISH | => | empty DISH | 11 | | food | => | DISH + food | 12 | 13 | **Food Crates** 14 | 15 | | Carried Item | Type of Crate | => | Carried Item | 16 | |:---:|:---:|:---:|:---:| 17 | | none | any | => | corresponding food | 18 | | CHOPPED_DOUGH | BLUEBERRY | => | RAW_TART | 19 | | DISH | BLUEBERRY or ICE_CREAM | => | DISH + (BLUEBERRY or ICE_CREAM) | 20 | | DISH + food | BLUEBERRY or ICE_CREAM | => | DISH + food + (BLUEBERRY or ICE_CREAM) | 21 | | DISH | STRAWBERRY or DOUGH | => | _Invalid action_ | 22 | | any food but CHOPPED_DOUGH | any | => | _Invalid action_ | 23 | 24 | Food in food crates is unlimited. 25 | 26 | **Chopping Board** 27 | 28 | | Carried Item | => | Carried Item | 29 | |:---:|:---:|:---:| 30 | | STRAWBERRIES | => | CHOPPED_STRAWBERRIES | 31 | | DOUGH | => | CHOPPED_DOUGH | 32 | | any other item | => | _Invalid action_ | 33 | 34 | **Oven** 35 | 36 | | Carried Item | State of Oven | => | State of Oven | Carried Item | 37 | |:---:|:---:|:---:|:--:|:---:| 38 | | DOUGH | empty | => | DOUGH cooking | empty | 39 | | RAW_TART | empty | => | RAW_TART cooking | empty | 40 | | none | CROISSANT or TART | => | empty | CROISSANT or TART | 41 | | DISH | CROISSANT or TART | => | empty | DISH + (CROISSANT or TART) | 42 | | any other item | empty | => | _Invalid action_ | _Invalid action_ | 43 | | RAW_TART or DOUGH | cooking (DOUGH or RAW_TART) | => | _Invalid action_ | _Invalid action_ | 44 | 45 | 46 | **Tables** 47 | 48 | | Carried Item | State of Table | => | State of Table | Carried Item | 49 | |:---:|:---:|:---:|:--:|:---:| 50 | | any item | empty | => | item | none | 51 | | none | any item | => | empty | item | 52 | | DISH | any finished dessert | => | empty | DISH + finished dessert | 53 | | DISH | any non-finished dessert | => | _Invalid action_ | _Invalid action_ | 54 | | DISH | DISH | => | _Invalid action_ | _Invalid action_ | 55 | | any finished dessert | DISH | => | DISH + finished dessert | none | 56 | | any non-finished dessert | DISH | => | _Invalid action_ | _Invalid action_ | 57 | | CHOPPED_DOUGH | BLUEBERRY | => | none | RAW_TART | 58 | | BLUEBERRY | CHOPPED_DOUGH | => | none | RAW_TART | 59 | | any other food | any other food | => | _Invalid action_ | _Invalid action_ | 60 | 61 | -------------------------------------------------------------------------------- /code-a-la-mode.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /config/Boss.kt: -------------------------------------------------------------------------------- 1 | import java.io.InputStream 2 | import java.io.PrintStream 3 | import java.util.* 4 | import java.lang.Math.abs 5 | 6 | fun Boolean.then(thenExpr: T) = if (this) thenExpr else null 7 | 8 | fun Iterator.tryNext() = if (hasNext()) next() else null 9 | 10 | fun Iterator.findNext(pred: (T) -> Boolean): T? { 11 | while (hasNext()) { 12 | next().also { if (pred(it)) return it } 13 | } 14 | return null 15 | } 16 | 17 | inline fun Array>.transpose(default: () -> T): Array> { 18 | val rows = this.size 19 | val cols = this[0].size 20 | val trans = Array(cols) { Array(rows) { default() } } 21 | for (i in 0 until cols) { 22 | for (j in 0 until rows) trans[i][j] = this[j][i] 23 | } 24 | return trans 25 | } 26 | 27 | fun Sequence.splitAccumulate() = sequence { 28 | var currentLine = "" 29 | 30 | forEach { tok -> 31 | if (currentLine.length + tok.length > 9) { 32 | yield(currentLine.substring(1)) 33 | currentLine = "" 34 | } 35 | currentLine += " $tok" 36 | } 37 | 38 | yield(currentLine.substring(1)) 39 | } 40 | 41 | @Suppress("unused") 42 | abstract class BaseCALMPlayer(val stdin: InputStream = System.`in`, val stdout: PrintStream = System.out, val stderr: PrintStream = System.err) { 43 | val scanner = Scanner(stdin) 44 | val width: Int = 11 45 | val height: Int = 7 46 | val layout: List 47 | 48 | init { 49 | val longQueue = List(scanner.nextInt()) { 50 | Customer(Item.parse(scanner.next())!!, scanner.nextInt()) 51 | } 52 | 53 | layout = List(height) { scanner.next() } 54 | .also { stderr.println("Table layout:\n$it")} 55 | } 56 | 57 | protected fun readInputs(): GameState { 58 | val turnsRemaining = scanner.nextInt() 59 | .also { stderr.println("Turns remaining: $it") } 60 | val myPlayer = Player(scanner.nextInt(), scanner.nextInt(), Item.parse(scanner.next())) 61 | val myFriend = Player(scanner.nextInt(), scanner.nextInt(), Item.parse(scanner.next())) 62 | 63 | val tables = (0 until height).flatMap { row -> (0 until width) 64 | .filter { col -> layout[row][col] != '.'} 65 | .map { col -> Table(col, row, layout[row][col].nullIf('#')) } 66 | } 67 | 68 | repeat(scanner.nextInt()) { 69 | val x = scanner.nextInt() 70 | val y = scanner.nextInt() 71 | tables.find { it.x == x && it.y == y }!!.item = Item.parse(scanner.next()) 72 | } 73 | 74 | val ovenContents = scanner.next() 75 | val ovenTimer = scanner.nextInt() 76 | 77 | val queue = List(scanner.nextInt()) { 78 | Customer(Item.parse(scanner.next())!!, scanner.nextInt()) 79 | } 80 | 81 | return GameState(myPlayer, myFriend, tables, queue, ovenContents, ovenTimer) 82 | } 83 | } 84 | 85 | private fun Char.nullIf(char: Char) = if (this == char) null else this 86 | 87 | data class GameState( 88 | val myPlayer: Player, 89 | val myFriend: Player, 90 | val tables: List, 91 | val queue: List, 92 | val ovenContents: String, 93 | val ovenTimer: Int) 94 | 95 | data class Table( 96 | val x: Int, val y: Int, 97 | var equipment: Char? = null, 98 | var item: Item? = null) 99 | 100 | data class Item(val description: String) { 101 | val toks = description.split("-") 102 | val itemType = toks[0] 103 | val itemContents = toks.drop(1) 104 | 105 | companion object { 106 | fun parse(description: String) = 107 | if (description == "NONE") null 108 | else Item(description) 109 | 110 | } 111 | } 112 | 113 | data class Player(val x: Int, val y: Int, val carrying: Item?) 114 | data class Customer(val dish: Item, val award: Int) 115 | 116 | object Constants { 117 | enum class ITEM { 118 | FOOD, DOUGH, DISH, STRAWBERRIES, CHOPPED_DOUGH, RAW_TART 119 | } 120 | 121 | enum class FOOD { 122 | ICE_CREAM, 123 | BLUEBERRIES, 124 | CHOPPED_STRAWBERRIES, 125 | CROISSANT, 126 | TART 127 | } 128 | 129 | const val OVEN_COOKTIME = 10 130 | const val OVEN_BURNTIME = 10 131 | val TIP = 200 132 | } 133 | 134 | 135 | class NaiveAllItemsPlayer( 136 | stdin: InputStream = System.`in`, stdout: PrintStream = System.out, stderr: PrintStream = System.err): BaseCALMPlayer(stdin, stdout, stderr) { 137 | 138 | lateinit var goal: Item 139 | lateinit var inputs: GameState 140 | lateinit var crates: Map 141 | 142 | private fun findEquipment(equipmentChar: Char) = 143 | inputs.tables.firstOrNull { it.equipment == equipmentChar } 144 | 145 | init { 146 | var turn = 0 147 | while (true) { 148 | turn++ 149 | inputs = readInputs() 150 | 151 | crates = listOf( 152 | Constants.FOOD.BLUEBERRIES to 'B', 153 | Constants.FOOD.ICE_CREAM to 'I', 154 | Constants.ITEM.DOUGH to 'H', 155 | Constants.ITEM.STRAWBERRIES to 'S' 156 | ).map { (item, char) -> 157 | item.name to (inputs.tables.firstOrNull { it.equipment == char } ) 158 | }.filter { (_, v) -> v != null }.map { (k, v) -> k to v!! }.toMap() 159 | 160 | if (turn % 3 == 0) { 161 | stdout.println("WAIT") 162 | } else { 163 | stdout.println(act() ?: "WAIT") 164 | } 165 | 166 | } 167 | } 168 | 169 | private fun act(): String? { 170 | val carrying = inputs.myPlayer.carrying 171 | 172 | // 0. If the oven has something ready, go get it! 173 | if (inputs.ovenContents in listOf( 174 | Constants.FOOD.CROISSANT.name, 175 | Constants.FOOD.TART.name 176 | )) 177 | return if (carrying == null) findEquipment('O')!!.use() else useEmptyTable() 178 | 179 | goal = inputs.queue.firstOrNull()?.dish ?: return null 180 | stderr.println("Current goal is: $goal") 181 | 182 | // make all the items and leave them on tables. then grab a plate and collect them all. 183 | val goalItems = goal.itemContents.toSet() 184 | 185 | // 1. if we're holding a plate, grab missing items from tables and such. 186 | // If one is missing, drop the plate 187 | if (carrying?.itemType == Constants.ITEM.DISH.name) { 188 | stderr.println("we are carrying a plate") 189 | 190 | val dishContents = carrying.itemContents.toSet() 191 | 192 | // if it has anything we don't need, dishwasher it 193 | if ((dishContents - goalItems).isNotEmpty()) 194 | return findEquipment('D')!!.use() 195 | 196 | // find next missing item from dish 197 | val missingItems = goalItems - dishContents 198 | val missingItem = missingItems.firstOrNull() ?: 199 | return findEquipment('W')!!.use() 200 | 201 | stderr.println("missing item: $missingItem") 202 | 203 | return when (missingItem) { 204 | in crates.keys -> { 205 | val crateLoc = crates[missingItem]!! 206 | stderr.println("going for crate at $crateLoc") 207 | crateLoc.use() 208 | } 209 | else -> { 210 | val tableLoc = inputs.tables.find { 211 | it.item?.itemType == missingItem 212 | } 213 | if (tableLoc != null) tableLoc.use() 214 | else useEmptyTable() // put down the dish 215 | } 216 | } 217 | } 218 | 219 | // 2. Build missing items. If all items are built, grab an empty plate. 220 | goalItems.forEach { item -> 221 | if (isReady(item)) return@forEach 222 | return when(item) { 223 | Constants.FOOD.CROISSANT.name -> buildCroissant() 224 | Constants.FOOD.TART.name -> buildTart() 225 | Constants.FOOD.CHOPPED_STRAWBERRIES.name -> buildStrawberries() 226 | else -> throw Exception("Unrecognized dish: $item") 227 | } 228 | } 229 | 230 | // 3. if we're holding something we shouldn't, put it down. 231 | if (carrying != null) return useEmptyTable() 232 | 233 | return getEmptyPlate() 234 | } 235 | 236 | private fun buildStrawberries(): String? { 237 | val carrying = inputs.myPlayer.carrying 238 | return when { 239 | carrying == null -> crates[Constants.ITEM.STRAWBERRIES.name]!!.use() 240 | carrying.itemType == Constants.ITEM.STRAWBERRIES.name -> findEquipment('C')?.use() 241 | carrying.itemType == Constants.FOOD.CHOPPED_STRAWBERRIES.name -> useEmptyTable() 242 | else -> { stderr.println("uhhh, holding: $carrying"); return useEmptyTable() } 243 | } 244 | } 245 | 246 | private fun isReady(item: String): Boolean = 247 | item in crates.keys || 248 | inputs.tables.any { it.item?.itemType == item } || 249 | (item == "CROISSANT" && inputs.ovenContents in listOf( 250 | Constants.ITEM.DOUGH.name, 251 | Constants.FOOD.CROISSANT.name 252 | )) 253 | 254 | private fun getEmptyPlate(): String = ( 255 | inputs.tables.find { it.item?.itemType == Constants.ITEM.DISH.name } 256 | ?: findEquipment('D')!!).use() 257 | 258 | private fun useEmptyTable(): String { 259 | return inputs.myPlayer.run { 260 | inputs.tables.filter { it.equipment == null && it.item == null } 261 | .minBy { abs(it.x - x) + abs(it.y - y) }!!.use() 262 | } 263 | } 264 | 265 | private fun buildCroissant(): String? { 266 | return when(inputs.myPlayer.carrying?.itemType) { 267 | null -> crates[Constants.ITEM.DOUGH.name]!!.use() 268 | Constants.ITEM.DOUGH.name -> findEquipment('O')!!.use() 269 | else -> useEmptyTable() 270 | } 271 | } 272 | 273 | private fun buildTart(): String? { 274 | stderr.println("building tart") 275 | val x = inputs.myPlayer.x 276 | val y = inputs.myPlayer.y 277 | 278 | if (inputs.ovenContents != "NONE") { 279 | stderr.println("waiting for tart in oven") 280 | return findEquipment('O')!!.use() 281 | } 282 | 283 | val carrying = inputs.myPlayer.carrying 284 | when { 285 | carrying == null -> // find the closest tart CHOPPED_DOUGH, or get one from the box 286 | { 287 | stderr.println("looking for CHOPPED_DOUGH") 288 | val tart = (inputs.tables.filter { 289 | it.item?.itemType == Constants.ITEM.CHOPPED_DOUGH.name 290 | } + crates[Constants.ITEM.DOUGH.name]!!) 291 | .minBy { abs(it.x - x) + abs(it.y - y) } 292 | 293 | return tart!!.use() 294 | } 295 | 296 | carrying.itemType == Constants.ITEM.DOUGH.name -> 297 | return findEquipment('C')!!.use() 298 | 299 | carrying.itemType == Constants.ITEM.CHOPPED_DOUGH.name -> 300 | return crates[Constants.FOOD.BLUEBERRIES.name]!!.use() 301 | 302 | carrying.itemType == Constants.ITEM.RAW_TART.name -> { 303 | stderr.println("CHOPPED_DOUGH is complete; heading for oven") 304 | return findEquipment('O')!!.use() 305 | } 306 | } 307 | 308 | return useEmptyTable() 309 | } 310 | 311 | private fun Table.use(): String = "USE $x $y" 312 | } 313 | 314 | fun main() { 315 | NaiveAllItemsPlayer() 316 | } -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | #Thu Aug 30 12:53:06 EDT 2018 2 | max_players=3 3 | title=Code a la Mode 4 | type=multi 5 | min_players=3 6 | -------------------------------------------------------------------------------- /config/level1/menu1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/config/level1/menu1.jpg -------------------------------------------------------------------------------- /config/level2/menu2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/config/level2/menu2.jpg -------------------------------------------------------------------------------- /config/level2/welcome_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league

3 |
4 |
5 | -------------------------------------------------------------------------------- /config/level2/welcome_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

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

3 |
4 |
-------------------------------------------------------------------------------- /config/level3/menu3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/config/level3/menu3.jpg -------------------------------------------------------------------------------- /config/level3/welcome_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league

3 |
4 |
5 | -------------------------------------------------------------------------------- /config/level3/welcome_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

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

3 |
4 |
-------------------------------------------------------------------------------- /config/level4/menu4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/config/level4/menu4.jpg -------------------------------------------------------------------------------- /config/level4/welcome_en.html: -------------------------------------------------------------------------------- 1 |
2 |

You've made it to the next league

3 |
4 |
5 | -------------------------------------------------------------------------------- /config/level4/welcome_fr.html: -------------------------------------------------------------------------------- 1 |
2 |

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

3 |
4 |
-------------------------------------------------------------------------------- /config/statement_en.html.tpl: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
11 |
12 | 13 |
14 |

15 | 16 | This is a league based challenge. 17 | 18 | 19 | Welcome to the Wood2 league! 20 | 21 | 22 | Welcome to the Wood1 league! 23 | 24 | 25 | Welcome to the Bronze league! 26 | 27 |

28 | 29 | 30 | Wood leagues should be considered as a tutorial which lets players discover the different rules of the game.
31 | In Bronze league, all rules will be unlocked and the real challenge will begin.

32 | 33 | 34 | In Wood 2, customers can order a more complex dessert: chopped strawberries. Strawberries need to be chopped at the chopping board.

35 | 36 | 37 | In Wood 1, customers can order a more complex dessert: croissants. Dough is cooked into croissants at the oven.

38 | 39 | 40 | In Bronze, customers can order an even more complex dessert: blueberry tart. Dough needs to be chopped at the chopping board. Then, blueberries should be added to have a raw tart. The raw tart needs then to be cooked into a blueberry tart at the oven. 41 | 42 | 43 | Starter AIs are available in the Starter Kit. They can help you get started with coding your own bot. 44 | 45 |
46 |
47 | 48 | 49 | 50 |
51 |

52 |   53 | The Goal 54 |

55 |
56 | Control a chef and prepare food for customers as quickly as possible to earn more points than the other players. 57 |
58 |
59 | 60 | 61 |
62 |

63 |   64 | The Game 65 |

66 |
67 |

68 | This is a three-player game played on a grid 11 cells wide and 7 cells high. A match is played in 3 rounds, each round with only 2 of the players. 69 |

70 |
    71 |
  • Round 1: player A with player B.
  • 72 |
  • Round 2: player C with player A.
  • 73 |
  • Round 3: player B with player C.
  • 74 |
75 |

76 | Each player will thus play two rounds in each match. A player's total points is the sum of both rounds' points. 77 |

78 |

79 | Each round lasts for 200 turns and is played with the same kitchen and customers. 80 |

81 |
82 |

83 | A round 84 |

85 |

86 | Each player controls a chef who moves around the kitchen and prepares food for customers. 87 |

88 |

89 | Both players play collaboratively, and perform their actions one after the other. Each player will have 100 turns to act per round. 90 |

91 |
92 |

93 | The kitchen 94 |

95 | The kitchen contains: 96 |
    97 |
  • Floor cells, on which the chefs can move (., 0, 1).
  • 98 |
  • Empty tables (#).
  • 99 |
  • A dishwasher (D).
  • 100 |
  • A customer window represented by a bell (W).
  • 101 |
102 | 103 | It also contains different food crates that dispense: 104 |
    105 |
  • Blueberries (B).
  • 106 |
  • Ice cream (I).
  • 107 |
108 | 109 | 110 |
113 | It also contains different food crates that dispense: 114 |
    115 |
  • Blueberries (B).
  • 116 |
  • Ice cream (I).
  • 117 |
  • Strawberries (S).
  • 118 |
119 |

120 | It also contains a new appliance: a chopping board (C). 121 |

122 |
123 | 124 | 125 |
128 | It also contains different food crates that dispense: 129 |
    130 |
  • Blueberries (B).
  • 131 |
  • Ice cream (I).
  • 132 |
  • Strawberries (S).
  • 133 |
  • Dough (H).
  • 134 |
135 | 136 | It also contains two extra appliances: 137 |
    138 |
  • A chopping board (C).
  • 139 |
  • An oven (O).
  • 140 |
141 |
142 | 143 | 144 | It also contains different food crates that dispense: 145 |
    146 |
  • Blueberries (B).
  • 147 |
  • Ice cream (I).
  • 148 |
  • Strawberries (S).
  • 149 |
  • Dough (H).
  • 150 |
151 | 152 | It also contains two extra appliances: 153 |
    154 |
  • A chopping board (C).
  • 155 |
  • An oven (O).
  • 156 |
157 | 158 |
159 |

160 | The desserts 161 |

162 | The chefs can prepare two basic desserts: 163 |
    164 |
  • Blueberries (BLUEBERRIES).
  • 165 |
  • Ice cream (ICE_CREAM).
  • 166 |
167 | 168 |
171 |

172 | The chefs can also prepare a classic dessert: chopped strawberries (CHOPPED_STRAWBERRIES). 173 |

174 |

175 | Strawberries need to be cut at the chopping board before being added to a dish.
176 | Chopping board: STRAWBERRIES => (CHOPPED_STRAWBERRIES) 177 |

178 |
179 | 180 | 181 | 182 |
185 | The chefs can also prepare two classic desserts: 186 |
    187 |
  • Chopped strawberries (CHOPPED_STRAWBERRIES).
  • 188 |
  • Croissants (CROISSANT).
  • 189 |
190 |
191 |

192 | Strawberries need to be cut at the chopping board before being added to a dish.
193 | Chopping board: STRAWBERRIES => (CHOPPED_STRAWBERRIES) 194 |

195 |
198 |

199 | A ball of dough needs to be cooked into a croissant at the oven before being added to a dish.
200 | Oven: DOUGH => CROISSANT 201 |

202 |

203 | Cooking takes 10 turns, after which the food is READY. The food will remain READY for 10 more turns, after which it will be burned away and need to be restarted. 204 |

205 |
206 | 207 | 208 | The chefs can also prepare two classic desserts: 209 |
    210 |
  • Chopped strawberries (CHOPPED_STRAWBERRIES).
  • 211 |
  • Croissants (CROISSANT).
  • 212 |
213 | 214 |
217 |

218 | The chefs can also prepare one advanced dessert: blueberry tart (TART). 219 |

220 |
221 | 222 |

223 | Strawberries need to be cut at the chopping board before being added to a dish.
224 | Chopping board: STRAWBERRIES => (CHOPPED_STRAWBERRIES) 225 |

226 |

227 | A ball of dough needs to be cooked into a croissant at the oven before being added to a dish.
228 | Oven: DOUGH => CROISSANT 229 |

230 | 231 |
234 |

235 | To make a blueberry tart, a ball of dough needs to be chopped at the chopping board, then mixed with blueberries and then cooked into a blueberry tart in the oven before being added to a dish.
236 | Chopping board: DOUGH => CHOPPED_DOUGH 237 | CHOPPED_DOUGH + BLUEBERRIES => RAW_TART 238 | Oven: RAW_TART => TART 239 |

240 |
241 |

242 | Cooking takes 10 turns, after which the food is READY. The food will remain READY for 10 more turns, after which it will be burned away and need to be restarted. 243 |

244 | 245 | 246 | The chefs can also prepare two classic desserts: 247 |
    248 |
  • Chopped strawberries (CHOPPED_STRAWBERRIES).
  • 249 |
  • Croissants (CROISSANT).
  • 250 |
251 |

252 | The chefs can also prepare one advanced dessert: blueberry tart (TART). 253 |

254 |

255 | Strawberries need to be cut at the chopping board before being added to a dish.
256 | Chopping board: STRAWBERRIES => (CHOPPED_STRAWBERRIES) 257 |

258 |

259 | A ball of dough needs to be cooked into a croissant at the oven before being added to a dish.
260 | Oven: DOUGH => CROISSANT 261 |

262 |

263 | To make a blueberry tart, a ball of dough needs to be chopped at the chopping board, then mixed with blueberries and then cooked into a blueberry tart in the oven before being added to a dish.
264 | Chopping board: DOUGH => CHOPPED_DOUGH 265 | CHOPPED_DOUGH + BLUEBERRIES => RAW_TART 266 | Oven: RAW_TART => TART 267 |

268 |

269 | Cooking takes 10 turns, after which the food is READY. The food will remain READY for 10 more turns, after which it will be burned away and need to be restarted. 270 |

271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 |
285 |

286 | The customers 287 |

288 |

289 | At most 3 customers are waiting for their order. Each delivered order rewards both active chefs with points, but the longer the customer waits, the fewer points the chefs get. 290 |

291 | 292 | Every customer requests exactly ICE_CREAM and BLUEBERRIES. 293 | 294 | 295 | Every customer requests between 2-3 items, among 296 |
    297 |
  • ICE_CREAM
  • 298 |
  • BLUEBERRIES
  • 299 |
  • CHOPPED_STRAWBERRIES
  • 300 |
301 | 302 | 303 | Every customer requests between 2-4 items, among 304 |
    305 |
  • ICE_CREAM
  • 306 |
  • BLUEBERRIES
  • 307 |
  • CHOPPED_STRAWBERRIES
  • 308 |
  • CROISSANT
  • 309 |
310 | 311 | 312 | Every customer requests between 2-4 items, among 313 |
    314 |
  • ICE_CREAM
  • 315 |
  • BLUEBERRIES
  • 316 |
  • CHOPPED_STRAWBERRIES
  • 317 |
  • CROISSANT
  • 318 |
  • TART
  • 319 |
320 | 321 | 322 | (no duplicates). 323 | 324 | 325 | 326 |

327 | A customer's order should be served on a dish (DISH). 328 |

329 | 330 | 331 |
334 |

335 | A customer's order should be served on a dish (DISH). A dish can only contain (finished) desserts. 336 |

337 |
338 | 339 | 340 |

341 | A customer's order should be served on a dish (DISH). A dish can only contain (finished) desserts. 342 |

343 | 344 |

345 | There are at maximum 3 dishes in play. As soon as an order is sent through the window, a new dish appears in the dishwasher. 346 |

347 |
348 |

349 | Actions 350 |

351 | 352 |

353 | MOVE x y 354 |

355 |

356 | Use this command to move towards a different cell. The chefs move horizontally and vertically, of 4 cells at most. They can't occupy the same cell or pass through each other. 357 |

358 |
359 |

360 | USE x y 361 |

362 |

363 | Use this command to interact with the cell (x,y). If the chef is adjacent to the cell when using the USE command, the action is successful. Otherwise, the chef will move closer to that cell. The USE command works diagonally (8-adjacency). 364 |

365 |

366 | Depending on the cell and what the chef is holding, the USE will have different effects. The main effects are summarized below: 367 |

368 |
    369 |
  • 370 | The USE action on an equipment will make you use that equipment. 371 |
  • 372 |
  • 373 | The USE action on a table with an item (food or dish) while holding nothing will make you pick up that item. 374 |
  • 375 |
  • 376 | The USE action on a table with a finished dessert while holding a dish will make you add that dessert to the dish. 377 |
  • 378 | 379 |
  • 382 | The USE action on a table with food while holding food will make you attempt to fuse both.
    383 | (works only if food is: CHOPPED_DOUGH and BLUEBERRIES) 384 |
  • 385 | 386 | 387 |
  • 388 | The USE action on a table with food while holding food will make you attempt to fuse both.
    389 | (works only if food is: CHOPPED_DOUGH and BLUEBERRIES) 390 |
  • 391 | 392 |
393 |
394 |

395 | WAIT 396 |

397 |

398 | Use this command to do nothing. 399 |

400 |
401 |

402 | To display a message in a viewer, append a semicolon followed by your message to the output.
403 | Ex: USE 0 0; my message 404 |

405 |
406 |
407 | 408 | 409 |
410 |
411 |
412 |
Victory Conditions
413 |
414 |
    415 |
  • 416 | You earn more points than your opponents after the three rounds. 417 |
  • 418 |
419 |
420 |
421 |
422 | 423 | 424 |
425 |
426 |
427 |
Loss Conditions
428 |
429 |
    430 |
  • 431 | Your program times out. 432 |
  • 433 |
  • 434 | Your program provides invalid output. 435 |
  • 436 |
437 |
438 |
439 |
440 |
441 | 442 | 443 |
444 |

445 |   446 | Advanced Details 447 |

448 |
449 |

450 | You can see the game's source code here: https://github.com/csj/code-a-la-mode. 451 |

452 |
    453 |
  • 454 | The chefs cannot exchange any food or dish with each other. They need to put it down on a table for the other to pick it up. 455 |
  • 456 |
  • 457 | The chefs cannot put food or dishes on the floor. 458 |
  • 459 |
  • 460 | The chefs cannot pick up a dish if they're already carrying one. 461 |
  • 462 |
  • 463 | As soon as food is put on a dish, it cannot be removed from it. To empty a dish, USE the dishwasher while holding it. 464 |
  • 465 |
  • 466 | A dish cannot contain more than 4 desserts. 467 |
  • 468 |
  • 469 | All possible cases of the USE are listed here. 470 |
  • 471 |
  • 472 | For every turn a customer waits for an order, the reward is decreased by 1. 473 |
  • 474 |
475 |
476 |
477 | 478 |
479 |

480 |   481 | Game Input 482 |

483 | 484 | 485 |
486 |
Input for the first turn
487 |
488 | First line: an integer numAllCustomers for the total number of customers (same list of customers for each round).
489 | Next numAllCustomers lines: 490 |
    491 |
  • 492 | A string customerItem for the customer's order
    493 | Ex: DISH-BLUEBERRIES-ICE_CREAM 494 |
  • 495 |
  • 496 | An integer customerAward for the number of points awarded if the customer's order is delivered 497 |
  • 498 |
499 | Next 7 lines: A string kitchenLine of size 11 representing a part of the kitchen.
500 |
    501 |
  • 502 | .: walkable cell 503 |
  • 504 |
  • 505 | 0: first player spawn location (also walkable) 506 |
  • 507 |
  • 508 | 1: second player spawn location (also walkable) 509 |
  • 510 |
  • 511 | D: the dishwasher 512 |
  • 513 |
  • 514 | W: the window 515 |
  • 516 |
  • 517 | B: the blueberry crate 518 |
  • 519 |
  • 520 | I: the ice cream crate 521 |
  • 522 | 523 |
  • 524 | S: the strawberry crate 525 |
  • 526 |
  • 527 | C: the chopping board 528 |
  • 529 | 530 | 531 |
  • 532 | H: the dough crate 533 |
  • 534 |
  • 535 | O: the oven 536 |
  • 537 | 538 |
539 |
540 |
541 |
542 |
Input for one game turn
543 |
544 | First line: An integer turnsRemaining for the number of turns remaining before the end of the current round.
545 | Next 3 lines: 546 |
    547 |
  • 548 | Two integers playerX and playerY for the player's chef position 549 |
  • 550 |
  • 551 | A string playerItem for what the player's chef is carrying
    552 | Ex: DISH-BLUEBERRIES-ICE_CREAM
    553 | If no item is being carried: NONE 554 |
  • 555 |
556 | Next 3 lines: 557 |
    558 |
  • 559 | Two integers partnerX and partnerY for the other player's chef position 560 |
  • 561 |
  • 562 | A string partnerItem for what the other player's chef is carrying 563 |
  • 564 |
565 | Next line: An integer numTablesWithItems for the number of non-empty tables
566 | Next numTablesWithItems lines: 567 |
    568 |
  • 569 | Two integers tableX and tableY for the table's position 570 |
  • 571 |
  • 572 | A string item for what's on the table.
    573 | Ex: DISH-BLUEBERRIES-ICE_CREAM 574 |
  • 575 |
576 | 577 | Next line: to ignore in this league
578 | 579 | 580 | Next line: A string ovenContents for what's in the oven and an integer ovenTimer for the number of turns the food will stay in the oven before being cooked or burned.
581 | 582 | First line: an integer numCustomers for the current number of customers waiting for their order.
583 | Next numCustomers lines: 584 |
    585 |
  • 586 | A string customerItem for the customer's order
    587 | Ex: DISH-BLUEBERRIES-ICE_CREAM 588 |
  • 589 |
  • 590 | An integer customerAward for the number of points awarded if the customer's order is delivered 591 |
  • 592 |
593 |
594 |
595 | 596 | 597 |
598 |
Output for a turn
599 |
600 |
    601 |
  • 602 | MOVE x y to move to the cell (x,y). 603 |
  • 604 |
  • 605 | USE x y to interact with the cell (x,y). 606 |
  • 607 |
  • 608 | WAIT to do nothing. 609 |
  • 610 |
611 |
612 |
613 | 614 | 615 |
616 |
Constraints
617 |
618 | Response time for the first turn ≤ 1s
619 | Response time per turn ≤ 50ms
620 |
621 |
622 |
623 | 624 | 625 |
630 |
631 | 632 |
633 |

634 | What is in store in the higher leagues? 635 |

636 | The extra rules available in higher leagues are: 637 |
    638 | 639 |
  • In Wood 2, chefs can cut strawberries at the chopping board.
  • 640 | 641 | 642 |
  • In Wood 1, chefs can cook a dough into croissants.
  • 643 | 644 | 645 |
  • In Bronze, chefs can prepare blueberry tarts.
  • 646 | 647 |
648 |
649 | -------------------------------------------------------------------------------- /config/stub.txt: -------------------------------------------------------------------------------- 1 | read numAllCustomers:int 2 | loop numAllCustomers read customerItem:word(100) customerAward:int 3 | loop 7 read kitchenLine:string(16) 4 | gameloop 5 | read turnsRemaining:int 6 | read playerX:int playerY:int playerItem:word(100) 7 | read partnerX:int partnerY:int partnerItem:word(100) 8 | read numTablesWithItems:int 9 | loop numTablesWithItems read tableX:int tableY:int item:word(100) 10 | read ovenContents:word(100) ovenTimer:int 11 | read numCustomers:int 12 | loop numCustomers read customerItem:word(100) customerAward:int 13 | write WAIT 14 | 15 | INPUT 16 | numTablesWithItems: the number of tables in the kitchen that currently hold an item 17 | numCustomers: the number of customers currently waiting for food 18 | customerAward: the number of points awarded for delivering the food 19 | customerItem: the food the customer is waiting for 20 | ovenContents: ignore until wood 1 league 21 | 22 | OUTPUT 23 | MOVE x y 24 | USE x y 25 | WAIT 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.codingame.game 6 | code-a-la-mode 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 1.3.11 11 | 3.4.5 12 | true 13 | 14 | 15 | 16 | 17 | com.codingame.gameengine 18 | core 19 | ${gameengine.version} 20 | 21 | 22 | 23 | com.codingame.gameengine 24 | module-endscreen 25 | ${gameengine.version} 26 | 27 | 28 | 29 | com.codingame.gameengine 30 | module-entities 31 | ${gameengine.version} 32 | 33 | 34 | 35 | com.codingame.gameengine 36 | runner 37 | ${gameengine.version} 38 | 39 | 40 | 41 | org.jetbrains.kotlin 42 | kotlin-stdlib-jdk8 43 | ${kotlin.version} 44 | 45 | 46 | 47 | 48 | 49 | 50 | org.jetbrains.kotlin 51 | kotlin-maven-plugin 52 | ${kotlin.version} 53 | 54 | 55 | compile 56 | compile 57 | 58 | compile 59 | 60 | 61 | 62 | src/main/java 63 | src/main/kotlin 64 | src/test/kotlin/com/codingame/game/sample 65 | src/test/java 66 | 67 | 68 | 69 | 70 | test-compile 71 | test-compile 72 | 73 | test-compile 74 | 75 | 76 | 77 | src/test/kotlin 78 | src/test/kotlin/specs 79 | src/test/java 80 | 81 | 82 | 83 | 84 | 85 | 1.8 86 | 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-compiler-plugin 92 | 3.7.0 93 | 94 | 95 | compile 96 | compile 97 | 98 | compile 99 | 100 | 101 | 102 | testCompile 103 | test-compile 104 | 105 | testCompile 106 | 107 | 108 | 109 | 110 | 1.8 111 | 1.8 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/main/java/nicknameHandlerModule/TextLimitModule.java: -------------------------------------------------------------------------------- 1 | package nicknameHandlerModule; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import com.codingame.gameengine.core.AbstractMultiplayerPlayer; 7 | 8 | import com.codingame.gameengine.core.Module; 9 | import com.codingame.gameengine.core.MultiplayerGameManager; 10 | import com.codingame.gameengine.module.entities.Text; 11 | import com.google.inject.Inject; 12 | 13 | public class TextLimitModule implements Module { 14 | 15 | class TextLimit { 16 | int textId; 17 | int width, height; 18 | boolean shrink; 19 | } 20 | 21 | List limits = new ArrayList<>(6); 22 | private MultiplayerGameManager gameManager; 23 | 24 | @Inject 25 | public TextLimitModule(MultiplayerGameManager gameManager) { 26 | this.gameManager = gameManager; 27 | gameManager.registerModule(this); 28 | } 29 | 30 | @Override 31 | public void onGameInit() { 32 | gameManager.setViewGlobalData("limits", limits.toArray()); 33 | } 34 | 35 | private void setAvailableSpace(Text nick, int width, int height) { 36 | TextLimit textLimit = new TextLimit(); 37 | textLimit.textId = nick.getId(); 38 | textLimit.width = width; 39 | textLimit.height = height; 40 | 41 | limits.add(textLimit); 42 | } 43 | 44 | public void limitAvailableSpace(Text nick, int width, int height) { 45 | setAvailableSpace(nick, width, height); 46 | } 47 | 48 | @Override 49 | public void onAfterGameTurn() { 50 | 51 | } 52 | 53 | @Override 54 | public void onAfterOnEnd() { 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/tooltipModule/TooltipModule.java: -------------------------------------------------------------------------------- 1 | package tooltipModule; 2 | 3 | import java.io.Console; 4 | import java.util.Arrays; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 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.gameengine.module.entities.Entity; 12 | import com.codingame.gameengine.module.entities.GraphicEntityModule; 13 | import com.google.inject.Inject; 14 | 15 | public class TooltipModule implements Module { 16 | 17 | GameManager gameManager; 18 | @Inject 19 | GraphicEntityModule entityModule; 20 | Map> registrations; 21 | Map extra, newExtra; 22 | Map> newRegistrations; 23 | Map> previousRegistrations = new HashMap<>(); 24 | int size = 5; 25 | 26 | @Inject 27 | public TooltipModule(GameManager gameManager) { 28 | this.gameManager = gameManager; 29 | gameManager.registerModule(this); 30 | registrations = new HashMap<>(); 31 | newRegistrations = new HashMap<>(); 32 | extra = new HashMap<>(); 33 | newExtra = new HashMap<>(); 34 | } 35 | 36 | @Override 37 | public void onGameInit() { 38 | sendFrameData(); 39 | } 40 | 41 | @Override 42 | public void onAfterGameTurn() { 43 | sendFrameData(); 44 | } 45 | 46 | @Override 47 | public void onAfterOnEnd() { 48 | sendFrameData(); 49 | } 50 | 51 | private void sendFrameData() { 52 | Object[] data = new Object[]{ 53 | newRegistrations, 54 | newExtra}; 55 | 56 | gameManager.setViewData("tooltips", data); 57 | previousRegistrations.clear(); 58 | for(Integer key : newRegistrations.keySet()){ 59 | previousRegistrations.put(key, newRegistrations.get(key)); 60 | } 61 | 62 | newRegistrations.clear(); 63 | newExtra.clear(); 64 | } 65 | 66 | public void registerEntity(Entity entity) { 67 | registerEntity(entity, new HashMap<>()); 68 | } 69 | 70 | public void registerEntity(Entity entity, String tooltip) { 71 | Map params = new HashMap<>(); 72 | String[] tt = tooltip.split("\n"); 73 | for(String s : tt){ 74 | String[] splitted = s.split(":"); 75 | if(splitted.length < 2) params.put(s, ""); 76 | else params.put(splitted[0], splitted[1]); 77 | } 78 | 79 | registerEntity(entity.getId(), params); 80 | } 81 | 82 | public void registerEntity(Entity entity, Map params) { 83 | registerEntity(entity.getId(), params); 84 | } 85 | 86 | public void registerEntity(int id, Map params) { 87 | if (!params.equals(registrations.get(id))) { 88 | newRegistrations.put(id, params); 89 | registrations.put(id, params); 90 | } 91 | } 92 | 93 | boolean deepEquals(String[] a, String[] b) { 94 | return Arrays.deepEquals(a,b); 95 | } 96 | 97 | public Map getParams(int id) { 98 | Map params = registrations.get(id); 99 | if (params == null) { 100 | params = new HashMap<>(); 101 | registrations.put(id, params); 102 | } 103 | return params; 104 | } 105 | 106 | public void updateExtraTooltipText(Entity entity, String... lines) { 107 | int id = entity.getId(); 108 | 109 | String[] newLines = new String[lines.length]; 110 | for(int i = 0; i < lines.length; i++){ 111 | newLines[i] = replaceKnownTooltips(lines[i]); 112 | } 113 | 114 | if (!deepEquals(newLines, extra.get(id))) 115 | { 116 | newExtra.put(id, newLines); 117 | extra.put(id, newLines); 118 | } 119 | } 120 | 121 | private String replaceKnownTooltips(String s){ 122 | return s.replaceAll("DISH", "#D") 123 | .replaceAll("ICE_CREAM", "#I") 124 | .replaceAll("DOUGH", "#H") 125 | .replaceAll("STRAWBERRIES", "#S") 126 | .replaceAll("TART", "#T") 127 | .replaceAll("BLUEBERRIES", "#B") 128 | .replaceAll("CROISSANT", "#C") 129 | .replaceAll("CHOPPED", "#O"); 130 | } 131 | //#D DISH 132 | //#I ICE_CREAM 133 | //#H DOUGH 134 | //#S STRAWBERRIES 135 | //#T TART 136 | //#B BLUEBERRIES 137 | //#C CROISSANT 138 | //#O CHOPPED 139 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/BoardBuilding.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.model.* 4 | import java.util.* 5 | 6 | lateinit var rand: Random 7 | fun List.random(): E { 8 | val index = rand.nextInt(size) 9 | return this[index] 10 | } 11 | 12 | val boardLayout = listOf( 13 | // ABCDEFGHIJK 14 | /* 0 */ "***********", // 0 15 | /* 1 */ "*.........*", // 1 16 | /* 2 */ "*.****.**.*", // 2 17 | /* 3 */ "*.*..*..*.*", // 3 18 | /* 4 */ "*.**.****.*", // 4 19 | /* 5 */ "*.........*", // 5 20 | /* 6 */ "***********" // 6 21 | // ABCDEFGHIJK 22 | ) 23 | 24 | val equipment = listOf( 25 | "A0", "B0", "C0", "D0", "E0" ,"G0", "H0", "I0", "J0", "K0", 26 | "A1", "A2", "A3", "A4", "A5", 27 | "K1", "K2", "K3", "K4", "K5", 28 | "C2", "D2", "E2", "F2", "H2", "I2", 29 | "C3", "F3", "I3", 30 | "C4", "D4", "F4", "G4", "H4", "I4", 31 | "A6", "B6", "C6", "D6", "E6" ,"G6", "H6", "I6", "J6", "K6" 32 | ) 33 | 34 | val ovenLocs = listOf( 35 | "B0", "C0", "D0", "E0" ,"G0", "H0", "I0", "J0", 36 | "A1", "A2", "A3", "A4", "A5", 37 | "K1", "K2", "K3", "K4", "K5" 38 | ) 39 | 40 | val spawnLocs = "B1 C1 D1 E1 F1 G1 H1 I1 J1 B5 C5 D5 E5 F5 G5 H5 I5 J5 B2 B3 B4 J2 J3 J4 D3 E3 E4 G2 G3 H3" 41 | 42 | 43 | fun buildEmptyBoard(): Board = Board(boardLayout, spawnLocs) 44 | 45 | fun buildBoard(): Board { 46 | val board = buildEmptyBoard() 47 | 48 | val dishReturn = DishWasher().also { board["F0"].equipment = it } 49 | val window = Window(dishReturn) 50 | 51 | board["F6"].equipment = window 52 | board.window = window 53 | 54 | val equipmentLocs = equipment.shuffled(rand).iterator() 55 | 56 | val equipments = listOfNotNull( 57 | (league >= League.IceCreamBerries).then(IceCreamCrate()), 58 | (league >= League.IceCreamBerries).then(BlueberryCrate()), 59 | (league >= League.StrawberriesChoppingBoard).then(StrawberryCrate()), 60 | (league >= League.StrawberriesChoppingBoard).then(ChoppingBoard()), 61 | (league >= League.Croissants).then(DoughCrate()), 62 | (league >= League.Croissants).then(Oven()) 63 | ) 64 | 65 | equipments.forEach { eq -> 66 | val loc = equipmentLocs.findNext { eq !is Oven || it in ovenLocs } 67 | board[loc!!].equipment = eq 68 | } 69 | 70 | return board 71 | } 72 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Player.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.model.Cell 4 | import com.codingame.game.model.Item 5 | import com.codingame.game.model.LogicException 6 | import com.codingame.game.view.BoardView 7 | import com.codingame.gameengine.core.AbstractMultiplayerPlayer 8 | import com.codingame.gameengine.module.entities.Group 9 | import com.codingame.gameengine.module.entities.Sprite 10 | 11 | const val REACH_DISTANCE = 3 12 | const val WALK_DISTANCE = 9 13 | operator fun Int?.compareTo(other: Int): Int = (this ?: Int.MAX_VALUE).compareTo(other) 14 | 15 | class Player : AbstractMultiplayerPlayer() { 16 | var message : String = "" 17 | var crashed : Boolean = false 18 | fun toViewString() = "CHEF:${this.nicknameToken}" 19 | override fun toString() = this.nicknameToken 20 | 21 | override fun getExpectedOutputLines() = 1 22 | lateinit var sprite:Group 23 | lateinit var itemSprite: BoardView.ItemSpriteGroup 24 | lateinit var characterSprite: Sprite 25 | 26 | fun sendInputLine(toks: List) = sendInputLine(toks.joinToString(" ", transform = { it.toString() })) 27 | fun sendInputLine(singleTok: Int) = sendInputLine(singleTok.toString()) 28 | 29 | fun use(cell: Cell): List { 30 | val useCellPath = listOf(location, cell, cell, location) 31 | 32 | if (!cell.isTable) throw Exception("Cannot use $cell: not a table!") 33 | if (cell.distanceTo(location) > REACH_DISTANCE) return moveTo(cell) 34 | val equipment = cell.equipment 35 | if (equipment != null) { 36 | equipment.use(this) 37 | return useCellPath 38 | } 39 | 40 | // try drop 41 | if (heldItem != null) { 42 | cell.item?.also { it.receiveItem(this, heldItem!!, cell); return useCellPath } 43 | cell.item = heldItem 44 | heldItem = null 45 | return useCellPath 46 | } 47 | 48 | // try take 49 | cell.item?.also { it.take(this, cell); return useCellPath } 50 | 51 | throw LogicException("Cannot USE this table now, nothing to do!") 52 | } 53 | 54 | fun moveTo(cell: Cell): List { 55 | val blockedCell = if (partner.isActive) partner.location else null 56 | 57 | val (fromSource, traceBack) = location.buildDistanceMap(blockedCell) 58 | val target = 59 | if (!cell.isTable && cell != blockedCell) 60 | cell 61 | else 62 | cell.neighbours.map { it.first } 63 | .filter { !it.isTable } 64 | .filter { it in fromSource.keys } 65 | .minBy { fromSource[it]!! } ?: return moveTo(blockedCell!!) 66 | 67 | if (target !in fromSource.keys) { 68 | System.err.println("Warning: cannot move! Moving to partner location instead") 69 | return moveTo(blockedCell!!) 70 | } 71 | 72 | if (location.distanceTo(target, blockedCell) <= WALK_DISTANCE) { 73 | location = target 74 | return trace(cell, location, traceBack) 75 | } 76 | 77 | val fromTarget = target.buildDistanceMap(blockedCell).distances 78 | 79 | location = fromSource 80 | .filter { (cell, dist) -> dist <= WALK_DISTANCE && !cell.isTable } 81 | .minBy { (cell, _) -> fromTarget[cell]!! }!! 82 | .key 83 | 84 | return trace(cell, location, traceBack) 85 | } 86 | 87 | private fun trace(source: Cell, target: Cell, traceBack: Map): List { 88 | return sequence { 89 | var cur = target 90 | yield(target) 91 | while (cur != source) { 92 | cur = traceBack[cur] ?: break 93 | yield(cur) 94 | } 95 | }.toList().reversed() 96 | 97 | } 98 | 99 | var heldItem: Item? = null 100 | lateinit var location: Cell 101 | lateinit var partner: Player 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Referee.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | import com.codingame.game.model.* 4 | import com.codingame.game.view.BoardView 5 | import com.codingame.game.view.GameView 6 | import com.codingame.game.view.QueueView 7 | import com.codingame.game.view.ScoresView 8 | import com.codingame.gameengine.core.AbstractPlayer 9 | import com.codingame.gameengine.core.AbstractReferee 10 | import com.codingame.gameengine.core.MultiplayerGameManager 11 | import com.codingame.gameengine.module.endscreen.EndScreenModule 12 | import com.codingame.gameengine.module.entities.GraphicEntityModule 13 | import com.google.inject.Inject 14 | import nicknameHandlerModule.TextLimitModule 15 | import tooltipModule.TooltipModule 16 | import java.util.* 17 | 18 | typealias ScoreBoard = Map 19 | 20 | enum class League { 21 | IceCreamBerries, 22 | StrawberriesChoppingBoard, 23 | Croissants, 24 | All 25 | } 26 | 27 | lateinit var league: League 28 | 29 | @Suppress("unused") // injected by magic 30 | class Referee : AbstractReferee() { 31 | @Inject 32 | private lateinit var gameManager: MultiplayerGameManager 33 | @Inject 34 | private lateinit var graphicEntityModule: GraphicEntityModule 35 | @Inject private lateinit var tooltipModule: TooltipModule 36 | @Inject private lateinit var endScreenModule :EndScreenModule 37 | @Inject private lateinit var textLimitModule: TextLimitModule 38 | 39 | 40 | private lateinit var board: Board 41 | private lateinit var queue: CustomerQueue 42 | val view = GameView() 43 | private lateinit var matchPlayers: MutableList 44 | 45 | class ScoreEntry(var roundScores: Array) { 46 | fun total() = roundScores.filterNotNull().sum() 47 | override fun toString() = roundScores.let { 48 | "[${it[0]}, ${it[1]}, ${it[2]}]" 49 | } 50 | } 51 | private lateinit var scoreBoard: ScoreBoard 52 | 53 | override fun init() { 54 | rand = Random(gameManager.seed) 55 | com.codingame.game.view.graphicEntityModule = graphicEntityModule 56 | com.codingame.game.view.tooltipModule = tooltipModule 57 | com.codingame.game.view.textLimitModule = textLimitModule 58 | 59 | matchPlayers = gameManager.players.toMutableList() 60 | scoreBoard = mapOf( 61 | matchPlayers[0] to ScoreEntry(arrayOf(0, 0, null)), 62 | matchPlayers[1] to ScoreEntry(arrayOf(0, null, 0)), 63 | matchPlayers[2] to ScoreEntry(arrayOf(null, 0, 0)) 64 | ) 65 | gameManager.maxTurns = 606 66 | gameManager.frameDuration = 400 67 | 68 | league = when (gameManager.leagueLevel) { 69 | 1 -> League.IceCreamBerries 70 | 2 -> League.StrawberriesChoppingBoard 71 | 3 -> League.Croissants 72 | else -> League.All 73 | } 74 | 75 | board = buildBoard() 76 | view.boardView = BoardView(board, matchPlayers) 77 | 78 | view.queueView = QueueView() 79 | view.scoresView = ScoresView(matchPlayers) 80 | 81 | matchPlayers.forEach { player -> 82 | player.describeCustomers(originalQueue) 83 | 84 | board.cells.transpose { Cell() }.forEach { cellRow -> 85 | player.sendInputLine(cellRow.map { it.describeChar(player.index == 1) }.joinToString("")) 86 | } 87 | } 88 | 89 | } 90 | 91 | private var currentRound: RoundReferee? = null 92 | private var roundNumber: Int = -1 93 | 94 | private fun nextRound(): RoundReferee { 95 | val roundPlayers = matchPlayers.take(2) 96 | view.boardView.removePlayer(matchPlayers[2]) 97 | Collections.rotate(matchPlayers, 1) 98 | board.reset() 99 | queue = CustomerQueue() 100 | queue.getNewCustomers() 101 | queue.onFailure = { view.queueView.failed = true } 102 | 103 | roundPlayers[0].apply { location = board[board.spawnLocations[0]]; heldItem = null } 104 | roundPlayers[1].apply { location = board[board.spawnLocations[1]]; heldItem = null } 105 | view.boardView.board = board 106 | view.boardView.players = roundPlayers 107 | view.queueView.queue = queue 108 | 109 | roundNumber++ 110 | return RoundReferee(roundPlayers, roundNumber) 111 | } 112 | 113 | override fun gameTurn(turn: Int) { 114 | if (currentRound == null || currentRound!!.isOver()) { 115 | currentRound = nextRound() 116 | if (roundNumber >= 3) { 117 | gameManager.endGame() 118 | return 119 | } 120 | currentRound!!.players.forEach { 121 | gameManager.addTooltip(it, "Round ${roundNumber + 1} starting!") 122 | } 123 | view.boardView.resetPlayers() 124 | } else { 125 | currentRound!!.gameTurn(turn) 126 | } 127 | 128 | view.scoresView.currentRoundNumber = roundNumber 129 | view.scoresView.update(scoreBoard) 130 | view.queueView.updateQueue() 131 | view.boardView.updateCells(board) 132 | } 133 | 134 | override fun onEnd() { 135 | scoreBoard.forEach { player, entry -> 136 | player.score = if (player.crashed) -1 else entry.total() 137 | } 138 | 139 | endScreenModule.titleRankingsSprite = "logo.png" 140 | endScreenModule.setScores(gameManager.players.map { it.score }.toIntArray()) 141 | } 142 | 143 | inner class RoundReferee(val players: List, roundNumber: Int) { 144 | var score = 0 145 | 146 | private fun ScoreBoard.setScore(roundNumber: Int, score: Int) { 147 | forEach { _, entry -> 148 | if (entry.roundScores[roundNumber] != null) entry.roundScores[roundNumber] = score 149 | } 150 | } 151 | 152 | init { 153 | queue.onPointsAwarded = { 154 | score += it 155 | scoreBoard.setScore(roundNumber, score) 156 | } 157 | board.window.onDelivery = queue::delivery 158 | 159 | players.forEach { player -> 160 | 161 | player.partner = players.find { it != player }!! 162 | } 163 | } 164 | 165 | var turn = 0 166 | var nextPlayerId = 0 167 | 168 | fun isOver(): Boolean = turn > 200 169 | 170 | fun gameTurn(matchTurn: Int) { 171 | turn++ 172 | if (isOver()) return 173 | 174 | board.tick() 175 | val thePlayer = players[nextPlayerId] 176 | nextPlayerId = 1-nextPlayerId 177 | if (!thePlayer.isActive) { 178 | System.err.println("(Turn $turn) WARNING: ${thePlayer.nicknameToken} is dead; skipping") 179 | if (thePlayer.score == 0) thePlayer.score = -1000 + matchTurn 180 | thePlayer.crashed = true 181 | view.boardView.removePlayer(thePlayer) 182 | return gameTurn(matchTurn) 183 | } 184 | 185 | fun sendGameState(player: Player) { 186 | 187 | // 0. Describe turns remaining 188 | player.sendInputLine(200 - turn) 189 | 190 | // 1. Describe self, then partner 191 | players.sortedByDescending { it == player }.forEach { 192 | 193 | val toks = if (it.isActive) listOf( 194 | it.location.x, 195 | it.location.y, 196 | it.heldItem?.describe() ?: "NONE" 197 | ) else listOf(-1, -1, "NONE") 198 | 199 | // println("Sending player toks $toks to $player") 200 | player.sendInputLine(toks) 201 | } 202 | 203 | // 2. Describe all table cells with items 204 | board.allCells.filter { it.isTable && it.item != null } 205 | .also { player.sendInputLine(it.size) } 206 | .forEach { 207 | val toks = listOf( 208 | it.x, 209 | it.y, 210 | it.item!!.describe() 211 | ) 212 | player.sendInputLine(toks) 213 | } 214 | 215 | // 3. Describe oven 216 | board.oven().let { 217 | player.sendInputLine(it?.state?.toString() ?: "NONE 0") 218 | } 219 | 220 | // 4. Describe customer queue 221 | player.describeCustomers(queue.activeCustomers) 222 | } 223 | 224 | fun processPlayerActions(player: Player) { 225 | var line = if (!player.isActive) "WAIT" else player.outputs[0].trim() 226 | if(line.isEmpty()) line = "WAIT" 227 | 228 | val semicolon = line.indexOf(';').nullIf(-1) 229 | 230 | val fullCommand = if (semicolon != null) { 231 | player.message = line.substring(semicolon + 1).replace(";", "").take(18) 232 | line.substring(0, semicolon) 233 | } else { 234 | player.message = "" 235 | line 236 | } 237 | 238 | val toks = fullCommand.split(" ").iterator() 239 | val command = toks.next() 240 | var path: List? = null 241 | 242 | if (command != "WAIT") { 243 | val target = try { 244 | val cellx = toks.next().toInt() 245 | val celly = toks.next().toInt() 246 | 247 | board[cellx, celly] 248 | } catch (_: Exception) { 249 | throw Exception("Invalid command: $fullCommand") 250 | } 251 | 252 | path = when (command) { 253 | "MOVE" -> player.moveTo(target) 254 | "USE" -> player.use(target) 255 | else -> throw Exception("Invalid command: $fullCommand") 256 | } 257 | } 258 | 259 | view.boardView.updatePlayer(player, path) 260 | } 261 | 262 | //println("Current players: ${players.map { it.nicknameToken }}") 263 | queue.getNewCustomers() 264 | sendGameState(thePlayer) 265 | thePlayer.execute() 266 | 267 | try { 268 | processPlayerActions(thePlayer) 269 | } catch (ex: LogicException) { 270 | gameManager.addToGameSummary("${thePlayer.nicknameToken}: ${ex.message}") 271 | } 272 | catch(ex: AbstractPlayer.TimeoutException){ 273 | disablePlayer(thePlayer, "timeout") 274 | } 275 | catch (ex: Exception) { 276 | disablePlayer(thePlayer, ex.message) 277 | } 278 | 279 | queue.updateRemainingCustomers() 280 | } 281 | 282 | fun disablePlayer(player: Player, message: String?){ 283 | player.crashed = true 284 | gameManager.addToGameSummary("${player.nicknameToken}: ${message} (deactivating!)") 285 | player.deactivate("${player.nicknameToken}: ${message}") 286 | if (player.heldItem is Dish) { 287 | board.allCells.mapNotNull { (it.equipment as? DishWasher) } 288 | .first().let { it.addDish() } 289 | } 290 | } 291 | } 292 | } 293 | 294 | 295 | 296 | private fun Player.describeCustomers(customers: List) { 297 | customers 298 | .also { sendInputLine(it.size) } 299 | .also { 300 | it.forEach { 301 | val toks = listOf(it.dish.describe(), it.award.toString()) 302 | sendInputLine(toks) 303 | } 304 | } 305 | } 306 | 307 | 308 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/Util.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game 2 | 3 | fun Boolean.then(thenExpr: T) = if (this) thenExpr else null 4 | 5 | fun Iterator.tryNext() = if (hasNext()) next() else null 6 | 7 | fun Iterator.findNext(pred: (T) -> Boolean): T? { 8 | while (hasNext()) { 9 | next().also { if (pred(it)) return it } 10 | } 11 | return null 12 | } 13 | 14 | inline fun Array>.transpose(default: () -> T): Array> { 15 | val rows = this.size 16 | val cols = this[0].size 17 | val trans = Array(cols) { Array(rows) { default() } } 18 | for (i in 0 until cols) { 19 | for (j in 0 until rows) trans[i][j] = this[j][i] 20 | } 21 | return trans 22 | } 23 | 24 | fun Sequence.splitAccumulate(length: Int) = sequence { 25 | var currentLine = "" 26 | 27 | forEach { tok -> 28 | if (currentLine.length + tok.length > length) { 29 | yield(currentLine.substring(1)) 30 | currentLine = "" 31 | } 32 | currentLine += " $tok" 33 | } 34 | 35 | yield(currentLine.substring(1)) 36 | } 37 | 38 | fun List.loopingIterator(): Iterator = 39 | sequence { while (true) yieldAll(this@loopingIterator) }.iterator() 40 | 41 | fun T.nullIf(nullVal: T): T? = if (this == nullVal) null else this 42 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/BoardFeatures.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.rand 4 | import com.codingame.game.view.BoardView 5 | import com.codingame.gameengine.module.entities.* 6 | import java.util.* 7 | import kotlin.math.absoluteValue 8 | 9 | class CellView(val cell: Cell) { 10 | lateinit var background: Rectangle 11 | lateinit var content: Sprite 12 | lateinit var secondaryContent: Sprite 13 | var itemSpriteGroup: BoardView.ItemSpriteGroup? = null 14 | lateinit var group: Group 15 | // lateinit var text: Text 16 | } 17 | 18 | class Cell(val x: Int, val y: Int, val isTable: Boolean = true, val character: Char? = null, val playerId: Int? = null) { 19 | constructor(): this(-1, -1) 20 | override fun toString(): String = "($x, $y)" 21 | private val straightNeighbours = mutableListOf() 22 | private val diagonalNeighbours = mutableListOf() 23 | 24 | val neighbours by lazy { straightNeighbours.map {it to 2} + 25 | diagonalNeighbours.filter { it.isTable || isTable }.map {it to 3} } 26 | 27 | fun connect(other: Cell, isStraight: Boolean) { 28 | (if (isStraight) straightNeighbours else diagonalNeighbours) += other 29 | } 30 | 31 | var equipment: Equipment? = null 32 | var item: Item? = null 33 | 34 | data class DistanceMap(val distances: Map, val traceBack: Map) 35 | 36 | fun buildDistanceMap(blockedCell: Cell?): DistanceMap { 37 | val visitedCells = mutableMapOf() 38 | val traceBack = mutableMapOf() 39 | val floodedCells = PriorityQueue> { (_,d1), (_,d2) -> d1.compareTo(d2) } 40 | floodedCells += this to 0 41 | var isFirst = true 42 | 43 | while (floodedCells.any()) { 44 | val (cell, dist) = floodedCells.remove()!! 45 | if (cell in visitedCells) continue 46 | visitedCells += cell to dist 47 | if ((!cell.isTable && cell != blockedCell) || isFirst) { 48 | floodedCells += cell.neighbours 49 | .filterNot { (nc, _) -> nc == blockedCell } 50 | .filterNot { (nc, _) -> nc in visitedCells.keys } 51 | .map { (nc, nd) -> 52 | traceBack[nc] = cell 53 | nc to dist + nd 54 | } 55 | } 56 | isFirst = false 57 | } 58 | return DistanceMap(visitedCells, traceBack) 59 | } 60 | 61 | fun distanceTo(target: Cell, partnerCell: Cell? = null): Int? { 62 | return buildDistanceMap(partnerCell).distances[target] 63 | } 64 | 65 | fun describeChar(invert: Boolean): Char = if(playerId != null) { 66 | '0' + if(invert) (1 - playerId) else playerId 67 | } else { 68 | if (!isTable) '.' else equipment?.describeChar ?: '#' 69 | } 70 | } 71 | 72 | 73 | class Board(val width: Int, val height: Int, allSpawns: String, val layout: List? = null) { 74 | lateinit var window: Window 75 | constructor(layout: List, allSpawns : String): this(layout[0].length, layout.size, allSpawns, layout) 76 | val spawnLocations = allSpawns.split(' ').shuffled(rand).take(2) 77 | 78 | fun reset() { allCells.forEach { c -> 79 | c.equipment?.reset() 80 | c.item = null 81 | } } 82 | 83 | val cells = Array(width) { x -> 84 | Array(height) { y -> 85 | Cell(x, y, layout != null && layout[y][x] != '.', 86 | layout?.get(y)?.get(x)?.let { if (it !in listOf('*', '.')) it else null }, getPlayerPos(x, y)) 87 | } 88 | } 89 | 90 | val allCells = cells.flatten() 91 | 92 | private fun getPlayerPos(x: Int, y: Int) : Int?{ 93 | for (i in 0..1){ 94 | val posX = spawnLocations[i][0]-'A' 95 | val posY = spawnLocations[i][1]-'0' 96 | if(posX == x && posY == y) return i 97 | } 98 | 99 | return null 100 | } 101 | operator fun get(x: Int, y: Int): Cell = cells[x][y] 102 | operator fun get(cellName: String): Cell { 103 | val file = cellName[0] 104 | val x = file - 'A' 105 | if (x !in xRange) throw IllegalArgumentException("x: $x") 106 | return get(x, cellName.substring(1).toInt()) 107 | } 108 | 109 | fun tick() { 110 | allCells.forEach { cell -> (cell.equipment as? TimeSensitiveEquipment)?.tick() } 111 | } 112 | 113 | private val xRange = 0 until width 114 | private val yRange = 0 until height 115 | 116 | init { 117 | for (x in xRange) { 118 | for (y in yRange) { 119 | for (dx in -1..1) for (dy in -1..1) { 120 | if (dx != 0 || dy != 0) { 121 | val x2 = x+dx; val y2 = y+dy 122 | if (x2 in xRange && y2 in yRange) { 123 | this[x,y].connect(this[x2,y2], dx*dy == 0) 124 | } 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | fun oven() = allCells.mapNotNull { it.equipment as? Oven }.firstOrNull() 132 | } 133 | 134 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/ChoppingBoard.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | class ChoppingBoard: Equipment() { 6 | override val describeChar = 'C' 7 | override val tooltipString = "Chopping board" 8 | 9 | override fun receiveItem(player: Player, item: Item) { 10 | if (item === Strawberries) { 11 | player.heldItem = ChoppedStrawberries 12 | return 13 | } 14 | 15 | if (item === Dough) { 16 | player.heldItem = Shell() 17 | return 18 | } 19 | 20 | super.receiveItem(player, item) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | object Constants { 4 | enum class ITEM { 5 | FOOD, DOUGH, DISH, STRAWBERRIES, CHOPPED_DOUGH, RAW_TART 6 | } 7 | 8 | enum class FOOD { 9 | ICE_CREAM, 10 | BLUEBERRIES, 11 | CHOPPED_STRAWBERRIES, 12 | CROISSANT, 13 | TART 14 | } 15 | 16 | const val OVEN_COOKTIME = 10 17 | const val OVEN_BURNTIME = 10 18 | val TIP = 200 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Containers.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | abstract class EdibleItem(private val singleToken: String): Item() { 6 | override fun receiveItem(player: Player, item: Item, cell: Cell?) { 7 | if (item is Dish) { 8 | item += this 9 | cell!!.item = null 10 | return 11 | } 12 | return super.receiveItem(player, item, cell) 13 | } 14 | 15 | override fun describeTokens() = listOf(singleToken) 16 | override fun toString() = singleToken 17 | } 18 | 19 | interface Container { 20 | val contents: MutableSet 21 | } 22 | 23 | infix operator fun Container.plusAssign(item: EdibleItem) { 24 | if (item in contents) throw LogicException("Can't add: $this already contains $item") 25 | contents += item 26 | } 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Crates.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | class IceCreamCrate: GeneralCrate(IceCream, 'I', "Ice cream crate") 6 | class BlueberryCrate: GeneralCrate(Blueberries, 'B', "Blueberry crate") 7 | class StrawberryCrate: GeneralCrate(Strawberries, 'S', "Strawberry crate") 8 | class DoughCrate: GeneralCrate(Dough, 'H', "Dough crate") 9 | 10 | abstract class AllInstancesAreConsideredEqual: Equipment() { 11 | override fun equals(other: Any?): Boolean = other!!.javaClass == this.javaClass 12 | override fun hashCode(): Int = 0 13 | } 14 | 15 | abstract class GeneralCrate( 16 | private val newItem: Item, 17 | override val describeChar: Char, 18 | override val tooltipString: String 19 | ) : AllInstancesAreConsideredEqual() { 20 | 21 | override fun receiveItem(player: Player, item: Item) { 22 | item.receiveItem(player, newItem, null) 23 | player.heldItem = item 24 | } 25 | 26 | override fun takeFrom(player: Player) = newItem 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Customers.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.* 4 | 5 | val originalQueue = List(20) { Customer.randomCustomer() } 6 | 7 | class CustomerQueue { 8 | private val queueIterator = originalQueue.loopingIterator() 9 | 10 | val activeCustomers: MutableList = mutableListOf() 11 | 12 | lateinit var onPointsAwarded: (Int) -> Unit 13 | lateinit var onFailure: () -> Unit 14 | 15 | fun delivery(item: Item) { 16 | activeCustomers.filter { it.dish == item }.maxBy { it.award }?.also { 17 | onPointsAwarded(it.award) 18 | it.satisfaction = Satisfaction.Satisfied 19 | } ?: onFailure() 20 | } 21 | 22 | fun getNewCustomers() { 23 | activeCustomers.removeIf { 24 | it.satisfaction in listOf(Satisfaction.Satisfied) 25 | } 26 | while(activeCustomers.size < 3) { 27 | activeCustomers += queueIterator.next().also { 28 | it.award = it.originalAward 29 | it.satisfaction = Satisfaction.Waiting 30 | }} 31 | } 32 | 33 | fun updateRemainingCustomers() { 34 | activeCustomers 35 | .filter { it.satisfaction != Satisfaction.Satisfied } 36 | .forEach { it.updateSatisfaction() } 37 | } 38 | } 39 | 40 | enum class Satisfaction { 41 | Waiting, 42 | Satisfied, 43 | } 44 | 45 | data class Customer(val dish: Dish, var award: Int) { 46 | val originalAward = award 47 | var satisfaction: Satisfaction = Satisfaction.Waiting 48 | 49 | fun updateSatisfaction() { 50 | award -= 1 51 | } 52 | 53 | companion object { 54 | private val possiblePlateContents = 55 | mapOf(IceCream to 200, Blueberries to 250) + 56 | (if (league >= League.StrawberriesChoppingBoard) mapOf(ChoppedStrawberries to 400) else mapOf()) + 57 | (if (league >= League.Croissants) mapOf(Croissant to 650) else mapOf()) + 58 | (if (league >= League.All) mapOf(Tart to 1000) else mapOf()) 59 | 60 | private fun randomOrder(): Dish = 61 | Dish(possiblePlateContents.keys.shuffled(rand) 62 | .take( 63 | when (rand.nextDouble()) { 64 | in 0.0 .. 0.25 -> 4 65 | in 0.25 .. 0.5 -> 3 66 | else -> 2 67 | } 68 | ).toMutableSet()) 69 | 70 | fun randomCustomer(): Customer { 71 | val order = randomOrder() 72 | val price = Constants.TIP + order.contents.sumBy { possiblePlateContents[it]!! } 73 | return Customer(order, price) 74 | } 75 | } 76 | } 77 | 78 | class Window(private val dishWasher: DishWasher? = null) : Equipment() { 79 | override val tooltipString = "Window" 80 | var onDelivery: (Item) -> Unit = { } 81 | 82 | override val describeChar = 'W' 83 | 84 | private fun deliver(dish: Dish) { 85 | onDelivery(dish) 86 | dishWasher?.addDish() 87 | } 88 | 89 | override fun receiveItem(player: Player, item: Item) { 90 | if (item is Dish) { 91 | deliver(item) 92 | player.heldItem = null 93 | return 94 | } 95 | super.receiveItem(player, item) 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Dishes.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | data class Dish(override val contents: MutableSet = mutableSetOf()) : Item(), Container { 6 | constructor(vararg initialContents: EdibleItem): this(mutableSetOf(*initialContents)) 7 | 8 | override fun receiveItem(player: Player, item: Item, cell: Cell?) { 9 | if (item is EdibleItem) { 10 | this += item 11 | player.heldItem = null 12 | return 13 | } 14 | super.receiveItem(player, item, cell) 15 | } 16 | 17 | override fun describeTokens(): List { 18 | return listOf(Constants.ITEM.DISH.name) + contents.map { it.describe() } 19 | } 20 | } 21 | 22 | class DishWasher: Equipment() { 23 | override val describeChar = 'D' 24 | override val tooltipString = "Dish washer" 25 | 26 | override fun reset() { dishes = 3 } 27 | 28 | var dishes: Int = 3 29 | private set 30 | 31 | override fun receiveItem(player: Player, item: Item) { 32 | if (item is Dish) { 33 | player.heldItem = Dish() 34 | return 35 | } 36 | 37 | if (item is EdibleItem) { 38 | removeDish() 39 | player.heldItem = Dish(item) 40 | return 41 | } 42 | 43 | super.receiveItem(player, item) 44 | } 45 | 46 | override fun takeFrom(player: Player): Item { 47 | removeDish() 48 | return Dish() 49 | } 50 | 51 | fun addDish() { dishes++ } 52 | 53 | fun removeDish() { 54 | if (dishes <= 0) throw LogicException("Dishwasher is empty!") 55 | dishes-- 56 | } 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Items.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | object IceCream : EdibleItem(Constants.FOOD.ICE_CREAM.name) 6 | object Strawberries: EasilyDescribedItem(Constants.ITEM.STRAWBERRIES.name) 7 | object ChoppedStrawberries: EdibleItem(Constants.FOOD.CHOPPED_STRAWBERRIES.name) 8 | object Dough: EasilyDescribedItem(Constants.ITEM.DOUGH.name) 9 | object Croissant: EdibleItem(Constants.FOOD.CROISSANT.name) 10 | object Tart: EdibleItem(Constants.FOOD.TART.name) 11 | 12 | data class Shell(var hasBlueberry: Boolean = false): Item() { 13 | override fun receiveItem(player: Player, item: Item, cell: Cell?) { 14 | if (item !is Blueberries) throw LogicException("Cannot add $item to $this!") 15 | if (hasBlueberry) throw LogicException("This already has blueberries!") 16 | 17 | player.heldItem = this.also { it.hasBlueberry = true } 18 | cell?.item = null 19 | } 20 | 21 | override fun describeTokens() = 22 | listOf(if (hasBlueberry) Constants.ITEM.RAW_TART.name else Constants.ITEM.CHOPPED_DOUGH.name) 23 | } 24 | 25 | object Blueberries: EdibleItem(Constants.FOOD.BLUEBERRIES.name) { 26 | override fun receiveItem(player: Player, item: Item, cell: Cell?) { 27 | if (item !is Shell) return super.receiveItem(player, item, cell) 28 | if (item.hasBlueberry) throw LogicException("This already has blueberries!") 29 | 30 | player.heldItem = item.also { it.hasBlueberry = true } 31 | cell?.item = null 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/ItemsAndEquipment.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | abstract class Item { 6 | open fun receiveItem(player: Player, item: Item, cell: Cell?) { 7 | throw LogicException("Cannot take $this while holding $item!") 8 | } 9 | 10 | open fun take(player: Player, cell: Cell) { 11 | cell.item = null 12 | player.heldItem = this 13 | } 14 | 15 | abstract fun describeTokens(): List 16 | fun describe() = describeTokens().joinToString("-") 17 | override fun toString() = describe() 18 | } 19 | 20 | abstract class EasilyDescribedItem(private val singleItemToken: String): Item() { 21 | override fun describeTokens() = listOf(singleItemToken) 22 | } 23 | 24 | /** 25 | * Represents a feature of the board. Cannot be moved or picked up, but can be USEd. 26 | */ 27 | abstract class Equipment { 28 | open fun use(player: Player) { 29 | player.heldItem?.also { return receiveItem(player, it) } 30 | player.heldItem = takeFrom(player) 31 | } 32 | 33 | open fun takeFrom(player: Player): Item { 34 | throw LogicException("Cannot USE $this now, not holding anything!") 35 | } 36 | 37 | open fun receiveItem(player: Player, item: Item) { 38 | throw LogicException("Cannot drop $item onto $this!") 39 | } 40 | 41 | open fun reset() { } 42 | 43 | abstract val describeChar: Char? 44 | abstract val tooltipString: String 45 | override fun toString() = tooltipString 46 | } 47 | 48 | /** 49 | * Marks the passage of time 50 | */ 51 | abstract class TimeSensitiveEquipment: Equipment() { 52 | abstract fun tick() 53 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/LogicException.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | class LogicException(message: String): Exception(message) -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/model/Oven.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.model 2 | 3 | import com.codingame.game.Player 4 | 5 | sealed class OvenState(private val contentsStr: String, private val timer: Int) { 6 | override fun toString() = "$contentsStr $timer" 7 | 8 | object Empty: OvenState("NONE", 0) 9 | class Baking(val contents: Item, val timeUntilCooked: Int): OvenState( 10 | contents.describe(), timeUntilCooked 11 | ) 12 | class Ready(val contents: EdibleItem, val timeUntilBurnt: Int): OvenState( 13 | contents.describe(), timeUntilBurnt 14 | ) 15 | object Burning: OvenState("NONE", 0) 16 | } 17 | 18 | class Oven( 19 | private val cookTime: Int = Constants.OVEN_COOKTIME, 20 | private val burnTime: Int = Constants.OVEN_BURNTIME, 21 | var state: OvenState = OvenState.Empty) : TimeSensitiveEquipment() { 22 | 23 | override val tooltipString = "Oven" 24 | 25 | fun toViewString() : String { 26 | val curState = state 27 | return when (curState) { 28 | is OvenState.Empty -> "" 29 | is OvenState.Baking -> "Item:${curState.contents.describe()}\nTimer:${curState.timeUntilCooked}" 30 | is OvenState.Ready -> "Item:${curState.contents.describe()}\nBurn timer:${curState.timeUntilBurnt}" 31 | else -> "Food burnt to a crisp" 32 | } 33 | } 34 | 35 | override fun reset() { state = OvenState.Empty } 36 | override val describeChar = 'O' 37 | 38 | override fun tick() { 39 | val curState = state 40 | state = when (curState) { 41 | is OvenState.Empty -> return 42 | is OvenState.Burning -> OvenState.Empty 43 | is OvenState.Baking -> { 44 | if (curState.timeUntilCooked == 1) 45 | OvenState.Ready( 46 | when (curState.contents) { 47 | is Dough -> Croissant 48 | is Shell -> Tart 49 | else -> throw Exception("Wasn't expecting oven contents: ${curState.contents}!") 50 | }, 51 | burnTime) 52 | else 53 | OvenState.Baking(curState.contents, curState.timeUntilCooked - 1) 54 | } 55 | is OvenState.Ready -> { 56 | val time = curState.timeUntilBurnt 57 | if (time == 1) 58 | OvenState.Burning 59 | else 60 | OvenState.Ready(curState.contents, curState.timeUntilBurnt - 1) 61 | } 62 | } 63 | } 64 | 65 | override fun receiveItem(player: Player, item: Item) { 66 | if (state is OvenState.Ready && item is Dish) { 67 | item.receiveItem(player, (state as OvenState.Ready).contents, null) 68 | player.heldItem = item 69 | state = OvenState.Empty 70 | return 71 | } 72 | 73 | if (state !in listOf(OvenState.Empty, OvenState.Burning)) 74 | throw LogicException("Cannot insert: oven not empty!") 75 | 76 | if (item is Shell && item.hasBlueberry) { 77 | state = OvenState.Baking(item, cookTime) 78 | player.heldItem = null 79 | return 80 | } 81 | 82 | if (item is Dough) { 83 | state = OvenState.Baking(item, cookTime) 84 | player.heldItem = null 85 | return 86 | } 87 | 88 | super.receiveItem(player, item) 89 | } 90 | 91 | override fun takeFrom(player: Player): Item { 92 | lateinit var retVal: Item 93 | val curState = state 94 | state = when (curState) { 95 | OvenState.Empty, OvenState.Burning -> 96 | throw LogicException("Cannot take from $this: nothing inside!") 97 | is OvenState.Baking -> throw LogicException("Cannot take from $this: food is baking!") 98 | is OvenState.Ready -> OvenState.Empty.also { retVal = curState.contents } 99 | } 100 | return retVal 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/view/BoardView.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.view 2 | 3 | import com.codingame.game.Player 4 | import com.codingame.game.model.* 5 | import com.codingame.gameengine.module.entities.* 6 | import tooltipModule.TooltipModule 7 | import java.awt.geom.Point2D 8 | 9 | 10 | class BoardView(baseBoard: Board, matchPlayers: List) { 11 | companion object { 12 | lateinit var yRange: IntRange 13 | lateinit var xRange: IntRange 14 | } 15 | 16 | var cellWidth: Int = 0 17 | var cellHeight: Int = 0 18 | val cellSpacing = 5 19 | 20 | lateinit var board: Board 21 | lateinit var players: List 22 | var ovenSprite: Sprite? = null 23 | var ovenGlowSprite: Sprite? = null 24 | var ovenContentSprite: Sprite? = null 25 | lateinit var dishesSprites: List 26 | 27 | private var cellViews: MutableList = mutableListOf() 28 | 29 | init { 30 | fun setTooltip(tooltipModule: TooltipModule, cell: Cell, group: Group) { 31 | if(cell.equipment != null){ 32 | tooltipModule.registerEntity(group, "Equipment:${cell.equipment?.tooltipString}") 33 | } 34 | else 35 | tooltipModule.registerEntity(group) 36 | } 37 | 38 | val floorColor = 0xe0e0eb 39 | val tableColor = 0x756b68 40 | // val floorColor = 0xe0e0eb 41 | // val tableColor = 0xb35900 42 | 43 | val background = graphicEntityModule.createSprite().apply { 44 | image = "background03.jpg" 45 | baseWidth = 1920 46 | baseHeight = 1080 47 | anchorX = 0.0 48 | anchorY = 0.0 49 | x = 0 50 | y = 0 51 | } 52 | tooltipModule.registerEntity(background) 53 | 54 | val gridHeight = yRange.last - yRange.first 55 | val gridWidth = xRange.last - xRange.first 56 | 57 | //cellWidth = Math.min(gridHeight / baseBoard.height, gridWidth / baseBoard.width) - cellSpacing 58 | 59 | var nextX = xRange.first 60 | var nextY = yRange.first 61 | 62 | baseBoard.cells.forEachIndexed { colIndex, cellCol -> 63 | val isEdgeCol = colIndex == 0 || colIndex == 10 64 | nextY = yRange.first 65 | 66 | cellWidth = if (isEdgeCol) 140 else 132 67 | 68 | cellCol.forEachIndexed { rowIndex, cell -> 69 | val isFirstRow = rowIndex == 0 70 | cellHeight = if (isFirstRow) 136 else 110 71 | val x = nextX 72 | val y = nextY 73 | 74 | nextY += cellHeight 75 | 76 | cellViews.add(CellView(cell).apply { 77 | 78 | val equipment = cell.equipment 79 | if (equipment != null) { 80 | content = graphicEntityModule.createSprite().apply { 81 | baseHeight = (110 * 0.75).toInt() 82 | baseWidth = (132 * 0.75).toInt() 83 | anchorX = 0.5 84 | anchorY = 0.0 85 | setX(cellWidth / 2) 86 | setY(10) 87 | when (equipment) { 88 | is ChoppingBoard -> image = "board02.png" 89 | is GeneralCrate -> image = "bowl.png" 90 | is Oven -> { 91 | image = if (cell.x == 0) "oven_left.png" else if (cell.x == 10) "oven_right.png" else "microwave.png" 92 | // baseHeight = if (cell.x == 0 || cell.x == 10) 131 else 99 93 | // baseWidth = if (cell.x == 0 || cell.x == 10) 140 else 132 94 | baseHeight = cellHeight 95 | baseWidth = cellWidth 96 | anchorX = if (cell.x == 10) 1.0 else 0.0 97 | anchorY = 0.0 98 | if (cell.x == 0) setX(8) else if (cell.x == 10) setX(cellWidth - 8) else setX(0) 99 | setY(0) 100 | } 101 | is Window -> { 102 | image = "window.png" 103 | baseWidth = cellWidth / 2 104 | baseHeight = cellHeight / 2 105 | } 106 | // is DishWasher -> image = "dishwasher.png" 107 | } 108 | } 109 | 110 | when (equipment) { 111 | is Oven -> { 112 | ovenSprite = content 113 | secondaryContent = graphicEntityModule.createSprite().apply { 114 | baseWidth = cellWidth * 3 / 4 115 | baseHeight = cellHeight * 3 / 4 116 | anchorX = 0.5 117 | anchorY = 0.5 118 | setX(cellWidth / 2) 119 | setY(cellHeight / 2) 120 | zIndex = 4000 121 | isVisible = false 122 | alpha = 0.5 123 | } 124 | ovenGlowSprite = graphicEntityModule.createSprite().apply { 125 | baseWidth = cellWidth 126 | baseHeight = cellHeight 127 | anchorX = if (cell.x == 0) -0.2 else if (cell.x == 10) 0.2 else 0.0 128 | anchorY = 0.0 129 | zIndex = 3999 130 | isVisible = false 131 | alpha = 1.0 132 | image = if (cell.x == 0) "hot_right.png" else if (cell.x == 10) "hot_left.png" else if (cell.y == 0) "hot_top.png" else "" 133 | } 134 | (ovenGlowSprite as Sprite).setLocation(cell) 135 | ovenContentSprite = secondaryContent 136 | tooltipModule.registerEntity(ovenSprite) 137 | } 138 | is DishWasher -> { 139 | secondaryContent = graphicEntityModule.createSprite().apply { isVisible = false } 140 | dishesSprites = List(3) { i -> 141 | graphicEntityModule.createSprite().apply { 142 | anchorX = 0.5 143 | anchorY = 0.0 144 | baseHeight = 78 + (7 * i) 145 | baseWidth = 78 + (7 * i) 146 | zIndex = 50 + i 147 | image = "plate.png" 148 | setX((132 + (-10..10).random()) / 2) 149 | setY(15 - (20 * i)) 150 | isVisible = true 151 | } 152 | } 153 | } 154 | else -> secondaryContent = graphicEntityModule.createSprite().apply { 155 | when (equipment) { 156 | is StrawberryCrate -> image = "strawberry.png" 157 | is BlueberryCrate -> image = "blueberries.png" 158 | is IceCreamCrate -> image = "ice-cream.png" 159 | is DoughCrate -> image = "dough.png" 160 | else -> isVisible = false 161 | } 162 | baseHeight = cellHeight / 2 163 | baseWidth = cellWidth / 2 164 | anchorX = 0.5 165 | anchorY = 0.0 166 | alpha = 1.0 167 | setX(cellWidth / 2) 168 | setY(if (cell.y == 0) 10 else 20) 169 | } 170 | } 171 | 172 | itemSpriteGroup = null 173 | 174 | // itemSpriteGroup = ItemSpriteGroup() 175 | // itemSpriteGroup.group.setY(-20).setX(10) 176 | 177 | group = graphicEntityModule.createGroup(content, secondaryContent) 178 | .setX(x).setY(y) 179 | 180 | if (equipment is DishWasher) { 181 | graphicEntityModule.createGroup().apply { 182 | add(dishesSprites[0]) 183 | add(dishesSprites[1]) 184 | add(dishesSprites[2]) 185 | }.setX(x).setY(y) 186 | } 187 | 188 | 189 | } else { 190 | itemSpriteGroup = null 191 | 192 | // itemSpriteGroup = ItemSpriteGroup() 193 | // itemSpriteGroup!!.group.setY(-20).setX(10) 194 | group = graphicEntityModule.createGroup().setX(x).setY(y) 195 | } 196 | 197 | setTooltip(tooltipModule, cell, group) 198 | }) 199 | } 200 | 201 | nextX += cellWidth 202 | } 203 | 204 | for (player in matchPlayers) { 205 | player.characterSprite = graphicEntityModule.createSprite().apply { 206 | // image = "player00" 207 | scaleX = 0.92 208 | scaleY = 0.92 209 | x = 132 / 2 210 | y = 100 211 | anchorX = 0.5 212 | anchorY = 1.0 213 | isVisible = false 214 | } 215 | player.itemSprite = ItemSpriteGroup(cellWidth) 216 | 217 | player.itemSprite.group.apply { 218 | y = -165 + (132 / 2) 219 | } 220 | 221 | player.sprite = graphicEntityModule.createGroup(player.characterSprite, player.itemSprite.group) 222 | tooltipModule.registerEntity(player.sprite, player.toViewString()) 223 | } 224 | } 225 | 226 | fun updateCells(board: Board) { 227 | board.allCells.zip(cellViews).forEach { (cell, view) -> 228 | // view.itemSpriteGroup.update(cell.item) 229 | if (cell.item !== null && view.itemSpriteGroup === null) { 230 | view.itemSpriteGroup = ItemSpriteGroup() 231 | view.group.add(view.itemSpriteGroup!!.group) 232 | view.itemSpriteGroup!!.update(cell.item) 233 | graphicEntityModule.commitEntityState(0.5, view.group, view.itemSpriteGroup!!.group) 234 | } 235 | if (view.itemSpriteGroup !== null) { 236 | view.itemSpriteGroup!!.update(cell.item) 237 | } 238 | if (cell.equipment is DishWasher) { 239 | var dishCount = (cell.equipment as DishWasher).dishes 240 | for ((index, dishSprite) in dishesSprites.withIndex()) { 241 | val prevVisible = dishSprite.isVisible 242 | dishSprite.isVisible = index <= dishCount - 1 243 | if (prevVisible != dishSprite.isVisible) { 244 | graphicEntityModule.commitEntityState(0.5, dishSprite) 245 | } 246 | } 247 | } 248 | } 249 | 250 | board.oven()?.also { 251 | tooltipModule.updateExtraTooltipText(ovenSprite, it.toViewString()) 252 | var showOvenOverlay = it.state is OvenState.Burning || it.state is OvenState.Baking || it.state is OvenState.Ready 253 | var ovenImage = when (it.state) { 254 | is OvenState.Baking -> when ((it.state as OvenState.Baking).contents) { 255 | is Dough -> "dough.png" 256 | is Shell -> "paton_bb.png" 257 | else -> "" 258 | } 259 | is OvenState.Ready -> when ((it.state as OvenState.Ready).contents) { 260 | is Croissant -> "croissant.png" 261 | is Tart -> "tart_big_bb.png" 262 | else -> "" 263 | } 264 | else -> "smoke.png" 265 | } 266 | ovenContentSprite!!.apply { 267 | var shouldCommit = (isVisible != showOvenOverlay || image != ovenImage) 268 | isVisible = showOvenOverlay 269 | image = ovenImage 270 | if (shouldCommit) { 271 | graphicEntityModule.commitEntityState(0.5, this) 272 | } 273 | } 274 | ovenGlowSprite!!.isVisible = it.state is OvenState.Baking || it.state is OvenState.Ready 275 | } 276 | 277 | 278 | } 279 | 280 | fun ?> Entity.setLocation(cell: Cell, hardTransition: Boolean = false) { 281 | val newX = 140 + (cell.x - 1) * (132) + xRange.first 282 | val newY = 136 + (cell.y - 1) * (110) + yRange.first 283 | 284 | if (hardTransition) { 285 | setX(newX, Curve.NONE) 286 | setY(newY, Curve.NONE) 287 | } else { 288 | x = newX 289 | y = newY 290 | zIndex = 200 + newY 291 | } 292 | } 293 | 294 | fun resetPlayers() { 295 | players.forEach { updatePlayer(it, null, true) } 296 | } 297 | 298 | fun updatePlayer(player: Player, playerPath: List?, hardTransition: Boolean = false) { 299 | if (player.crashed) return 300 | 301 | player.characterSprite.isVisible = true 302 | player.itemSprite.isVisible = true 303 | 304 | player.itemSprite.update(player.heldItem) 305 | 306 | if(player.characterSprite.image === null) { 307 | player.characterSprite.image = playerSprites[player.index][0] 308 | player.characterSprite.anchorX = playerSpriteAnchors[0].getX() 309 | player.characterSprite.anchorY = playerSpriteAnchors[0].getY() 310 | } 311 | 312 | if (playerPath == null) { 313 | player.sprite.setLocation(board[player.location.x, player.location.y], hardTransition) 314 | } else { 315 | println(playerPath) 316 | playerPath.forEachIndexed { index, cell -> 317 | if(playerPath.size > 1 && playerPath.first() !== playerPath.last()) { 318 | if (index + 1 < playerPath.size) { 319 | var nextCell = playerPath[index + 1] 320 | var spriteNum = 0 321 | when { 322 | nextCell.x < cell.x -> spriteNum = 1 323 | nextCell.x > cell.x -> spriteNum = 2 324 | nextCell.y > cell.y -> spriteNum = 3 325 | nextCell.y < cell.y -> spriteNum = 4 326 | } 327 | player.characterSprite.image = playerSprites[player.index][spriteNum] 328 | player.characterSprite.anchorX = playerSpriteAnchors[spriteNum].getX() 329 | player.characterSprite.anchorY = playerSpriteAnchors[spriteNum].getY() 330 | graphicEntityModule.commitEntityState(0.2 * index + 0.1, player.characterSprite) 331 | } 332 | } 333 | player.sprite.setLocation(cell) 334 | graphicEntityModule.commitEntityState(0.2 * index + 0.1, player.sprite) 335 | } 336 | } 337 | 338 | graphicEntityModule.commitEntityState(0.9, player.sprite) 339 | } 340 | 341 | fun removePlayer(player: Player) { 342 | player.characterSprite.isVisible = false 343 | player.itemSprite.isVisible = false 344 | } 345 | 346 | inner class ItemSpriteGroup(width: Int = 132) { 347 | val mainSprite = graphicEntityModule.createSprite().apply { 348 | anchorY = 0.5 349 | anchorX = 0.5 350 | x = width / 2 351 | y = width / 2 352 | baseHeight = width * 3 / 4 353 | baseWidth = width * 3 / 4 354 | zIndex = 50 355 | isVisible = false 356 | } 357 | 358 | val subSprites = List(4) { i -> 359 | graphicEntityModule.createSprite().apply { 360 | anchorX = 0.0 361 | anchorY = 0.0 362 | baseHeight = 40 363 | baseWidth = 50 364 | zIndex = 50 + i 365 | x = 20 + (i % 2) * 52 366 | y = if (i < 2) 30 else 70 367 | isVisible = false 368 | } 369 | } 370 | 371 | 372 | val group = graphicEntityModule.createGroup().apply { 373 | add(subSprites[0]) 374 | add(subSprites[1]) 375 | add(subSprites[2]) 376 | add(subSprites[3]) 377 | add(mainSprite) 378 | x = 10 379 | y = -20 380 | } 381 | 382 | init { 383 | tooltipModule.registerEntity(group) 384 | } 385 | 386 | var isVisible: Boolean = true 387 | set(value) { 388 | mainSprite.isVisible = value 389 | subSprites.forEach { it.isVisible = value } 390 | field = value 391 | } 392 | 393 | fun update(item: Item?) { 394 | subSprites.forEach { it.isVisible = false } 395 | 396 | val prevVisible = mainSprite.isVisible 397 | val prevImage = mainSprite.image 398 | var dishChange = false 399 | 400 | mainSprite.apply { 401 | isVisible = true 402 | 403 | when (item) { 404 | is IceCream -> image = "ice-cream.png" 405 | is Blueberries -> image = "blueberries.png" 406 | is Dough -> image = "dough.png" 407 | is Strawberries -> image = "strawberry.png" 408 | is ChoppedStrawberries -> image = "strawberries-cut.png" 409 | is Croissant -> image = "croissant.png" 410 | is Tart -> image = "tart_big_bb.png" 411 | is Dish -> { 412 | image = "plate.png" 413 | item.contents.zip(subSprites).forEach { (edible, subSprite) -> 414 | val prevSubImage = subSprite.image 415 | val prevSubVisible = subSprite.isVisible 416 | subSprite.apply { 417 | isVisible = true 418 | when (edible) { 419 | is IceCream -> image = "ice-cream.png" 420 | is Blueberries -> image = "blueberries.png" 421 | is ChoppedStrawberries -> image = "strawberries-cut.png" 422 | is Croissant -> image = "croissant.png" 423 | is Tart -> image = "tart_big_bb.png" 424 | } 425 | if (prevSubImage != subSprite.image || prevSubVisible != subSprite.isVisible) { 426 | dishChange = true 427 | } 428 | } 429 | } 430 | } 431 | is Shell -> 432 | image = if (item.hasBlueberry) "paton_bb.png" else "paton_cut_big.png" 433 | 434 | else -> mainSprite.isVisible = false 435 | } 436 | } 437 | 438 | if (prevImage != mainSprite.image || prevVisible != mainSprite.isVisible) { 439 | graphicEntityModule.commitEntityState(0.5, mainSprite) 440 | } 441 | if (dishChange) { 442 | subSprites.forEach { 443 | graphicEntityModule.commitEntityState(0.5, it) 444 | } 445 | 446 | } 447 | 448 | if (mainSprite.isVisible) { 449 | tooltipModule.updateExtraTooltipText(group,"Item: " + item?.describe()) 450 | } else { 451 | tooltipModule.updateExtraTooltipText(group) 452 | } 453 | } 454 | } 455 | } 456 | 457 | private fun Sprite.center() { 458 | anchorY = 0.5 459 | anchorX = 0.5 460 | x = 132 / 2 461 | y = 110 / 2 462 | } 463 | 464 | val playerSpriteAnchors = arrayOf( 465 | Point2D.Double(73/135.toDouble(), 1.toDouble()), 466 | Point2D.Double(59/117.toDouble(), 1.toDouble()), 467 | Point2D.Double(57/116.toDouble(), 169/156.toDouble()), 468 | Point2D.Double(83/141.toDouble(), 168/160.toDouble()), 469 | Point2D.Double(59/143.toDouble(), 180/158.toDouble()) 470 | ) 471 | 472 | val playerSprites = arrayOf(0,1,2).map { index -> 473 | Array(5) { "player$index$it" } 474 | } 475 | 476 | -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/view/GameView.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.view 2 | 3 | import com.codingame.gameengine.module.entities.GraphicEntityModule 4 | import nicknameHandlerModule.TextLimitModule 5 | import tooltipModule.TooltipModule 6 | 7 | lateinit var graphicEntityModule: GraphicEntityModule 8 | lateinit var tooltipModule: TooltipModule 9 | lateinit var textLimitModule: TextLimitModule 10 | 11 | class GameView { 12 | lateinit var boardView: BoardView 13 | lateinit var queueView: QueueView 14 | lateinit var scoresView: ScoresView 15 | 16 | init { 17 | BoardView.xRange = 430..1920 // = 11 * 120 18 | BoardView.yRange = 264..1080 // = 7 * 120 19 | QueueView.xRange = 0..410 20 | QueueView.yRange = 240..840 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/view/QueueView.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.view 2 | 3 | import com.codingame.game.model.* 4 | import com.codingame.gameengine.module.entities.Curve 5 | import com.codingame.gameengine.module.entities.Sprite 6 | import com.codingame.gameengine.module.entities.Text 7 | import tooltipModule.TooltipModule 8 | 9 | class QueueView { 10 | lateinit var queue: CustomerQueue 11 | 12 | var failed = false 13 | 14 | companion object { 15 | lateinit var xRange: IntRange 16 | lateinit var yRange: IntRange 17 | } 18 | 19 | private var customerViews: List = List(3) { 20 | CustomerView() 21 | } 22 | 23 | val failureBox = graphicEntityModule.createRectangle().apply { 24 | width = QueueView.xRange.last - QueueView.xRange.first 25 | height = QueueView.yRange.last - QueueView.yRange.first 26 | fillColor = 0xff0000 27 | isVisible = false 28 | zIndex = -1000 29 | } 30 | 31 | val wholeGroup = graphicEntityModule.createGroup().apply { 32 | for(c in customerViews) 33 | add(c.group) 34 | add(failureBox) 35 | x = QueueView.xRange.first 36 | y = QueueView.yRange.first 37 | } 38 | 39 | fun updateQueue() { 40 | customerViews.forEachIndexed { index, custView -> 41 | queue.activeCustomers[index].let { 42 | custView.update(it, index) 43 | } 44 | } 45 | 46 | if(failed != failureBox.isVisible){ 47 | failureBox.isVisible = failed 48 | graphicEntityModule.commitEntityState(0.0, failureBox) 49 | failed = false 50 | } 51 | } 52 | 53 | inner class CustomerView { 54 | var model : Customer? = null 55 | val viewWidth = 343 56 | val viewHeight = 187 57 | 58 | val customerSpritePadding = 5 59 | val customerSpriteWidth = 214 / 2 - customerSpritePadding * 2 60 | 61 | private fun Sprite.center() { 62 | anchorY = 0.0 63 | anchorX = 0.5 64 | x = customerSpriteWidth / 2 65 | y = customerSpriteWidth / 2 66 | } 67 | 68 | val foodSprites = List(4) { i -> 69 | graphicEntityModule.createSprite().apply { 70 | baseHeight = customerSpriteWidth * 3 / 4 71 | baseWidth = customerSpriteWidth * 3 / 4 72 | anchorX = 0.5 73 | x = (135 + (i % 2) * (customerSpriteWidth + customerSpritePadding * 2)) + customerSpriteWidth / 2 74 | y = if (i < 2) 11 else 99 75 | zIndex = 300 76 | isVisible = false 77 | } 78 | } 79 | 80 | val awardText = graphicEntityModule.createText("0").apply { 81 | fillColor = 0xffffff 82 | strokeColor = 0x000000 83 | strokeThickness = 2.0 84 | fontSize = 35 85 | fontWeight = Text.FontWeight.BOLDER 86 | 87 | x = 127 / 2 88 | y = (viewHeight / 6 * 1.5).toInt() 89 | anchorX = 0.5 90 | anchorY = 0.5 91 | zIndex = 350 92 | alpha = 1.0 93 | } 94 | 95 | 96 | val plusText = graphicEntityModule.createText("+").apply { 97 | fillColor = 0xffffff 98 | strokeColor = 0x000000 99 | strokeThickness = 2.0 100 | fontSize = 25 101 | fontWeight = Text.FontWeight.BOLDER 102 | x = 127 / 2 103 | y = viewHeight / 2 104 | anchorX = 0.5 105 | anchorY = 0.5 106 | zIndex = 350 107 | alpha = 1.0 108 | } 109 | 110 | val tiptext = graphicEntityModule.createText("0").apply { 111 | fillColor = 0xffffff 112 | strokeColor = 0x000000 113 | strokeThickness = 2.0 114 | fontSize = 35 115 | fontWeight = Text.FontWeight.BOLDER 116 | x = 127 / 2 117 | y = (viewHeight / 6 * 4.5).toInt() 118 | anchorX = 0.5 119 | anchorY = 0.5 120 | zIndex = 350 121 | alpha = 1.0 122 | } 123 | 124 | val waitingColour = 0x4286f4 125 | val happyColour = 0x37c648 126 | 127 | val backgroundBox = graphicEntityModule.createRectangle().apply { 128 | fillColor = waitingColour 129 | width = viewWidth 130 | height = viewHeight 131 | zIndex = 200 132 | alpha = 0.0 133 | } 134 | val group = graphicEntityModule.createGroup().apply { 135 | for (f in foodSprites) 136 | add(f) 137 | add(plusText) 138 | add(tiptext) 139 | add(awardText) 140 | add(backgroundBox) 141 | } 142 | 143 | init { 144 | tooltipModule.registerEntity(group) 145 | } 146 | 147 | fun update(customer: Customer, index: Int) { 148 | val baseAward = customer.originalAward - Constants.TIP 149 | val tip = customer.award - baseAward 150 | 151 | tiptext.text = tip.toString() 152 | if(customer.satisfaction == Satisfaction.Satisfied){ 153 | backgroundBox.setFillColor(happyColour).setAlpha(0.5, Curve.IMMEDIATE) 154 | graphicEntityModule.commitEntityState(0.0, backgroundBox) 155 | } 156 | 157 | if(customer == model) 158 | return 159 | 160 | model = customer 161 | tooltipModule.updateExtraTooltipText(group, customer.dish.describe()) 162 | backgroundBox.setFillColor(waitingColour, Curve.IMMEDIATE).setAlpha(0.0, Curve.IMMEDIATE) 163 | 164 | group.setX(36).setY(index * (187 + 20)) 165 | graphicEntityModule.commitEntityState(0.0, group) 166 | // group.apply { y = index * (187 + 20); x = 36; } 167 | 168 | awardText.text = baseAward.toString() 169 | 170 | val edibles = customer.dish.contents 171 | foodSprites.forEach { it.isVisible = false } 172 | edibles.zip(foodSprites).forEach { (edible, foodSprite) -> 173 | foodSprite.apply { 174 | isVisible = true 175 | when (edible) { 176 | is Tart -> image = "tart_big_bb.png" 177 | is IceCream -> image = "ice-cream.png" 178 | is Blueberries -> image = "blueberries.png" 179 | is Croissant -> image = "croissant.png" 180 | is ChoppedStrawberries -> image = "strawberries-cut.png" 181 | } 182 | } 183 | } 184 | 185 | graphicEntityModule.commitEntityState(0.0, awardText) 186 | graphicEntityModule.commitEntityState(0.0, tiptext) 187 | for(f in foodSprites) 188 | graphicEntityModule.commitEntityState(0.0, f) 189 | } 190 | } 191 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/codingame/game/view/ScoresView.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.view 2 | 3 | import com.codingame.game.Player 4 | import com.codingame.game.Referee 5 | import com.codingame.game.ScoreBoard 6 | import com.codingame.gameengine.module.entities.Text 7 | 8 | class ScoresView(matchPlayers: List) { 9 | 10 | var currentRoundNumber = 0 /* 0, 1, 2 */ 11 | 12 | val playerScoreViews = matchPlayers.mapIndexed { i, player -> 13 | player to PlayerScoreView(player).apply { 14 | group.x = 36 15 | group.y = 34 + (i * (1080 - 36 - 36 - 185)) 16 | } 17 | }.toMap() 18 | 19 | fun update(scores: ScoreBoard) { 20 | var players = ArrayList(playerScoreViews.keys) 21 | 22 | when (currentRoundNumber) { 23 | 0 -> { 24 | playerScoreViews[players[0]]!!.group.apply { 25 | y = 34 26 | isVisible = true 27 | } 28 | playerScoreViews[players[1]]!!.group.apply { 29 | y = 857 30 | isVisible = true 31 | } 32 | playerScoreViews[players[2]]!!.group.apply { 33 | isVisible = false 34 | } 35 | } 36 | 1 -> { 37 | playerScoreViews[players[0]]!!.group.apply { 38 | y = 857 39 | isVisible = true 40 | } 41 | playerScoreViews[players[1]]!!.group.apply { 42 | isVisible = false 43 | } 44 | playerScoreViews[players[2]]!!.group.apply { 45 | y = 34 46 | isVisible = true 47 | } 48 | } 49 | 2 -> { 50 | playerScoreViews[players[0]]!!.group.apply { 51 | isVisible = false 52 | } 53 | playerScoreViews[players[1]]!!.group.apply { 54 | y = 34 55 | isVisible = true 56 | } 57 | playerScoreViews[players[2]]!!.group.apply { 58 | y = 857 59 | isVisible = true 60 | } 61 | } 62 | } 63 | scores.forEach { player, entry -> 64 | graphicEntityModule.commitEntityState(0.0, playerScoreViews[player]!!.group) 65 | playerScoreViews[player]!!.update(entry) 66 | 67 | playerScoreViews[player]!!.messageText.text = (player.message) 68 | } 69 | } 70 | 71 | inner class PlayerScoreView(player: Player) { 72 | val viewWidth = 343 73 | val viewHeight = 186 74 | 75 | private val playerAvatar = graphicEntityModule.createSprite().apply { 76 | image = player.avatarToken 77 | x = 22 78 | y = 72 79 | anchorX = 0.0 80 | anchorY = 0.0 81 | baseWidth = 93 82 | baseHeight = 93 83 | zIndex = 350 84 | } 85 | 86 | private val playerNameText = graphicEntityModule.createText(player.nicknameToken).apply { 87 | x = viewWidth / 2 88 | y = 20 89 | anchorX = 0.5 90 | anchorY = 0.0 91 | fontSize = 40 92 | fontWeight = Text.FontWeight.BOLDER 93 | fillColor = 0xFFFFFF 94 | strokeThickness = 3.0 95 | strokeColor = 0 96 | zIndex = 350 97 | } 98 | 99 | private val scoreTexts = // List(3) { i -> 100 | graphicEntityModule.createText("--").apply { 101 | fillColor = 0xffffff 102 | fontSize = 25 103 | x = 120 104 | y = 87 105 | anchorX = 0.0 106 | anchorY = 0.0 107 | zIndex = 350 108 | } 109 | // } 110 | 111 | val messageText = 112 | graphicEntityModule.createText("").apply { 113 | fillColor = 0xffffff 114 | fontSize = 38 115 | x = 120 116 | y = viewHeight - 20 117 | anchorX = 0.0 118 | anchorY = 1.0 119 | zIndex = 350 120 | } 121 | 122 | 123 | init { 124 | textLimitModule.limitAvailableSpace(playerNameText, 350, 100) 125 | textLimitModule.limitAvailableSpace(messageText, 205, 55) 126 | } 127 | 128 | val backgroundBox = graphicEntityModule.createRectangle().apply { 129 | fillColor = player.colorToken 130 | width = viewWidth 131 | height = viewHeight 132 | zIndex = 200 133 | alpha = 0.5 134 | }!! 135 | 136 | val group = graphicEntityModule.createGroup().apply { 137 | add(scoreTexts) 138 | add(backgroundBox) 139 | add(playerAvatar) 140 | add(playerNameText) 141 | add(messageText) 142 | } 143 | 144 | fun update(entry: Referee.ScoreEntry) { 145 | if(playerNameText.text.length > 8 && !playerNameText.text.endsWith("...")) 146 | playerNameText.text = playerNameText.text.substring(0,8) + "..." 147 | 148 | scoreTexts.text = entry.roundScores.joinToString(" ") { it?.toString() ?: "--" } 149 | } 150 | 151 | } 152 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/Player_blue_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "player11": { 4 | "frame": { 5 | "x": 157, 6 | "y": 0, 7 | "w": 117, 8 | "h": 204 9 | }, 10 | "rotated": false, 11 | "trimmed": false, 12 | "spriteSourceSize": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 117, 16 | "h": 204 17 | }, 18 | "sourceSize": { 19 | "w": 117, 20 | "h": 204 21 | } 22 | }, 23 | "player10": { 24 | "frame": { 25 | "x": 0, 26 | "y": 34, 27 | "w": 135, 28 | "h": 162 29 | }, 30 | "rotated": false, 31 | "trimmed": false, 32 | "spriteSourceSize": { 33 | "x": 0, 34 | "y": 0, 35 | "w": 135, 36 | "h": 162 37 | }, 38 | "sourceSize": { 39 | "w": 135, 40 | "h": 162 41 | } 42 | }, 43 | "player12": { 44 | "frame": { 45 | "x": 291, 46 | "y": 38, 47 | "w": 116, 48 | "h": 156 49 | }, 50 | "rotated": false, 51 | "trimmed": false, 52 | "spriteSourceSize": { 53 | "x": 0, 54 | "y": 0, 55 | "w": 116, 56 | "h": 156 57 | }, 58 | "sourceSize": { 59 | "w": 116, 60 | "h": 156 61 | } 62 | }, 63 | "player14": { 64 | "frame": { 65 | "x": 575, 66 | "y": 38, 67 | "w": 143, 68 | "h": 158 69 | }, 70 | "rotated": false, 71 | "trimmed": false, 72 | "spriteSourceSize": { 73 | "x": 0, 74 | "y": 0, 75 | "w": 143, 76 | "h": 158 77 | }, 78 | "sourceSize": { 79 | "w": 143, 80 | "h": 158 81 | } 82 | }, 83 | "player13": { 84 | "frame": { 85 | "x": 415, 86 | "y": 40, 87 | "w": 141, 88 | "h": 160 89 | }, 90 | "rotated": false, 91 | "trimmed": false, 92 | "spriteSourceSize": { 93 | "x": 0, 94 | "y": 0, 95 | "w": 141, 96 | "h": 160 97 | }, 98 | "sourceSize": { 99 | "w": 141, 100 | "h": 160 101 | } 102 | } 103 | }, 104 | "meta": { 105 | "app": "https://www.leshylabs.com/apps/sstool/", 106 | "version": "Leshy SpriteSheet Tool v0.8.4", 107 | "image": "Player_blue_v3.png", 108 | "size": { 109 | "w": 718, 110 | "h": 204 111 | }, 112 | "scale": 1 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/Player_blue_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/Player_blue_v3.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/Player_green_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "player21": { 4 | "frame": { 5 | "x": 157, 6 | "y": 0, 7 | "w": 117, 8 | "h": 204 9 | }, 10 | "rotated": false, 11 | "trimmed": false, 12 | "spriteSourceSize": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 117, 16 | "h": 204 17 | }, 18 | "sourceSize": { 19 | "w": 117, 20 | "h": 204 21 | } 22 | }, 23 | "player20": { 24 | "frame": { 25 | "x": 0, 26 | "y": 34, 27 | "w": 135, 28 | "h": 162 29 | }, 30 | "rotated": false, 31 | "trimmed": false, 32 | "spriteSourceSize": { 33 | "x": 0, 34 | "y": 0, 35 | "w": 135, 36 | "h": 162 37 | }, 38 | "sourceSize": { 39 | "w": 135, 40 | "h": 162 41 | } 42 | }, 43 | "player22": { 44 | "frame": { 45 | "x": 291, 46 | "y": 38, 47 | "w": 116, 48 | "h": 156 49 | }, 50 | "rotated": false, 51 | "trimmed": false, 52 | "spriteSourceSize": { 53 | "x": 0, 54 | "y": 0, 55 | "w": 116, 56 | "h": 156 57 | }, 58 | "sourceSize": { 59 | "w": 116, 60 | "h": 156 61 | } 62 | }, 63 | "player24": { 64 | "frame": { 65 | "x": 575, 66 | "y": 39, 67 | "w": 142, 68 | "h": 157 69 | }, 70 | "rotated": false, 71 | "trimmed": false, 72 | "spriteSourceSize": { 73 | "x": 0, 74 | "y": 0, 75 | "w": 142, 76 | "h": 157 77 | }, 78 | "sourceSize": { 79 | "w": 142, 80 | "h": 157 81 | } 82 | }, 83 | "player23": { 84 | "frame": { 85 | "x": 416, 86 | "y": 40, 87 | "w": 140, 88 | "h": 160 89 | }, 90 | "rotated": false, 91 | "trimmed": false, 92 | "spriteSourceSize": { 93 | "x": 0, 94 | "y": 0, 95 | "w": 140, 96 | "h": 160 97 | }, 98 | "sourceSize": { 99 | "w": 140, 100 | "h": 160 101 | } 102 | } 103 | }, 104 | "meta": { 105 | "app": "https://www.leshylabs.com/apps/sstool/", 106 | "version": "Leshy SpriteSheet Tool v0.8.4", 107 | "image": "Player_green_v3.png", 108 | "size": { 109 | "w": 717, 110 | "h": 204 111 | }, 112 | "scale": 1 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/Player_green_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/Player_green_v3.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/Player_red_v3.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "player01": { 4 | "frame": { 5 | "x": 157, 6 | "y": 0, 7 | "w": 117, 8 | "h": 205 9 | }, 10 | "rotated": false, 11 | "trimmed": false, 12 | "spriteSourceSize": { 13 | "x": 0, 14 | "y": 0, 15 | "w": 117, 16 | "h": 205 17 | }, 18 | "sourceSize": { 19 | "w": 117, 20 | "h": 205 21 | } 22 | }, 23 | "player00": { 24 | "frame": { 25 | "x": 0, 26 | "y": 35, 27 | "w": 135, 28 | "h": 162 29 | }, 30 | "rotated": false, 31 | "trimmed": false, 32 | "spriteSourceSize": { 33 | "x": 0, 34 | "y": 0, 35 | "w": 135, 36 | "h": 162 37 | }, 38 | "sourceSize": { 39 | "w": 135, 40 | "h": 162 41 | } 42 | }, 43 | "player02": { 44 | "frame": { 45 | "x": 291, 46 | "y": 39, 47 | "w": 116, 48 | "h": 156 49 | }, 50 | "rotated": false, 51 | "trimmed": false, 52 | "spriteSourceSize": { 53 | "x": 0, 54 | "y": 0, 55 | "w": 116, 56 | "h": 156 57 | }, 58 | "sourceSize": { 59 | "w": 116, 60 | "h": 156 61 | } 62 | }, 63 | "player04": { 64 | "frame": { 65 | "x": 575, 66 | "y": 39, 67 | "w": 143, 68 | "h": 158 69 | }, 70 | "rotated": false, 71 | "trimmed": false, 72 | "spriteSourceSize": { 73 | "x": 0, 74 | "y": 0, 75 | "w": 143, 76 | "h": 158 77 | }, 78 | "sourceSize": { 79 | "w": 143, 80 | "h": 158 81 | } 82 | }, 83 | "player03": { 84 | "frame": { 85 | "x": 415, 86 | "y": 41, 87 | "w": 141, 88 | "h": 160 89 | }, 90 | "rotated": false, 91 | "trimmed": false, 92 | "spriteSourceSize": { 93 | "x": 0, 94 | "y": 0, 95 | "w": 141, 96 | "h": 160 97 | }, 98 | "sourceSize": { 99 | "w": 141, 100 | "h": 160 101 | } 102 | } 103 | }, 104 | "meta": { 105 | "app": "https://www.leshylabs.com/apps/sstool/", 106 | "version": "Leshy SpriteSheet Tool v0.8.4", 107 | "image": "Player_red_v3.png", 108 | "size": { 109 | "w": 718, 110 | "h": 205 111 | }, 112 | "scale": 1 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/resources/view/assets/Player_red_v3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/Player_red_v3.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/background03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/background03.jpg -------------------------------------------------------------------------------- /src/main/resources/view/assets/blueberries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/blueberries.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/board02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/board02.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/bowl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/bowl.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/croissant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/croissant.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/dough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/dough.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/hot_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/hot_left.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/hot_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/hot_right.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/hot_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/hot_top.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/ice-cream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/ice-cream.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/logo.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/microwave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/microwave.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/oven_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/oven_left.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/oven_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/oven_right.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/oven_top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/oven_top.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/paton_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/paton_bb.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/paton_cut_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/paton_cut_big.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/plate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/plate.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/smoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/smoke.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/strawberries-cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/strawberries-cut.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/strawberry.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/tart_big_bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/tart_big_bb.png -------------------------------------------------------------------------------- /src/main/resources/view/assets/window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csj/code-a-la-mode/5fbd06c63d1e5d0312aaf22b1945c00e50fc35dc/src/main/resources/view/assets/window.png -------------------------------------------------------------------------------- /src/main/resources/view/config.js: -------------------------------------------------------------------------------- 1 | import { GraphicEntityModule } from './entity-module/GraphicEntityModule.js'; 2 | import { TooltipModule } from './modules/TooltipModule.js'; 3 | import { EndScreenModule } from './endscreen-module/EndScreenModule.js'; 4 | import { TextLimitModule } from './modules/TextLimitModule.js'; 5 | 6 | // List of viewer modules that you want to use in your game 7 | export const modules = [ 8 | GraphicEntityModule, TooltipModule , EndScreenModule, TextLimitModule 9 | ]; 10 | 11 | export const playerColors = ['#ff1d5c', '#22a1e4', '#6ac371']; -------------------------------------------------------------------------------- /src/main/resources/view/modules/TextLimitModule.js: -------------------------------------------------------------------------------- 1 | import { api as entityModule } from '../entity-module/GraphicEntityModule.js' 2 | import { fitAspectRatio } from '../core/utils.js' 3 | export const api = { 4 | showDamage: true, 5 | showCurve: false 6 | } 7 | 8 | export class TextLimitModule { 9 | constructor (assets) { 10 | this.mustShrinkNickname = true 11 | this.nicknameIds = [] 12 | } 13 | 14 | static get name () { 15 | return 'limits' 16 | } 17 | 18 | updateScene (previousData, currentData, progress) { 19 | this.limits.forEach(limit => { 20 | const entityId = limit.textId 21 | const width = limit.width 22 | const height = limit.height 23 | 24 | let entity = entityModule.entities.get(entityId) 25 | if (entity.currentState.text) { 26 | entity.graphics.scale.set(1) 27 | if (entity.graphics.width > width) { 28 | let aspectRatio = fitAspectRatio(entity.graphics.width, entity.graphics.height, width, height) 29 | entity.graphics.scale.set(aspectRatio) 30 | } 31 | } 32 | }) 33 | } 34 | 35 | handleFrameData (frameInfo, nothing) { 36 | return { ...frameInfo } 37 | } 38 | 39 | reinitScene (container, canvasData) { 40 | this.mustShrinkNickname = true 41 | } 42 | 43 | animateScene (delta) { 44 | } 45 | 46 | handleGlobalData (players, limits) { 47 | this.limits = limits || [] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/resources/view/modules/TooltipModule.js: -------------------------------------------------------------------------------- 1 | import { 2 | WIDTH, 3 | HEIGHT 4 | } from '../core/constants.js' 5 | 6 | import { 7 | api as entityModule 8 | } from '../entity-module/GraphicEntityModule.js' 9 | 10 | function getMouseOverFunc (id, tooltip) { 11 | return function () { 12 | tooltip.inside[id] = true 13 | } 14 | } 15 | 16 | function getMouseOutFunc (id, tooltip) { 17 | return function () { 18 | delete tooltip.inside[id] 19 | } 20 | } 21 | 22 | function getEntityState (entity, frame, progress) { 23 | const subStates = entity.states[frame] 24 | if (subStates && subStates.length) { 25 | return subStates[subStates.length - 1] 26 | } 27 | return null 28 | } 29 | 30 | function replaceAll (str, replace, find) { 31 | return str.replace(new RegExp(find, 'g'), replace) 32 | } 33 | 34 | function getMouseMoveFunc (tooltip, container, module) { 35 | return function (ev) { 36 | if (tooltip) { 37 | var pos = ev.data.getLocalPosition(container) 38 | tooltip.x = pos.x 39 | tooltip.y = pos.y 40 | 41 | var point = { 42 | x: pos.x * entityModule.toWorldUnits, 43 | y: pos.y * entityModule.toWorldUnits 44 | } 45 | 46 | var cellX = (Math.floor((point.x - module.x0) / 132)) 47 | var cellY = (Math.floor((point.y - module.y0) / 110)) 48 | 49 | const showing = [] 50 | const ids = Object.keys(tooltip.inside).map(n => +n) 51 | const tooltipBlocks = [] 52 | 53 | for (let id of ids) { 54 | if (tooltip.inside[id]) { 55 | const entity = entityModule.entities.get(id) 56 | const state = entity && getEntityState(entity, module.currentFrame.number) 57 | if (!state || state.alpha === 0 || !state.visible) { 58 | delete tooltip.inside[id] 59 | } else { 60 | showing.push(id) 61 | } 62 | } 63 | } 64 | 65 | if (showing.length) { 66 | for (let show of showing) { 67 | const entity = entityModule.entities.get(show) 68 | const state = getEntityState(entity, module.currentFrame.number) 69 | 70 | if (entity && state) { 71 | const params = module.currentFrame.registered[show] 72 | 73 | if (params != null) { 74 | let paramBlocks = [] 75 | for (var key in params) { 76 | if (key.length > 0) { 77 | const txt = key + ': ' + params[key] 78 | if (paramBlocks.indexOf(txt) > -1) continue 79 | paramBlocks.push(txt) 80 | } 81 | } 82 | if (paramBlocks.length) { 83 | tooltipBlocks.push(paramBlocks.join('\n')) 84 | } 85 | } 86 | 87 | var extra = module.currentFrame.extraText[show] 88 | if (extra && extra.length) { 89 | tooltipBlocks.push(extra) 90 | } else { 91 | extra = module.currentFrame.extraText[show - 1] 92 | if (extra && extra.length && tooltipBlocks.indexOf(extra) === -1) { 93 | tooltipBlocks.push(extra) 94 | } 95 | } 96 | } 97 | } 98 | } 99 | if (cellY >= 0 && cellY < 7 && cellX >= 0 && cellX < 11) { 100 | tooltipBlocks.unshift('x: ' + cellX + 101 | '\ny: ' + cellY) 102 | } 103 | 104 | if (tooltipBlocks.length) { 105 | for (var i = 0; i < tooltipBlocks.length; i++) { 106 | for (var p = 0; p < playerList.length; p++) { 107 | tooltipBlocks[i] = tooltipBlocks[i].toString().replace('$' + p, playerList[p].name) 108 | } 109 | } 110 | 111 | var txt = tooltipBlocks.filter(t => t !== '').join('\n') 112 | txt = replaceAll(txt, 'DISH', '#D') 113 | txt = replaceAll(txt, 'ICE_CREAM', '#I') 114 | txt = replaceAll(txt, 'DOUGH', '#H') 115 | txt = replaceAll(txt, 'STRAWBERRIES', '#S') 116 | txt = replaceAll(txt, 'TART', '#T') 117 | txt = replaceAll(txt, 'BLUEBERRIES', '#B') 118 | txt = replaceAll(txt, 'CROISSANT', '#C') 119 | txt = replaceAll(txt, 'CHOPPED', '#O') 120 | tooltip.label.text = txt 121 | tooltip.visible = true 122 | } else { 123 | tooltip.visible = false 124 | } 125 | 126 | tooltip.background.width = tooltip.label.width + 20 127 | tooltip.background.height = tooltip.label.height + 20 128 | 129 | tooltip.pivot.x = -30 130 | tooltip.pivot.y = -50 131 | 132 | if (tooltip.y - tooltip.pivot.y + tooltip.height > HEIGHT) { 133 | tooltip.pivot.y = 10 + tooltip.height 134 | tooltip.y -= tooltip.y - tooltip.pivot.y + tooltip.height - HEIGHT 135 | } 136 | 137 | if (tooltip.x - tooltip.pivot.x + tooltip.width > WIDTH) { 138 | tooltip.pivot.x = tooltip.width 139 | } 140 | } 141 | } 142 | }; 143 | 144 | var playerList = [] 145 | 146 | export class TooltipModule { 147 | constructor (assets) { 148 | this.interactive = {} 149 | this.previousFrame = { 150 | registrations: {}, 151 | extra: {} 152 | } 153 | this.lastProgress = 1 154 | this.lastFrame = 0 155 | this.width = 1910 - 430 156 | this.height = 1080 - 264 157 | this.x0 = 430 158 | this.y0 = 290 159 | } 160 | 161 | static get name () { 162 | return 'tooltips' 163 | } 164 | 165 | updateScene (previousData, currentData, progress) { 166 | this.currentFrame = currentData 167 | this.currentProgress = progress 168 | } 169 | 170 | handleFrameData (frameInfo, [registrations, extra]) { 171 | const registered = { ...this.previousFrame.registered, 172 | ...registrations 173 | } 174 | const extraText = { ...this.previousFrame.extraText, 175 | ...extra 176 | } 177 | 178 | Object.keys(registrations).forEach( 179 | k => { 180 | this.interactive[k] = true 181 | } 182 | ) 183 | 184 | const frame = { 185 | registered, 186 | extraText, 187 | number: frameInfo.number 188 | } 189 | this.previousFrame = frame 190 | return frame 191 | } 192 | 193 | reinitScene (container, canvasData) { 194 | this.tooltip = this.initTooltip() 195 | entityModule.entities.forEach(entity => { 196 | if (this.interactive[entity.id]) { 197 | entity.container.interactive = true 198 | entity.container.mouseover = getMouseOverFunc(entity.id, this.tooltip) 199 | entity.container.mouseout = getMouseOutFunc(entity.id, this.tooltip) 200 | } 201 | }) 202 | this.container = container 203 | container.interactive = true 204 | container.mousemove = getMouseMoveFunc(this.tooltip, container, this) 205 | container.addChild(this.tooltip) 206 | } 207 | 208 | generateText (text, size, color, align) { 209 | var textEl = new PIXI.Text(text, { 210 | fontSize: Math.round(size / 1.2) + 'px', 211 | fontFamily: 'monospace', 212 | fontWeight: 'bold', 213 | fill: color 214 | }) 215 | 216 | textEl.lineHeight = Math.round(size / 1.2) 217 | if (align === 'right') { 218 | textEl.anchor.x = 1 219 | } else if (align === 'center') { 220 | textEl.anchor.x = 0.5 221 | } 222 | 223 | return textEl 224 | }; 225 | 226 | initTooltip () { 227 | var tooltip = new PIXI.Container() 228 | var background = tooltip.background = new PIXI.Graphics() 229 | var label = tooltip.label = this.generateText('', 36, 0xFFFFFF, 'left') 230 | 231 | background.beginFill(0x0, 0.7) 232 | background.drawRect(0, 0, 200, 185) 233 | background.endFill() 234 | background.x = -10 235 | background.y = -10 236 | 237 | tooltip.visible = false 238 | tooltip.inside = {} 239 | 240 | tooltip.addChild(background) 241 | tooltip.addChild(label) 242 | 243 | tooltip.interactiveChildren = false 244 | return tooltip 245 | }; 246 | 247 | animateScene (delta) { 248 | 249 | } 250 | 251 | handleGlobalData (players, globalData) { 252 | playerList = players 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/test/java/com/codingame/game/sample/Main.java: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample; 2 | 3 | import com.codingame.gameengine.runner.MultiplayerGameRunner; 4 | 5 | public class Main { 6 | public static void main(String[] args) { 7 | 8 | MultiplayerGameRunner gameRunner = new MultiplayerGameRunner(); 9 | // gameRunner.setSeed(-2749567458568488030L); 10 | 11 | // Adds as many player as you need to test your game 12 | // gameRunner.addAgent(IceCreamPlayer.class); 13 | gameRunner.addAgent(NaiveAllItemsPlayer.class); 14 | // gameRunner.addAgent(InstaFoodPlayer.class); 15 | gameRunner.addAgent(NaiveAllItemsPlayer.class); 16 | gameRunner.addAgent(NaiveAllItemsPlayer.class); 17 | // gameRunner.addAgent(HugPlayer.class); 18 | 19 | gameRunner.setLeagueLevel(4); 20 | 21 | gameRunner.start(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/BaseCALMPlayer.kt: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import com.codingame.game.model.Constants 4 | import java.io.InputStream 5 | import java.io.PrintStream 6 | import java.util.* 7 | 8 | @Suppress("unused") 9 | abstract class BaseCALMPlayer(val stdin: InputStream, val stdout: PrintStream, val stderr: PrintStream) { 10 | val scanner = Scanner(stdin) 11 | val width: Int = 11 12 | val height: Int = 7 13 | val layout: List 14 | 15 | lateinit var inputs: GameState 16 | private set 17 | 18 | init { 19 | val longQueue = List(scanner.nextInt()) { 20 | Customer(Item.parse(scanner.next())!!, scanner.nextInt()) 21 | } 22 | 23 | layout = List(height) { scanner.next() } 24 | .also { stderr.println("Table layout:\n$it")} 25 | } 26 | 27 | protected fun readInputs() { 28 | val turnsRemaining = scanner.nextInt() 29 | .also { stderr.println("Turns remaining: $it") } 30 | val myPlayer = Player(scanner.nextInt(), scanner.nextInt(), Item.parse(scanner.next())) 31 | val myFriend = Player(scanner.nextInt(), scanner.nextInt(), Item.parse(scanner.next())) 32 | 33 | val tables = (0 until height).flatMap { row -> (0 until width) 34 | .filter { col -> layout[row][col] != '.'} 35 | .map { col -> Table(col, row, layout[row][col].nullIf('#')) } 36 | } 37 | 38 | repeat(scanner.nextInt()) { 39 | val x = scanner.nextInt() 40 | val y = scanner.nextInt() 41 | tables.find { it.x == x && it.y == y }!!.item = Item.parse(scanner.next()) 42 | } 43 | 44 | val ovenContents = scanner.next() 45 | val ovenTimer = scanner.nextInt() 46 | 47 | val queue = List(scanner.nextInt()) { 48 | Customer(Item.parse(scanner.next())!!, scanner.nextInt()) 49 | } 50 | 51 | inputs = GameState(myPlayer, myFriend, tables, queue, ovenContents, ovenTimer) 52 | } 53 | 54 | fun findEquipment(equipmentChar: Char) = 55 | inputs.tables.firstOrNull { it.equipment == equipmentChar } 56 | 57 | fun getEmptyPlate(): String = ( 58 | inputs.tables.find { it.item?.itemType == Constants.ITEM.DISH.name } 59 | ?: findEquipment('D')!!).use() 60 | 61 | fun useEmptyTable(): String { 62 | return inputs.myPlayer.run { 63 | inputs.tables.filter { it.equipment == null && it.item == null } 64 | .minBy { Math.abs(it.x - x) + Math.abs(it.y - y) }!!.use() 65 | } 66 | } 67 | 68 | fun Table.use(): String = "USE $x $y" 69 | 70 | } 71 | 72 | private fun Char.nullIf(char: Char) = if (this == char) null else this 73 | 74 | data class GameState( 75 | val myPlayer: Player, 76 | val myFriend: Player, 77 | val tables: List
, 78 | val queue: List, 79 | val ovenContents: String, 80 | val ovenTimer: Int) 81 | 82 | data class Table( 83 | val x: Int, val y: Int, 84 | var equipment: Char? = null, 85 | var item: Item? = null) 86 | 87 | data class Item(val description: String) { 88 | val toks = description.split("-") 89 | val itemType = toks[0] 90 | val itemContents = toks.drop(1) 91 | 92 | companion object { 93 | fun parse(description: String) = 94 | if (description == "NONE") null 95 | else Item(description) 96 | 97 | } 98 | } 99 | 100 | data class Player(val x: Int, val y: Int, val carrying: Item?) 101 | data class Customer(val dish: Item, val award: Int) -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/HugPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample 2 | 3 | import sample.BaseCALMPlayer 4 | import java.io.InputStream 5 | import java.io.PrintStream 6 | 7 | class HugPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BaseCALMPlayer(stdin, stdout, stderr) { 8 | init { 9 | while (true) { 10 | readInputs() 11 | stdout.println("MOVE ${inputs.myFriend.x} ${inputs.myFriend.y}") 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/InstaFoodPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample 2 | 3 | import com.codingame.game.model.Constants 4 | import sample.BaseCALMPlayer 5 | import java.io.InputStream 6 | import java.io.PrintStream 7 | 8 | class InstaFoodPlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BaseCALMPlayer(stdin, stdout, stderr) { 9 | init { 10 | while (true) { 11 | readInputs() 12 | val useTarget = when { 13 | inputs.myPlayer.carrying?.itemType == Constants.FOOD.BLUEBERRIES.name -> 14 | findEquipment('D') 15 | else -> 16 | findEquipment('B') 17 | }!! 18 | stdout.println("USE ${useTarget.x} ${useTarget.y}") 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/MovePlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample 2 | 3 | import sample.BaseCALMPlayer 4 | import java.io.InputStream 5 | import java.io.PrintStream 6 | 7 | class MovePlayer(stdin: InputStream, stdout: PrintStream, stderr: PrintStream) : BaseCALMPlayer(stdin, stdout, stderr) { 8 | init { 9 | while (true) { 10 | val inputs = readInputs() 11 | stdout.println("MOVE 6 6") 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/NaiveAllItemsPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample 2 | 3 | import com.codingame.game.model.* 4 | import com.codingame.game.splitAccumulate 5 | import com.codingame.game.tryNext 6 | import sample.* 7 | import sample.Item 8 | import java.io.InputStream 9 | import java.io.PrintStream 10 | import java.lang.Math.abs 11 | 12 | class NaiveAllItemsPlayer( 13 | stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BaseCALMPlayer(stdin, stdout, stderr) { 14 | 15 | val lyrics = """ 16 | How come you're 17 | always such a 18 | fussy young man? 19 | Don't want no 20 | Captain Crunch, 21 | don't want no 22 | Raisin Bran?! 23 | Well, don't you 24 | know that other kids 25 | are starving in 26 | Japan? So eat it! 27 | Just eat it. 28 | Don't wanna argue, 29 | I don't wanna debate 30 | Don't wanna hear 31 | about what kind of 32 | food you hate! 33 | You won't get no 34 | dessert 'till you 35 | clean off your plate 36 | So eat it. Don't you 37 | tell me you're full, 38 | just eat it, eat it, 39 | eat it, eat it, 40 | Get yourself an egg 41 | and beat it! 42 | Have some more 43 | chicken, have some 44 | more pie, it doesn't 45 | matter if it's 46 | broiled or fried, 47 | Just eat it, eat it, 48 | just eat it, eat it! 49 | Your table manners are 50 | a cryin' shame. 51 | You're playin' with 52 | your food, this 53 | ain't some kind of 54 | game! Now, if you 55 | starve to death, 56 | you'll just have 57 | yourself to blame. 58 | So eat it 59 | just eat it! 60 | You better listen, 61 | better do what 62 | you're told, you 63 | haven't even 64 | touched your tuna 65 | casserole! 66 | You better chow 67 | down or it's gonna 68 | get cold, so eat it. 69 | I don't care if 70 | you're full, 71 | just eat it, eat it 72 | Eat it, eat it. 73 | Open up your mouth 74 | and feed it. 75 | Have some more 76 | yogurt, have some 77 | more Spam. 78 | It doesn't matter 79 | if it's fresh or 80 | canned, just eat it 81 | Eat it, eat it 82 | Don't you make me 83 | Repeat it! 84 | Have a banana, 85 | Have a whole bunch. 86 | It doesn't matter 87 | what you had for 88 | lunch, just eat it!""" 89 | .split("\n", " ") 90 | .asSequence() 91 | .splitAccumulate(18) 92 | .iterator() 93 | 94 | lateinit var goal: Item 95 | lateinit var crates: Map 96 | 97 | init { 98 | var turn = 0 99 | while (true) { 100 | turn++ 101 | readInputs() 102 | 103 | crates = listOf( 104 | Constants.FOOD.BLUEBERRIES to 'B', 105 | Constants.FOOD.ICE_CREAM to 'I', 106 | Constants.ITEM.DOUGH to 'H', 107 | Constants.ITEM.STRAWBERRIES to 'S' 108 | ).map { (item, char) -> 109 | item.name to (inputs.tables.firstOrNull { it.equipment == char } ) 110 | }.filter { (_, v) -> v != null }.map { (k, v) -> k to v!! }.toMap() 111 | 112 | stdout.println((act() ?: "WAIT") + ";" + (lyrics.tryNext() ?: "")) 113 | } 114 | } 115 | 116 | private fun act(): String? { 117 | val carrying = inputs.myPlayer.carrying 118 | 119 | // 0. If the oven has something ready, go get it! 120 | if (inputs.ovenContents in listOf( 121 | Constants.FOOD.CROISSANT.name, 122 | Constants.FOOD.TART.name 123 | )) 124 | return if (carrying == null) findEquipment('O')!!.use() else useEmptyTable() 125 | 126 | goal = inputs.queue.firstOrNull()?.dish ?: return null 127 | stderr.println("Current goal is: $goal") 128 | 129 | // make all the items and leave them on tables. then grab a plate and collect them all. 130 | val goalItems = goal.itemContents.toSet() 131 | 132 | // 1. if we're holding a plate, grab missing items from tables and such. 133 | // If one is missing, drop the plate 134 | if (carrying?.itemType == Constants.ITEM.DISH.name) { 135 | stderr.println("we are carrying a plate") 136 | 137 | val dishContents = carrying.itemContents.toSet() 138 | 139 | // if it has anything we don't need, dishwasher it 140 | if ((dishContents - goalItems).isNotEmpty()) 141 | return findEquipment('D')!!.use() 142 | 143 | // find next missing item from dish 144 | val missingItems = goalItems - dishContents 145 | val missingItem = missingItems.firstOrNull() ?: 146 | return findEquipment('W')!!.use() 147 | 148 | stderr.println("missing item: $missingItem") 149 | 150 | return when (missingItem) { 151 | in crates.keys -> { 152 | val crateLoc = crates[missingItem]!! 153 | stderr.println("going for crate at $crateLoc") 154 | crateLoc.use() 155 | } 156 | else -> { 157 | val tableLoc = inputs.tables.find { 158 | it.item?.itemType == missingItem 159 | } 160 | if (tableLoc != null) tableLoc.use() 161 | else useEmptyTable() // put down the dish 162 | } 163 | } 164 | } 165 | 166 | // 2. Build missing items. If all items are built, grab an empty plate. 167 | goalItems.forEach { item -> 168 | if (isReady(item)) return@forEach 169 | return when(item) { 170 | Constants.FOOD.CROISSANT.name -> buildCroissant() 171 | Constants.FOOD.TART.name -> buildTart() 172 | Constants.FOOD.CHOPPED_STRAWBERRIES.name -> buildStrawberries() 173 | else -> throw Exception("Unrecognized dish: $item") 174 | } 175 | } 176 | 177 | // 3. if we're holding something we shouldn't, put it down. 178 | if (carrying != null) return useEmptyTable() 179 | 180 | return getEmptyPlate() 181 | } 182 | 183 | private fun buildStrawberries(): String? { 184 | val carrying = inputs.myPlayer.carrying 185 | return when { 186 | carrying == null -> crates[Constants.ITEM.STRAWBERRIES.name]!!.use() 187 | carrying.itemType == Constants.ITEM.STRAWBERRIES.name -> findEquipment('C')?.use() 188 | carrying.itemType == Constants.FOOD.CHOPPED_STRAWBERRIES.name -> useEmptyTable() 189 | else -> { stderr.println("uhhh, holding: $carrying"); return useEmptyTable() } 190 | } 191 | } 192 | 193 | private fun isReady(item: String): Boolean = 194 | item in crates.keys || 195 | inputs.tables.any { it.item?.itemType == item } || 196 | (item == "CROISSANT" && inputs.ovenContents in listOf( 197 | Constants.ITEM.DOUGH.name, 198 | Constants.FOOD.CROISSANT.name 199 | )) 200 | 201 | private fun buildCroissant(): String? { 202 | return when(inputs.myPlayer.carrying?.itemType) { 203 | null -> crates[Constants.ITEM.DOUGH.name]!!.use() 204 | Constants.ITEM.DOUGH.name -> findEquipment('O')!!.use() 205 | else -> useEmptyTable() 206 | } 207 | } 208 | 209 | private fun buildTart(): String? { 210 | stderr.println("building tart") 211 | val x = inputs.myPlayer.x 212 | val y = inputs.myPlayer.y 213 | 214 | if (inputs.ovenContents != "NONE") { 215 | stderr.println("waiting for tart in oven") 216 | return findEquipment('O')!!.use() 217 | } 218 | 219 | val carrying = inputs.myPlayer.carrying 220 | when { 221 | carrying == null -> // find the closest tart shell, or get one from the box 222 | { 223 | stderr.println("looking for shell") 224 | val tart = (inputs.tables.filter { 225 | it.item?.itemType == Constants.ITEM.CHOPPED_DOUGH.name 226 | } + crates[Constants.ITEM.DOUGH.name]!!) 227 | .minBy { abs(it.x - x) + abs(it.y - y) } 228 | 229 | return tart!!.use() 230 | } 231 | 232 | carrying.itemType == Constants.ITEM.DOUGH.name -> 233 | return findEquipment('C')!!.use() 234 | 235 | carrying.itemType == Constants.ITEM.CHOPPED_DOUGH.name -> 236 | return crates[Constants.FOOD.BLUEBERRIES.name]!!.use() 237 | 238 | carrying.itemType == Constants.ITEM.RAW_TART.name -> { 239 | stderr.println("shell is complete; heading for oven") 240 | return findEquipment('O')!!.use() 241 | } 242 | } 243 | 244 | return useEmptyTable() 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/TartMakerOnTable.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample 2 | 3 | import com.codingame.game.model.Blueberries 4 | import com.codingame.game.model.Constants 5 | import sample.BaseCALMPlayer 6 | import java.io.InputStream 7 | import java.io.PrintStream 8 | 9 | class TartMakerOnTable(stdin: InputStream, stdout: PrintStream, stderr: PrintStream): BaseCALMPlayer(stdin, stdout, stderr) { 10 | init { 11 | while (true) { 12 | readInputs() 13 | 14 | val bluebs = inputs.tables.find { it.item?.itemType == Constants.FOOD.BLUEBERRIES.name } 15 | val dough = inputs.tables.find { it.item?.itemType == Constants.ITEM.CHOPPED_DOUGH.name } 16 | 17 | val action = when (inputs.myPlayer.carrying?.itemType) { 18 | Constants.FOOD.BLUEBERRIES.name -> 19 | dough?.use() ?: useEmptyTable() 20 | Constants.ITEM.DOUGH.name -> 21 | findEquipment('C')!!.use() 22 | Constants.ITEM.CHOPPED_DOUGH.name -> 23 | bluebs?.use() ?: useEmptyTable() 24 | null -> 25 | if (bluebs == null) findEquipment('B')!!.use() 26 | else findEquipment('H')!!.use() 27 | else -> 28 | useEmptyTable() 29 | } 30 | 31 | stdout.println(action) 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/codingame/game/sample/VanillaIceCreamPlayer.kt: -------------------------------------------------------------------------------- 1 | package com.codingame.game.sample 2 | 3 | import com.codingame.game.model.Constants 4 | import sample.BaseCALMPlayer 5 | import java.io.InputStream 6 | import java.io.PrintStream 7 | 8 | class IceCreamPlayer( 9 | stdin: InputStream, stdout: PrintStream, stderr: PrintStream) 10 | : BaseCALMPlayer(stdin, stdout, stderr) { 11 | 12 | private val ballVal = Constants.FOOD.ICE_CREAM.name 13 | 14 | init { 15 | while (true) { 16 | readInputs() 17 | val me = inputs.myPlayer 18 | 19 | val iceCreamLoc = inputs.tables.firstOrNull { it.equipment == 'I' } 20 | ?: throw Exception("Couldn't find ice cream crate") 21 | val window = inputs.tables.firstOrNull { it.equipment == 'W' } 22 | ?: throw Exception("Couldn't find window") 23 | val dishReturn = inputs.tables.firstOrNull { it.equipment == 'D' } 24 | ?: throw Exception("Couldn't find dish return") 25 | val emptyDishes = inputs.tables.filter { it.item?.itemType == Constants.ITEM.DISH.name && it.item!!.itemContents.isEmpty() } 26 | val emptyDish = emptyDishes.firstOrNull() ?: dishReturn 27 | 28 | when { 29 | // if holding a plate of ice cream, head for the window 30 | me.carrying?.itemType == Constants.ITEM.DISH.name && me.carrying.itemContents == listOf(ballVal) -> { 31 | stderr.println("I'm holding a plate of ice cream: going to window") 32 | stdout.println("USE ${window.x} ${window.y}") 33 | } 34 | 35 | // if holding an empty plate, go get some ice cream 36 | me.carrying?.itemType == Constants.ITEM.DISH.name -> { 37 | stderr.println("I'm holding an empty dish, going for ice cream") 38 | stdout.println("USE ${iceCreamLoc.x} ${iceCreamLoc.y}") 39 | } 40 | 41 | // go get an empty plate 42 | else -> { 43 | stderr.println("I'm going to get a plate") 44 | stdout.println("USE ${emptyDish.x} ${emptyDish.y}") 45 | } 46 | } 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /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{yyyy/MM/dd HH:mm:ss}][GF] %-5p : %c{1} - %m%n 9 | 10 | rootLogger.level = info 11 | rootLogger.appenderRef.console.ref = CONSOLE 12 | -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.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 | public class Game 9 | { 10 | public Player[] Players = new Player[2]; 11 | public Table Dishwasher; 12 | public Table Window; 13 | public Table Blueberry; 14 | public Table IceCream; 15 | public List
Tables = new List
(); 16 | } 17 | 18 | public class Table 19 | { 20 | public Position Position; 21 | public bool HasFunction; 22 | public Item Item; 23 | } 24 | 25 | public class Item 26 | { 27 | public string Content; 28 | public bool HasPlate; 29 | public Item(string content){ 30 | Content = content; 31 | HasPlate = Content.Contains(MainClass.Dish); 32 | } 33 | } 34 | 35 | public class Player 36 | { 37 | public Position Position; 38 | public Item Item; 39 | public Player(Position position, Item item){ 40 | Position = position; 41 | Item = item; 42 | } 43 | public void Update(Position position, Item item){ 44 | Position = position; 45 | Item = item; 46 | } 47 | } 48 | 49 | public class Position 50 | { 51 | public int X, Y; 52 | public Position(int x, int y){ 53 | X = x; 54 | Y = y; 55 | } 56 | 57 | public int Manhattan(Position p2) => Math.Abs(X - p2.X) + Math.Abs(Y - p2.Y); 58 | 59 | public override string ToString() 60 | { 61 | return X + " " + Y; 62 | } 63 | } 64 | 65 | 66 | 67 | public class MainClass 68 | { 69 | public static bool Debug = true; 70 | public const string Dish = "DISH"; 71 | 72 | public static Game ReadGame(){ 73 | var game = new Game(); 74 | game.Players[0] = new Player(null, null); 75 | game.Players[1] = new Player(null, null); 76 | 77 | for (int i = 0; i < 7; i++) 78 | { 79 | string kitchenLine = ReadLine(); 80 | for (var x = 0; x < kitchenLine.Length; x++){ 81 | if (kitchenLine[x] == 'W') game.Window = new Table { Position = new Position(x, i), HasFunction = true }; 82 | if (kitchenLine[x] == 'D') game.Dishwasher = new Table { Position = new Position(x, i), HasFunction = true }; 83 | if (kitchenLine[x] == 'I') game.IceCream = new Table { Position = new Position(x, i), HasFunction = true }; 84 | if (kitchenLine[x] == 'B') game.Blueberry = new Table { Position = new Position(x, i), HasFunction = true }; 85 | if (kitchenLine[x] == '#') game.Tables.Add(new Table { Position = new Position(x, i) }); 86 | } 87 | } 88 | 89 | return game; 90 | } 91 | 92 | private static void Move(Position p) => Console.WriteLine("MOVE " + p); 93 | 94 | private static void Use(Position p){ 95 | Console.WriteLine("USE " + p + "; C# Starter AI"); 96 | } 97 | 98 | private static string ReadLine(){ 99 | var s = Console.ReadLine(); 100 | if (Debug) 101 | Console.Error.WriteLine(s); 102 | return s; 103 | } 104 | 105 | 106 | static void Main() 107 | { 108 | string[] inputs; 109 | 110 | // ALL CUSTOMERS INPUT: to ignore until Bronze 111 | int numAllCustomers = int.Parse(ReadLine()); 112 | for (int i = 0; i < numAllCustomers; i++) 113 | { 114 | inputs = ReadLine().Split(' '); 115 | string customerItem = inputs[0]; // the food the customer is waiting for 116 | int customerAward = int.Parse(inputs[1]); // the number of points awarded for delivering the food 117 | } 118 | 119 | // KITCHEN INPUT 120 | var game = ReadGame(); 121 | 122 | while (true) 123 | { 124 | int turnsRemaining = int.Parse(ReadLine()); 125 | 126 | // PLAYERS INPUT 127 | inputs = ReadLine().Split(' '); 128 | game.Players[0].Update(new Position(int.Parse(inputs[0]), int.Parse(inputs[1])), new Item(inputs[2])); 129 | inputs = ReadLine().Split(' '); 130 | game.Players[1].Update(new Position(int.Parse(inputs[0]), int.Parse(inputs[1])), new Item(inputs[2])); 131 | 132 | //Clean other tables 133 | foreach(var t in game.Tables){ 134 | t.Item = null; 135 | } 136 | int numTablesWithItems = int.Parse(ReadLine()); // the number of tables in the kitchen that currently hold an item 137 | for (int i = 0; i < numTablesWithItems; i++) 138 | { 139 | inputs = ReadLine().Split(' '); 140 | var table = game.Tables.First(t => t.Position.X == int.Parse(inputs[0]) && t.Position.Y == int.Parse(inputs[1])); 141 | table.Item = new Item(inputs[2]); 142 | } 143 | 144 | inputs = ReadLine().Split(' '); 145 | string ovenContents = inputs[0]; // ignore until bronze league 146 | int ovenTimer = int.Parse(inputs[1]); 147 | int numCustomers = int.Parse(ReadLine()); // the number of customers currently waiting for food 148 | for (int i = 0; i < numCustomers; i++) 149 | { 150 | inputs = ReadLine().Split(' '); 151 | string customerItem = inputs[0]; 152 | int customerAward = int.Parse(inputs[1]); 153 | } 154 | 155 | // GAME LOGIC 156 | // fetch a dish, pick ice cream and drop the dish on an empty table 157 | var myChef = game.Players[0]; 158 | if (!myChef.Item?.HasPlate ?? false) 159 | Use(game.Dishwasher.Position); 160 | else if(!myChef.Item.Content.Contains("ICE_CREAM")) 161 | Use(game.IceCream.Position); 162 | // once ready, put it on the first empty table for now 163 | else 164 | Use(game.Tables.First(t => t.Item == null).Position); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // Game parameters 11 | const ( 12 | WIDTH = 11 13 | HEIGHT = 7 14 | ) 15 | 16 | // Tile Types 17 | const ( 18 | TABLE = "#" 19 | EMPTY = "." 20 | ICE_CREAM_CRATE = "I" 21 | BLUEBERRY_CRATE = "B" 22 | DISH_WASHER = "D" 23 | WINDOW = "W" 24 | ) 25 | 26 | // Item Types 27 | const ( 28 | NONE = "NONE" 29 | DISH = "DISH" 30 | BLUEBERRIES = "BLUEBERRIES" 31 | ICE_CREAM = "ICE_CREAM" 32 | ) 33 | 34 | type Position struct { 35 | X int 36 | Y int 37 | } 38 | 39 | type Entity struct { 40 | Position 41 | Type string 42 | } 43 | 44 | type Item struct { 45 | Entity 46 | } 47 | 48 | func NewItem(x, y int, item string) *Item { 49 | i := Item{ 50 | Entity: Entity{ 51 | Position: Position{ 52 | X: x, 53 | Y: y, 54 | }, 55 | Type: item, 56 | }, 57 | } 58 | return &i 59 | } 60 | 61 | type Chef struct { 62 | Entity 63 | } 64 | 65 | func NewChef() *Chef { 66 | c := Chef{} 67 | return &c 68 | } 69 | 70 | type Cell struct { 71 | Type string 72 | Index int 73 | } 74 | 75 | func NewCell(cellType string, index int) *Cell { 76 | cell := Cell{ 77 | Type: cellType, 78 | Index: index, 79 | } 80 | return &cell 81 | } 82 | 83 | func (c *Cell) CanWalkOnIt() bool { 84 | return c.Type == EMPTY 85 | } 86 | 87 | func (c *Cell) GetPos() Position { 88 | return Position{ 89 | c.Index % WIDTH, 90 | c.Index / WIDTH, 91 | } 92 | } 93 | 94 | type Grid struct { 95 | Cells []*Cell 96 | Items []*Item 97 | } 98 | 99 | func (g *Grid) AddRow(line string) { 100 | for _, r := range line { 101 | g.Cells = append(g.Cells, NewCell(string(r), len(g.Cells))) 102 | } 103 | } 104 | 105 | func (g *Grid) GetCell(x, y int) *Cell { 106 | return g.Cells[x+WIDTH*y] 107 | } 108 | 109 | func (g *Grid) GetCellFromType(cellType string) *Cell { 110 | for _, c := range g.Cells { 111 | if c.Type == cellType { 112 | return c 113 | } 114 | } 115 | 116 | return nil 117 | } 118 | 119 | func (g *Grid) String() string { 120 | var output []string 121 | for _, c := range g.Cells { 122 | pos := c.GetPos() 123 | output = append(output, fmt.Sprintf("Cell X: %d // Cell Y: %d", pos.X, pos.Y)) 124 | output = append(output, fmt.Sprintf("Cell Type: %s", c.Type)) 125 | } 126 | return strings.Join(output, "\n-------------------------\n") 127 | } 128 | 129 | type Customer struct { 130 | Items []string 131 | Award int 132 | } 133 | 134 | func NewCustomer(items string, award int) *Customer { 135 | c := Customer{ 136 | Items: strings.Split(items, "-"), 137 | Award: award, 138 | } 139 | return &c 140 | } 141 | 142 | type Game struct { 143 | Grid Grid 144 | Player Chef 145 | Partner Chef 146 | Customers []*Customer 147 | } 148 | 149 | func NewGame() *Game { 150 | g := Game{ 151 | Grid: Grid{}, 152 | Player: Chef{}, 153 | Partner: Chef{}, 154 | } 155 | return &g 156 | } 157 | 158 | func main() { 159 | scanner := bufio.NewScanner(os.Stdin) 160 | scanner.Buffer(make([]byte, 1000000), 1000000) 161 | 162 | game := NewGame() 163 | 164 | // ALL CUSTOMERS INPUT: to ignore until Bronze 165 | var numAllCustomers int 166 | scanner.Scan() 167 | fmt.Sscan(scanner.Text(), &numAllCustomers) 168 | for i := 0; i < numAllCustomers; i++ { 169 | // customerItem: the food the customer is waiting for 170 | var customerItem string 171 | // customerAward: the number of points awarded for delivering the food 172 | var customerAward int 173 | scanner.Scan() 174 | fmt.Sscan(scanner.Text(), &customerItem, &customerAward) 175 | } 176 | 177 | // KITCHEN INPUT 178 | for i := 0; i < 7; i++ { 179 | scanner.Scan() 180 | kitchenLine := scanner.Text() 181 | game.Grid.AddRow(kitchenLine) 182 | } 183 | 184 | // game loop 185 | for { 186 | // Reset Items 187 | game.Grid.Items = nil 188 | // Reset customers 189 | game.Customers = nil 190 | 191 | var turnsRemaining int 192 | scanner.Scan() 193 | fmt.Sscan(scanner.Text(), &turnsRemaining) 194 | 195 | // PLAYERS INPUT 196 | scanner.Scan() 197 | fmt.Sscan(scanner.Text(), &game.Player.X, &game.Player.Y, &game.Player.Type) 198 | 199 | scanner.Scan() 200 | fmt.Sscan(scanner.Text(), &game.Partner.X, &game.Partner.Y, &game.Partner.Type) 201 | 202 | // numTablesWithItems: the number of tables in the kitchen that currently hold an item 203 | var numTablesWithItems int 204 | scanner.Scan() 205 | fmt.Sscan(scanner.Text(), &numTablesWithItems) 206 | 207 | for i := 0; i < numTablesWithItems; i++ { 208 | var tableX, tableY int 209 | var item string 210 | scanner.Scan() 211 | fmt.Sscan(scanner.Text(), &tableX, &tableY, &item) 212 | 213 | game.Grid.Items = append(game.Grid.Items, NewItem(tableX, tableY, item)) 214 | } 215 | 216 | // ovenContents: ignore until wood 1 league 217 | var ovenContents string 218 | var ovenTimer int 219 | scanner.Scan() 220 | fmt.Sscan(scanner.Text(), &ovenContents, &ovenTimer) 221 | 222 | // CURRENT CUSTOMERS INPUT 223 | // numCustomers: the number of customers currently waiting for food 224 | var numCustomers int 225 | scanner.Scan() 226 | fmt.Sscan(scanner.Text(), &numCustomers) 227 | 228 | for i := 0; i < numCustomers; i++ { 229 | var customerItem string 230 | var customerAward int 231 | scanner.Scan() 232 | fmt.Sscan(scanner.Text(), &customerItem, &customerAward) 233 | 234 | game.Customers = append(game.Customers, NewCustomer(customerItem, customerAward)) 235 | } 236 | 237 | // GAME LOGIC 238 | // fetch dish, then blueberries, then ice cream, then drop it at first empty table 239 | if game.Player.Type == NONE { 240 | dishwasher := game.Grid.GetCellFromType(DISH_WASHER).GetPos() 241 | fmt.Printf("USE %d %d; Go starter AI\n", dishwasher.X, dishwasher.Y) 242 | } else if game.Player.Type == DISH { 243 | blueberries := game.Grid.GetCellFromType(BLUEBERRY_CRATE).GetPos() 244 | fmt.Printf("USE %d %d; Go starter AI\n", blueberries.X, blueberries.Y) 245 | } else { 246 | emptyTable := game.Grid.GetCellFromType(TABLE).GetPos() 247 | fmt.Printf("USE %d %d; Go starter AI\n", emptyTable.X, emptyTable.Y) 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | 4 | class Player { 5 | 6 | public static void main(String args[]) { 7 | Scanner in = new Scanner(System.in); 8 | 9 | // ALL CUSTOMERS INPUT: to ignore until Bronze 10 | int numAllCustomers = in.nextInt(); 11 | for (int i = 0; i < numAllCustomers; i++) { 12 | String customerItem = in.next(); 13 | int customerAward = in.nextInt(); 14 | } 15 | 16 | // KITCHEN INPUT 17 | Kitchen k = new Kitchen(); 18 | k.init(in); 19 | 20 | // game loop 21 | while (true) { 22 | int turnsRemaining = in.nextInt(); 23 | 24 | // PLAYERS INPUT 25 | int playerX = in.nextInt(); 26 | int playerY = in.nextInt(); 27 | String playerItem = in.next(); 28 | int partnerX = in.nextInt(); 29 | int partnerY = in.nextInt(); 30 | String partnerItem = in.next(); 31 | 32 | int numTablesWithItems = in.nextInt(); 33 | for (int i = 0; i < numTablesWithItems; i++) { 34 | int tableX = in.nextInt(); 35 | int tableY = in.nextInt(); 36 | String item = in.next(); 37 | } 38 | // oven to ignore until bronze 39 | String ovenContents = in.next(); 40 | int ovenTimer = in.nextInt(); 41 | 42 | // CURRENT CUSTOMERS INPUT 43 | int numCustomers = in.nextInt(); 44 | for (int i = 0; i < numCustomers; i++) { 45 | String customerItem = in.next(); 46 | int customerAward = in.nextInt(); 47 | } 48 | 49 | // GAME LOGIC 50 | // fetch dish, then blueberries, then ice cream, then drop it at first empty table 51 | if (!playerItem.contains("DISH")) { 52 | Table table = k.tables.stream().filter(t -> t.isDish()).findFirst().get(); 53 | table.use(); 54 | } 55 | else if (!playerItem.contains("BLUEBERRIES")) { 56 | k.tables.stream().filter(t -> t.isBlueBerries()).findFirst().get().use(); 57 | } 58 | else { 59 | k.tables.stream().filter(t -> t.isEmpty()).findFirst().get().use(); 60 | } 61 | } 62 | } 63 | } 64 | 65 | class Kitchen { 66 | 67 | List
tables; 68 | 69 | public Kitchen() { 70 | tables = new ArrayList
(); 71 | } 72 | 73 | public void init(Scanner in) { 74 | in.nextLine(); 75 | for (int i = 0; i < 7; i++) { 76 | String kitchenLine = in.nextLine(); 77 | for (int j = 0; j < kitchenLine.length(); j++) { 78 | char c = kitchenLine.charAt(j); 79 | TableType t = TableType.get(c); 80 | if (t != null) { 81 | tables.add(new Table(t, j, i)); 82 | } 83 | } 84 | System.err.println(kitchenLine); 85 | } 86 | } 87 | } 88 | 89 | class Position { 90 | 91 | int x, y; 92 | 93 | public Position(int x, int y) { 94 | this.x = x; 95 | this.y = y; 96 | } 97 | 98 | // @TODO 99 | public int distance(Position p) { 100 | return 0; 101 | } 102 | } 103 | 104 | class Table { 105 | 106 | TableType type; 107 | 108 | int x, y; 109 | 110 | public Table(TableType t, int x, int y) { 111 | type = t; 112 | this.x = x; 113 | this.y = y; 114 | } 115 | 116 | public void error() { 117 | System.err.println("Table of type "+type+" at "+x+" "+y); 118 | } 119 | 120 | public boolean isDish() { 121 | return isTableType(TableType.PLATES); 122 | } 123 | 124 | public boolean isBlueBerries() { 125 | return isTableType(TableType.BLUEBERRY); 126 | } 127 | 128 | public boolean isIceCream() { 129 | return isTableType(TableType.ICE_CREAM); 130 | } 131 | 132 | public boolean isEmpty() { 133 | return isTableType(TableType.EMPTY); 134 | } 135 | 136 | public boolean isWindow() { 137 | return isTableType(TableType.WINDOW); 138 | } 139 | 140 | private boolean isTableType(TableType t) { 141 | return type.equals(t); 142 | } 143 | 144 | public void use() { 145 | System.out.println("USE "+x+" "+y+"; Java Starter AI"); 146 | } 147 | } 148 | 149 | enum TableType { 150 | 151 | BLUEBERRY, 152 | ICE_CREAM, 153 | PLATES, 154 | EMPTY, 155 | WINDOW; 156 | 157 | static TableType get(Character c) { 158 | switch (c) { 159 | case 'D': 160 | return PLATES; 161 | case 'B': 162 | return BLUEBERRY; 163 | case 'I': 164 | return ICE_CREAM; 165 | case 'W': 166 | return WINDOW; 167 | case '#': 168 | return EMPTY; 169 | 170 | } 171 | return null; 172 | } 173 | } -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.js: -------------------------------------------------------------------------------- 1 | // Game parameters 2 | const WIDTH = 11; 3 | const HEIGHT = 7; 4 | 5 | // Tile Types 6 | const TABLE = "#"; 7 | const EMPTY = "."; 8 | const ICE_CREAM_CRATE = "I"; 9 | const BLUEBERRY_CRATE = "B"; 10 | const DISH_WASHER = "D"; 11 | const WINDOW = "W"; 12 | 13 | // Item Types 14 | const NONE = "NONE"; 15 | const DISH = "DISH"; 16 | const BLUEBERRIES = "BLUEBERRIES"; 17 | const ICE_CREAM = "ICE_CREAM"; 18 | 19 | 20 | class Entity{ 21 | constructor(x, y, type){ 22 | this.x = x; 23 | this.y = y; 24 | this.type = type; 25 | } 26 | } 27 | 28 | class Item extends Entity{ 29 | constructor(x, y, type){ 30 | super(x, y, type); 31 | } 32 | } 33 | 34 | class Chef extends Entity{ 35 | constructor(x, y, type){ 36 | super(x, y, type); 37 | } 38 | } 39 | 40 | class Cell { 41 | constructor(type, index) { 42 | this.type = type; 43 | this.index = index; 44 | } 45 | 46 | canWalkOnIt() { 47 | return this.type === EMPTY; 48 | } 49 | 50 | getPos() { 51 | return { 52 | x: this.index % WIDTH, 53 | y: Math.floor(this.index / WIDTH) 54 | }; 55 | } 56 | } 57 | 58 | class Grid{ 59 | constructor(){ 60 | this.cells = []; 61 | this.items = []; 62 | } 63 | 64 | addRow(line) { 65 | for (let i = 0; i < line.length; i++) { 66 | this.cells.push(new Cell(line[i], this.cells.length)); 67 | } 68 | } 69 | 70 | getCell(x, y) { 71 | return this.cells[x + WIDTH * y]; 72 | } 73 | 74 | getCellFromType(type){ 75 | return this.cells.filter(cell => { 76 | return cell.type === type; 77 | })[0]; 78 | } 79 | 80 | debug(){ 81 | this.cells.forEach(cell => { 82 | let pos = cell.getPos(); 83 | console.error(`Cell X: ${pos.x} // Cell Y: ${pos.y}`); 84 | console.error(`Cell Type: ${cell.type}`); 85 | console.error(`-------------------------`); 86 | }) 87 | } 88 | } 89 | 90 | class Customer{ 91 | constructor(item, award){ 92 | this.items = item.split('-'); 93 | this.award = award; 94 | } 95 | } 96 | 97 | class Game{ 98 | constructor(){ 99 | this.grid = new Grid(); 100 | this.chefs = [new Chef(), new Chef()]; 101 | this.customers = []; 102 | } 103 | } 104 | 105 | 106 | let GAME = new Game(); 107 | 108 | // ALL CUSTOMERS INPUT: to ignore until Bronze 109 | const numAllCustomers = parseInt(readline()); 110 | for (let i = 0; i < numAllCustomers; i++) { 111 | let inputs = readline().split(' '); 112 | const customerItem = inputs[0]; // the food the customer is waiting for 113 | const customerAward = parseInt(inputs[1]); // the number of points awarded for delivering the food 114 | } 115 | 116 | // KITCHEN INPUT 117 | for (let i = 0; i < 7; i++) { 118 | const kitchenLine = readline(); 119 | GAME.grid.addRow(kitchenLine); 120 | } 121 | 122 | // GAME_GRID.debug(); 123 | 124 | // game loop 125 | while (true) { 126 | // Reset Items 127 | GAME.grid.items = []; 128 | // Reset customers 129 | GAME.customers = []; 130 | // Reset chefs 131 | GAME.chefs = []; 132 | 133 | const turnsRemaining = parseInt(readline()); 134 | 135 | // PLAYERS INPUT 136 | let inputsPlayer = readline().split(' '); 137 | const playerX = parseInt(inputsPlayer[0]); 138 | const playerY = parseInt(inputsPlayer[1]); 139 | const playerItem = inputsPlayer[2]; 140 | GAME.chefs[0] = new Chef(playerX, playerY, playerItem); 141 | 142 | let inputsPartner = readline().split(' '); 143 | const partnerX = parseInt(inputsPartner[0]); 144 | const partnerY = parseInt(inputsPartner[1]); 145 | const partnerItem = inputsPartner[2]; 146 | GAME.chefs[1] = new Chef(partnerX, partnerY, partnerItem); 147 | 148 | 149 | const numTablesWithItems = parseInt(readline()); // the number of tables in the kitchen that currently hold an item 150 | for (let i = 0; i < numTablesWithItems; i++) { 151 | let inputs = readline().split(' '); 152 | const tableX = parseInt(inputs[0]); 153 | const tableY = parseInt(inputs[1]); 154 | const item = inputs[2]; 155 | GAME.grid.items.push(new Item(tableX, tableY, item)); 156 | } 157 | 158 | let inputs = readline().split(' '); 159 | const ovenContents = inputs[0]; // ignore until bronze league 160 | const ovenTimer = parseInt(inputs[1]); 161 | const numCustomers = parseInt(readline()); // the number of customers currently waiting for food 162 | 163 | // CURRENT CUSTOMERS INPUT 164 | for (let i = 0; i < numCustomers; i++) { 165 | let inputs = readline().split(' '); 166 | const customerItem = inputs[0]; 167 | const customerAward = parseInt(inputs[1]); 168 | GAME.customers.push(new Customer(customerItem, customerAward)) 169 | } 170 | 171 | // GAME LOGIC 172 | // fetch dish, then blueberries, then ice cream, then drop it at first empty table 173 | if(playerItem === NONE){ 174 | let dishwasher = GAME.grid.getCellFromType(DISH_WASHER).getPos(); 175 | console.log(`USE ${dishwasher.x} ${dishwasher.y}; JS starter AI`) 176 | } 177 | else if(playerItem === DISH){ 178 | let blueberries = GAME.grid.getCellFromType(BLUEBERRY_CRATE).getPos(); 179 | console.log(`USE ${blueberries.x} ${blueberries.y}; JS starter AI`); 180 | } 181 | else{ 182 | let emptyTable = GAME.grid.getCellFromType(TABLE).getPos(); 183 | console.log(`USE ${emptyTable.x} ${emptyTable.y}; JS starter AI`); 184 | } 185 | } -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.kt: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | import kotlin.math.abs 3 | 4 | 5 | // Strategy: 6 | // 1. Grab a blueberry, put it down 7 | // 2. Grab some ice cream, put it down 8 | // 3. Grab a plate, put it down 9 | // 4. Repeat 10 | 11 | 12 | 13 | data class XY(val x: Int, val y: Int) 14 | 15 | fun main(args : Array) { 16 | val input = Scanner(System.`in`) 17 | val numAllCustomers = input.nextInt() 18 | for (i in 0 until numAllCustomers) { 19 | val customerItem = input.next() // the food the customer is waiting for 20 | val customerAward = input.nextInt() // the number of points awarded for delivering the food 21 | } 22 | input.nextLine() 23 | val map = List(7) { 24 | input.nextLine() 25 | } 26 | 27 | val tables = mutableListOf() 28 | map.forEachIndexed { y, line -> 29 | line.forEachIndexed { x, ch -> 30 | if (ch == '#') tables += XY(x,y) 31 | } 32 | } 33 | 34 | fun findEquipment(searchChar: Char): XY? { 35 | map.forEachIndexed { y, line -> 36 | line.forEachIndexed { x, ch -> 37 | if (ch == searchChar) return XY(x, y) 38 | } 39 | } 40 | return null 41 | } 42 | 43 | val blueberryXY = findEquipment('B')!! 44 | val iceCreamXY = findEquipment('I')!! 45 | val dishWasherXY = findEquipment('D')!! 46 | 47 | val equipmentLocations = listOf(blueberryXY, iceCreamXY, dishWasherXY) 48 | var nextEquipmentIndex = 0 49 | var useEmptyTable = false 50 | 51 | // game loop 52 | while (true) { 53 | val turnsRemaining = input.nextInt() 54 | val playerX = input.nextInt() 55 | val playerY = input.nextInt() 56 | val playerItem = input.next() 57 | val partnerX = input.nextInt() 58 | val partnerY = input.nextInt() 59 | val partnerItem = input.next() 60 | val numTablesWithItems = input.nextInt() // the number of tables in the kitchen that currently hold an item 61 | 62 | val filledTables = mutableListOf() 63 | for (i in 0 until numTablesWithItems) { 64 | val tableX = input.nextInt() 65 | val tableY = input.nextInt() 66 | val item = input.next() 67 | filledTables += XY(tableX, tableY) 68 | } 69 | 70 | val ovenContents = input.next() // ignore until bronze league 71 | val ovenTimer = input.nextInt() 72 | val numCustomers = input.nextInt() // the number of customers currently waiting for food 73 | for (i in 0 until numCustomers) { 74 | val customerItem = input.next() 75 | val customerAward = input.nextInt() 76 | } 77 | 78 | val useTarget = if (useEmptyTable) { 79 | (tables - filledTables)[0] // the first empty table 80 | } else { 81 | equipmentLocations[nextEquipmentIndex] // the next needed equipment 82 | } 83 | 84 | // if we're adjacent to it, it should work. 85 | val dx = playerX - useTarget.x 86 | val dy = playerY - useTarget.y 87 | if (abs(dx) <= 1 && abs(dy) <= 1) { 88 | if (useEmptyTable) { 89 | nextEquipmentIndex++ 90 | if (nextEquipmentIndex >= equipmentLocations.size) 91 | nextEquipmentIndex = 0 92 | } 93 | useEmptyTable = !useEmptyTable 94 | } 95 | 96 | println("USE ${useTarget.x} ${useTarget.y}; Kotlin Starter AI") 97 | } 98 | } -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.ml: -------------------------------------------------------------------------------- 1 | (* Types *) 2 | 3 | type ingredient = 4 | | IceCream 5 | | BlueBerry 6 | | Unknown 7 | 8 | type tile = 9 | | Dish 10 | | Source of ingredient 11 | | Bell 12 | | Table 13 | | EmptyTile 14 | 15 | type item = 16 | | Ingredient of ingredient 17 | | Dish of ingredient list 18 | | Nothing 19 | 20 | type order = ingredient list * int 21 | ;; 22 | 23 | (* Parsing functions *) 24 | let print line = prerr_endline line; line in 25 | 26 | let parseIngredient ingredient = match ingredient with 27 | | "BLUEBERRIES" -> BlueBerry 28 | | "ICE_CREAM" -> IceCream 29 | | _ -> Unknown 30 | in 31 | 32 | let parseDish dishString = 33 | String.split_on_char '-' dishString |> 34 | List.filter (fun s -> s <> "DISH") |> 35 | List.map parseIngredient 36 | in 37 | 38 | let parseItem itemString = 39 | if itemString = "NONE" then 40 | Nothing 41 | else if String.sub itemString 0 4 = "DISH" then 42 | Dish (parseDish itemString) 43 | else 44 | Ingredient (parseIngredient itemString) 45 | in 46 | 47 | let parseOrder line = 48 | Scanf.sscanf line "%s %d" (fun dish award -> (parseDish dish, award)) 49 | in 50 | 51 | let explode s = 52 | let rec exp i l = 53 | if i < 0 then l else exp (i - 1) (s.[i] :: l) in 54 | exp (String.length s - 1) [] 55 | in 56 | 57 | let parseTileLine line y = List.mapi 58 | (fun x c -> ((x,y), match c with 59 | | '#' -> Table 60 | | 'D' -> Dish 61 | | 'W' -> Bell 62 | | 'B' -> Source BlueBerry 63 | | 'I' -> Source IceCream 64 | | _ -> EmptyTile)) 65 | (explode line) 66 | in 67 | 68 | let buildGrid tileList = 69 | let grid = Array.make_matrix 11 7 EmptyTile in 70 | List.iter (fun ((x,y), tile) -> Array.set grid.(x) y tile) tileList; 71 | grid 72 | in 73 | 74 | (* action functions *) 75 | 76 | let use ((x,y),_) = Printf.sprintf ("USE %d %d;OCAML STARTER AI") x y |> print_endline in 77 | 78 | let find tileList tile = 79 | List.find (fun (_, t) -> t = tile) tileList 80 | in 81 | 82 | let findemptytable tileList tablewithitems = 83 | List.filter (fun (_,tile) -> match tile with Table -> true | _ -> false) tileList |> 84 | List.find (fun (point,tile) -> not (List.exists (fun (p,_) -> p = point) tablewithitems)) 85 | in 86 | (* Init *) 87 | 88 | let numallcustomers = int_of_string (print (input_line stdin)) in 89 | 90 | let orders = List.init numallcustomers (fun _ -> parseOrder (print (input_line stdin))) in 91 | 92 | let tileList = List.flatten (List.init 7 (fun y -> parseTileLine (print (input_line stdin)) y)) in 93 | 94 | let grid = buildGrid tileList in 95 | 96 | (* game loop *) 97 | 98 | while true do 99 | let turnsremaining = int_of_string (input_line stdin) in 100 | 101 | let line = print(input_line stdin) in 102 | let playerx, playery, playeritem = Scanf.sscanf line "%d %d %s" (fun playerx playery playeritem -> (playerx, playery, parseItem playeritem)) in 103 | 104 | let line = input_line stdin in 105 | let partnerx, partnery, partneritem = Scanf.sscanf line "%d %d %s" (fun partnerx partnery partneritem -> (partnerx, partnery, parseItem partneritem)) in 106 | 107 | let numtableswithitems = int_of_string (input_line stdin) in (* the number of tables in the kitchen that currently hold an item *) 108 | let tablewithitems = List.init numtableswithitems (fun _ -> Scanf.sscanf (input_line stdin) "%d %d %s" (fun tablex tabley item -> ((tablex, tabley), parseItem item))) in 109 | 110 | (* ovencontents: ignore until bronze league *) 111 | 112 | let line = input_line stdin in 113 | let ovencontents, oventimer = Scanf.sscanf line "%s %d" (fun ovencontents oventimer -> (ovencontents, oventimer)) in 114 | let numcustomers = int_of_string (input_line stdin) in (* the number of customers currently waiting for food *) 115 | let orders = List.init numcustomers (fun i -> parseOrder (input_line stdin)) in 116 | 117 | (* Write an action using print_endline *) 118 | (* To debug: prerr_endline "Debug message"; *) 119 | 120 | 121 | (* MOVE x y *) 122 | (* USE x y *) 123 | (* WAIT *) 124 | 125 | match playeritem with 126 | | Nothing -> find tileList Dish |> use 127 | | Dish [] -> find tileList (Source BlueBerry) |> use 128 | | Dish _ -> findemptytable tileList tablewithitems |> use 129 | | _ -> print_endline "WAIT" 130 | done; -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.py: -------------------------------------------------------------------------------- 1 | #Begin Imports 2 | import sys 3 | import math 4 | #End Imports 5 | 6 | #Begin Util Code 7 | def log(x): 8 | print(x, file=sys.stderr) 9 | 10 | class Player: 11 | def __init__(self): 12 | self.x = 0 13 | self.y = 0 14 | self.item = NONE 15 | 16 | class Tile: 17 | def __init__(self, x, y, name): 18 | self.x = x 19 | self.y = y 20 | self.name = name 21 | self.item = None 22 | 23 | def parse_name(self): 24 | return self.name.split("-") 25 | 26 | def __repr__(self): 27 | return "Tile: " + str(self.x) + ", " + str(self.y) 28 | 29 | # Cells 30 | BLUEBERRIES_CRATE = "B" 31 | ICE_CREAM_CRATE = "I" 32 | WINDOW = "W" 33 | EMPTY_TABLE = "#" 34 | DISHWASHER = "D" 35 | FLOOR_CELL = "." 36 | 37 | # Items 38 | NONE = "NONE" 39 | DISH = "DISH" 40 | ICE_CREAM = "ICE_CREAM" 41 | BLUEBERRIES = "BLUEBERRIES" 42 | 43 | class Game: 44 | def __init__(self): 45 | self.player = Player() 46 | self.partner = Player() 47 | self.tiles = [] 48 | 49 | def addTile(self, x, y, tileChar): 50 | if tileChar != '.': 51 | self.tiles.append(Tile(x, y, tileChar)) 52 | 53 | def getTileByName(self, name): 54 | for t in self.tiles: 55 | if t.name == name: 56 | return t 57 | 58 | #If tile not found 59 | log("Error: Tile not found in function getTileByName") 60 | 61 | def getTileByItem(self, item): 62 | for t in self.tiles: 63 | if t.item == item: 64 | return t 65 | 66 | #If tile not found 67 | log("Error: Tile not found in function getTileByItem") 68 | 69 | 70 | def getTileByCoords(self, x, y): 71 | for t in self.tiles: 72 | if t.x == x and t.y == y: 73 | return t 74 | 75 | #If tile not found 76 | log("Error: Tile not found in function getTileByCoords") 77 | 78 | def updatePlayer(self, x, y, item): 79 | self.player.x = x 80 | self.player.y = y 81 | self.player.item = item 82 | 83 | def updatePartner(self, x, y, item): 84 | self.partner.x = x 85 | self.partner.y = y 86 | self.partner.item = item 87 | 88 | def use(self, tile): 89 | print("USE", tile.x, tile.y,"; Python Starter AI") 90 | 91 | def move(self, tile): 92 | print("MOVE", tile.x, tile.y) 93 | #End Util code 94 | 95 | #Begin game code 96 | game = Game() 97 | 98 | # ALL CUSTOMERS INPUT: to ignore until bronze 99 | num_all_customers = int(input()) 100 | for i in range(num_all_customers): 101 | # customer_item: the food the customer is waiting for 102 | # customer_award: the number of points awarded for delivering the food 103 | customer_item, customer_award = input().split() 104 | customer_award = int(customer_award) 105 | 106 | # KITCHEN INPUT 107 | for y in range(7): 108 | kitchen_line = input() 109 | for x, tileChar in enumerate(kitchen_line): 110 | game.addTile(x, y, tileChar) 111 | 112 | # game loop 113 | while True: 114 | turns_remaining = int(input()) 115 | 116 | # PLAYERS INPUT 117 | #Gather and update player information 118 | player_x, player_y, player_item = input().split() 119 | player_x = int(player_x) 120 | player_y = int(player_y) 121 | game.updatePlayer(player_x, player_y, player_item) 122 | 123 | #Gather and update partner information 124 | partner_x, partner_y, partner_item = input().split() 125 | partner_x = int(partner_x) 126 | partner_y = int(partner_y) 127 | game.updatePartner(partner_x, partner_y, partner_item) 128 | 129 | #Gather and update table information 130 | for t in game.tiles: 131 | t.item = None 132 | num_tables_with_items = int(input()) # the number of tables in the kitchen that currently hold an item 133 | for i in range(num_tables_with_items): 134 | table_x, table_y, item = input().split() 135 | table_x = int(table_x) 136 | table_y = int(table_y) 137 | game.getTileByCoords(table_x, table_y).item = item 138 | 139 | # oven_contents: ignore until bronze league 140 | oven_contents, oven_timer = input().split() 141 | oven_timer = int(oven_timer) 142 | num_customers = int(input()) # the number of customers currently waiting for food 143 | for i in range(num_customers): 144 | customer_item, customer_award = input().split() 145 | customer_award = int(customer_award) 146 | 147 | # GAME LOGIC 148 | #Gather plate & Icecream 149 | if DISH not in game.player.item: 150 | game.use(game.getTileByName(DISHWASHER)) 151 | elif ICE_CREAM not in game.player.item: 152 | game.use(game.getTileByName(ICE_CREAM_CRATE)) 153 | else: 154 | game.use(game.getTileByName(EMPTY_TABLE)) 155 | #End game code -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.rb: -------------------------------------------------------------------------------- 1 | STDOUT.sync = true 2 | # Ported from kotlin starter because originality is dead -> https://github.com/csj/code-a-la-mode/blob/master/src/test/starterkit/starterAI.kt 3 | # by https://www.codingame.com/profile/1d1729a2d8c008c6cf728ee88f1faa6d4978712 https://github.com/Unihedro follow me pls 4 | 5 | def parseIntIntString((a, b, c)) [a.to_i, b.to_i, c] end 6 | def parseStringInt((a, b)) [a, b.to_i] end 7 | 8 | # 15 | # {[x, y] "coordinates" => true} for all coordinates which are tables 16 | @tables = {} 17 | # array of strings, makes up the 7x11 map 18 | @map = 7.times.map { |y| 19 | line = gets.chomp 20 | line.chars.map.with_index { |v, x| 21 | @tables[[x, y]] = true if v == '#' 22 | } 23 | line 24 | } 25 | # char -> coordinates, else nil if not found 26 | findEquipment = -> searchChar { 27 | index = nil 28 | found = @map.index { |x| index = x.index(searchChar) } 29 | [index, found] if found 30 | } 31 | # so apparently the above function is only used once why did I even optimize it 32 | @equipment_locations = %w|B I D|.map &findEquipment 33 | blueberryXY, iceCreamXY, dishWasherXY = @equipment_locations 34 | @next_equipment_index = 0 35 | @use_empty_table = false 36 | # game loop 37 | loop do 38 | turns_remaining = gets.to_i 39 | player_x, player_y, player_item = parseIntIntString gets.split 40 | partner_x, partner_y, partner_item = parseIntIntString gets.split 41 | # {[x, y] "coordinates" => string "item"} for all coordinates which are tables with items 42 | @tables_with_items = gets.to_i.times.map { 43 | x, y, item = parseIntIntString gets.split 44 | [[x, y], item] 45 | }.to_h 46 | # 52 | 53 | # Strategy: 54 | # 1. Grab a blueberry, put it down 55 | # 2. Grab some ice cream, put it down 56 | # 3. Grab a plate, put it down 57 | # 4. Repeat 58 | target_x, target_y = @use_empty_table ? 59 | (@tables.keys - @tables_with_items.keys).first # The first empty table. 60 | : 61 | @equipment_locations[@next_equipment_index] # The next needed equipment. 62 | 63 | # If we're adjacent to it, it should work. 64 | dx = player_x - target_x 65 | dy = player_y - target_y 66 | if dx.abs <= 1 && dy.abs <= 1 67 | if @use_empty_table 68 | @next_equipment_index += 1 69 | @next_equipment_index = 0 if @next_equipment_index == @equipment_locations.size 70 | end 71 | @use_empty_table = !@use_empty_table 72 | end 73 | puts "USE #{target_x} #{target_y}; Ruby Starter AI" 74 | end 75 | -------------------------------------------------------------------------------- /src/test/starterkit/starterAI.vb: -------------------------------------------------------------------------------- 1 | Imports System.Collections.Generic 2 | Module CodeALaMode 3 | 4 | Public Debug As Boolean = True 5 | Public Const Dish As String = "DISH" 6 | 7 | Public Class Game 8 | Public Players As Player() = New Player(1) {} 9 | Public Dishwasher As Table 10 | Public Window As Table 11 | Public Blueberry As Table 12 | Public IceCream As Table 13 | Public Tables As New List(Of Table) 14 | 15 | Function getFirstEmptyTable() As Table 16 | For Each t As Table In Tables 17 | If t.Item Is Nothing Then Return t 18 | Next 19 | Throw New InvalidOperationException 20 | End Function 21 | Function getTableAtPosition(p As Position) As Table 22 | For Each t As Table In Tables 23 | If t.Position.X = p.X AndAlso t.Position.Y = p.Y Then 24 | Return t 25 | End If 26 | Next 27 | Throw New InvalidOperationException 28 | End Function 29 | End Class 30 | 31 | Public Class Table 32 | Public Position As Position 33 | Public HasFunction As Boolean 34 | Public Item As Item 35 | End Class 36 | 37 | Public Class Item 38 | Public Content As String 39 | Public HasPlate As Boolean 40 | Public Sub New(content As String) 41 | Me.Content = content 42 | Me.HasPlate = content.Contains(CodeALaMode.Dish) 43 | End Sub 44 | End Class 45 | 46 | Public Class Player 47 | Public Position As Position 48 | Public Item As Item 49 | Public Sub New(Position As Position, Item As Item) 50 | Me.Position = Position 51 | Me.Item = Item 52 | End Sub 53 | Public Sub Update(position As Position, item As Item) 54 | Me.Position = position 55 | Me.Item = item 56 | End Sub 57 | End Class 58 | 59 | Public Class Position 60 | Public X, Y As Integer 61 | Public Sub New(x As Integer, y As Integer) 62 | Me.X = x 63 | Me.Y = y 64 | End Sub 65 | 66 | Public Function Manhattan(p2 As Position) As Integer 67 | Return Math.Abs(X - p2.X) + Math.Abs(Y - p2.Y) 68 | End Function 69 | 70 | Public Overrides Function ToString() As String 71 | Return X & " " & Y 72 | End Function 73 | End Class 74 | 75 | Public Function ReadGame() As Game 76 | Dim game As Game = New Game() 77 | game.Players(0) = New Player(Nothing, Nothing) 78 | game.Players(1) = New Player(Nothing, Nothing) 79 | 80 | For i As Integer = 0 To 7 - 1 81 | Dim kitchenLine As String = ReadLine() 82 | For x As Integer = 0 To kitchenLine.Length - 1 83 | Dim t As New Table 84 | Select Case kitchenLine(x) 85 | Case "W"c 86 | t.Position = New Position(x, i) : t.HasFunction = True 87 | game.Window = t 88 | Case "D"c 89 | t.Position = New Position(x, i) : t.HasFunction = True 90 | game.Dishwasher = t 91 | Case "I"c 92 | t.Position = New Position(x, i) : t.HasFunction = True 93 | game.IceCream = T 94 | Case "B"c 95 | T.Position = New Position(x, i) : T.HasFunction = True 96 | game.Blueberry = T 97 | Case "#"c 98 | t.Position = New Position(x, i) 99 | game.Tables.Add(t) 100 | End Select 101 | Next 102 | Next 103 | 104 | Return game 105 | End Function 106 | 107 | Private Sub Move(p As Position) 108 | Console.WriteLine("MOVE " & p.ToString()) 109 | End Sub 110 | 111 | Private Sub Use(p As Position) 112 | Console.WriteLine("USE " & p.ToString() & "; VB Starter AI") 113 | End Sub 114 | 115 | Private Function ReadLine() As String 116 | Dim s As String = Console.ReadLine() 117 | If Debug Then Console.Error.WriteLine(s) 118 | Return s 119 | End Function 120 | 121 | 122 | Sub Main() 123 | Dim inputs As String() 124 | 125 | ' ALL CUSTOMERS INPUT to ignore until Bronze 126 | Dim numAllCustomers As Integer = CInt(ReadLine()) 127 | For i As Integer = 0 To numAllCustomers - 1 128 | inputs = ReadLine().Split(" "c) 129 | Dim customerItem As String = inputs(0) ' the food the customer Is waiting for 130 | Dim customerAward As Integer = CInt(inputs(1)) ' the number Of points awarded For delivering the food 131 | Next 132 | 133 | ' KITCHEN INPUT 134 | Dim Game As Game = ReadGame() 135 | 136 | While True 137 | Dim turnsRemaining As Integer = CInt(ReadLine()) 138 | 139 | ' PLAYERS INPUT 140 | inputs = ReadLine().Split(" "c) 141 | Game.Players(0).Update(New Position(CInt(inputs(0)), CInt(inputs(1))), New Item(inputs(2))) 142 | inputs = ReadLine().Split(" "c) 143 | Game.Players(1).Update(New Position(CInt(inputs(0)), CInt(inputs(1))), New Item(inputs(2))) 144 | 145 | 'Clean other tables 146 | For Each t As Table In Game.Tables 147 | t.Item = Nothing 148 | Next 149 | Dim numTablesWithItems As Integer = CInt(ReadLine()) ' the number Of tables In the kitchen that currently hold an item 150 | For i As Integer = 0 To numTablesWithItems - 1 151 | inputs = ReadLine().Split(" "c) 152 | Dim position As New Position(CInt(inputs(0)), CInt(inputs(1))) 153 | Dim Table as Table = Game.getTableAtPosition(position) 154 | Table.Item = New Item(inputs(2)) 155 | Next 156 | 157 | inputs = ReadLine().Split(" "c) 158 | Dim ovenContents As String = inputs(0) ' ignore until bronze league 159 | Dim ovenTimer As Integer = CInt(inputs(1)) 160 | Dim numCustomers As Integer = CInt(ReadLine()) ' the number Of customers currently waiting For food 161 | For i As Integer = 0 To numCustomers - 1 162 | inputs = ReadLine().Split(" "c) 163 | Dim customerItem As String = inputs(0) 164 | Dim customerAward As Integer = CInt(inputs(1)) 165 | Next 166 | 167 | ' GAME LOGIC 168 | ' fetch a dish, pick ice cream And drop the dish on an empty table 169 | Dim myChef As Player = Game.Players(0) 170 | Dim myChefhasPlate As Boolean = If(myChef.Item IsNot Nothing, myChef.Item.HasPlate, False) 171 | If (Not myChefhasPlate) Then 172 | Use(Game.Dishwasher.Position) 173 | ElseIf (Not myChef.Item.Content.Contains("ICE_CREAM")) Then 174 | Use(Game.IceCream.Position) 175 | ' once ready, put it on the first empty table for now 176 | Else 177 | Use(Game.getFirstEmptyTable.Position) 178 | End If 179 | End While 180 | End Sub 181 | 182 | End Module 183 | --------------------------------------------------------------------------------