Skip to content

Commit

Permalink
Merge branch 'develop'. Adding RoboRally.
Browse files Browse the repository at this point in the history
  • Loading branch information
kenganong committed Feb 11, 2017
2 parents eb99008 + b601218 commit 100baa7
Show file tree
Hide file tree
Showing 60 changed files with 2,976 additions and 0 deletions.
45 changes: 45 additions & 0 deletions docs/robo-rally-guide.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Programmer's Guide:
To program you AI and test your code, you will need Python 3. The version on my machine is Python 3.5.2.

Create a python file (like my_robot.py) and drop it in the 'roborally/robots' directory. Your file should look like:
from roborally.api import *
def move():
# Your AI code here
# return one of the moves from the MOVES list

The API (at roborally.api) contains a bunch of helpful functions for writing your bot. The most important function
here is sight() which will give you everything your robot currently knows. All the other functions in the API are
helper functions to process that information. The API is heavily documented and you can either read the code, or use
pydoc to read through my documentation. On my system, I type:
pydoc3 roborally/api.py

Once you've written some of your AI code, you'll want to test it! In order to run simulations of the arena, run
roborally.py while in the robotarena directory. To do this with the default configuration, you would use the
following command:
python3 roborally.py

Most likely, you'll want to create your own config file so you can test different scenarios. To do this, you would:
cp roborally/config.py ./my_config.py
vim my_config.py (or whatever text editor you like to use)
python3 roborally.py my_config.py

The comments in the config file should guide your edits, but I will suggest two in particular:
1. debug_bots = True
This will make the game exit with a stacktrace if a bot raises an exception. Good for debugging your AI.
2. iteractive = True OR save_replay = True
If you want to see what your robots are doing iteration-by-iteration, one of these configurations is essential.
The default configuration will use every module in the roborally/robots as an AI in the game with crappy names on a
rather normal map. As you test your robot, you'll probably want to customize which map to use and which robots are
included.

If you've chosen to use the save_replay configuration, every execution of roborally.py will create a pickle file with
a copy of every iteration encountered during the game. By executing the create_web_replay.py script, the contents of
this pickle file will be used to create HTML pages for every iteration of the game so that you can watch a visual
replay of what went down in robot town.
python3 create_web_replay.py roborally/replays/<replay_name>.pickle
Open common/replays/<replay_name>.html in your browser of choice.

If you discover any bugs in the game or would like to suggest an enhancement (such as a new function in the api),
you can submit pull requests on github and/or email me about it.

Once you are happy with your AI, remember to submit your code with a pithy name and description of its strategy.
196 changes: 196 additions & 0 deletions docs/robo-rally-rules.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
The factory robots are competing in a race. Flags have been set out and each robot will need to touch each flag in
numerical order to win. Each robot is equipped with industrial grade lasers to make the race more interesting.

In this Robot Arena, you control the robots on the factory floor and win by either one of your robots touching each
of the numbered flags in order, or by being the last robot left alive. Armed with your laser, you can destroy anything
in the arena that gets in your way, just don't take too many laser hits yourself!

Robot Arena:
If you are new to robot arenas, this paragraph is for you! In robot arenas, I am designing a simple game supporting
lots of players. This game functions like board game (usually on a very large board) but is entirely virtual.
All the players are computer-controlled and you write the AI via simple scripts (usually small python modules).
You win if players controlled by your AI win. You don't have to play to win...maybe your AI just creates havoc.
As the game master, I will run the official game. However, I am sharing all of my code to run the game as well as all
the AI submissions by other contributors, so you can run your own simulations to test your AI or just for fun.

Submission:
What I need from YOU are two AI submissions, a single python file each which will act as the AI for robots.
Along with your python file, please provide a pithy name for your AI and a short description of its strategy.
I need your first submission by 2017-02-26. Just respond to this email with your python file attached.
On 2017-02-28, I will share everyone's first submission to the group.
I need your second submission by 2017-03-09. You can borrow ideas from other people's first submissions.
On 2017-03-11, I will run the official game and report the results. I will be hosting a get together at my home for
anyone who would like to watch the results unfold!
Please ask any questions not answered in the following rules and descriptions.

Order of the Match:
1. An arena designed by the game master is created.
2. 30 robots of each robot type will be created and added to random locations in the arena.
3. Turns are taken until an end-game situation is reached
A: A robot has touched all 6 flags in order
B: Only 1 robot type survives
C: All robots have died
4. The winner and runner-up are determined.

Order of the Turn:
1. All active robots decide their moves
2. All robots with priority moves perform their moves
3. All active robots with laser moves fire their lasers
4. All active robots perform normal moves
5. All spinners turn
6. All active robots and mounted lasers fire their lasers
7. Each active robot on top of its next flag scores the flag
8. Every 10th turn, robots receive a charge counter
9. (starting at turn 300) Every 30th turn, an unseen source damages a random robot
10. Check for end-game situation

Arena:
The arena is square-shaped with square tiles. The arena is surrounded by pits.
The arena that will be used during the official game is secret and to be designed by the game master. However, there
are example arena maps available in the maps directory (https://github.com/kenganong/robot-arena/tree/master/robotarena/roborally/maps).
Since there are 30 copies of each robot type, the arena will be very large. You can expect that around 5% to 6% of
the arena will be populated with robots at the beginning.
Each square of the arena has a floor (which can be empty, a spinner, a flag, or a pit) and content (which can be
empty, a robot, a robot corpse, a mounted laser, or a wall).
When reading maps, these are the symbols and what they mean:
* <space> - Empty
* <number> - A flag with that number on it
* # - A wall
* <,>,^,v - A mounted laser which fires in one of the four directions.
* {,} - A spinner which turns either clockwise or counter-clockwise
* & - A pit
Feel free to design your own maps when testing your AI.
No robot can see the entire arena all the time. Robots can only see an 11x11 square around themselves (5 tiles away
in each direction).

Lasers:
Robots and Mounted Lasers fire a laser each turn. When a laser is fired, it will hit the first Robot, Robot Corpse,
Wall, or Mounted Laser it encounters in the direction it was fired dealing its target 1 damage. Lasers have infinite
range and can therefore hit a robot even if the robot cannot see the source of the laser.

Pits:
Pits are represented by a floor of None. Anything that enters a pit square falls into the pit and is gone forever.

Walls:
Walls are impassable and cannot be pushed. A robot that moves into a wall, will bounce back and not move.
Walls have 50 life and can therefore be shot with lasers until they crumble into nothing.

Mounted Lasers:
Mounted Lasers are basically walls that fire lasers. They are impassable and cannot be pushed. Each Mounted Laser
fires its laser in exactly one direction.
Mounted Lasers have 50 life and can be shot with lasers until they crumble into nothing.

Spinners:
When spinners activate, they turn any robot on top of the spinner 90 degrees. When looking down at the arena, the
LEFT_SPINNER turns counter-clockwise and the RIGHT_SPINNER turns clockwise.
Spinners are floor elements and are immovable and indestructible.

Flags:
There are a total of 6 flags in the arena and each has a number associated with it. Robots score flags only
if they have already touched all previously numbered flags. To score a flag, a robot must end the turn on the
flag tile, this means that each turn a maximum of one robot can be awarded each flag. When a robot scores a flag,
that robot also gains 10 life.
Robots can only see the next flag, all other flags (including previous flags) are invisible to that robot.
Robots can always sense the direction the next flag is in, even if it is outside their sight range.
Flags are floor elements and are immovable and indestructible.

Corpses:
After a robot has taken 10 or more damage, it deactivates and becomes a corpse.
Corpses can be pushed by robots. They do not fire lasers or move aside from being pushed.
Corpses start with 10 life. After taking 10 or more damage, they explode dealing 1 damage to everything in the
8 adjacent tiles. After exploding, the corpse no longer exists on the arena.

Robots:
Robots are the players in the game. There are 30 robots of each robot type, meaning each AI controls 30 robots.
Robots have the following attributes:
* life - Each robot starts with 20 life.
* flags - The number of flags the robot has scored.
* charges - The number of charges the robot currently has. Each robot starts with 1 charge.
Charges allow robots to use priority moves without taking damage.
* name - The pithy name for the AI controlling the robot.
* facing - The direction the robot is facing.
* memory - This robot's memory as populated by its AI.
Your AI will have access to all of this information for your robot when deciding the robot's move. However, other
robots you see will only have life, name, and facing available to you.

Moves:
Each turn, every active robot chooses one move to make.
Normal moves:
* FORWARD - your robot moves forward one tile.
* REVERSE - your robot moves backward one tile.
* TURN_LEFT - your robot turns counter-clockwise 90 degrees.
* TURN_RIGHT - your robot turns clockwise 90 degrees.
* U_TURN - your robot turns 180 degrees.
Attack moves:
Attack moves happen before all normal moves and can therefore be used to hit a robot before it moves.
* Laser - your robot fires its laser instead of moving. This will cause your robot to fire its laser twice during the
turn; once before normal moves and once after all moves.
Priority moves:
Priority moves happen before all attack and normal moves which means they can be used to dodge attack moves.
Performing a priority move will use 1 charge. If your robot has no charges left, it will take 1 damage.
* FORWARD_TWO - your robot moves forward two tiles. This move will not 'jump' over anything.
* SIDESTEP_LEFT - your robot moves one tile to its left without turning.
* SIDESTEP_RIGHT - your robot moves one tile to its right without turning.
When two or more robots would move onto the same tile or otherwise conflict with one another, the robots will perform
their moves in a random order.

Sight:
Your robot's sight is represented as a list of lists, 11 rows and 11 columns fill with cell objects each with two
attributes, 'floor' and 'content'. Your robot will always be in the center (5, 5) of your sight.
cell.floor will contain one of the following constants (from roborally/api.py)
* PIT - A pit. This constant is equal to None.
* EMPTY - No floor element present. There may still be content.
* LEFT_SPINNER - A counter-clockwise spinner.
* RIGHT_SPINNER - A clockwise spinner.
* FLAG - Your robot's next flag.
cell.content will contain either None or a dict with the following keys:
* TYPE - One of the constants (from roborally/api), WALL, MOUNTED_LASER, CORPSE, ROBOT
* POSITION - A coordinate tuple in your sight.
* LIFE - Remaining life.
* FACING - One of the constants (from roborally/api), AHEAD, BEHIND, LEFT, RIGHT representing the direction the
Robot or Mounted Laser is pointing relative to your robot. Meaningless for Walls and Corpses.
* NAME - Pithy name for the AI controlling the Robot. Meaningless for non-Robots.
* FLAGS_SCORED - Only available for your robot. Number of flags your robot has scored.
* CHARGES - Only available for your robot. Number of charges your robot has.
* MEMORY - Only available for your robot. A dict representing your robot's memory. Nothing is automatically entered
into this dict. Your AI controls its entries.
* FLAG_SENSE - Only available for your robot. A list of directions toward your robot's next flag.

Random Damage:
Starting at turn 300, some invisible force will start dealing damage to robots. Every 30 turns, one random robot will
take this damage. On turn 300, the randomly selected robot will take 1 damage. On turn 330 another randomly selected
robot will take 2 damage. On turn 360 it will be 3 damage and so on.
Although any active robot can be hit, robots which have collected less flags are more likely to be selected.

Winner and Rankings:
The winner will be the AI which collects all 6 flags. If no AI does this, the winner will be the AI which survives the
longest. Complete rankings are essentially decided by determining the rankings for best flag gatherers and best
survivors then interleaving the two lists.
Your AI will rank highly if it is the best at one of those two determinations.
Tie-breakers for best flag gatherers: Maximum flag number scored, total flags scored, turns survived, and surviving
robots at the end.
Tie-breakers for best survivors: turns survived, surviving robots at the end, maximum flag number scored, and total
flags scored.

Special Rules (for special people):

1. Do not share memory
Your robots are individuals and prefer to be treated as such. Do not write to memory in a place from which other
robots, even of the same type, read. Just use your personal memory dict.
2. Do not raise exceptions
If a robot raises an exception when asked for a move, that robot will be considered to overheat, taking 1 damage.
3. Do not actually overheat
Infinite loops, unbounded memory, or other such issues will cause the game manager to retroactively erase the
existence of offending robot types. Your robots are not Fixed Points and history will forget them.
4. Do not break the fourth wall
Python is a hackable language, but please do not attempt have your robots discover more knowledge about the arena
or life outside than the API provides. Doing so would admit to being a machine and therefore against robot protocol.
5. Write simply
Once your robot easily trashes all the others in the arena, the other contestants will be more amazed to find your
code elegant and simple than a complex beast they could never hope to understand.
There are many dangers and twists to the arena and it may be best to ignore some, moving with simple purpose. Feel
free to write concise strategies that may not have a chance to win.
6. You may show discernment
When a robot examines their surroundings in the arena, they are able to see the names of other robots nearby. It is
acceptable to act differently to different robots such as attempting not to shoot robots with particular names.
It is also acceptable to have one robot's purpose be to help a particular other robot to victory.
Empty file added robotarena/__init__.py
Empty file.
Empty file added robotarena/common/__init__.py
Empty file.
Empty file.
90 changes: 90 additions & 0 deletions robotarena/common/model/board.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import itertools

RECTANGLE = 0
SQUARE = 1
HEX = 2

NONE = 0
ALL = 1

def init_board(size, fill=' ', shape=RECTANGLE, cell_shape=SQUARE, wrap=NONE):
if shape == RECTANGLE and cell_shape == SQUARE:
return RectangleSquareBoard(size, fill, wrap)
elif shape == RECTANGLE and cell_shape == HEX:
return ToroidalHexBoard(size, fill, wrap)

class RectangleBoard:
def __init__(self, size, fill, wrap):
self.rows = size[0]
self.cols = size[1]
self.empty = fill
self.wrap = wrap
self.clear()
def clear(self):
self.clear_fill(self.empty)
def clear_fill(self, fill):
self.contents = [[(fill() if callable(fill) else fill) for j in range(self.cols)] for i in range(self.rows)]
def get_item(self, pos):
if self.off_board(pos):
if self.wrap == NONE:
return None
else:
pos = self.wrap_pos(pos)
return self.contents[pos[0]][pos[1]]
def take_item(self, pos):
fill_item = self.empty() if callable(self.empty) else self.empty
return exchange_item(pos, fill_item)
def exchange_item(self, pos, exchange):
if self.off_board(pos):
if self.wrap == NONE:
return None
else:
pos = self.wrap_pos(pos)
item = self.contents[pos[0]][pos[1]]
self.contents[pos[0]][pos[1]] = exchange
return item
def put_item(self, pos, item):
if self.off_board(pos):
if self.wrap == NONE:
raise IndexError('Position out-of-bounds')
else:
pos = self.wrap_pos(pos)
self.contents[pos[0]][pos[1]] = item
def off_board(self, pos):
return pos[0] < 0 or pos[0] >= self.rows or pos[1] < 0 or pos[1] >= self.cols
def wrap_pos(self, pos):
row, col = pos
if pos[0] >= self.rows:
row -= self.rows
if pos[1] >= self.cols:
col -= self.cols
return row, col
def traverse(self):
return ((self.contents[pos[0]][pos[1]], pos) for pos in itertools.product(range(self.rows), range(self.cols)))

class RectangleSquareBoard(RectangleBoard):
def get_surroundings(self, pos, distance):
# TODO: read wrap
if self.off_board(pos):
raise IndexError('Position out-of-bounds')
surroundings = []
for row in range(pos[0] - distance, pos[0] + distance + 1):
row_content = []
for col in range(pos[1] - distance, pos[1] + distance + 1):
row_content.append(self.get_item((row, col)))
surroundings.append(row_content)
return surroundings

class ToroidalHexBoard(RectangleBoard):
def get_surroundings(self, pos, distance):
surroundings = []
for row in range(pos[0] - distance, pos[0] + distance + 1):
current_row = []
rows_away = abs(pos[0] - row)
offset = pos[0] % 2
start_col = pos[1] - (distance - ((rows_away + offset) // 2))
num_cols = distance * 2 + 1 - rows_away
for col in range(start_col, start_col + num_cols):
current_row.append(self.get_item((row, col)))
surroundings.append(current_row)
return surroundings
Binary file added robotarena/common/replay/images/corpse.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag3.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag4.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag5.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag6.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag7.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/flag8.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/floor.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/mounted.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/pit.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_10.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_11.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_12.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_13.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_14.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_15.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_16.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_3.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_4.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_5.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_6.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added robotarena/common/replay/images/robot_7.gif
Binary file added robotarena/common/replay/images/robot_8.gif
Binary file added robotarena/common/replay/images/robot_9.gif
Binary file added robotarena/common/replay/images/spinner.gif
Binary file added robotarena/common/replay/images/wall.gif
31 changes: 31 additions & 0 deletions robotarena/common/replay/roborally.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.rotateimg90 {
-webkit-transform:rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg);
}

.rotateimg180 {
-webkit-transform:rotate(180deg);
-moz-transform: rotate(180deg);
-ms-transform: rotate(180deg);
-o-transform: rotate(180deg);
transform: rotate(180deg);
}

.rotateimg270 {
-webkit-transform:rotate(270deg);
-moz-transform: rotate(270deg);
-ms-transform: rotate(270deg);
-o-transform: rotate(270deg);
transform: rotate(270deg);
}

.fliphorizontal {
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
Loading

0 comments on commit 100baa7

Please sign in to comment.