A competitive bot game where the key to victory is a clever algorithm
This repository consists of three parts.
- The first part is the game server,
which calculates the results of rounds and the whole skirmish. - Second part is bot template, to fill up with clever algorithm
- Third part is Jupyter Notebook for statistics generation after bot battle for more insight.
To run Battle Server you need Python 3.8 with some dependencies.
The easiest way to do this is to install the Miniconda package.
Then, to install all necessary tools use:
conda env create -f environment.yml
inside capturenetwork directory.
When the installation of the environment is complete type:
conda activate capnet
To switch to capnet
environment with all tools installed.
When prerequisites are met then open console prompt:
.\capturenetwork>python exec.py -h
usage: exec.py [-h] [--host HOST] [--port PORT]
optional arguments:
-h, --help show this help message and exit
--host HOST defines server host. [default host: localhost]
--port PORT defines server port number [default port: 21000]
Battle Server must be up&ready to run bot,
you need to know host and port to connect.
Bot also needs capnet
environment to work properly.
So open another terminal, activate capnet
and you are ready to go.
We have prepared some bots so that you can quickly
and easily check the operation of the server.
If two bots with the same name participate in a match,
this will result in incorrect stats being generated after the clash.
Bots participating in a match must have different self.__my_name
at mind.py
.\capturenetwork\playbot>python playbot.py -h
usage: playbot.py [-h] [--host HOST] [--port PORT]
optional arguments:
-h, --help show this help message and exit
--host HOST defines server host. [default host: localhost]
--port PORT defines server port number [default port: 21000]
To start your adventure with Capture The Network, you need to modify your PlayBot template.
All what you need to modify is mind.py
file.
One game consists of a limited number of rounds. Each round consists of three phases:
- Battle.Server sends request for action in the current round.
- Battle.Server sends confirmation of the received action.
- Battle.Server sends summary of current round after round is over.
After Skirmish ends your bot can do something (for example save some data)
Below is a method containing main loop which controls bot's behaviour during the battle.
You will find this method inside playbot.py
file
def play(self):
while self.game:
data = self.getData()
if data == '':
sleep(0.001)
if 'Command>' in data: # Phase 1
self.mind.move()
elif data.startswith('Command: '): # Phase 2
self.mind.move_ack(data)
elif data.startswith('{"TIME": '): # Phase 3
self.mind.round_ends(data)
elif data.startswith('{"WINNER":'): # After Skirmish
self.mind.game_ends(data)
self.game = False
When Battle.Server sends request for action
PlayBot template will call move(self)
method from mind.py
:
def move(self):
"""
Method used at PHASE 1. Responsible for selecting the next round's play / movement
Should always end with 'self.__send(self.__my_move)'
"""
self.__my_move = self.__moves[randrange(1, len(self.__moves))]
self.__send(self.__my_move)
In the template, the declared movement is selected randomly.
It is easy to guess that this is not a definitive way to win. ;)
Here you need add your unbeatable Choose Move Algorithm of victory.
There are seven possible actions to choose from:
- NOP()
- PATCH()
- SCAN()
- OVERLOAD()
- OVERHEAR()
- EXPLOIT()
- INFECT()
NOP() | PATCH() | SCAN() | OVERLOAD() | OVERHEAR() | EXPLOIT() | INFECT() | |
---|---|---|---|---|---|---|---|
POINTS: | 0 | 3 | 3 | 4 | 1 | 2 | 4 |
Though in reality of brutal war, Bot should never choose NOP().
This action will be assigned to Bot if Battle.Server will not receive action on time.
It is the weakest move and will definitely won't bring any points.
Below is table with action to action results:
you\rival | NOP() | PATCH() | SCAN() | OVERLOAD() | OVERHEAR() | EXPLOIT() | INFECT() |
---|---|---|---|---|---|---|---|
NOP() | DRAW | DRAW | DRAW | LOSE | LOSE | LOSE | LOSE |
PATCH() | DRAW | DRAW | DRAW | DRAW | WIN | WIN | LOSE |
SCAN() | DRAW | DRAW | DRAW | LOSE | DRAW | WIN | DRAW |
OVERLOAD() | WIN | DRAW | WIN | FASTER WIN | FASTER WIN | FASTER WIN | LOSE |
OVERHEAR() | WIN | LOSE | DRAW | FASTER WIN | FASTER WIN | FASTER WIN | WIN |
EXPLOIT() | WIN | LOSE | LOSE | FASTER WIN | FASTER WIN | FASTER WIN | WIN |
INFECT() | WIN | WIN | DRAW | WIN | LOSE | LOSE | FASTER WIN |
TABLE LEGEND:
- WIN - your bot will gain points in this round
- FASTER WIN - the faster bot will score points in this round
- DRAW - none of the bots will score points
- LOSE - rival bot will collect points in this round
Faster winner mechanics is controlled by table below:
you\rival | NOP() | PATCH() | SCAN() | OVERLOAD() | OVERHEAR() | EXPLOIT() | INFECT() |
---|---|---|---|---|---|---|---|
NOP() | NO CHANGE | GAIN | GAIN | LOST | LOST | LOST | LOST |
PATCH() | LOST | NO CHANGE | NO CHANGE | GAIN | GAIN | GAIN | LOST |
SCAN() | LOST | NO CHANGE | NO CHANGE | LOST | GAIN | GAIN | GAIN |
OVERLOAD() | GAIN | NO CHANGE | GAIN | NO CHANGE | NO CHANGE | NO CHANGE | LOST |
OVERHEAR() | GAIN | LOST | GAIN | NO CHANGE | NO CHANGE | GAIN | GAIN |
EXPLOIT() | GAIN | LOST | LOST | NO CHANGE | LOST | NO CHANGE | GAIN |
INFECT() | GAIN | GAIN | LOST | GAIN | LOST | LOST | NO CHANGE |
TABLE LEGEND:
- GAIN - if at the next round result will be
FASTER WIN
, then your bot will score points. - NO CHANGE - if at the next round result will be
FASTER WIN
,
then earlier timestamp of the received action decide. - LOST - If at the next round result will be
FASTER WIN
, then your rival bot will score points.
This phase allows you to check if the action sent has reached the server and correct the error if necessary.
If the bot changes its mind, this is the phase to change the selected action.
def move_ack(self, data):
"""
Method used at PHASE 2. Responsible for checking if battle server
receive correctly move chosen by bot in PHASE 1.
:param data: string object which contains move that the server assigns to this bot
"""
if self.__move_ok:
return
if self.__my_move in data:
self.__log('Move ACK.')
self.__move_ok = True
else:
self.__send(self.__my_move)
data
argument contains data from Battle.Server in format 'Command: METHOD()\x04'
For example: 'Command: NOP()\x04'
Now it's time to collect and analyze what happened during the round.
Battle.Server sends complete data about PlayBot moves, time of reaction and advantage gain.
This information can be used to tweak PlayBot algorithm and to change its tactics.
def round_ends(self, data):
"""
Method used at PHASE 3. Responsible for gathering information about flow of the game
:param data: JSON object contains round summary.
"""
try:
self.__move_ok = False
data = loads(data)
self.__log(data['BOT_1'])
except JSONDecodeError as e:
self.__log(f'Exception: {e.msg} while parsing data.')
You need to put data processing logic between try-except.
If data will be corrupted then lines after except
will be called.
So you can prepare your bot for that circumstances.
After line data = loads(data)
all information from Battle.Server will be parsed to object.
Bot can get access to distinct field by calling data[FIELD]
.
For example:
you can write data['ADVANTAGE']
to check value of ADVANTAGE
field.
Line name = data['BOT_1']['NAME']
will assign name of your bot to name
variable.
To get information about action played by your rival write data['BOT_2']['USED']
ATTENTION:
Your bot data is always behind BOT_1
field. Battle.Server sends customized data to the bot.
The summary of the round is in the following json format:
{
"ADVANTAGE": 0,
"BOT_1": {
"NAME": "Prime",
"POINTS": 0,
"TIME": 0.0443,
"USED": "OVERLOAD()"
},
"BOT_2": {
"NAME": "Kappa",
"POINTS": 2,
"TIME": 0.0443,
"USED": "EXPLOIT()"
},
"ROUND": "1/5000",
"TIME": "2020-04-19T15:04:19",
"WINNER": 2
}
Field 'ADVANTAGE'
contains value which corresponds to:
TIME = 0
BOT_1 = 1
BOT_2 = 2
Value of 'WINNER
field corresponds to:
DRAW = 0
BOT_1 = 1
BOT_2 = 2
Here is place to save some data and do after thoughts.
def game_ends(self, data):
"""
Method used after Skirmish. Responsible for saving important data after game ends.
:param data: JSON object contains short game summary
"""
self.__log(data)
After game ends Battle.Server will send json similar to this one:
{
"WINNER":
{
"ID": 2,
"NAME": "Kappa",
"POINTS": 4071
}
}
To give special, selected name to your PlayBot you need to modify method below:
def name(self):
"""
Method used to send name to battle server. Unique name of the bot allows
it to be easily identified when viewing record from the game.
Should always end with 'self.__send(self.__my_name)'
"""
self.__log(f'Logged in as: {self.__my_name}.')
self.__send(self.__my_name)
You can find UI for this project here: capturenetworkUI
It is developed by Paweł Polakiewicz
- Grzegorz Maciaszek - Initial work - Devdo.eu
- Paweł Polakiewicz - Colaboration - Ppolakiewicz
MIT License
Copyright (c) 2020, 2021 Grzegorz Maciaszek