Commit 28cdcaa5 authored by Taddeüs Kroes's avatar Taddeüs Kroes

Mostly implemented backend (still need a gui)

parent 3e5b6d5e
.PHONY: all clean .PHONY: all check clean
all: www/style.css www/game.js all: www/style.css www/game.js
...@@ -8,5 +8,9 @@ www/%.css: %.sass ...@@ -8,5 +8,9 @@ www/%.css: %.sass
www/%.js: %.coffee www/%.js: %.coffee
coffee -o $(@D) $< coffee -o $(@D) $<
check:
pyflakes *.py
pep8 *.py
clean: clean:
rm -f www/*.js www/*.css rm -f www/*.js www/*.css
...@@ -18,7 +18,7 @@ divin = (parent, cls) -> ...@@ -18,7 +18,7 @@ divin = (parent, cls) ->
parent.appendChild elem parent.appendChild elem
elem elem
class Game class Board
constructor: (@w, @h, elem) -> constructor: (@w, @h, elem) ->
@render elem if elem @render elem if elem
...@@ -75,24 +75,74 @@ class Game ...@@ -75,24 +75,74 @@ class Game
divin row, "wall-h#{clicked}" divin row, "wall-h#{clicked}"
divin row, 'dot' divin row, 'dot'
game = new Game 6, 6 destroy: ->
game.render document.getElementById 'game' @board.parentNode.removeChild @board
game.click_wall 2, 2, WALL_RIGHT
game.click_wall 2, 2, WALL_BOTTOM show_error = alert
game.click_wall 1, 2, WALL_RIGHT
game.click_wall 2, 2, WALL_TOP set_this_player = (id) ->
game.occupy 2, 2, 2
add_other_player = (id) ->
#ws = new WebSocket URL
# remove_other_player = (id) ->
#ws.onopen = ->
# console.log 'open' set_turn_player = (id) ->
#
#ws.onclose = -> finish = (scores) ->
# console.log 'close' console.log 'scores:', scores
#
#ws.onerror = (e) -> ws = new WebSocket URL
# console.log 'error', e
# ws.send_msg = (mtype, args...) ->
#ws.onmessage = (msg) -> @send [mtype].concat(args).join ';'
# console.log 'msg', msg
ws.onopen = ->
console.debug 'open'
if location.hash
@send_msg 'join', location.hash.substr 1
else
@send_msg 'newgame', 5, 6
ws.onclose = ->
console.debug 'close'
@board?.destroy()
ws.onerror = (e) ->
console.debug 'error', e
ws.onmessage = (msg) ->
[mtype, args...] = msg.data.split ';'
args = ((if s.match /^\d+$/ then parseInt s else s) for s in args)
console.debug 'msg:', mtype, args
switch mtype
when 'newgame'
[@sid, w, h, player] = args
@board = new Board w, h
@board.render document.getElementById 'board'
location.hash = @sid
set_this_player player
when 'join'
add_other_player args[0]
when 'leave'
remove_other_player args[0]
when 'clickwall'
[x, y, direction] = args
@board.click_wall x, y, direction
when 'occupy'
[x, y, player] = args
@board.occupy x, y, player
when 'turn'
set_turn_player args[0]
when 'finish'
finish (s.split(':').map parseInt for s in args)
when 'error'
error = args[0]
if error == 'no such session'
@send_msg 'newgame', 5, 6
else
show_error error
else
show_error 'received invalid message from server'
...@@ -6,7 +6,7 @@ WALL_LEFT = 8 ...@@ -6,7 +6,7 @@ WALL_LEFT = 8
WALL_ALL = WALL_TOP | WALL_RIGHT | WALL_BOTTOM | WALL_LEFT WALL_ALL = WALL_TOP | WALL_RIGHT | WALL_BOTTOM | WALL_LEFT
class Game: class Board:
def __init__(self, w, h): def __init__(self, w, h):
assert w > 1 and h > 1 assert w > 1 and h > 1
self.w = w self.w = w
......
#!/usr/bin/env python2 #!/usr/bin/env python2
import sys
import logging
import time import time
from hashlib import sha1 from hashlib import sha1
import wspy from wspy import AsyncServer, TextMessage
from game import Board
class BadRequest(RuntimeError):
pass
def check(condition, error='invalid type or args'):
if not condition:
raise BadRequest(error)
class Msg:
def __init__(self, mtype, *args):
self.mtype = mtype
self.args = args
@classmethod
def decode(cls, message):
check(isinstance(message, TextMessage))
parts = message.payload.split(';')
check(parts)
mtype = parts[0]
args = [int(p) if p.isdigit() else p for p in parts[1:]]
return cls(mtype, *args)
def encode(self):
return TextMessage(';'.join([self.mtype] + map(str, self.args)))
STATE_JOINING = 0
STATE_STARTED = 1
STATE_FINISHED = 2
class Session: class Session:
def __init__(self): def __init__(self, w, h, owner):
self.sid = sha1(str(time.time())) self.sid = sha1(str(time.time())).hexdigest()
self.clients = [] self.clients = [owner]
owner.player = 1
self.player_counter = 2
self.state = STATE_JOINING
self.board = Board(w, h)
self.turn = owner
owner.send(Msg('newgame', self.sid, w, h).encode())
owner.send(Msg('turn', self.turn.player).encode())
def __str__(self):
return '<Session %s state=%d size=%dx%d>' % \
(self.sid, self.state, self.board.w, self.board.h)
def click_wall(self, client, x, y, direction):
check(self.turn is client, 'not your turn')
check(self.state < STATE_FINISHED, 'already finished')
self.state = STATE_STARTED
occupied = self.board.click_wall(x, y, direction, client.player)
self.bcast('clickwall', x, y, direction)
if occupied:
for x, y in occupied:
self.bcast('occupy', x, y, client.player)
if self.board.is_finished():
scores = self.board.scores()
for player in xrange(1, self.player_counter):
scores.setdefault(player, 0)
scores = scores.items()
scores.sort(key=lambda (player, score): score, reverse=True)
self.bcast('finish', *['%d:%d' % s for s in scores])
logging.info('finishing session %s' % self.sid)
self.state = STATE_FINISHED
else:
index = (self.clients.index(self.turn) + 1) % len(self.clients)
self.turn = self.clients[index]
def bcast(self, mtype, *args):
encoded = Msg(mtype, *args).encode()
for client in self.clients:
client.send(encoded)
def join(self, client):
client.player = self.player_counter
self.player_counter += 1
self.bcast('join', client.player)
client.send(Msg('newgame', self.sid, self.board.w, self.board.h,
client.player).encode())
client.send(Msg('turn', self.turn.player).encode())
for other in self.clients:
client.send(Msg('join', other.player).encode())
self.clients.append(client)
def leave(self, client):
self.clients.remove(client)
self.bcast('leave', client.player)
def is_dead(self):
return not self.clients
class GameServer(AsyncServer):
def __init__(self, *args, **kwargs):
super(GameServer, self).__init__(*args, **kwargs)
self.sessions = {}
class GameServer(wspy.AsyncServer):
def onmessage(self, client, message): def onmessage(self, client, message):
pass try:
msg = Msg.decode(message)
if msg.mtype == 'newgame':
check(len(msg.args) == 2)
w, h = msg.args
client.session = session = Session(w, h, client)
self.sessions[session.sid] = session
logging.info('%s created session %s' % (client, session))
elif msg.mtype == 'join':
check(len(msg.args) == 1)
sid = msg.args[0]
check(not hasattr(client, 'session'), 'already in a session')
check(sid in self.sessions, 'no such session')
session = self.sessions[sid]
check(session.state == STATE_JOINING, 'game already started')
session.join(client)
client.session = session
logging.info('%s joined %s' % (client, session))
elif msg.mtype == 'clickwall':
check(len(msg.args) == 3)
x, y, direction = msg.args
check(client.session, 'no session associated with client')
client.session.click_wall(client, x, y, direction)
else:
raise BadRequest('unknown message type')
except BadRequest as e:
logging.warning('bad request: %s' % e.message)
client.send(Msg('error', e.message).encode())
def onclose(self, client, code, reason):
if hasattr(client, 'session'):
client.session.leave(client)
logging.info('%s left %s' % (client, client.session))
if client.session.is_dead():
logging.info('deleting session %s' % client.session)
del self.sessions[client.session.sid]
if __name__ == '__main__': if __name__ == '__main__':
GameServer(('', 8099)).run() if len(sys.argv) < 2:
print >>sys.stderr, 'usage: % PORT' % sys.argv[0]
sys.exit(1)
port = int(sys.argv[1])
GameServer(('', port)).run()
#GameServer(('', port), loglevel=logging.DEBUG).run()
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<link href="style.css" rel="stylesheet" type="text/css"> <link href="style.css" rel="stylesheet" type="text/css">
</head> </head>
<body> <body>
<div id="game" class="game"></div> <div id="board"></div>
<script src="game.js"></script> <script src="game.js"></script>
</body> </body>
</html> </html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment