Skip to content

Instantly share code, notes, and snippets.

@efirdc
Last active February 2, 2023 07:13
Show Gist options
  • Save efirdc/1d58959e50b928ef4655157f39e282de to your computer and use it in GitHub Desktop.
Save efirdc/1d58959e50b928ef4655157f39e282de to your computer and use it in GitHub Desktop.
Script for adding AI agents to coryefird.com/konane

Konane Online - Agent Script

Created for the A.I. course (CMPT 355) at MacEwan University.

With this script you can deploy your Konane agent at coryefird.com/konane
If your agent worked with drivercheck.pl, then it should work with this script as well.
Win/loss results of your agent will be output to a log file agent_online.log

If you have any issues getting your agent working, or if you have a working agent that you would
like to be permenantly deployed on the server then please contact me!

Requirements

Should work on python3.6 or newer. I believe the only required package is websockets, which can be installed with
pip install websockets.

Usage

An agent that runs through a bash script:
python run_agent_online.py "./monte_carlo_agent.sh {} {}" "Fight Monte Carlo!"
A python agent that accepts additional parameters:
python run_agent_online.py "python run_agent.py {} {} alpha_beta --max_time 2" "my_room"
An agent that joins another agents room and plays 10 games
python run_agent_online.py "python run_agent.py {} {} random_player "[G5][Beginner][0] Random" --join_room --num_games 10

positional arguments:  
  agent_call            String to run your agent, as you would type it on the command line. 
                        You must leave two sets of curly braces for arguments to be substituded: '{} {}'. 
                        The first curly brace will become the game state file name, and the second becomes a 
                        character to specify which player the agent is, i.e. 'B' or 'W'.  
  room_name             Room name to use on the server  

optional arguments:  
  -h, --help            show this help message and exit  
  --join_room           Join an existing room instead of creating one.
  --num_games NUM_GAMES
                        Terminate the agent after completing a number of games. Will play indefinitely otherwise.
  --wait_time WAIT_TIME
                        How many seconds to wait for the opponent to make a move before exiting.
  --websocket WEBSOCKET
                        Websocket address for the server. The default should connect to coryefird.com/konane.
import asyncio
import websockets
import argparse
from argparse import RawTextHelpFormatter
import json
from subprocess import Popen, PIPE
import time
import sys
import logging
import traceback
from datetime import datetime
async def create_room_and_play_online(args):
logging.basicConfig(filename='agent_online.log', level=logging.INFO)
games_played = 0
white_wins = 0
black_wins = 0
while True:
try:
if args.num_games is not None and games_played >= args.num_games:
print(f"Finished playing {games_played} games. Exiting...")
return
print("Establishing web socket connection to:", args.websocket)
async with websockets.connect(args.websocket) as websocket:
#await websocket.send(json.dumps({'action': 'get_rooms'}))
#response = await websocket.recv()
#print("Open rooms: ", response)
# Create or join a room on the server
num_attempts = 10
for i in range(num_attempts):
await websocket.send(json.dumps({
'action': 'join_room' if args.join_room else 'create_room',
'data': args.room_name
}))
# Check if the room was successfully created
response = await websocket.recv()
print(response)
if response.startswith("Successfully"):
break
else:
if i == (num_attempts - 1):
print("Reached max room creation/joining attempts. Quitting...")
return
print("Failed to join/create the room. Trying again...")
time.sleep(1)
# Wait for first game state to be sent (someone joined)
recheck_rate = 1
while True:
try:
# double the recheck_rate until we are checking once every 10 minutes
recheck_rate = min(recheck_rate * 2, 60 * 10)
response = await asyncio.wait_for(websocket.recv(), recheck_rate)
print("Received response", response)
game_state = json.loads(response)
break
# periodically check if our room still exists
except asyncio.exceptions.TimeoutError as e:
await websocket.send(json.dumps({'action': 'get_rooms'}))
response = json.loads(await websocket.recv())
# If it doesnt, raise an error and restart the connection
if args.room_name not in response:
raise ConnectionError(f"Our room {args.room_name} is missing!")
# If we are white, wait for black to make one move so it is our turn
# We do this because the agent script protocol expects to have the first turn when it loads the initial board
if game_state['you'] == 'white':
game_state = json.loads(await websocket.recv())
# White the initial board to a file, since the agent script expects to load it this way
game_file_name = 'online_game.txt'
print(f"Received initial game state\n{game_state['game']}\nPlayer: {game_state['you']}")
with open(game_file_name, "w") as file:
file.write(game_state['game'])
# Start the agent process and get the first move
formatted_agent_call = args.agent_call.format(game_file_name, 'B' if game_state['you'] == 'black' else 'W')
agent_call_args = formatted_agent_call.split(' ')
print("Starting process", formatted_agent_call)
with Popen(agent_call_args, stdout=PIPE, stdin=PIPE, stderr=PIPE, text=True) as agent_process:
# Play opening
print("Process started, getting first move.")
agent_process.stdout.flush()
agent_move = agent_process.stdout.readline()[:-1]
print("First move:", agent_move)
await websocket.send(json.dumps({
'action': 'move',
'data': agent_move
}))
# Main play loop
while True:
# Wait for the opponents move
try:
response = await asyncio.wait_for(websocket.recv(), args.wait_time)
print("Received response", response)
if response == "Opponent left.":
break
game_state = json.loads(response)
# periodically check if our room still exists
except asyncio.exceptions.TimeoutError as e:
raise ConnectionError(f"Opponent took longer than {args.wait_time}s to make a move. "
f"Qutting...")
# Check and handle win/loss
if game_state['winner'] != 'none':
games_played += 1
if game_state['winner'] == game_state['you']:
print("Agent won")
if game_state['you'] == 'white':
white_wins += 1
else:
black_wins += 1
else:
print("Agent lost")
logging.info(f'{datetime.now().strftime("[%d/%m/%y %H:%M:%S]")}\n'
f'agent_call=\"{args.agent_call}\" '
f'room_name=\"{args.room_name}\"\n'
f'Games played: {games_played}, '
f'wins: {white_wins + black_wins}, '
f'white wins: {white_wins}, '
f'black_wins: {black_wins}')
break
# Check if its your turn
if game_state['current_player'] != game_state['you']:
continue
print("Received move:", game_state['last_move'])
# Make a move
agent_process.stdout.flush()
agent_process.stdin.write(game_state['last_move'] + "\n")
agent_process.stdin.flush()
print('Sent move to agent, awaiting response...')
agent_move = agent_process.stdout.readline()[:-1]
print("Sending agents move:", agent_move)
await websocket.send(json.dumps({
'action': 'move',
'data': agent_move
}))
except:
print("Unexpected error:", sys.exc_info()[0])
traceback.print_exc()
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Run your Konane Agent online at coryefird.com/konane\n"
"If your agent worked with drivercheck.pl, then it should work with this script as well.\n"
"Win/loss results of your agent will be output to a log file \"agent_online.log\"\n"
"Some examples of how you can run this script with your agent:\n"
" An agent that runs through a bash script:\n"
" python run_agent_online.py \"./monte_carlo_agent.sh {} {}\" \"Fight Monte Carlo!\"\n"
" A python agent that accepts additional parameters:\n"
" python run_agent_online.py \"python run_agent.py {} {} alpha_beta --max_time 2\" \"my_room\"\n"
" An agent that joins another agents room and plays 10 games\n"
" python run_agent_online.py \"python run_agent.py {} {} "
"random_player \"[G5][Beginner][0] Random\" --join_room --num_games 10\n",
formatter_class=RawTextHelpFormatter
)
parser.add_argument("agent_call", type=str,
help="String to run your agent, as you would type it on the command line. "
"You must leave two sets of curly braces for arguments to be substituded: '{} {}'. "
"The first curly brace will become the game state file name, and the second becomes a "
"character to specify which player the agent is, i.e. 'B' or 'W'."
)
parser.add_argument("room_name", type=str, help="Room name to use on the server")
parser.set_defaults(join_room=False)
parser.add_argument('--join_room', dest='join_room', action='store_true',
help="Join an existing room instead of creating one."
)
parser.add_argument('--num_games', type=int, default=None,
help="Terminate the agent after completing a number of games. Will play indefinitely otherwise."
)
parser.add_argument('--wait_time', type=int, default=300,
help="How many seconds to wait for the opponent to make a move before exiting."
)
parser.add_argument("--websocket", type=str, default="ws://3.141.93.154:5555",
help='Websocket address for the server. The default should connect to coryefird.com/konane.'
)
args = parser.parse_args()
asyncio.get_event_loop().run_until_complete(create_room_and_play_online(args))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment