JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
more test cards
[peach-cgt.git] / server.coffee
1 listen_port = 8333
2 sys = require 'sys'
3 fs = require 'fs'
4 http = require 'http'
5 querystring = require 'querystring'
6 url = require 'url'
7 less = require 'less'
8 coffee = require 'coffee-script'
9 model = require './common.coffee'
10
11 games = {}
12
13 # timeout function with args in convenient order
14 timeout = (ms, func) -> setTimeout func, ms
15
16 css_handler = (args, out, request, url_parts) ->
17         fs.readFile 'style.less', 'utf8', (err, data) ->
18                 if err
19                         return out.end 'Server failed to read style.less'
20                 less.render data, (err, css) ->
21                         if err
22                                 return out.end "Server failed to make css: #{err}"
23                         out.end css
24
25 js_handler = (args, out, request, url_parts) ->
26         basename = url_parts.pathname.substr 1, (url_parts.pathname.length - 4)
27         if basename is 'client'
28                 filename = 'client.coffee'
29         else if basename is 'common'
30                 filename = 'common.coffee'
31         else
32                 error = "Unknown js basename: #{basename}"
33                 console.log error
34                 out.end(error)
35                 return
36
37         fs.readFile filename, 'utf8', (err, data) ->
38                 if err
39                         return out.end "Server failed to read #{filename}"
40                 out.end coffee.compile data
41
42 html_handler = (args, out, request, url_parts) ->
43         fs.readFile 'index.html', 'utf8', (err, data) ->
44                 if err
45                         return out.end 'Server failed to read index.html'
46                 out.end data
47
48 clean_pathname_regex = new RegExp('[^a-zA-Z/_.-]')
49 clean_pathname_regex2 = new RegExp('/[.]')
50 clean_pathname_regex3 = new RegExp('^[.-]')
51 clean_pathname = (str) ->
52         str = str.replace clean_pathname_regex, '_'
53         str = str.replace clean_pathname_regex2, '/_'
54         return str.replace clean_pathname_regex3, '_'
55
56 # serve javascript files from within /usr/share/javascript
57 javascript_handler = (args, out, request, url_parts) ->
58         filename = clean_pathname "/usr/share/#{url_parts.pathname}"
59         fs.readFile filename, 'utf8', (err, data) ->
60                 if err
61                         out.writeHead 404
62                         return out.end "Server failed to read #{filename}"
63                 out.writeHead 200, 'Content-Type': 'text/javascript'
64                 out.end data
65
66
67 get_handler = (args, out, request, url_parts) ->
68         console.log "get handler: ", args
69         unless args.game?.length
70                 out.writeHead 404, "Content-Type": 'text/plain'
71                 out.end 'Missing (or empty) "game" argument'
72                 return
73
74         unless args.agent is 'p1' or args.agent is 'p2'
75                 out.writeHead 404, "Content-Type": 'text/plain'
76                 out.end '"agent" argument must be set to p1 or p2'
77                 return
78
79         unless games[args.game]?
80                 out.writeHead 404, "Content-Type": 'text/plain'
81                 out.end 'Game not found'
82                 return
83
84         game = games[args.game]
85
86         waiter = games["#{args.agent}_waiter"]
87         if waiter?
88                 waiter.writeHead 200, 'Content-Type': 'text/javascript'
89                 waiter.end '[]'
90
91         game["#{args.agent}_waiter"] = out
92
93         answer_soon game # in case there's something queued already
94
95 set_handler = (args, out, request, url_parts) ->
96         console.log "set handler: ", args
97         unless args.game?.length
98                 out.writeHead 404, "Content-Type": 'text/plain'
99                 out.end 'Missing (or empty) "game" argument'
100                 return
101
102         unless args.agent is 'p1' or args.agent is 'p2'
103                 out.writeHead 404, "Content-Type": 'text/plain'
104                 out.end '"agent" argument must be set to p1 or p2'
105                 return
106
107         unless args.messages?
108                 out.writeHead 404, "Content-Type": 'text/plain'
109                 out.end '"messages" argument must be set'
110                 return
111
112         unless games[args.game]?
113                 out.writeHead 404, "Content-Type": 'text/plain'
114                 out.end 'Game not found'
115                 return
116
117         game = games[args.game]
118
119         # FIXME add error checking (json validity at least)
120         game.process_messages JSON.parse args.messages
121
122         out.writeHead 200, "Content-Type": 'text/plain'
123         out.end 'ok'
124
125 # don't call this directly, call answer_soon instead
126 answer_now = (game) ->
127         if game.p1_waiter and game.p1_queue.length
128                 waiter = game.p1_waiter
129                 queue = game.p1_queue
130                 game.p1_waiter = false
131                 game.p1_queue = []
132                 waiter.writeHead 200, 'Content-Type': 'text/javascript'
133                 waiter.end JSON.stringify queue
134         if game.p2_waiter and game.p2_queue.length
135                 waiter = game.p2_waiter
136                 queue = game.p2_queue
137                 game.p2_waiter = false
138                 game.p2_queue = []
139                 waiter.writeHead 200, 'Content-Type': 'text/javascript'
140                 waiter.end JSON.stringify queue
141
142 # this marks a game as "dirty" and makes sure there's exactly one timeout
143 # that'll respond to any clients that are waiting, and now have messages.
144 answer_soon = (game) ->
145         unless game.replier_id
146                 game.replier_id = timeout 1, ->
147                         delete game.replier_id
148                         answer_now game
149
150 forward_events = (message...) ->
151         unless message[1] is 'p1'
152                 @p1_queue.push message
153                 answer_soon this
154         unless message[1] is 'p2'
155                 @p2_queue.push message
156                 answer_soon this
157         console.log this
158
159 new_game = (id) ->
160         game = games[id] = model.new 'server'
161         game.p1_waiter = false
162         game.p2_waiter = false
163         game.p1_queue = []
164         game.p2_queue = []
165
166         game.on 'move', (agent, card, x, y) ->
167                 forward_events.call this, 'move', agent, card, x, y
168         game.on 'mark', (agent, card, state) ->
169                 forward_events.call this, 'mark', agent, card, state
170         game.on 'flip', (agent, card, state) ->
171                 forward_events.call this, 'flip', agent, card, state
172         game.on 'set_cards', (agent, cards) ->
173                 forward_events.call this, 'set_cards', agent, cards
174         game.on 'send_state', (agent) ->
175                 timeout 10, =>
176                         if agent is 'p1'
177                                 @p1_queue.push ['set_cards', 'server', @cards]
178                                 answer_soon this
179                         if agent is 'p2'
180                                 @p2_queue.push ['set_cards', 'server', @cards]
181                                 answer_soon this
182
183         return game
184
185 test_init = ->
186         test_game = new_game 'test'
187         timeout 4000, ->
188                 test_game.set_cards 'server', [
189                         { text: "Wildabeast 2/2", x: 220, y: 200}
190                         { text: "Wild beast 2/2", x: 350, y: 200}
191                         { text: "Angora bunny 1/1", x: 500, y: 200}
192                         { text: "Ambulatory Cactus 2/1", x: 650, y: 200}
193                         { text: "Ent 0/5", x: 800, y: 200}
194                         { text: "Carnivore 2/1", x: 220, y: 420}
195                         { text: "Herbivore 1/2", x: 350, y: 420}
196                         { text: "Stone Wall 0/10", x: 500, y: 420}
197                         { text: "Log 0/1", x: 650, y: 420}
198                         { text: "Ent 0/5", x: 800, y: 420}
199                 ]
200
201 test_init()
202
203
204 http_server = http.createServer (req, res) ->
205         url_parts = url.parse req.url, true
206         if url_parts.query is undefined
207                 url_parts.query = {}
208
209         rel_path = url_parts.pathname.substr 1
210
211         if rel_path.substr(0, 11) is 'javascript/'
212                 return javascript_handler url_parts.query, res, req, url_parts
213         else if rel_path.substr(rel_path.length - 4) is '.css'
214                 res.writeHead 200, 'Content-Type': 'text/css'
215                 return css_handler url_parts.query, res, req, url_parts
216         else if rel_path.substr(rel_path.length - 3) is '.js'
217                 res.writeHead 200, 'Content-Type': 'text/javascript'
218                 return js_handler url_parts.query, res, req, url_parts
219         else if rel_path.substr(rel_path.length - 4) is '/set'
220                 data = ''
221                 req.on 'data', (chunk) ->
222                         data += chunk
223                 req.on 'end', ->
224                         query = url_parts.query
225                         post_args = querystring.parse data
226                         console.log data, post_args
227                         for key, parg of post_args
228                                 query[key] = parg
229                         return set_handler query, res, req, url_parts
230         else if rel_path.substr(rel_path.length - 4) is '/get'
231                 return get_handler url_parts.query, res, req, url_parts
232         else if rel_path.substr(rel_path.length - 4) is '.ico'
233                 res.writeHead 404
234                 return res.end()
235
236         return html_handler url_parts.query, res, req, url_parts
237
238 http_server.listen listen_port, "127.0.0.1"
239 console.log "Server running at http://127.0.0.1:#{listen_port}/"