Deal with clients not making moves quickly enough

This commit is contained in:
Neale Pickett 2009-08-26 17:46:06 -06:00
parent de6a455ebc
commit e1a43cb9a7
2 changed files with 143 additions and 31 deletions

166
game.py
View File

@ -15,6 +15,44 @@ timeout = 30.0
# The current time of day # The current time of day
now = time.time() 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
##
class Listener(asyncore.dispatcher): class Listener(asyncore.dispatcher):
def __init__(self, addr, player_factory, manager): def __init__(self, addr, player_factory, manager):
asyncore.dispatcher.__init__(self) asyncore.dispatcher.__init__(self)
@ -77,6 +115,13 @@ class Manager:
self.games = {} self.games = {}
self.lobby = set() self.lobby = set()
self.contestants = [] self.contestants = []
add_heart(self.heartbeat)
def heartbeat(self):
games = list(self.games)
for game in games:
print('heartbeat', game)
game.heartbeat()
def enter_lobby(self, player): def enter_lobby(self, player):
self.lobby.add(player) self.lobby.add(player)
@ -86,13 +131,13 @@ class Manager:
self.contestants.append(player) self.contestants.append(player)
self.run_contest() self.run_contest()
def leave(self, player): def disconnect(self, player):
"""Player has left the tournament""" """Player has disconnected."""
pass pass
def set_flag(self, player): def set_flag(self, player):
"""Player has the flag""" """Player has the flag."""
self.flagger.set_flag(player.name) self.flagger.set_flag(player.name)
@ -143,18 +188,10 @@ class Manager:
player.attach_game(game) player.attach_game(game)
def declare_winner(self, game, winner): def declare_winner(self, game, winner):
print('winner', game)
players = self.games[game] players = self.games[game]
del self.games[game] del self.games[game]
# Game is over, detach all players
for p in players:
p.detach_game()
# Inform losers of their loss
losers = [p for p in players if p != winner]
for p in losers:
p.lose()
# Winner stays in the contest # Winner stays in the contest
winner.win() winner.win()
self.add_contestant(winner) self.add_contestant(winner)
@ -225,10 +262,12 @@ class Player(asynchat.async_chat):
self._write_val(['ERR', msg]) self._write_val(['ERR', msg])
def win(self): def win(self):
self.detach_game()
self._write_val(['WIN']) self._write_val(['WIN'])
self.unblock() self.unblock()
def lose(self): def lose(self):
self.detach_game()
self._write_val(['LOSE']) self._write_val(['LOSE'])
self.unblock() self.unblock()
@ -273,9 +312,8 @@ class Player(asynchat.async_chat):
def close(self): def close(self):
if self.game: if self.game:
self.game.forfeit(self) self.game.disconnect(self)
self.manager.leave(self) self.manager.disconnect(self)
asynchat.async_chat.close(self) asynchat.async_chat.close(self)
def send(self, data): def send(self, data):
@ -294,14 +332,21 @@ class Game:
self.manager = manager self.manager = manager
self.players = players self.players = players
self.setup() self.setup()
if not hasattr(self, 'forfeit'):
if len(self.players) == 2:
self.forfeit = self.forfeit_2p
else:
raise NotImplementedError('forfeit method undefined')
def declare_winner(self, player): def heartbeat(self):
self.manager.declare_winner(self, player) pass
def declare_winner(self, winner):
self.manager.declare_winner(self, winner)
# Congratulate winner
winner.win()
# Inform losers of their loss
losers = [p for p in players if p != winner]
for p in losers:
p.lose()
def handle(self, player, cmd, args): def handle(self, player, cmd, args):
"""Handle a command from player. """Handle a command from player.
@ -317,7 +362,7 @@ class Game:
except AttributeError: except AttributeError:
raise ValueError('Invalid command: %s' % cmd) raise ValueError('Invalid command: %s' % cmd)
def forfeit_2p(self, player): def forfeit(self, player):
"""Player forfeits the game, in a 2-player game. """Player forfeits the game, in a 2-player game.
If your game has more than 2 players, you need to define If your game has more than 2 players, you need to define
@ -325,24 +370,83 @@ class Game:
""" """
if len(self.players) == 2:
if player == self.players[0]: if player == self.players[0]:
self.declare_winner(self.players[1]) self.declare_winner(self.players[1])
else: else:
self.declare_winner(self.players[0]) self.declare_winner(self.players[0])
else:
raise NotImplementedError('forfeit method undefined')
def disconnect(self, player):
"""Disconnect the player."""
self.forfeit(player)
class TurnBasedGame(Game): class TurnBasedGame(Game):
# How long you get to make a move (in seconds)
move_timeout = 2.0
def __init__(self, manager, players): def __init__(self, manager, players):
global now
self.ended_turn = set() self.ended_turn = set()
self.winner = None
self.lastmoved = dict([(p, now) for p in players])
Game.__init__(self, manager, players) Game.__init__(self, manager, players)
def heartbeat(self):
global now
for p, when in self.lastmoved.items():
if now - when > self.move_timeout:
self.disconnect(p)
if self.winner:
break
def disconnect(self, player):
Game.disconnect(self, player)
self.end_turn(player)
def declare_winner(self, winner):
"""Declare winner.
In a turn-based game, you can't tell anyone that the game has
ended until they make a move. Otherwise, you ruin the illusion
of the game being synchronous. This only sets the winner variable,
which is checked in self.end_turn().
"""
self.manager.declare_winner(self, winner)
self.winner = winner
def calculate_moves(self): def calculate_moves(self):
"""Override this to define what to do when the turn is over""" """Calculate all moves at the end of a turn.
Override this to define what to do when every player has ended
their turn.
"""
pass pass
def end_turn(self, player): def end_turn(self, player):
"""End player's turn""" """End player's turn."""
global now
# The player has ended their turn; it's okay to tell them now
# that the game has ended.
if self.winner:
if self.winner == player:
player.win()
else:
player.lose()
return
self.ended_turn.add(player) self.ended_turn.add(player)
self.lastmoved[player] = now
player.block() player.block()
if len(self.ended_turn) == len(self.players): if len(self.ended_turn) == len(self.players):
for p in self.players: for p in self.players:
@ -351,12 +455,20 @@ class TurnBasedGame(Game):
self.ended_turn = set() self.ended_turn = set()
##
## Running a game
##
def loop(): def loop():
global timeout, now global timeout, pulse, now
my_timeout = min(timeout, pulse)
while True: while True:
now = time.time() now = time.time()
asyncore.poll2(timeout=timeout) beat_heart()
asyncore.poll2(timeout=my_timeout)
def run(nplayers, game_factory, port, auth): def run(nplayers, game_factory, port, auth):

View File

@ -55,7 +55,7 @@ class RandomBot(threading.Thread):
ret = c.command(move) ret = c.command(move)
if ret == ['WIN']: if ret == ['WIN']:
print('%s wins' % self.team) print('%s wins' % self.team)
time.sleep(random.uniform(0.2, 2)) time.sleep(random.uniform(0.2, 3))
def main(): def main():
bots = [] bots = []