JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Piles display and removing cards mostly working
[peach-cgt.git] / server.coffee
index 9beb901..089523a 100644 (file)
@@ -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) ->
@@ -57,6 +64,144 @@ javascript_handler = (args, out, request, url_parts) ->
                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
        if url_parts.query is undefined
@@ -69,9 +214,24 @@ http_server = http.createServer (req, res) ->
        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