X-Git-Url: https://jasonwoof.com/gitweb/?p=peach-cgt.git;a=blobdiff_plain;f=server.coffee;h=44084e8972c85f259d286cc45d3618b1383eb89d;hp=46d48c594c06bf8d6542b4097f1609ee99e57deb;hb=daaa50433a47f3893e39839f1f868992565d864e;hpb=3be596d7cfcd16a09505b8c68cad974d7b2901ac diff --git a/server.coffee b/server.coffee index 46d48c5..44084e8 100644 --- a/server.coffee +++ b/server.coffee @@ -1,4 +1,20 @@ -listen_port = 8333 +# Peach CGT -- Card Game Table simulator +# Copyright (C) 2011 Jason Woofenden +# +# This program is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +listen_port = 13850 sys = require 'sys' fs = require 'fs' http = require 'http' @@ -9,10 +25,43 @@ coffee = require 'coffee-script' model = require './common.coffee' games = {} +max_concurrent_games = 50 +max_game_idle = 3 * 60 * 60 * 1000 # three hours (in miliseconds) # timeout function with args in convenient order timeout = (ms, func) -> setTimeout func, ms +now_s = -> + d = new Date() + return d.getTime() + +expire_old_games = -> + count = 0 + for slug, g of games + count += 1 + oldest_slug = slug + oldest_seen = g.last_seen + + return unless count > 0 + + # check all the games + # track oldest + # delete old ones + too_old = now_s() - max_game_idle + kills = [] + for slug, g of games + if g.last_seen < oldest_seen + oldest_seen = g.last_seen + oldest_slug = slug + if g.last_seen < too_old + kills.push slug + if count > max_concurrent_games and kills.length is 0 + console.log "hit max_concurrent_games, destroying oldest" + kills.push oldest_slug + for slug in kills + console.log "killing game #{slug}" + delete games[slug] + css_handler = (args, out, request, url_parts) -> fs.readFile 'style.less', 'utf8', (err, data) -> if err @@ -23,11 +72,17 @@ css_handler = (args, out, request, url_parts) -> out.end css js_handler = (args, out, request, url_parts) -> + convert = false basename = url_parts.pathname.substr 1, (url_parts.pathname.length - 4) if basename is 'client' filename = 'client.coffee' + convert = true else if basename is 'common' filename = 'common.coffee' + convert = true + else if basename is 'cs_cards' + filename = 'cs_cards.js' + convert = false else error = "Unknown js basename: #{basename}" console.log error @@ -37,7 +92,14 @@ js_handler = (args, out, request, url_parts) -> fs.readFile filename, 'utf8', (err, data) -> if err return out.end "Server failed to read #{filename}" - out.end coffee.compile data + if convert + try + converted = coffee.compile data + catch e + out.end "alert(\"server faild to compile #{filename}\");" + out.end converted + else + out.end data html_handler = (args, out, request, url_parts) -> fs.readFile 'index.html', 'utf8', (err, data) -> @@ -94,28 +156,50 @@ get_handler = (args, out, request, url_parts) -> 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' + out.end '{"status":1,"text_status":"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' + out.end '{"status":2,"text_status":"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' + out.end '{"status":3,"text_status":"messages argument must be set"}' return + try + messages = JSON.parse args.messages + catch e + out.writeHead 400, "Content-Type": 'text/plain' + out.end '{"status":4,"text_status":"Invalid JSON"}' + return + + # special handling of 'new_game' api, because for this one we don't have a + # game object to pass the message to + if messages?[0]?[0] is 'new_game' + message = messages.shift() + slug = message[1] + if games[slug]? + out.writeHead 403, "Content-Type": 'text/plain' + out.end '{"status":6,"text_status":"Game already exists"}' + return + game = games[slug] = new_game slug, 'server' + game.last_seen = now_s() + console.log "new game: #{slug}" + expire_old_games() + unless games[args.game]? out.writeHead 404, "Content-Type": 'text/plain' - out.end 'Game not found' + out.end '{"status":5,"text_status":"Game not found"}' return game = games[args.game] - # FIXME add error checking (json validity at least) - game.process_messages JSON.parse args.messages + game.last_seen = now_s() + + game.process_messages messages out.writeHead 200, "Content-Type": 'text/plain' out.end '{"status":0,"text_status":"Success"}' @@ -134,9 +218,8 @@ answer_now = (game) -> 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 + 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. @@ -154,19 +237,23 @@ forward_events = (message...) -> @p2_queue.push message answer_soon this -new_game = (id) -> - game = games[id] = model.new 'server' +new_game = (slug) -> + game = games[slug] = model.new slug, 'server' game.p1_waiter = false game.p2_waiter = false game.p1_queue = [] game.p2_queue = [] - game.on 'move', (agent, card, x, y) -> - forward_events.call this, 'move', agent, card, x, y + game.on 'move', (agent, card, x, y, z, pile) -> + forward_events.call this, 'move', agent, card, x, y, z, pile 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 'new_cards', (agent, cards) -> + # server assigns card numbers, and tells both clients + # (unlike all other api calls, sending agent expects to get this one back) + forward_events.call this, 'new_cards', 'server', cards game.on 'set_cards', (agent, cards) -> forward_events.call this, 'set_cards', agent, cards game.on 'send_state', (agent) -> @@ -180,25 +267,6 @@ new_game = (id) -> return game -test_init = -> - test_game = new_game 'test' - timeout 4000, -> - test_game.set_cards 'server', [ - { text: "Wildabeast 2/2", x: 220, y: 200, owner: 'p2'} - { text: "Wild beast 2/2", x: 360, y: 200, owner: 'p2'} - { 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'} - { text: "Log 0/1", x: 660, y: 420, owner: 'p1'} - { text: "Ent 0/5", x: 800, y: 420, owner: 'p1'} - ] - -test_init() - - http_server = http.createServer (req, res) -> url_parts = url.parse req.url, true if url_parts.query is undefined @@ -224,6 +292,7 @@ http_server = http.createServer (req, res) -> for key, parg of post_args query[key] = parg return set_handler query, res, req, url_parts + return 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' @@ -232,5 +301,7 @@ http_server = http.createServer (req, res) -> return html_handler url_parts.query, res, req, url_parts +setInterval expire_old_games, 2 * 60 * 1000 # check every 2 minutes for expired games + http_server.listen listen_port, "127.0.0.1" console.log "Server running at http://127.0.0.1:#{listen_port}/"