JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
added server.js wrapper
[peach-cgt.git] / server.coffee
index 91250cb..44084e8 100644 (file)
@@ -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 <http://www.gnu.org/licenses/>.
+
+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
@@ -44,7 +93,11 @@ js_handler = (args, out, request, url_parts) ->
                if err
                        return out.end "Server failed to read #{filename}"
                if convert
-                       out.end coffee.compile data
+                       try
+                               converted = coffee.compile data
+                       catch e
+                               out.end "alert(\"server faild to compile #{filename}\");"
+                       out.end converted
                else
                        out.end data
 
@@ -103,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"}'
@@ -162,8 +237,8 @@ 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 = []
@@ -192,11 +267,6 @@ new_game = (id) ->
 
        return game
 
-test_init = ->
-       test_game = new_game 'test'
-test_init()
-
-
 http_server = http.createServer (req, res) ->
        url_parts = url.parse req.url, true
        if url_parts.query is undefined
@@ -231,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}/"