X-Git-Url: https://jasonwoof.com/gitweb/?p=peach-cgt.git;a=blobdiff_plain;f=server.coffee;h=089523ad4d5116b8bcfae57a47c8351dbc49b892;hp=fad63475eb06e91997a0d87dcd7e19ecdfe1ebe4;hb=078a999a7b7ed2ae37e27d88b3039221fbc1d90c;hpb=3a8a99fd78775bdd7f71470443858e19b09dc2ed diff --git a/server.coffee b/server.coffee index fad6347..089523a 100644 --- a/server.coffee +++ b/server.coffee @@ -2,9 +2,16 @@ listen_port = 8333 sys = require 'sys' fs = require 'fs' http = require 'http' +querystring = require 'querystring' url = require 'url' less = require 'less' coffee = require 'coffee-script' +model = require './common.coffee' + +games = {} + +# timeout function with args in convenient order +timeout = (ms, func) -> setTimeout func, ms css_handler = (args, out, request, url_parts) -> fs.readFile 'style.less', 'utf8', (err, data) -> @@ -16,9 +23,20 @@ css_handler = (args, out, request, url_parts) -> out.end css js_handler = (args, out, request, url_parts) -> - fs.readFile 'client.coffee', 'utf8', (err, data) -> + basename = url_parts.pathname.substr 1, (url_parts.pathname.length - 4) + if basename is 'client' + filename = 'client.coffee' + else if basename is 'common' + filename = 'common.coffee' + else + error = "Unknown js basename: #{basename}" + console.log error + out.end(error) + return + + fs.readFile filename, 'utf8', (err, data) -> if err - return out.end 'Server failed to read client.coffee' + return out.end "Server failed to read #{filename}" out.end coffee.compile data html_handler = (args, out, request, url_parts) -> @@ -26,7 +44,163 @@ html_handler = (args, out, request, url_parts) -> if err return out.end 'Server failed to read index.html' out.end data - + +clean_pathname_regex = new RegExp('[^a-zA-Z/_.-]') +clean_pathname_regex2 = new RegExp('/[.]') +clean_pathname_regex3 = new RegExp('^[.-]') +clean_pathname = (str) -> + str = str.replace clean_pathname_regex, '_' + str = str.replace clean_pathname_regex2, '/_' + return str.replace clean_pathname_regex3, '_' + +# serve javascript files from within /usr/share/javascript +javascript_handler = (args, out, request, url_parts) -> + filename = clean_pathname "/usr/share/#{url_parts.pathname}" + fs.readFile filename, 'utf8', (err, data) -> + if err + out.writeHead 404 + return out.end "Server failed to read #{filename}" + out.writeHead 200, 'Content-Type': 'text/javascript' + out.end data + + +get_handler = (args, out, request, url_parts) -> + unless args.game?.length + out.writeHead 404, "Content-Type": 'text/plain' + out.end 'Missing (or empty) "game" argument' + return + + unless args.agent is 'p1' or args.agent is 'p2' + out.writeHead 404, "Content-Type": 'text/plain' + out.end '"agent" argument must be set to p1 or p2' + return + + unless games[args.game]? + out.writeHead 404, "Content-Type": 'text/plain' + out.end 'Game not found' + return + + game = games[args.game] + + waiter = games["#{args.agent}_waiter"] + if waiter? + waiter.writeHead 200, 'Content-Type': 'text/javascript' + waiter.end '[]' + + game["#{args.agent}_waiter"] = out + + answer_soon game # in case there's something queued already + +set_handler = (args, out, request, url_parts) -> + unless args.game?.length + out.writeHead 404, "Content-Type": 'text/plain' + out.end 'Missing (or empty) "game" argument' + return + + unless args.agent is 'p1' or args.agent is 'p2' + out.writeHead 404, "Content-Type": 'text/plain' + out.end '"agent" argument must be set to p1 or p2' + return + + unless args.messages? + out.writeHead 404, "Content-Type": 'text/plain' + out.end '"messages" argument must be set' + return + + unless games[args.game]? + out.writeHead 404, "Content-Type": 'text/plain' + out.end 'Game not found' + return + + game = games[args.game] + + # FIXME add error checking (json validity at least) + game.process_messages JSON.parse args.messages + + out.writeHead 200, "Content-Type": 'text/plain' + out.end '{"status":0,"text_status":"Success"}' + +# don't call this directly, call answer_soon instead +answer_now = (game) -> + if game.p1_waiter and game.p1_queue.length + waiter = game.p1_waiter + queue = game.p1_queue + game.p1_waiter = false + game.p1_queue = [] + waiter.writeHead 200, 'Content-Type': 'text/javascript' + waiter.end JSON.stringify queue + if game.p2_waiter and game.p2_queue.length + waiter = game.p2_waiter + queue = game.p2_queue + game.p2_waiter = false + game.p2_queue = [] + timeout 2000, -> # FIXME remove this delay for player 2 (just here to test lag handling) + waiter.writeHead 200, 'Content-Type': 'text/javascript' + waiter.end JSON.stringify queue + +# this marks a game as "dirty" and makes sure there's exactly one timeout +# that'll respond to any clients that are waiting, and now have messages. +answer_soon = (game) -> + unless game.replier_id + game.replier_id = timeout 1, -> + delete game.replier_id + answer_now game + +forward_events = (message...) -> + unless message[1] is 'p1' + @p1_queue.push message + answer_soon this + unless message[1] is 'p2' + @p2_queue.push message + answer_soon this + +new_game = (id) -> + game = games[id] = model.new 'server' + game.p1_waiter = false + game.p2_waiter = false + game.p1_queue = [] + game.p2_queue = [] + + game.on 'move', (agent, card, x, y, z) -> + forward_events.call this, 'move', agent, card, x, y, z + game.on 'mark', (agent, card, state) -> + forward_events.call this, 'mark', agent, card, state + game.on 'flip', (agent, card, state) -> + forward_events.call this, 'flip', agent, card, state + game.on 'set_cards', (agent, cards) -> + forward_events.call this, 'set_cards', agent, cards + game.on 'send_state', (agent) -> + timeout 10, => + if agent is 'p1' + @p1_queue.push ['set_cards', 'server', @cards] + answer_soon this + if agent is 'p2' + @p2_queue.push ['set_cards', 'server', @cards] + answer_soon this + + return game + +test_init = -> + test_game = new_game 'test' + timeout 2, -> + test_game.set_cards 'server', [ + { text: "Wildabeast 2/2", x: 220, y: 200, owner: 'p2'} + { text: "Boar 2/2", x: 360, y: 200, owner: 'p2', pile: 'p2_discard'} + { text: "Angora bunny 1/1", x: 500, y: 200, owner: 'p2'} + { text: "Ambulatory Cactus 2/1", x: 660, y: 200, owner: 'p2'} + { text: "Ent 0/5", x: 800, y: 200, owner: 'p2'} + { text: "Carnivore 2/1", x: 220, y: 420, owner: 'p1'} + { text: "Herbivore 1/2", x: 360, y: 420, owner: 'p1'} + { text: "Stone Wall 0/10", x: 500, y: 420, owner: 'p1', pile: 'p1_draw', flipped: true} + { text: "Log 0/1", x: 660, y: 420, owner: 'p1', pile: 'p1_draw', flipped: true} + { text: "Ent 0/5", x: 800, y: 420, owner: 'p1', pile: 'p1_draw', flipped: true} + { text: "Barricade 0/10", x: 500, y: 420, owner: 'p1', pile: 'p1_draw', flipped: true} + { text: "O(log(n)) 0/1", x: 660, y: 420, owner: 'p1', pile: 'p1_draw', flipped: true} + { text: "Fence 0/5", x: 800, y: 420, owner: 'p1', pile: 'p1_draw', flipped: true} + ] + +test_init() + http_server = http.createServer (req, res) -> url_parts = url.parse req.url, true @@ -35,13 +209,30 @@ http_server = http.createServer (req, res) -> rel_path = url_parts.pathname.substr 1 - if rel_path.substr(rel_path.length - 4) is '.css' + if rel_path.substr(0, 11) is 'javascript/' + return javascript_handler url_parts.query, res, req, url_parts + else if rel_path.substr(rel_path.length - 4) is '.css' res.writeHead 200, 'Content-Type': 'text/css' return css_handler url_parts.query, res, req, url_parts - else if rel_path.substr rel_path.length - 3 is '.js' + else if rel_path.substr(rel_path.length - 3) is '.js' res.writeHead 200, 'Content-Type': 'text/javascript' return js_handler url_parts.query, res, req, url_parts - + else if rel_path.substr(rel_path.length - 4) is '/set' + data = '' + req.on 'data', (chunk) -> + data += chunk + req.on 'end', -> + query = url_parts.query + post_args = querystring.parse data + for key, parg of post_args + query[key] = parg + return set_handler query, res, req, url_parts + else if rel_path.substr(rel_path.length - 4) is '/get' + return get_handler url_parts.query, res, req, url_parts + else if rel_path.substr(rel_path.length - 4) is '.ico' + res.writeHead 404 + return res.end() + return html_handler url_parts.query, res, req, url_parts http_server.listen listen_port, "127.0.0.1"