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
# The current time of day
now = time.time()
# Heartbeat frequency (in seconds)
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
##
@ -59,6 +25,7 @@ class Listener(asyncore.dispatcher):
self.listen(4)
self.player_factory = player_factory
self.manager = manager
self.last_beat = 0
def handle_accept(self):
conn, addr = self.accept()
@ -66,6 +33,12 @@ class Listener(asyncore.dispatcher):
# We don't need to keep the player, asyncore.socket_map already
# 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):
"""Connection to flagd"""
@ -115,11 +88,12 @@ class Manager:
self.games = set()
self.lobby = set()
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):
game.heartbeat()
game.heartbeat(now)
def enter_lobby(self, player):
self.lobby.add(player)
@ -143,6 +117,7 @@ class Manager:
"""Start a new contest."""
self.contestants = list(self.lobby)
print('new playoff:', [c.name for c in self.contestants])
def run_contest(self):
# Purge any disconnected players
@ -175,7 +150,7 @@ class Manager:
player.attach_game(game)
def declare_winner(self, game, winner=None):
print('winner', game, winner)
print('Winner:', winner and winner.name)
self.games.remove(game)
# Winner stays in the contest
@ -199,8 +174,6 @@ class Player(asynchat.async_chat):
timeout = 10.0
def __init__(self, sock, manager):
global now
asynchat.async_chat.__init__(self, sock=sock)
self.manager = manager
self.game = None
@ -209,14 +182,12 @@ class Player(asynchat.async_chat):
self.blocked = None
self.name = None
self.pending = None
self.last_activity = now
self.last_activity = time.time()
def readable(self):
global now, timeout
ret = (not self.blocked) and asynchat.async_chat.readable(self)
if ret:
if now - self.last_activity > self.timeout:
if time.time() - self.last_activity > self.timeout:
# They waited too long.
self.err('idle timeout')
self.close()
@ -229,10 +200,8 @@ class Player(asynchat.async_chat):
def unblock(self):
"""Unblock reads"""
global now
self.blocked = False
self.last_activity = now
self.last_activity = time.time()
def attach_game(self, game):
self.game = game
@ -330,7 +299,7 @@ class Game:
def setup(self):
pass
def heartbeat(self):
def heartbeat(self, now):
pass
def declare_winner(self, winner):
@ -382,8 +351,7 @@ class TurnBasedGame(Game):
game_timeout = 6.0
def __init__(self, manager, players):
global now
now = time.time()
self.ended_turn = set()
self.running = True
self.winner = None
@ -391,10 +359,8 @@ class TurnBasedGame(Game):
self.began = now
Game.__init__(self, manager, players)
def heartbeat(self):
global now
if now - self.began > self.game_timeout:
def heartbeat(self, now=None):
if now and (now - self.began > self.game_timeout):
self.running = False
# Idle players forfeit. They're also booted, so we don't have
@ -443,7 +409,7 @@ class TurnBasedGame(Game):
def end_turn(self, player):
"""End player's turn."""
global now
now = time.time()
self.ended_turn.add(player)
self.lastmoved[player] = now
@ -483,18 +449,13 @@ class TurnBasedGame(Game):
## Running a game
##
def loop():
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):
def start(game_factory, port, auth, minplayers, maxplayers=None):
flagger = Flagger(('localhost', 6668), auth)
manager = Manager(game_factory, flagger, minplayers, maxplayers)
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)
scoresfile.flush()
gp = os.popen('gnuplot 2> /dev/null', 'w')
gp.write('set style data lines\n')
gp.write('set xdata time\n')
gp.write('set timefmt "%s"\n')
gp.write('set format ""\n')
gp.write('set border 3\n')
gp.write('set xtics nomirror\n')
gp.write('set ytics nomirror\n')
gp.write('set nokey\n')
gp.write('set terminal png transparent size 640,200 x000000 xffffff\n')
gp.write('set output "histogram.png"\n')
gp.write('plot %s\n' % ','.join(plotparts))
gp.close()
instructions = tempfile.NamedTemporaryFile('w')
instructions.write('''
set style data lines
set xdata time
set timefmt "%%s"
set format ""
set border 3
set xtics nomirror
set ytics nomirror
set nokey
set terminal png transparent size 640,200 x000000 xffffff
set output "histogram.png"
plot %(plot)s\n''' % {'plot': ','.join(plotparts)})
instructions.flush()
gp = os.system('gnuplot %s' % instructions.name)
if __name__ == '__main__':
main()

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ for cat, total in categories:
for points, team in scores:
color = teamcolors[team]
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('</td>')
print('</tr>')

View File

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