mirror of https://github.com/dirtbags/moth.git
Game framework and roshambo game
This commit is contained in:
parent
0790f9b453
commit
c05f440b37
|
@ -0,0 +1,232 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
##
|
||||||
|
## XXX: Add timeout for Player if not blocked
|
||||||
|
## XXX: What if someone disconnects?
|
||||||
|
##
|
||||||
|
|
||||||
|
import json
|
||||||
|
import asyncore
|
||||||
|
import asynchat
|
||||||
|
import socket
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
class Listener(asyncore.dispatcher):
|
||||||
|
def __init__(self, addr, player_factory, manager):
|
||||||
|
asyncore.dispatcher.__init__(self)
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.set_reuse_addr()
|
||||||
|
self.bind(addr)
|
||||||
|
self.listen(4)
|
||||||
|
self.player_factory = player_factory
|
||||||
|
self.manager = manager
|
||||||
|
|
||||||
|
def handle_accept(self):
|
||||||
|
conn, addr = self.accept()
|
||||||
|
player = self.player_factory(conn, self.manager)
|
||||||
|
# We don't need to keep the player, asyncore.socket_map already
|
||||||
|
# has a reference to it for as long as it's open.
|
||||||
|
|
||||||
|
|
||||||
|
class Flagger(asynchat.async_chat):
|
||||||
|
"""Connection to flagd"""
|
||||||
|
|
||||||
|
def __init__(self, addr, auth):
|
||||||
|
asynchat.async_chat.__init__(self)
|
||||||
|
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.connect(addr)
|
||||||
|
self.push(auth)
|
||||||
|
self.flag = None
|
||||||
|
|
||||||
|
def handle_read(self):
|
||||||
|
msg = self.recv(4096)
|
||||||
|
raise ValueError("Flagger died: %r" % msg)
|
||||||
|
|
||||||
|
def handle_error(self):
|
||||||
|
# If we lose the connection to flagd, nobody can score any
|
||||||
|
# points. Terminate everything.
|
||||||
|
asynchat.async_chat.handle_error(self)
|
||||||
|
asyncore.close_all()
|
||||||
|
|
||||||
|
def set_flag(self, team):
|
||||||
|
self.push(b'%s\n' % (team.encode('utf-8')))
|
||||||
|
self.flag = team
|
||||||
|
|
||||||
|
|
||||||
|
class Manager:
|
||||||
|
"""Contest manager.
|
||||||
|
|
||||||
|
When a player connects and registers, they enter the lobby. As soon
|
||||||
|
as there are enough players in the lobby to run a game, everyone in
|
||||||
|
the lobby becomes a contestant. Contestants are assigned to games.
|
||||||
|
When the game declares a winner, the winner is added back to the list
|
||||||
|
of contestants, and other players are sent back to the lobby. When
|
||||||
|
a winner is declared by the last running game, that winner gets the
|
||||||
|
flag.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, nplayers, game_factory, flagger):
|
||||||
|
self.nplayers = nplayers
|
||||||
|
self.game_factory = game_factory
|
||||||
|
self.flagger = flagger
|
||||||
|
self.games = {}
|
||||||
|
self.lobby = []
|
||||||
|
self.contestants = []
|
||||||
|
|
||||||
|
def enter_lobby(self, player):
|
||||||
|
if not player.connected:
|
||||||
|
return
|
||||||
|
self.lobby.append(player)
|
||||||
|
if (not self.contestants) and (len(self.lobby) >= self.nplayers):
|
||||||
|
# If there are no contestants, the current contest has ended
|
||||||
|
# and we're ready for a new one.
|
||||||
|
self.contestants = self.lobby[:]
|
||||||
|
self.run_contest()
|
||||||
|
|
||||||
|
def add_contestant(self, player):
|
||||||
|
self.contestants.append(player)
|
||||||
|
self.run_contest()
|
||||||
|
|
||||||
|
def run_contest(self):
|
||||||
|
while len(self.contestants) >= self.nplayers:
|
||||||
|
players = self.contestants[:self.nplayers]
|
||||||
|
del self.contestants[:self.nplayers]
|
||||||
|
game = self.game_factory(self, players)
|
||||||
|
self.games[game] = players
|
||||||
|
for player in players:
|
||||||
|
player.attach_game(game)
|
||||||
|
|
||||||
|
def declare_winner(self, game, winner):
|
||||||
|
players = self.games[game]
|
||||||
|
del self.games[game]
|
||||||
|
players.remove(winner)
|
||||||
|
for p in players:
|
||||||
|
# Losers go back to the lobby
|
||||||
|
p.lose()
|
||||||
|
self.enter_lobby(p)
|
||||||
|
if not self.games:
|
||||||
|
# All games have ended and winner is the last player
|
||||||
|
# standing. They get the flag.
|
||||||
|
print('%r has the flag.' % winner)
|
||||||
|
winner.win(True)
|
||||||
|
self.flagger.set_flag(winner.name)
|
||||||
|
self.enter_lobby(winner)
|
||||||
|
else:
|
||||||
|
# Winner stays in the contest
|
||||||
|
winner.win()
|
||||||
|
self.add_contestant(winner)
|
||||||
|
|
||||||
|
def player_cmd(self, args):
|
||||||
|
cmd = args[0].lower()
|
||||||
|
if cmd == 'lobby':
|
||||||
|
return [p.name for p in self.lobby]
|
||||||
|
elif cmd == 'games':
|
||||||
|
return [[p.name for p in ps] for ps in self.games.values()]
|
||||||
|
elif cmd == 'flag':
|
||||||
|
return self.flagger.flag
|
||||||
|
else:
|
||||||
|
raise ValueError('Unrecognized manager command')
|
||||||
|
|
||||||
|
|
||||||
|
class Player(asynchat.async_chat):
|
||||||
|
def __init__(self, sock, manager):
|
||||||
|
asynchat.async_chat.__init__(self, sock=sock)
|
||||||
|
self.manager = manager
|
||||||
|
self.game = None
|
||||||
|
self.set_terminator(b'\n')
|
||||||
|
self.inbuf = []
|
||||||
|
self.blocked = None
|
||||||
|
self.name = None
|
||||||
|
self.pending = None
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
return (not self.blocked) and asynchat.async_chat.readable(self)
|
||||||
|
|
||||||
|
def block(self):
|
||||||
|
"""Block reads"""
|
||||||
|
self.blocked = True
|
||||||
|
|
||||||
|
def unblock(self):
|
||||||
|
"""Unblock reads"""
|
||||||
|
self.blocked = False
|
||||||
|
|
||||||
|
def attach_game(self, game):
|
||||||
|
self.game = game
|
||||||
|
if self.pending:
|
||||||
|
self.unblock()
|
||||||
|
self.game.handle(self, *self.pending)
|
||||||
|
|
||||||
|
def _write_val(self, val):
|
||||||
|
s = json.dumps(val) + '\n'
|
||||||
|
self.push(s.encode('utf-8'))
|
||||||
|
|
||||||
|
def write(self, val):
|
||||||
|
self._write_val(['OK', val])
|
||||||
|
|
||||||
|
def err(self, msg):
|
||||||
|
self._write_val(['ERR', msg])
|
||||||
|
|
||||||
|
def win(self, flag=False):
|
||||||
|
val = ['WIN']
|
||||||
|
if flag:
|
||||||
|
val.append('You have the flag')
|
||||||
|
self._write_val(val)
|
||||||
|
self.unblock()
|
||||||
|
|
||||||
|
def lose(self):
|
||||||
|
self._write_val(['LOSE'])
|
||||||
|
self.unblock()
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
self.inbuf.append(data)
|
||||||
|
|
||||||
|
def found_terminator(self):
|
||||||
|
try:
|
||||||
|
data = b''.join(self.inbuf)
|
||||||
|
self.inbuf = []
|
||||||
|
val = json.loads(data.decode('utf-8'))
|
||||||
|
cmd, args = val[0].lower(), val[1:]
|
||||||
|
|
||||||
|
if cmd == 'login':
|
||||||
|
if not self.name:
|
||||||
|
# XXX Check password
|
||||||
|
self.name = args[0]
|
||||||
|
self.write('Welcome to the fray, %s.' % self.name)
|
||||||
|
self.manager.enter_lobby(self)
|
||||||
|
else:
|
||||||
|
self.err('Already logged in.')
|
||||||
|
elif cmd == '^':
|
||||||
|
# Send to manager
|
||||||
|
ret = self.manager.player_cmd(args)
|
||||||
|
self.write(ret)
|
||||||
|
elif not self.name:
|
||||||
|
self.err('Log in first.')
|
||||||
|
else:
|
||||||
|
# Send to game
|
||||||
|
if not self.game:
|
||||||
|
self.pending = (cmd, args)
|
||||||
|
self.block()
|
||||||
|
else:
|
||||||
|
self.game.handle(self, cmd, args)
|
||||||
|
except Exception as err:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.err(str(err))
|
||||||
|
|
||||||
|
|
||||||
|
class Game:
|
||||||
|
def __init__(self, manager, players):
|
||||||
|
self.manager = manager
|
||||||
|
self.players = players
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def declare_winner(self, player):
|
||||||
|
self.manager.declare_winner(self, player)
|
||||||
|
|
||||||
|
|
||||||
|
def run(nplayers, game_factory, port, auth):
|
||||||
|
flagger = Flagger(('localhost', 6668), auth)
|
||||||
|
manager = Manager(2, game_factory, flagger)
|
||||||
|
listener = Listener(('', port), Player, manager)
|
||||||
|
asyncore.loop()
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
##
|
||||||
|
## XXX: Move more of this into game
|
||||||
|
##
|
||||||
|
|
||||||
|
import game
|
||||||
|
|
||||||
|
class Roshambo(game.Game):
|
||||||
|
def setup(self):
|
||||||
|
print("Hello from setup")
|
||||||
|
self.other_move = None
|
||||||
|
|
||||||
|
def make_move(self, player, move):
|
||||||
|
print(self.other_move, player, move)
|
||||||
|
if self.other_move:
|
||||||
|
other_player, other_move = self.other_move
|
||||||
|
moves = (move, other_move)
|
||||||
|
if move in (('rock', 'scissors'),
|
||||||
|
('scissors', 'paper'),
|
||||||
|
('paper', 'rock')):
|
||||||
|
# Player wins
|
||||||
|
self.declare_winner(player)
|
||||||
|
else:
|
||||||
|
self.declare_winner(other_player)
|
||||||
|
other_player.unblock()
|
||||||
|
else:
|
||||||
|
self.other_move = (player, move)
|
||||||
|
player.block()
|
||||||
|
|
||||||
|
def handle(self, player, cmd, args):
|
||||||
|
if cmd in ('rock', 'scissors', 'paper'):
|
||||||
|
self.make_move(player, cmd)
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid command')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
game.run(2, Roshambo, 5388, b'roshambo:::984233f357ecac03b3e38b9414cd262b')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
rbufsize = -1
|
||||||
|
wbufsize = 0
|
||||||
|
|
||||||
|
def __init__(self, addr):
|
||||||
|
self.conn = socket.create_connection(addr)
|
||||||
|
self.wfile = self.conn.makefile('wb', self.wbufsize)
|
||||||
|
self.rfile = self.conn.makefile('rb', self.rbufsize)
|
||||||
|
|
||||||
|
def write(self, *val):
|
||||||
|
s = json.dumps(val)
|
||||||
|
print('--> %s' % s)
|
||||||
|
self.wfile.write(s.encode('utf-8') + b'\n')
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
line = self.rfile.readline().strip().decode('utf-8')
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
print ('<-- %s' % line)
|
||||||
|
return json.loads(line)
|
||||||
|
|
||||||
|
def command(self, *val):
|
||||||
|
self.write(*val)
|
||||||
|
ret = self.read()
|
||||||
|
if ret[0] == 'OK':
|
||||||
|
return ret[1]
|
||||||
|
elif ret[0] == 'ERR':
|
||||||
|
raise ValueError(ret[1])
|
||||||
|
else:
|
||||||
|
print(ret)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
c = Client(('localhost', 5388))
|
||||||
|
c.command('^', 'lobby')
|
||||||
|
c.command('login', 'zebra', 'furble')
|
||||||
|
c.command('rock')
|
||||||
|
|
||||||
|
main()
|
Loading…
Reference in New Issue