Uberserv now does everything; simplify heartbeat

This commit is contained in:
Neale Pickett 2009-09-01 11:23:40 -06:00
parent f3f5e797b3
commit d638a3be0c
7 changed files with 68 additions and 93 deletions

93
game.py
View File

@ -9,43 +9,9 @@ import time
from errno import EPIPE from errno import EPIPE
# The current time of day
now = time.time()
# Heartbeat frequency (in seconds) # Heartbeat frequency (in seconds)
pulse = 2.0 pulse = 2.0
##
## Heartbeat stuff
##
hearts = set()
last_beat = 0
def add_heart(cb):
global hearts
hearts.add(cb)
def del_heart(cb):
global hearts
hearts.remove(cb)
def beat_heart():
global hearts, last_beat, now
if now - last_beat > pulse:
last_beat = now
for cb in hearts:
try:
cb()
except:
traceback.print_exc()
## ##
## Network stuff ## Network stuff
## ##
@ -59,6 +25,7 @@ class Listener(asyncore.dispatcher):
self.listen(4) self.listen(4)
self.player_factory = player_factory self.player_factory = player_factory
self.manager = manager self.manager = manager
self.last_beat = 0
def handle_accept(self): def handle_accept(self):
conn, addr = self.accept() conn, addr = self.accept()
@ -66,6 +33,12 @@ class Listener(asyncore.dispatcher):
# We don't need to keep the player, asyncore.socket_map already # We don't need to keep the player, asyncore.socket_map already
# has a reference to it for as long as it's open. # has a reference to it for as long as it's open.
def readable(self):
now = time.time()
if now > self.last_beat + pulse:
self.manager.heartbeat(now)
return True
class Flagger(asynchat.async_chat): class Flagger(asynchat.async_chat):
"""Connection to flagd""" """Connection to flagd"""
@ -115,11 +88,12 @@ class Manager:
self.games = set() self.games = set()
self.lobby = set() self.lobby = set()
self.contestants = [] self.contestants = []
add_heart(self.heartbeat) self.last_beat = 0
def heartbeat(self): def heartbeat(self, now):
# Called by listener to beat heart
for game in list(self.games): for game in list(self.games):
game.heartbeat() game.heartbeat(now)
def enter_lobby(self, player): def enter_lobby(self, player):
self.lobby.add(player) self.lobby.add(player)
@ -143,6 +117,7 @@ class Manager:
"""Start a new contest.""" """Start a new contest."""
self.contestants = list(self.lobby) self.contestants = list(self.lobby)
print('new playoff:', [c.name for c in self.contestants])
def run_contest(self): def run_contest(self):
# Purge any disconnected players # Purge any disconnected players
@ -175,7 +150,7 @@ class Manager:
player.attach_game(game) player.attach_game(game)
def declare_winner(self, game, winner=None): def declare_winner(self, game, winner=None):
print('winner', game, winner) print('Winner:', winner and winner.name)
self.games.remove(game) self.games.remove(game)
# Winner stays in the contest # Winner stays in the contest
@ -199,8 +174,6 @@ class Player(asynchat.async_chat):
timeout = 10.0 timeout = 10.0
def __init__(self, sock, manager): def __init__(self, sock, manager):
global now
asynchat.async_chat.__init__(self, sock=sock) asynchat.async_chat.__init__(self, sock=sock)
self.manager = manager self.manager = manager
self.game = None self.game = None
@ -209,14 +182,12 @@ class Player(asynchat.async_chat):
self.blocked = None self.blocked = None
self.name = None self.name = None
self.pending = None self.pending = None
self.last_activity = now self.last_activity = time.time()
def readable(self): def readable(self):
global now, timeout
ret = (not self.blocked) and asynchat.async_chat.readable(self) ret = (not self.blocked) and asynchat.async_chat.readable(self)
if ret: if ret:
if now - self.last_activity > self.timeout: if time.time() - self.last_activity > self.timeout:
# They waited too long. # They waited too long.
self.err('idle timeout') self.err('idle timeout')
self.close() self.close()
@ -229,10 +200,8 @@ class Player(asynchat.async_chat):
def unblock(self): def unblock(self):
"""Unblock reads""" """Unblock reads"""
global now
self.blocked = False self.blocked = False
self.last_activity = now self.last_activity = time.time()
def attach_game(self, game): def attach_game(self, game):
self.game = game self.game = game
@ -330,7 +299,7 @@ class Game:
def setup(self): def setup(self):
pass pass
def heartbeat(self): def heartbeat(self, now):
pass pass
def declare_winner(self, winner): def declare_winner(self, winner):
@ -382,8 +351,7 @@ class TurnBasedGame(Game):
game_timeout = 6.0 game_timeout = 6.0
def __init__(self, manager, players): def __init__(self, manager, players):
global now now = time.time()
self.ended_turn = set() self.ended_turn = set()
self.running = True self.running = True
self.winner = None self.winner = None
@ -391,10 +359,8 @@ class TurnBasedGame(Game):
self.began = now self.began = now
Game.__init__(self, manager, players) Game.__init__(self, manager, players)
def heartbeat(self): def heartbeat(self, now=None):
global now if now and (now - self.began > self.game_timeout):
if now - self.began > self.game_timeout:
self.running = False self.running = False
# Idle players forfeit. They're also booted, so we don't have # Idle players forfeit. They're also booted, so we don't have
@ -443,7 +409,7 @@ class TurnBasedGame(Game):
def end_turn(self, player): def end_turn(self, player):
"""End player's turn.""" """End player's turn."""
global now now = time.time()
self.ended_turn.add(player) self.ended_turn.add(player)
self.lastmoved[player] = now self.lastmoved[player] = now
@ -483,18 +449,13 @@ class TurnBasedGame(Game):
## Running a game ## Running a game
## ##
def loop(): def start(game_factory, port, auth, minplayers, maxplayers=None):
global pulse, now
while asyncore.socket_map:
now = time.time()
beat_heart()
asyncore.poll2(timeout=pulse, map=asyncore.socket_map)
def run(game_factory, port, auth, minplayers, maxplayers=None):
flagger = Flagger(('localhost', 6668), auth) flagger = Flagger(('localhost', 6668), auth)
manager = Manager(game_factory, flagger, minplayers, maxplayers) manager = Manager(game_factory, flagger, minplayers, maxplayers)
listener = Listener(('', port), Player, manager) listener = Listener(('', port), Player, manager)
loop() return (flagger, manager, listener)
def run(game_factory, port, auth, minplayers, maxplayers=None):
start(game_factory, port, auth, minplayers, maxplayers)
asyncore.loop(use_poll=True)

View File

@ -46,19 +46,22 @@ def main(s=None):
write_scores(when) write_scores(when)
scoresfile.flush() scoresfile.flush()
gp = os.popen('gnuplot 2> /dev/null', 'w') instructions = tempfile.NamedTemporaryFile('w')
gp.write('set style data lines\n') instructions.write('''
gp.write('set xdata time\n') set style data lines
gp.write('set timefmt "%s"\n') set xdata time
gp.write('set format ""\n') set timefmt "%%s"
gp.write('set border 3\n') set format ""
gp.write('set xtics nomirror\n') set border 3
gp.write('set ytics nomirror\n') set xtics nomirror
gp.write('set nokey\n') set ytics nomirror
gp.write('set terminal png transparent size 640,200 x000000 xffffff\n') set nokey
gp.write('set output "histogram.png"\n') set terminal png transparent size 640,200 x000000 xffffff
gp.write('plot %s\n' % ','.join(plotparts)) set output "histogram.png"
gp.close() plot %(plot)s\n''' % {'plot': ','.join(plotparts)})
instructions.flush()
gp = os.system('gnuplot %s' % instructions.name)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -17,11 +17,11 @@ def submit(sock, cat, team, score):
break break
b = sock.recv(500) b = sock.recv(500)
try: try:
when, txt = points.decode_response(b) when, cat_, txt = points.decode_response(b)
except ValueError: except ValueError:
# Ignore invalid packets # Ignore invalid packets
continue continue
if when != mark: if (when != mark) or (cat_ != cat):
# Ignore wrong timestamp # Ignore wrong timestamp
continue continue
if txt == 'OK': if txt == 'OK':

View File

@ -35,9 +35,13 @@ class Roshambo(game.TurnBasedGame):
self.make_move(player, 'paper') self.make_move(player, 'paper')
def main(): def start():
game.run(Roshambo, 5388, b'roshambo:::984233f357ecac03b3e38b9414cd262b', 2) return game.start(Roshambo, 5388, b'roshambo:::984233f357ecac03b3e38b9414cd262b', 2)
if __name__ == '__main__': if __name__ == '__main__':
main() import asyncore
start()
asyncore.loop(use_poll=True)

View File

@ -27,7 +27,7 @@ class Client:
if not line: if not line:
return return
if self.debug: if self.debug:
print ('<-- %s' % line) print (self, '<-- %s' % line)
return json.loads(line) return json.loads(line)
def command(self, *val): def command(self, *val):
@ -41,29 +41,33 @@ class Client:
return ret return ret
class RandomBot(threading.Thread): class IdiotBot(threading.Thread):
def __init__(self, team): def __init__(self, team, move):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.team = team self.team = team
self.move = move
def get_move(self):
return self.move
def run(self): def run(self):
c = Client(('localhost', 5388)) c = Client(('localhost', 5388))
c.debug = True c.debug = False
#print('lobby', c.command('^', 'lobby')) #print('lobby', c.command('^', 'lobby'))
c.command('login', self.team, 'furble') c.command('login', self.team, 'furble')
while True: while True:
move = random.choice(['rock', 'scissors', 'paper']) move = self.get_move()
ret = c.command(move) ret = c.command(move)
if ret == ['WIN']: if ret == ['WIN']:
print('%s wins' % self.team) print('%s wins' % self.team)
amt = random.uniform(0.2, 2.1) amt = random.uniform(0.1, 1.2)
if c.debug: if c.debug:
print(c, 'sleep %f' % amt) print(c, 'sleep %f' % amt)
time.sleep(amt) time.sleep(amt)
def main(): def main():
bots = [] bots = []
for i in ['zebra', 'aardvark', 'wembly']: for team, move in (('rockbot', 'rock'), ('cutbot', 'scissors'), ('paperbot', 'paper')):
bots.append(RandomBot(i).start()) bots.append(IdiotBot(team, move).start())
main() main()

View File

@ -35,7 +35,7 @@ for cat, total in categories:
for points, team in scores: for points, team in scores:
color = teamcolors[team] color = teamcolors[team]
print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(points * 100)/total, color)) print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(points * 100)/total, color))
print('<!-- %s --> %s: %d' % (cat, team, points)) print('<!-- category: %s --> %s: %d' % (cat, team, points))
print('</div>') print('</div>')
print('</td>') print('</td>')
print('</tr>') print('</tr>')

View File

@ -2,12 +2,15 @@
import asyncore import asyncore
import pointsd import pointsd
import roshambo
import game
import flagd import flagd
import histogram import histogram
def main(): def main():
pointsrv = pointsd.start() pointsrv = pointsd.start()
flagsrv = flagd.start() flagsrv = flagd.start()
roshambosrv = roshambo.start()
s = pointsrv.store s = pointsrv.store
slen = 0 slen = 0
while True: while True: