JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
start of piles implementation
[peach-cgt.git] / client.coffee
1 # globals
2 $table = null
3 table_height = 0
4 card_height = 0
5 state = null
6 server_url = null
7 top_card_z = 0 # css z-index of front-most card
8 $pile_captions = {}
9
10 show_message = (txt) -> txt
11         # FIXME implement chat box or something
12
13 # timeout function with args in convenient order
14 timeout = (ms, func) -> setTimeout func, ms
15
16 unless Array::shuffle?
17         Array::shuffle = ->
18                 return if @length is 0
19                 top = @length
20
21                 while --top
22                         current = Math.floor(Math.random() * (top + 1))
23                         tmp = @[current]
24                         @[current] = @[top]
25                         @[top] = tmp
26                 return
27
28 new_button = (text) -> $ $ "<div class=\"button\">#{text}</div>"
29
30 # transform coordinates from client-side coords to server-side coords (or back)
31 # this makes it so p2 view everything upside down (mirrored), but still sends coords rightside up
32 flip_y = (y) -> table_height - card_height - y
33 transform_x = (x) -> x
34 transform_y = (y) ->
35         return y unless state.agent is 'p2'
36         return flip_y y
37
38 next_card_z = -> return top_card_z += 1
39
40 bring_card_to_front = (card) ->
41         card.view.css "z-index": next_card_z()
42
43 new_blank_card = (x, y) ->
44         view = $ $ "<div class=\"blank_card\" style=\"left: #{transform_x x}px; top: #{transform_y y}px; z-index: 0\"></div>"
45         $table.append view
46         return view
47
48 instantiate_card = (card) ->
49         text = card.text
50         if card.owner is state.agent
51                 card_class = 'my_card'
52         else
53                 card_class = 'your_card'
54
55         # initial card state from server has z so that stacks come out with the right layers
56         if card.z?
57                 if card.z > top_card_z
58                         top_card_z = card.z
59         else
60                 unless card.pile
61                         card.z = next_card_z()
62
63         view = $ $ "<div class=\"card #{card_class}\" style=\"left: #{transform_x(card.x)}px; top: #{transform_y(card.y)}px; z-index: #{card.z}\"><span class=\"cardtext\">#{text}</span></div>"
64         button_box = $ $ '<div/>'
65         flip_button = new_button "flip over"
66         mark_button = new_button "mark"
67         flip_button.bind 'click', ->
68                 state.flip state.agent, card.number, ! view.hasClass 'flipped'
69         mark_button.bind 'click', ->
70                 state.mark state.agent, card.number, ! view.hasClass 'marked'
71         button_box.append flip_button
72         button_box.append mark_button
73         view.append button_box
74         if card.marked
75                 view.addClass 'marked'
76         if card.flipped
77                 view.addClass 'flipped'
78         $table.append view
79         view.draggable containment: '#table', grid: [20, 20]
80         view.bind 'dragstart', (event, ui) ->
81                 view.css 'z-index': card.z = next_card_z()
82         view.bind 'dragstop', (event, ui) ->
83                 p = view.position()
84                 state.move state.agent, card.number, transform_x(p.left), transform_y(p.top), card.z
85         card.view = view
86
87 error_lag = 3
88
89 outgoing_messages = []
90 # message should be [agent, method, args...]
91 # don't forget the agent (state.agent)
92 tell_server = (message) ->
93         outgoing_messages.push message
94         send_updates()
95
96 send_updates = ->
97         return if outgoing_messages.length is 0
98
99         messages = outgoing_messages
100         outgoing_messages = []
101
102         $.ajax "#{server_url}/set", {
103                 cache: false
104                 data: {
105                         agent: state.agent
106                         game: 'test' # FIXME, and it the /get call too
107                         messages: JSON.stringify(messages)
108                 }
109                 type: 'POST'
110                 dataType: 'json'
111                 error: (xhr, status, error) ->
112                         show_message "Network error while sending, you might want to refresh. Trying again in #{error_lag} seconds. (Status: #{status}, Error: #{error})"
113                         for message in messages
114                                 outgoing_messages.unshift message
115                         timeout error_lag * 1000, send_updates
116                         error_lag *= 2
117                 success: (data, status, xhr) ->
118                         show_message "update sent"
119                         error_lag = 3
120         }
121
122 error_lag = 3
123 poll_for_updates = ->
124         $.ajax "#{server_url}/get?agent=#{state.agent}&game=test", {
125                 cache: false
126                 type: 'GET'
127                 dataType: 'json'
128                 error: (xhr, status, error) ->
129                         message "Network error, you might want to refresh. Trying again in #{error_lag} seconds. (Status: #{status}, Error: #{error})"
130                         timeout error_lag * 1000, poll_for_updates
131                         error_lag *= 2
132                 success: (data, status, xhr) ->
133                         state.process_messages data
134                         timeout 100, poll_for_updates
135                         error_lag = 3
136         }
137
138 n_cards = (count) ->
139         return "#{count} cards" unless count is 1
140         return "1 card"
141
142 initialize_cards = () ->
143         # clear everything
144         $('.card').remove()
145         $('.blank_card').remove()
146
147         # instantiate cards in play
148         for card in state.cards
149                 instantiate_card card unless card.pile
150
151         # build piles
152         piles = [
153                 {key: 'p2_draw', x: transform_x(160), y: transform_y(20), name: "Draw Pile"}
154                 {key: 'p2_discard', x: transform_x(20), y: transform_y(20), name: "Discard Pile"}
155                 {key: 'p1_draw', x: transform_x(160), y: transform_y(flip_y(20)), name: "Draw Pile"}
156                 {key: 'p1_discard', x: transform_x(20), y: transform_y(flip_y(20)), name: "Discard Pile"}
157         ]
158         for pile in piles
159                 pile.$blank = new_blank_card pile.x, pile.y
160                 count = 0
161                 top = null
162                 if state.piles[pile.key]?.length
163                         count = state.piles[pile.key].length
164                         top = state.piles[pile.key][0]
165                 $caption = $ $ "<div class=\"pile_caption\"><div>#{pile.name}:</div><div class=\"n_cards\">#{n_cards count}</div></div>"
166                 pile.$caption = $caption
167                 if top?
168                         top.x = pile.x
169                         top.y = pile.y
170                         instantiate_card top
171                         top = top.view
172                 else
173                         top = pile.$blank
174
175                 top.append $caption
176
177                 #pile.drag_handler = top.bind 'dragstart', ->
178                 # FIXME
179
180
181 init = ->
182         if window.location.hash? and window.location.hash.length > 0
183                 me = window.location.hash.substr 1
184                 winloc = "#{window.location}"
185                 server_url = winloc.substr 0, winloc.length - window.location.hash.length
186         else
187                 me = 'p1'
188                 server_url = window.location
189
190         state = window.game_model.new me
191         state.on 'move', (agent, card, x, y, z) ->
192                 # FIXME add/handle pile argument
193                 if agent is me
194                         tell_server ['move', agent, card, x, y, z]
195                 else
196                         # FIXME should we use the z from the server? Should p1 use odd numbers and p2 even?
197                         bring_card_to_front state.cards[card]
198                         state.cards[card].view.animate { left: "#{transform_x x}px", top: "#{transform_y y}px"}, 800
199         state.on 'mark', (agent, card, state) ->
200                 @cards[card].view.toggleClass 'marked', state
201                 if agent is me
202                         tell_server ['mark', agent, card, state]
203         state.on 'flip', (agent, card, state) ->
204                 @cards[card].view.toggleClass 'flipped', state
205                 if agent is me
206                         tell_server ['flip', agent, card, state]
207         state.on 'set_cards', (agent, cards) ->
208                 initialize_cards()
209                 if agent is me
210                         tell_server ['set_cards', agent, cards]
211
212         # timeout so browser will stop showing that we're loading
213         timeout 1, poll_for_updates
214         timeout 2, ->
215                 # ask for initial state
216                 tell_server ['send_state', state.agent]
217
218 $ ->
219         $table = $ '#table'
220         table_height = $table.height()
221         card_height = $('#loading-card').outerHeight()
222
223         init()