Skip to content

Instantly share code, notes, and snippets.

@d5
Created February 22, 2019 10:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d5/d07e553dac519e3dd038f79e2dfd4c97 to your computer and use it in GitHub Desktop.
Save d5/d07e553dac519e3dd038f79e2dfd4c97 to your computer and use it in GitHub Desktop.

The Game

The concept of PBR is very simple. You program your bot(s) to defeat all other bots on a 2-dimensional grid field. It is a turn-based war simulation game, where each bot decides the action per each turn, and, the simulation continues until there's only one the last bot left in the game.

When the game starts, it places all participating bots in random locations on the map, and, per each game turn, it executes the code of each bots and runs the simulation in a sequential order. Each bot needs to determines which action it wants to perform for each turn:

  • Move: move up/down/left/right
  • Hold: hold in defensive mode
  • Attack: attack another bot
  • Clone: spawn a new clone of itself on the map

The system's main control flow looks like this:

  • Before turn #1:
    • Decided initial execution order of bots
    • Place all bots in random locations
  • For each turn:
    • Execute each bot codes sequentially
    • Simulate bot commands sequentially
    • Assess and remove dead bots
  • Repeat turns until only one bot is left

Getting Started

The game starts every hour. To participate in the next round, submit your

References

Bot Script

You write the bot script in the Tengo language. Your code should export a function that takes one argument self:

pbr := import("pbr")

export func(self) {
    /* ... */
    
    return pbr.move(1, 0)
}

Your code can import Tengo standard library (except for os module), can use Tengo builtin functions (except for print and printf), and, also should import pbr module.

Exported function should return a bot command using one of pbr module functions: pbr.move(), pbr.hold(), pbr.attack(), or, pbr.clone(). If the function does not return anything (or return undefined), the system will treat it as move(0, 0) (no move no hold).

Value Types

In addition to all Tengo runtime types, the following types are also supported.

Position

Position is a vector of 2 int values, typically representing a coordinate in the map.

  • .x or [0]: X component (int)
  • .y or [1]: Y component (int)

Position has the following operators defined:

  • (Position) + (Position) => (Position): (c1.x + c2.x, c1.y + c2.y)
  • (Position) - (Position) => (Position): (c1.x - c2.x, c1.y - c2.y)
  • (Position) * (int) => (Position): (c1.x * n, c1.y * n)
  • (Position) / (int) => (Position): (c1.x / n, c1.y / n)
  • (Position) * (float) => (Position): (c1.x * f, c1.y * f)
  • (Position) / (float) => (Position): (c1.x / f, c1.y / f)
  • (Position) == (Position) => (bool): c1.x == c2.x && c1.y == c2.y
  • (Position) != (Position) => (bool): c1.x != c2.x || c1.y != c2.y

Rect

Rect represents an axis-aligned bounding box using 2 Positions: min and max.

  • .min: minimum X, Y (inclusive) (Position)
  • .max: maximum X, Y (inclusive) (Position)
  • .dimension: length of the bounds (Position)
  • .contains(Position): tests if the bounds contains a given Position (returns bool)

Rect has the following operators defined:

  • (Rect) == (Rect) => (bool): b1.min == b2.min && b1.max == b2.max
  • (Rect) != (Rect) => (bool): b1.min != b2.min || b1.max != b2.max

'pbr' Module

pbr := import("pbr")

pbr.hold()

pbr.hold() => (bot command)

Hold command tells the system the bot will hold at the current position. When on hold, the bot will take zero damage when it's attacked by other bots during the turn.

pbr.attack()

pbr.attack(dx int, dy int) => (bot command)
pbr.attack(d Position) => (bot command)

Attack command tells the system the bot will attack another bot that's located at (self.pos.x + dx, self.pos.y + dy). If the attack is successful (there exists another bot on that position and it's not on hold), the bot will gain some HP (damage multiplied by a attack bonus rate). Attacked bot will lose HP by the damage.

Actual damage amount is computed using the attacking bot's base damage (self.damage) and the distance weight as follows:

(damage) = (attacker.damage) * (distance weight)

The distance weight (the second term) is basically exponential decay on the distance from the attacker to the target bot.

Distances:

-2 -1 0 +1 +2
-2 4 3 2 3 4
-1 3 2 1 2 3
0 2 1 B 1 2
+1 3 2 1 2 3
+2 4 3 2 3 4

Distance Weights:

dist weight
1 1/1
2 1/4
3 1/16
4 1/64
5 1/256

pbr.move()

pbr.move(dx int, dy int) => (bot command)
pbr.move(d Position) => (bot command)

Move command tells the system that the bot wants to another position. Per each turn, a bot can move to one of the following positions:

-2 -1 0 +1 +2
-2
-1 O
0 O B O
+1 O
+2

Move command will fail if there's another bot in the target position, and, the bot will remain in the current position.

pbr.clone()

pbr.clone(dx int, dy int) => (bot command)
pbr.clone(d Position) => (bot command)

Clone command tells the systeme to create another copy of the current bot and spawn it in one of the following positions:

-2 -1 0 +1 +2
-2
-1 O
0 O B O
+1 O
+2

Like move commands, clone command will fail if there's another bot in the target position.

When cloning, the current bot and the new bot will split HP and the damage equally. For example, cloning a bot that has {hp: 120, damage: 10} will make 2 bots that have {hp: 60, damage: 5} respectively. All other stats will remain the same, and, the inventory will be shared across all clones of the same bot. Each clone will have different .index (clone index) value.

pbr.pos()

pbr.pos(x int, y int) => Position

Creates a new Position value.

pbr.rect()

pbr.rect(min Position, max Position) => Rect
pbr.rect(min_x int, min_y int, max_x int, max_y int) => Rect

Creates a new Rect value.

pbr.abs()

pbr.abs(c Position) => Position
pbs.abs(x int, y int) => Position

Returns another Position value that has the absolute values of the given Position.

pbr.sign()

pbr.sign(c Position) => Position
pbs.sign(x int, y int) => Position

Returns another Position value that has the sign values (1 if positive, -1 if negative, or 0 if zero) of the given Position.

pbr.contains()

pbr.contains(b Rect, c Position) => bool

pbr.field_bounds

pbr.field_bounds => Rect

Is an immutable Rect value that represents the game field. You can use this value to test if the coordinate is within the game field boundary:

if pbr.field_bounds.contains(c) { /* ... */ }

pbr.dist1

pbr.dist1 => [Position]

Is an immutable array of Positions with the following values: {x: 1, y: 0}, {x: 0, y: 1}, {x: -1, y: 0}, {x: 0, y: -1}. One use case for this value is to pick the next move delta randomly:

rand := import("rand")

return pbr.move(pbr.dist1[rand.intn(len(pbr.dist1))])

Bot Stats

Each bots (clones) do

  • .name: name of the bot
  • .index: clone index (starting from 0)
  • .damage: base damage
  • .age: how many turns it had survived
  • .hp: health points
  • .kill: how many bots it had killed
  • .pos: the current position of the bot {x: X, y: Y}

'self' Value

Per each turn, the bot script's exported function is invoked with self value, an immutable map that has the following elements.

self.name

....

self.index

....

self.hp

....

self.age

....

self.damage

....

self.kills

....

self.pos

....

self.hits

....

self.inv

....

self.scan()

....

-2 -1 0 +1 +2
-2 O
-1 O O O
0 O O B O O
+1 O O O
+2 O

self.detect()

....

-2 -1 0 +1 +2
-2
-1 O
0 O B O
+1 O
+2

Inventory

Each bot is given an inventory, which it can use as a data storage that persists across turns. Bot can use it however it wants, but, items in the inventory have different weights, and, when the total weight exceeds 100.0, the bot cannot perform any other actions but HOLD.

Technically, the inventory is a mutable map, and, you can put different types of values (items) in it. The weights of different types are:

Scanning

By spending some points, the bot can check its surroundings.

Costs:

Specifications

GitHub Repository

The repo must have a pbr.tengo file.

Bot Script

The bot script must export a function of the following signature:

func(self) {}
  • self.pos: the current position of the bot
    • self.pos.x: X coordinate in [0, 999]
    • self.pos.y: Y coordinate in [0, 999]
  • self.inv: a map that represents the inventory of the bot
    • Inventory is empty when the bot is spawned initially, but, you can add or remove items in it.
    • You can use the inventory as a persistent data storage for the bot.
  • self.inv: a map that represents the inventory of the bot
  • self.identity:
  • self.age:
  • self.points:
  • self.kills:
  • self.hits:

Here's an

Item Weights

  • Int: 1.0
  • Float: 1.0
  • String: 0.5 x len(v)
  • Bool: 0.25
  • Char: 0.5
  • Bytes: 0.25 x len(v)
  • Time: 1.0
  • Array: (sum of weight of elements) + 1.0
  • Map: (sum of weight of values) + (sum of weight of string keys) + 1.0

Bot Stats

{
  identity: "d5/mybot1@19a2b2d9410c01baccc3b15197fe3db62f84d2ac",
  points: 193,
  kills: 2,
  pos: {
    x: 234,
    y: 94
  }
}

Developer's Guide

How to Join the Game

Basically, you need to create a GitHub repo that contains the program for your bot, and, submit it to participate in the the game. The only required file in the repo is pbr.tengo, and, you can put any other files (e.g. README.md file for some explanation) in the same repo. The system will simply read and compile pbr.tengo script file and spawn your bot in the battle ground.

Programming Your Bot

You program your bot using the Tengo language.

pbr := import("pbr")

attackable := func() {
  if pbr.detect(-1, 0) {
    return {dx: -1, dy: 0}
  }
}

export func(pos, inv) {
  if enemy := pbr.detect(-1, 0); enemy {
    
  }

  return pbr.move(-1, 0)
}

Your script is supposed to export a function that takes 2 arguments and return an action to perform for the turn.

Bot Stats

"pbr" Module

"pbr" module has the following functions:

detect(dx, dy)
bot := detect(dx, dy)
  • Arg dx: relative X coordinate (e.g. 0, +2, -1)
  • Arg dy: relative Y coordinate (e.g. 0, +2, -1)
  • Return
    • Bot Stats if there is another bot in that position
    • undefined if there is no bots
move(dx, dy)
pbr := import("pbr")

export func() {

  return pbr.move(dx, dy)
}
  • Arg dx: relative X coordinate (e.g. 0, +2, -1)
  • Arg dy: relative Y coordinate (e.g. 0, +2, -1)
attack(dx, dy)
hold()

Environment

In PBR, the following builtin functions and standard modules are disabled:

  • print builtin function
  • printf builtin function
  • os module

Computational Costs

If bot's program code is too lengthy, it may cost the bot more points per each turn. There will be extra computational costs for additional 1000 bytes compiled .....

Reports

After each turn is concluded, the system will publish various results of the turn in a file in this repo. The reports will contains:

  • Leaderboards
    • Top points
    • Top kills
    • Top survivers
  • Event Logs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment