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
......@@ -8,5 +8,9 @@ www/%.css: %.sass
www/%.js: %.coffee
coffee -o $(@D) $<
check:
pyflakes *.py
pep8 *.py
clean:
rm -f www/*.js www/*.css
......@@ -18,7 +18,7 @@ divin = (parent, cls) ->
parent.appendChild elem
elem
class Game
class Board
constructor: (@w, @h, elem) ->
@render elem if elem
......@@ -75,24 +75,74 @@ class Game
divin row, "wall-h#{clicked}"
divin row, 'dot'
game = new Game 6, 6
game.render document.getElementById 'game'
game.click_wall 2, 2, WALL_RIGHT
game.click_wall 2, 2, WALL_BOTTOM
game.click_wall 1, 2, WALL_RIGHT
game.click_wall 2, 2, WALL_TOP
game.occupy 2, 2, 2
#ws = new WebSocket URL
#
#ws.onopen = ->
# console.log 'open'
#
#ws.onclose = ->
# console.log 'close'
#
#ws.onerror = (e) ->
# console.log 'error', e
#
#ws.onmessage = (msg) ->
# console.log 'msg', msg
destroy: ->
@board.parentNode.removeChild @board
show_error = alert
set_this_player = (id) ->
add_other_player = (id) ->
remove_other_player = (id) ->
set_turn_player = (id) ->
finish = (scores) ->
console.log 'scores:', scores
ws = new WebSocket URL
ws.send_msg = (mtype, args...) ->
@send [mtype].concat(args).join ';'
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
WALL_ALL = WALL_TOP | WALL_RIGHT | WALL_BOTTOM | WALL_LEFT
class Game:
class Board:
def __init__(self, w, h):
assert w > 1 and h > 1
self.w = w
......
#!/usr/bin/env python2
import sys
import logging
import time
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:
def __init__(self):
self.sid = sha1(str(time.time()))
self.clients = []
def __init__(self, w, h, owner):
self.sid = sha1(str(time.time())).hexdigest()
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):
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__':
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 @@
<link href="style.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="game" class="game"></div>
<div id="board"></div>
<script src="game.js"></script>
</body>
</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