Write code instead of a cover letter!
1) Start a game in Story mode
2) Beat as many levels as you can by writing code in your favorite IDE
3) Win a job interview at ICF Tech Hungary without boring HR questions
Your code shall call the endpoints of this API to play the game
You play a turn-based game - You set what you want to do on the next tick (turn) of the Map, and then trigger a tick.
To beat a Map, collect all the treasures on it by controlling your Hero. A Hero is basically your Player's "presence" on a Map. You can control your Hero by calling endpoints described below:
The game uses an XY coordinate system of integers. (0;0) is the bottom-left corner of the map.
Right after your Map was generated (we'll talk about how this happens later), call the GET play/mapResource endpoint - it returns immutable resources that you should store until you finish the Map. I.e. you should only call this endpoint once for a Map.
After you got your Map's resources from GET play/mapResource, the turn-based game can start. There are two endpoints that you'll probably regularly call in your implementation:
Combining the prefetched data from the GET play/mapResource endpoint with data from the GET play/mapState endpoint gives you the full state of the Map you're playing on - thus your API client implementation can decide what the next action of the Hero should be.
There is a debugger accessible on /freeRoam. It visualizes the current state of all the entities on a map. Use it to make developing your API client easier and more convenient.
After your implementation calculated the action, it can call the POST play/approveHeroTurn endpoint to submit it. The submitted action will be executed on the next tick. POST play/approveHeroTurn also ticks the Map if all the Heroes on the Map approved their turn.
In Story mode, there is only one Hero on the map, so calling POST play/approveHeroTurn ticks the map instantly.
Game elements:
Enemies are moving towards the closest Hero. If they manage to step on the same field as a Hero, they deal Touch Damage. Enemies also shoot Bullets at you. A Bullet has a direction and advances one field on every tick (in a straight line).
To attack an Enemy, submit the "KICK_*" actions . These actions damage the Enemies standing on the Hero's field, on its neighboring field, and on its second neighboring field.
Hero and Enemy health ranges from 0 to 1. A "KICK_*" deals 0.6 damage to all Enemies in range. You can't "kick" "through" obstacles.
To protect against a Bullet, submit the USE_SHIELD action - it reduces the Bullet Damage to zero. You should apply "USE_SHIELD" exactly on the tick when the Bullet arrives on the field of your Hero.
A Bullet vanishes if it 1) hits an obstacle 2) goes out of map bounds or 3) hits a Hero with or without using a shield.
You can tell that a Map is finished if "$.map.status" in the response of GET play/mapState is "WON".
One Player can place and control multiple Heroes on the same Map thus you can even race your different algorithms against each other on the same Map using the same Player.
Order of execution in a tick:
A Story is a predefined list of Map specifications, i.e., levels. It starts when you start the game on ponypanic.io or call the POST story/begin endpoint.
Playing in Story mode means that there is a StoryPlaythrough entity that tracks your progress across Story levels (Maps). A StoryPlaythrough always provides a Map that you have to beat to get to the next level.
When you play a StoryPlaythrough, we auto-generate Maps for you based on the Story's Map specification belonging to your current level.
When playing in Story mode, both the Map and Player are provided by the StoryPlaythrough entity. You can check out the Authentication chapter on how to authenticate a StoryPlaythrough.
After you beat a Map, you can proceed to the next level by calling the POST story/nextLevel endpoint. This endpoint generates you a whole new Map that you shall beat. After you finished the last Map, you won the StoryPlaythrough - Yaay, congrats!
The levels (Maps) continuously get harder so you may need to make small tweaks in your API client implementation as you level up. Some of the starting maps are handcrafted to address particular scenarios and ease you in when you start developing your API client.
If you hit a deadlock, just call POST story/resetLevel to generate a new Map for your current level.
To win a StoryPlaythrough, you have to beat all the generated Maps in a consecutive order.
If you are curious about the current state of your StoryPlaythrough (e.g. which level are you on) call GET story/playthroughState.
You can generate Maps by custom generation parameters (size, difficulty, etc). To do this, call POST freestyle/createMap and provide the parameters.
POST freestyle/createMap returns a Map-Token that you can use to interact with the Map.
To put a Hero on a Map (i.e., to be able to play the Map), call the POST freestyle/joinMap endpoint.
You can share the Map-Token with other Players so they will also be able to join and you can all play multiplayer on the same Map
One Player can place and control multiple Heroes on the same map thus you can even race your different algorithms against each other on the same Map using the same Player. If your Player has multiple heroes on the same Map, you have to specify which Hero are you setting the desired action for when calling POST play/approveHeroTurn - to do so, set the heroId request param to the value returned by POST freestyle/joinMap
There are three entities that you can authenticate to:
To authenticate an entity, add its token to your HTTP requests as the value of the appropriate header:
If you're playing in Story mode, you only have to provide Story-Playthrough-Token. StoryPlaythrough refers to both Player and Map. If you provide Story-Playthrough-Token, you don't have to provide Player-Token or Map-Token.
Obtaining the tokens:
All these endpoints are HTTP, use JSON in their request & response bodies, and require partial auth described in the Authentication section above. Their base URL is https://ponypanic.io/playGameApi/v1/.
1{
2 "map": {
3 "id": 99642,
4 "width": 5,
5 "height": 5,
6 "elapsedTickCount": 0,
7 "status": "PLAYING", // V1MapStatus
8 "isGameOver": false,
9 "treasures": [], // V1TreasureResponse array
10 "enemies": [], // V1EnemyResponse array
11 "bullets": [] // V1BulletResponse array
12 },
13 "heroes": [] // V1HeroResponse array
14}
$.status: V1MapStatus
$.map.treasures: The heroes present on the map. Schema: V1HeroResponse
$.map.treasures: The treasures present on the map. Schema: V1TreasureResponse
$.map.enemies: The enemies present on the map. Schema: V1EnemyResponse
$.map.bullets: The bullets present on the map. Schema: V1BulletResponse
1{
2 "compressedObstacles": {
3 "coordinateMap": {
4 "1": [ 2, 6, 8 ],
5 "2": [ 4, 5 ],
6 "4": [ 4, 8 ]
7 }
8 }
9}
The $.compressedObstacles.coordinateMap object contains the coordinates of obstacles on the map. The keys are the X coordinates and the arrays of each key are the Y coordinates belonging to the X coordinate in the key.
E.g. the { "x1": [y1, y2], "x2": [y3, y4] } coordinateMap means the following four obstacles: (x1;y1), (x1;y2), (x2;y3), (x2;y4)
1{
2 "heroId": 17, // Ignore this if you're playing Story mode
3 "action": "NOTHING" // :HeroAction
4}
$.action is the action that the Hero will execute on the next tick of the Map. Schema: V1HeroAction
$.heroId is the ID of the Hero you set the action for. Ignore this parameter if you only have one Hero on the Map. Otherwise use the heroId you got back from POST freestyle/joinMap
1{
2 "storyPlaythroughId": 17,
3 "storyPlaythroughToken": "17_topsecret",
4 "playthroughState": V1PlaythroughStateResponse
5}
$.playthroughState: V1PlaythroughStateResponse
1{
2 "playthroughState": V1PlaythroughStateResponse
3}
$.playthroughState: V1PlaythroughStateResponse
1{
2 "playthroughState": V1PlaythroughStateResponse
3}
$.playthroughState: V1PlaythroughStateResponse
Response: V1PlaythroughStateResponse
1{
2 "width": 5, // nullable
3 "height": 5, // nullable
4 "treasureCount": 2, // nullable
5 "obstaclesPresent": true, // nullable
6 "enemyConfig": V1EnemyConfigRequest, // nullable
7}
$.enemyConfig: V1EnemyConfigRequest
1{
2 "heroId": 17 // ID of the new Hero
3}
1{
2 "currentLevel": 1, // number - nth level
3 "isCurrentLevelFinished": false, // boolean
4 "currentMapStatus": "PLAYING", // V1MapStatus
5 "storyLine": "hi there" // string
6}
$.currentMapStatus: V1MapStatus
1{
2 "id":1, // unique ID of the Hero
3 "position": {
4 "x": 4,
5 "y": 2
6 },
7 "health": 1.0, // dies when reaches 0
8}
1{
2 "id":1, // unique ID of the Treasure
3 "position": {
4 "x": 4,
5 "y": 2
6 },
7 "name": "Some treasure",
8 "collectedByHeroId": null // null or number
9}
$.collectedByHeroId:
1{
2 "id":1, // unique ID of the Enemy
3 "position": {
4 "x": 4,
5 "y": 2
6 },
7 "moveProbability": 0.2, // chance to step to a neighbouring field in a tick
8 "shootProbability": 0.3, // chance to release a bullet in a tick
9 "onTouchDamage": 0.4, // deals this much damage in every tick when located on the same field as a Hero
10 "bulletDamage": 0.5, // shoots bullets that has this much damage
11 "health": 0.6, // dies when reaches 0
12}
1{
2 "id":1, // unique ID of the Bullet
3 "position": {
4 "x": 4,
5 "y": 2
6 },
7 "damage": 0.3,
8 "direction": "RIGHT", // V1Direction
9 "shotByEnemyId": 23
10}
$.direction: The Bullet steps one field in this direction on every tick of the Map. Schema: V1Direction
$.damage: When the Bullet hits a Hero who does not use a shield, it deals this much damage
Possible values: "NOTHING", "USE_SHIELD", "MOVE_LEFT", "MOVE_RIGHT", "MOVE_UP", "MOVE_DOWN", "KICK_LEFT", "KICK_RIGHT","KICK_UP", "KICK_DOWN".
Possible values: "CREATED", "PLAYING", "WON", "LOST".
If a Map's status is "WON" or "LOST", no more Heroes can join or play on it.
Possible values: "NONE", "LEFT", "RIGHT", "UP" (north), "DOWN" (south)
1{
2 "count": number, // count of enemies to put on the map
3 "canMove": boolean, // sets if enemies can move
4 "canShoot": boolean, // sets if enemies can shoot
5 "canDamageOnTouch": boolean, // sets if enemies can deal damage when they touch a Hero
6}