Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pelita/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .exceptions import NoFoodWarning, PelitaBotError, PelitaIllegalGameState
from .gamestate_filters import noiser, relocate_expired_food, update_food_age, in_homezone
from .layout import get_legal_positions, initial_positions
from .network import Controller, RemotePlayerFailure, RemotePlayerRecvTimeout, RemotePlayerSendError, ZMQPublisher
from .network import Controller, RemotePlayerFailure, RemotePlayerRecvTimeout, RemotePlayerSendError, ZMQPublisher, POSTPublisher
from .team import RemoteTeam, make_team
from .viewer import (AsciiViewer, ProgressViewer, ReplayWriter, ReplyToViewer,
ResultPrinter)
Expand Down Expand Up @@ -274,6 +274,9 @@ def setup_viewers(viewers, print_result=True):
zmq_context = zmq.Context()
zmq_external_publisher = ZMQPublisher(address=viewer_opts, bind=False, zmq_context=zmq_context)
viewer_state['viewers'].append(zmq_external_publisher)
elif viewer == 'http-post-to':
post_publisher = POSTPublisher(address=viewer_opts)
viewer_state['viewers'].append(post_publisher)
elif viewer == 'tk':
zmq_context = zmq.Context()
zmq_publisher = ZMQPublisher(address='tcp://127.0.0.1', zmq_context=zmq_context)
Expand Down Expand Up @@ -419,6 +422,8 @@ def setup_game(team_specs, *, layout_dict, max_rounds=300, rng=None,
#: Name of the teams. Tuple of str
team_names=team_names,

team_specs=team_specs,

#: Additional team info. Tuple of str|None
team_infos=team_infos,

Expand Down
25 changes: 25 additions & 0 deletions pelita/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,31 @@ def recv_timeout(self, expected_id, timeout):
def __repr__(self):
return "RemotePlayerConnection(%r)" % self.socket


class POSTPublisher:
""" A viewer which dumps to a given stream.
"""
def __init__(self, address):
import httpx
self.url = address
self.http_session = httpx.Client()

def _send(self, action, data):
# import requests

info = {'round': data['round'], 'turn': data['turn']}
# TODO: this should be game_phase
if data['gameover']:
info['gameover'] = True
_logger.debug(f"--#> [{action}] %r", info)
message = {"__action__": action, "__data__": data}
as_json = json.dumps(message, cls=SetEncoder)
self.http_session.post(self.url, content=as_json)

def show_state(self, game_state):
self._send(action="observe", data=game_state)


class ZMQPublisher:
""" Sets up a simple Publisher which sends all viewed events
over a zmq connection.
Expand Down
4 changes: 4 additions & 0 deletions pelita/scripts/pelita_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ def long_help(s):
help=long_help('Communicate the result of the game on this channel.'))
advanced_settings.add_argument('--publish', type=str, metavar='URL', dest='publish_to',
help=long_help('Publish the game to this zmq socket.'))
advanced_settings.add_argument('--http-post', type=str, metavar='URL', dest='http_post_to',
help=long_help('POST the game to this http socket.'))
advanced_settings.add_argument('--controller', type=str, metavar='URL', default="tcp://127.0.0.1",
help=long_help('Channel for controlling the game.'))

Expand Down Expand Up @@ -373,6 +375,8 @@ def main():
viewers.append(('reply-to', args.reply_to))
if args.publish_to:
viewers.append(('publish-to', args.publish_to))
if args.http_post_to:
viewers.append(('http-post-to', args.http_post_to))
if args.write_replay:
viewers.append(('write-replay-to', args.write_replay))

Expand Down
7 changes: 7 additions & 0 deletions pelita/scripts/pelita_tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ def setup():
del config["bonusmatch"]
break

res = input_choice("Should the web-viewer be activated? (publishes to http://localhost:3000/api/collect) (y/n)", [], "yn")
if res == "y":
config['publish'] = "http://localhost:3000/api/collect"
elif res == "n":
config['publish'] = None

print("Specify the folder where we should look for teams (or none)")
folder = input().strip()
if folder:
Expand Down Expand Up @@ -294,6 +300,7 @@ def escape(s):
winner = tournament.play_round2(config, rr_ranking, state, rng)

config.print('The winner of the %s Pelita tournament is...' % config.location, wait=2, end=" ")
config.print()
config.print('{team_group}: {team_name}. Congratulations'.format(
team_group=config.team_group(winner),
team_name=config.team_name(winner)), wait=2)
Expand Down
70 changes: 65 additions & 5 deletions pelita/tournament/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import time
from dataclasses import dataclass

import httpx
import yaml
import zmq

Expand Down Expand Up @@ -98,7 +99,8 @@ def run_and_terminate_process(args, **kwargs):
p.kill()


def call_pelita(team_specs, *, rounds, size, viewer, seed, team_infos=None, write_replay=False, store_output=False):
def call_pelita(team_specs, *, rounds, size, viewer, seed, publish=None,
team_infos=None, write_replay=False, store_output=False):
""" Starts a new process with the given command line arguments and waits until finished.

Returns
Expand Down Expand Up @@ -134,6 +136,7 @@ def call_pelita(team_specs, *, rounds, size, viewer, seed, team_infos=None, writ
size = ['--size', size] if size else []
viewer = ['--' + viewer] if viewer else []
seed = ['--seed', seed] if seed else []
publish = ['--http-post', publish] if publish else []
write_replay = ['--write-replay', write_replay] if write_replay else []
store_output = ['--store-output', store_output] if store_output else []
append_blue = ['--append-blue', team_infos[0]] if team_infos[0] else []
Expand All @@ -142,6 +145,8 @@ def call_pelita(team_specs, *, rounds, size, viewer, seed, team_infos=None, writ
cmd = [sys.executable, '-m', 'pelita.scripts.pelita_main',
team1, team2,
'--reply-to', reply_addr,
'--stop-at', '0',
*publish,
*append_blue,
*append_red,
*rounds,
Expand Down Expand Up @@ -260,7 +265,8 @@ def __init__(self, config):
self.teams[team_id] = {
"spec": team_spec,
"name": team_name,
"members": team["members"]
"members": team["members"],
"color": team.get("color"),
}

self.location = config["location"]
Expand All @@ -270,6 +276,7 @@ def __init__(self, config):
self.size = config.get("size")

self.viewer = config.get("viewer")
self.publish = config.get("publish")
self.interactive = config.get("interactive")
self.statefile = config.get("statefile")

Expand All @@ -290,6 +297,11 @@ def __init__(self, config):
self.tournament_log_folder = None
self.tournament_log_file = None

if self.publish:
self.http_session = httpx.Client()
else:
self.http_session = None

@property
def team_ids(self):
return self.teams.keys()
Expand All @@ -306,6 +318,16 @@ def team_name_group(self, team):
def team_spec(self, team):
return self.teams[team]["spec"]

def send_remote(self, action, data=None):
if not self.http_session:
return

if data is None:
publish_string = {"__action__": action}
else:
publish_string = {"__action__": action, "__data__": data}
self.http_session.post(self.publish, content=json.dumps(publish_string))

def _print(self, *args, **kwargs):
print(*args, **kwargs)
if self.tournament_log_file:
Expand All @@ -317,12 +339,15 @@ def print(self, *args, **kwargs):
"""Speak while you print. To disable set speak=False.
You need the program %s to be able to speak.
Set wait=X to wait X seconds after speaking."""

if len(args) == 0:
self.send_remote("SPEAK", " ".join(args))
self._print()
return
stream = io.StringIO()
wait = kwargs.pop('wait', 0.5)
want_speak = kwargs.pop('speak', None)
self.send_remote("SPEAK", " ".join(args))
if (want_speak is False) or not self.speak:
self._print(*args, **kwargs)
else:
Expand Down Expand Up @@ -382,6 +407,28 @@ def input(self, str, values=None):
except IndexError:
pass

def metadata(self):
return {
'teams': self.teams,
'location': self.location,
'date': self.date,
'rounds': self.rounds,
'size': self.size,
'greeting': self.greeting,
'farewell': self.farewell,
'host': self.host,
'seed': self.seed,
'bonusmatch': self.bonusmatch
}

def init_tournament(self):
metadata = self.metadata()
print("Sending tournament metadata to the server:")
print(metadata)
self.send_remote("INIT", metadata)

def clear_page(self):
self.send_remote("CLEAR")

def wait_for_keypress(self):
if self.interactive:
Expand Down Expand Up @@ -424,7 +471,9 @@ def load(cls, config, filename):


def present_teams(config):
config.init_tournament()
config.wait_for_keypress()
config.clear_page()
print("\33[H\33[2J") # clear the screen

greeting = config.greeting
Expand Down Expand Up @@ -453,8 +502,11 @@ def set_name(team):
print(sys.stderr)
raise


def play_game_with_config(config, teams, rng, *, match_id=None):
# TODO: Log tournament match cmdline
def play_game_with_config(config: Config, teams, rng, *, match_id=None):
config.clear_page()
metadata = config.metadata()
config.send_remote("INIT", metadata)
team1, team2 = teams

if config.tournament_log_folder:
Expand All @@ -479,6 +531,7 @@ def play_game_with_config(config, teams, rng, *, match_id=None):
rounds=config.rounds,
size=config.size,
viewer=config.viewer,
publish=config.publish,
team_infos=team_infos,
seed=seed,
**log_kwargs)
Expand Down Expand Up @@ -508,6 +561,7 @@ def start_match(config, teams, rng, *, shuffle=False, match_id=None):
config.print('Starting match: '+ config.team_name_group(team1)+' vs ' + config.team_name_group(team2))
config.print()
config.wait_for_keypress()
config.clear_page()

(final_state, stdout, stderr) = play_game_with_config(config, teams, rng=rng, match_id=match_id)
try:
Expand Down Expand Up @@ -626,6 +680,7 @@ def play_round1(config, state, rng):
rr_played = state.round1["played"]

config.wait_for_keypress()
config.clear_page()
config.print()
config.print("ROUND 1 (Everybody vs Everybody)")
config.print('================================', speak=False)
Expand Down Expand Up @@ -655,6 +710,7 @@ def play_round1(config, state, rng):
winner = start_match_with_replay(config, match, rng=rng, match_id=match_id)
match_id.next_match()
config.wait_for_keypress()
config.clear_page()

if winner is False or winner is None:
rr_played.append({ "match": match, "winner": False })
Expand Down Expand Up @@ -697,9 +753,11 @@ def recur_match_winner(match):
def play_round2(config, teams, state, rng):
"""Run the second round and return the name of the winning team.

teams is the list [group0, group1, ...] not the names of the agens, sorted
teams is the list [group0, group1, ...] not the names of the agents, sorted
by the result of the first round.
"""
config.wait_for_keypress()
config.clear_page()
config.print()
config.print('ROUND 2 (K.O.)')
config.print('==============', speak=False)
Expand Down Expand Up @@ -729,6 +787,7 @@ def play_round2(config, teams, state, rng):
winner = start_deathmatch(config, t1_id, t2_id, rng=rng, match_id=match_id)
match.winner = winner

config.clear_page()
config.print(knockout_mode.print_knockout(last_match, config.team_name, highlight=[match]), speak=False)

state.round2["tournament"] = tournament
Expand All @@ -741,5 +800,6 @@ def play_round2(config, teams, state, rng):
match_id.next_match()

config.wait_for_keypress()
config.clear_page()

return last_match.winner
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies = [
"zeroconf",
"rich",
"click",
"httpx"
]
dynamic = ["version"]

Expand Down
3 changes: 3 additions & 0 deletions test/test_tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ def test_play_game_with_config(self):
config.viewer = 'ascii'
config.size = 'small'
config.tournament_log_folder = None
config.publish = None

teams = ["pelita/player/StoppingPlayer", "pelita/player/StoppingPlayer"]
(state, stdout, stderr) = tournament.play_game_with_config(config, teams, rng=RNG)
Expand Down Expand Up @@ -338,6 +339,7 @@ def mock_print(str="", *args, **kwargs):
config.size = 'small'
config.print = mock_print
config.tournament_log_folder = None
config.publish = None

team_ids = ["first_id", "first_id"]
result = tournament.start_match(config, team_ids, rng=RNG)
Expand Down Expand Up @@ -376,6 +378,7 @@ def mock_print(str="", *args, **kwargs):
config.size = 'small'
config.print = mock_print
config.tournament_log_folder = None
config.publish = None

result = tournament.start_deathmatch(config, *teams.keys(), rng=RNG)
assert result is not None
Expand Down
13 changes: 4 additions & 9 deletions tournament.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,17 @@ location: Munich
date: 2015
seed: null
bonusmatch: True
#publish: http://localhost:3000/api/collect
teams:
- spec: pelita/player/StoppingPlayer
members:
- "Stopper"
color: "#eee"
- spec: pelita/player/FoodEatingPlayer
members:
- "Food Eater"
color: "#e00"
- spec: pelita/player/RandomExplorerPlayer
members:
- "Random Explorer"
- spec: pelita/player/RandomPlayers
members:
- "Random Player"
- spec: pelita/player/SmartEatingPlayer
members:
- "Smart Eating Player"
- spec: pelita/player/SmartRandomPlayer
members:
- "Smarter Random Player"
color: "#e0e"
Loading