diff --git a/game.py b/game.py index a35d189..1bb42d6 100644 --- a/game.py +++ b/game.py @@ -1,5 +1,6 @@ from __future__ import division from collections import namedtuple, deque, defaultdict +from decimal import * import numpy as np @@ -82,13 +83,19 @@ def __init__(self, w=51, h=23, debug=False): def addPlayer(self, username): if username in self.players: raise excpt.UsernameTaken(username) - for player in self.players.itervalues(): - player.ammo += 5 self.players[username] = Player(self, username) + + def findPlayer(self, pos): + for player in self.players.itervalues(): + if player.pos == pos: + return player + def update(self): + # Update player movements for player in self.players.itervalues(): player.update() + # Update bullet movements for bullet in self.bullets: self.arena[bullet.pos] = " " @@ -104,10 +111,11 @@ def update(self): elif self.arena[bullet.pos] == "*": self.arena[bullet.pos] = " " elif self.arena[bullet.pos] in "<>v^": - for player in self.players.itervalues(): - if player.pos == bullet.pos: - player.hit(bullet.source, "bullet") - break + player = self.findPlayer(bullet.pos) + player.hit(bullet.source, "bullet") + # Update scores + for player in self.players.itervalues(): + player.updateScore() def __str__(self): height, width = self.arena.shape @@ -152,8 +160,9 @@ class Player(object): def __init__(self, game, name): self.game = game self.name = name - self.deaths = 0 - self.kills = 0 + self.deaths = self.kills = self.currentScore = 0 + self.score = Decimal(0) + self.cloak = self.scan = False self.rebirth() def rebirth(self, health=10, ammo=15): @@ -177,6 +186,9 @@ def rebirth(self, health=10, ammo=15): self.health = health self.ammo = ammo + for player in self.game.players.itervalues(): + player.ammo += 5 + def updateMask(self): y, x = self.pos if self.game.arena[self.pos] == "*": @@ -197,9 +209,13 @@ def updateMask(self): mask[y, x] = True - # Expand all masks so you can see the walls - # TODO Make expansion work with edges + # Scan for other player's locations + if self.scan: + for player in self.game.players.itervalues(): + if not player.cloak: + mask[player.pos] = True + # Expand all masks so you can see the walls mask[1:-1, 1:-1] = (mask[:-2, :-2] + mask[:-2, 1:-1] + mask[:-2, 2:] + mask[1:-1, :-2] + mask[1:-1,1:-1] + mask[1:-1, 2:] + mask[2:, :-2] + mask[2:, 1:-1] + mask[2:, 2:]) @@ -223,8 +239,10 @@ def update(self): self.lastActionTime += 1 self.actions.appendleft( (func, args, kwargs) ) elif func == "turn" and self.lastActionTime >= speed["turn"]: + self.lastActionTime += 1 self.turn(*args, **kwargs) elif func == "fire" and self.lastActionTime >= speed["fire"]: + self.lastActionTime += 1 self.fire(*args, **kwargs) else: self.lastActionTime += 1 @@ -235,19 +253,22 @@ def move(self, direction): self.game.arena[self.pos] = ' ' p = move(self.pos, direction) + if self.cloak: + self.ammo -= 0.05 + else: + for player in self.game.players.itervalues(): + if player.scan and player is not self: + player.ammo -= 0.05 + if self.game.inArena(p): if self.game.arena[p] == " ": self.pos = p self.updateMask() - elif self.game.arena[p] in "<>v^": # stabbing - other = None - for player in self.game.players.itervalues(): - if player.pos == p: - other = player - break + elif self.game.arena[p] in "<>v^" and direction == self.facing: + # if we're facing someone and move into them, stab + other = self.game.findPlayer(p) other.hit(self, "stab") - self.game.arena[self.pos] = self.facing def turn(self, direction): @@ -257,14 +278,21 @@ def turn(self, direction): def fire(self): pos = move(self.pos, self.facing) - if self.game.inArena(pos) and self.ammo > 0: - bullet = Bullet(pos, self.facing, self) - self.ammo -= 1 - if self.game.arena[bullet.pos] == " ": - self.game.bullets.append(bullet) - self.game.arena[bullet.pos] = ":" - elif self.game.arena[bullet.pos] == "*": - self.game.arena[bullet.pos] = " " + if self.game.inArena(pos): + if self.ammo > 0: + bullet = Bullet(pos, self.facing, self) + self.ammo -= 1 + if self.game.arena[bullet.pos] == " ": + self.game.bullets.append(bullet) + self.game.arena[bullet.pos] = ":" + elif self.game.arena[bullet.pos] == "*": + self.game.arena[bullet.pos] = " " + elif self.game.arena[bullet.pos] in "v^<>": # Direct hit + other = self.game.findPlayer(bullet.pos) + other.hit(self, "bullet") + else: + self.msg = "You are out of ammo" + def hit(self, src, method): if method == "bullet": @@ -276,13 +304,24 @@ def hit(self, src, method): self.health -= damage[method] if self.health <= 0: + self.msg = "{src} killed you".format(src=src.name) + src.msg = "You killed {target}".format(target=self.name) + src.health += 2 #generate health src.kills += 1 self.deaths += 1 + self.currentScore -= 1 + src.currentScore += 1 + self.game.arena[self.pos] = " " self.rebirth() + def updateScore(self): + # use Decimal to avoid annoying scores like 0.000000000001 + self.score = Decimal('0.998') * self.score + self.currentScore + self.currentScore = 0 + def __str__(self): h, w = self.view.shape s = "\n".join("".join( self.game.getType(x, y) @@ -291,11 +330,11 @@ def __str__(self): for x in xrange(w)) for y in xrange(h)) return s - def score(self): - return (self.kills + 1) / (self.deaths + 1) + def to_json(self): d = {"arena": str(self), "ammo": self.ammo, "health": self.health, - "scores": {name: {"kills":player.kills, "deaths":player.deaths} + "scores": {name: {"kills":player.kills, "deaths":player.deaths, + "score":round(player.score, 3)} # 3 decimal points of score for name, player in self.game.players.iteritems()}} if self.msg: d["msg"] = self.msg diff --git a/server.py b/server.py index f854196..e2f0a53 100644 --- a/server.py +++ b/server.py @@ -10,10 +10,18 @@ import custom_exceptions as excpt app = Flask(__name__) -socketio = SocketIO(app, excpt.exception_handler) +socketio = SocketIO(app) + +@socketio.on_error_default +def exception_handler(value): + if isinstance(value, excpt.UserError): + emit("error", str(value)) + else: + raise value m = game.Game() + @app.route("/") def index(interval=0.05): @copy_current_request_context @@ -37,6 +45,8 @@ def begin(msg): player = m.players[msg["username"]] join_room(msg["username"]) + socketio.emit("acknowledged", {}, room=msg["username"]) + @socketio.on("logoff") def logoff(msg): uname = msg["username"] @@ -60,5 +70,17 @@ def fire(msg): user = msg["username"] m.players[user].queue("fire") +@socketio.on("scan") +def scan(msg): + user = msg["username"] + m.players[user].scan = not m.players[user].scan + +@socketio.on("cloak") +def scan(msg): + user = msg["username"] + m.players[user].cloak = not m.players[user].cloak + if __name__ == "__main__": + for path in glob.glob("static/*.coffee"): + subprocess.call(["coffee", "-c", path]) socketio.run(app, port=8080) diff --git a/sockets.py b/sockets.py index a91c6a6..4382c69 100644 --- a/sockets.py +++ b/sockets.py @@ -21,14 +21,12 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - +from gevent import monkey +monkey.patch_all() import os import sys -from gevent import monkey -monkey.patch_all() - from socketio import socketio_manage from socketio.server import SocketIOServer from socketio.namespace import BaseNamespace @@ -38,6 +36,7 @@ from werkzeug._internal import _log + class SocketIOMiddleware(object): def __init__(self, app, socket): self.app = app @@ -58,16 +57,15 @@ def __call__(self, environ, start_response): class SocketIO(object): - def __init__(self, app=None, exception_handler=None): + def __init__(self, app=None): if app: self.init_app(app) self.messages = {} self.rooms = {} self.server = None - self.exception_handler = exception_handler - if exception_handler is not None and not callable(exception_handler): - raise ValueError("exception_handler must be callable") + self.exception_handlers = {} + self.default_exception_handler = None def init_app(self, app): app.wsgi_app = SocketIOMiddleware(app, self) @@ -149,7 +147,7 @@ def send(self, message, json=False, ns_name=None, callback=None, return request.namespace.base_send(message, json, callback) return request.namespace.socket[ns_name].base_send(message, json, callback) - namespaces = {ns_name: GenericNamespace for ns_name in self.messages} + namespaces = dict( (ns_name, GenericNamespace) for ns_name in self.messages) return namespaces def _dispatch_message(self, app, namespace, message, args=[]): @@ -189,32 +187,45 @@ def _leave_room(self, namespace, room): return True return False - def on_message(self, message, handler, ns_name=""): - if ns_name not in self.messages: - self.messages[ns_name] = {} - self.messages[ns_name][message] = handler + def on_message(self, message, handler, namespace=''): + if namespace not in self.messages: + self.messages[namespace] = {} + self.messages[namespace][message] = handler - def on(self, message, ns_name=""): - if self.exception_handler is not None: + def on(self, message, namespace=''): + if namespace in self.exception_handlers or self.default_exception_handler is not None: def decorator(f): def func(*args, **kwargs): try: f(*args, **kwargs) except: - print sys.exc_info() - self.exception_handler(*sys.exc_info(), ns_name=ns_name) - - self.on_message(message, func, ns_name) + handler = self.exception_handlers.get(namespace, + self.default_exception_handler) + type, value, traceback = sys.exc_info() + handler(value) + self.on_message(message, func, namespace) return func else: def decorator(f): - self.on_message(message, f, ns_name) + self.on_message(message, f, namespace) return f + return decorator + + def on_error(self, namespace=''): + def decorator(exception_handler): + if not callable(exception_handler): + raise ValueError('exception_handler must be callable') + self.exception_handlers[namespace] = exception_handler return decorator + def on_error_default(self, exception_handler): + if not callable(exception_handler): + raise ValueError('exception_handler must be callable') + self.default_exception_handler = exception_handler + def emit(self, event, *args, **kwargs): - ns_name = kwargs.pop('namespace', "") + ns_name = kwargs.pop('namespace', '') room = kwargs.pop('room', None) if room is not None: for client in self.rooms.get(ns_name, {}).get(room, set()): @@ -260,7 +271,6 @@ def run_server(): else: self.server.serve_forever() - def emit(event, *args, **kwargs): return request.namespace.emit(event, *args, **kwargs) diff --git a/static/css/main.css b/static/css/main.css index b4db564..a95ae99 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -11,4 +11,4 @@ body { #map { line-height: 16px; -} \ No newline at end of file +} diff --git a/static/events.coffee b/static/events.coffee index 03453f3..3a7f1ce 100644 --- a/static/events.coffee +++ b/static/events.coffee @@ -2,11 +2,18 @@ socket = io.connect() socket.on("error", ((err) -> alert(err))) socket.on("update", update) +socket.on("acknowledged", (info) -> + window.uname = $("#username").val()) + +socket.on("disconnect", () -> + window.uname = undefined + clear() + document.getElementById("status").innerHTML = "Disconnected from server" + null) $( "#play" ).on "click", (() -> if window.uname is undefined - window.uname = $( "#username" ).val() - socket.emit('begin', {width:51, height:23, username:window.uname}) + socket.emit('begin', {width:51, height:23, username:$("#username").val()}) else alert("Already logged in!") ) @@ -30,6 +37,8 @@ $( window ).keydown ((evt) -> when 'L' then direction = '>' when 'H' then direction = '<' when 'F' then fire = true + when 'S' then scan = true + when 'C' then cloak = true else return true @@ -41,6 +50,10 @@ $( window ).keydown ((evt) -> else type = "move" socket.emit(type, {direction: direction, username: window.uname}) - if fire + else if fire socket.emit("fire", {username: window.uname}) + else if scan + socket.emit("scan", {username: window.uname}) + else if cloak + socket.emit("cloak", {username: window.uname}) ) diff --git a/static/events.js b/static/events.js index 558b3d4..965c76e 100644 --- a/static/events.js +++ b/static/events.js @@ -10,13 +10,23 @@ socket.on("update", update); + socket.on("acknowledged", function(info) { + return window.uname = $("#username").val(); + }); + + socket.on("disconnect", function() { + window.uname = void 0; + clear(); + document.getElementById("status").innerHTML = "Disconnected from server"; + return null; + }); + $("#play").on("click", (function() { if (window.uname === void 0) { - window.uname = $("#username").val(); return socket.emit('begin', { width: 51, height: 23, - username: window.uname + username: $("#username").val() }); } else { return alert("Already logged in!"); @@ -32,7 +42,7 @@ }); $(window).keydown((function(evt) { - var chr, direction, fire, key, type; + var chr, cloak, direction, fire, key, scan, type; if (evt.target.tagName.toLowerCase() === "input" || window.uname === void 0 || evt.ctrlKey) { return true; } @@ -54,6 +64,12 @@ case 'F': fire = true; break; + case 'S': + scan = true; + break; + case 'C': + cloak = true; + break; default: return true; } @@ -64,15 +80,22 @@ } else { type = "move"; } - socket.emit(type, { + return socket.emit(type, { direction: direction, username: window.uname }); - } - if (fire) { + } else if (fire) { return socket.emit("fire", { username: window.uname }); + } else if (scan) { + return socket.emit("scan", { + username: window.uname + }); + } else if (cloak) { + return socket.emit("cloak", { + username: window.uname + }); } })); diff --git a/static/render.coffee b/static/render.coffee index ae845b1..b72096a 100644 --- a/static/render.coffee +++ b/static/render.coffee @@ -4,8 +4,14 @@ for key in ["health", "ammo", "msg", "arena"] if key of info document.getElementById(key).innerHTML = escape(info[key]) + null +@clear = () -> + for key in ["health", "ammo", "msg", "arena", "scores"] + document.getElementById(key).innerHTML = "" + + getScoreboard = (scores) -> unameLength = "Username".length statLengths = {} diff --git a/static/render.js b/static/render.js index 008de64..45f1f93 100644 --- a/static/render.js +++ b/static/render.js @@ -15,6 +15,17 @@ return null; }; + this.clear = function() { + var key, _i, _len, _ref, _results; + _ref = ["health", "ammo", "msg", "arena", "scores"]; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + _results.push(document.getElementById(key).innerHTML = ""); + } + return _results; + }; + getScoreboard = function(scores) { var key, len, map, stat, statLengths, stats, uname, unameLength, _ref; unameLength = "Username".length; diff --git a/templates/play.html b/templates/play.html index e388b78..6cbb740 100644 --- a/templates/play.html +++ b/templates/play.html @@ -28,14 +28,14 @@ -
-
-
+
+
+
Health:
Ammo:
+
-