New game dialog allows selecting engines.
[kaya.git] / lib / controller.rb
blob2888c9faafbd9254aec4c89fb819fddfa4de59b2
1 require 'observer_utils'
2 require 'history'
3 require 'board/pool_animator'
4 require 'clock'
5 require 'interaction/match'
7 class Controller
8   include Observer
9   include Player
10   
11   attr_reader :match
12   attr_reader :color
13   attr_reader :controlled
14   attr_reader :table
15   attr_reader :policy
16   attr_accessor :name
17   
18   def initialize(table)
19     @table = table
20     @scene = @table.scene
22     @pools = { }
23     @clocks = { }
24     
25     @field = AnimationField.new(20)
26   end
27   
28   def each_element
29     yield @board if @board
30     @pools.each {|c, pool| yield pool }
31     @clocks.each {|c, clock| yield clock }
32   end
33   
34   def on_board_click(p)
35     state = @match.state
36     if @board.selection
37       move = @policy.new_move(state, @board.selection, p)
38       validate = @match.game.validator.new(state)
39       if validate[move]
40         perform! move
41       end
42       
43       @board.selection = nil
44     elsif @policy.movable?(state, p) and movable?(p)
45       @board.selection = p
46     end
47   end
48   
49   def reset(match)
50     @match = match
51     @policy = match.game.policy.new
52     
53     @table.reset(@match)
54     @board = @table.elements[:board]
55     @pools = @table.elements[:pools]
56     @clocks = @table.elements[:clocks]
57     
58     @animator = @match.game.animator.new(@board)
59     @board.reset(match.state.board)
60     update_pools
61     
62     @clocks.each do |col, clock|
63       clock.stop
64     end
65     
66     @board.observe(:click) {|p| on_board_click(p) }
67     @board.observe(:drag) {|data| on_board_drag(data) }
68     @board.observe(:drop) {|data| on_board_drop(data) }
69     @pools.each do |col, pool|
70       pool.observe(:drag) {|data| on_pool_drag(col, data) }
71       pool.observe(:drop) {|data| on_pool_drop(col, data) }
72     end
73     @clocks.each do |col, clock|
74       clock.data = { :color => col,
75                      :player => match.player(col).name }
76     end
77     @noncontrolled = @match.observe(:move) do |data|
78       unless @controlled[data[:player].color] == data[:player]
79         animate(:forward, data[:state], data[:move])
80         @board.highlight(data[:move])
81         @clocks[data[:old_state].turn].stop
82         @clocks[data[:state].turn].start
83       end
84     end
85     
86     @clocks[@match.game.players.first].active = true
87     @table.flip(@color != @match.game.players.first)
88     
89     if @match.history.move
90       @board.highlight(@match.history.move)
91     end
92   end
93   
94   def perform!(move, opts = {})
95     col = @match.state.turn
96     if @controlled[col] and @match.move(@controlled[col], move)
97       animate(:forward, @match.state, move, opts)
98       @board.highlight(move)
99       
100       @clocks[col].stop
101       @clocks[@match.state.turn].start
102     end
103   end
104   
105   def back
106     state, move = @match.history.back
107     animate(:back, state, move)
108     @board.highlight(@match.history.move)
109   rescue History::OutOfBound
110     puts "error: first move"
111   end
112   
113   def forward
114     state, move = @match.history.forward
115     animate(:forward, state, move)
116     @board.highlight(move)
117   rescue History::OutOfBound
118     puts "error: last move"
119   end
120   
121   def go_to(index)
122     cur = @match.history.current
123     state, move = @match.history.go_to(index)
124     if index > cur
125       (cur + 1..index).each do |i|
126         animate(:forward, @match.history[i].state, @match.history[i].move)
127       end
128       @board.highlight(move)
129     elsif index < cur
130       (cur).downto(index + 1).each do |i|
131         animate(:back, @match.history[i - 1].state, @match.history[i].move)
132       end
133       @board.highlight(@match.history.move)
134     end
135   rescue History::OutOfBound
136     puts "error: no such index #{index}"
137   end
138   
139   def animate(direction, state, move, opts = {})
140     anim = @animator.send(direction, state, move, opts)
141     @field.run anim
142     
143     update_pools
144   end
145   
146   def update_pools
147     @pools.each do |col, pool|
148       anim = pool.animator.warp(@match.state.pool(col))
149       @field.run anim
150     end
151   end
152   
153   def on_board_drop(data)
154     if data[:src]
155       move = nil
156       
157       if data[:src] == data[:dst]
158         @board.selection = data[:src]
159       elsif data[:dst]
160         # normal move
161         move = @policy.new_move(
162           @match.state, data[:src], data[:dst])
163         validate = @match.game.validator.new(@match.state)
164         validate[move]
165       end
166       
167       if move and move.valid?
168         @board.add_to_group data[:item]
169         @board.lower data[:item]
170         perform! move, :adjust => true
171       else
172         cancel_drop(data)
173       end
174     elsif data[:index] and data[:dst]
175       # actual drop
176       move = @policy.new_move(
177         @match.state, nil, data[:dst], 
178         :dropped => data[:item].name)
179       validate = @match.game.validator.new(@match.state)
180       if validate[move]
181         @board.add_to_group data[:item]
182         @board.lower data[:item]
183         perform! move, :dropped => data[:item]
184       else
185         cancel_drop(data)
186       end
187     end
188   end
189   
190   def on_board_drag(data)
191     if @policy.movable?(@match.state, data[:src]) and 
192        movable?(data[:src])
193       @board.raise data[:item]
194       @board.remove_from_group data[:item]
195       @board.selection = nil
196       @scene.on_drag(data)
197     end
198   end
199   
200   def on_pool_drag(c, data)
201     if @policy.droppable?(@match.state, c, data[:index]) and 
202        droppable?(c, data[:index])
203        
204        
205       # replace item with a correctly sized one
206       item = @board.create_piece(data[:item].name)
207       @board.raise item
208       @board.remove_from_group item
209       anim = @pools[c].animator.remove_piece(data[:index])
210       data[:item] = item
211       data[:size] = @board.unit
212       data[:pool_color] = c
213       
214       @scene.on_drag(data)
215       
216       @field.run anim
217     end
218   end
219   
220   def on_pool_drop(color, data)
221     cancel_drop(data)
222   end
223   
224   def cancel_drop(data)
225     anim = if data[:index]
226       # remove dragged item
227       data[:item].remove
228       # make original item reappear in its place
229       @pools[data[:pool_color]].animator.insert_piece(
230         data[:index],
231         data[:item].name)
232     elsif data[:src]
233       @board.add_to_group data[:item]
234       @board.lower data[:item]
235       @animator.movement(data[:item], nil, data[:src], Path::Linear)
236     end
237     
238     @field.run(anim) if anim
239   end
240   
241   def on_time(time)
242     time.each do |pl, seconds|
243       @clocks[pl].clock ||= Clock.new(seconds, 0, nil)
244       @clocks[pl].clock.set_time(seconds)
245     end
246   end
247   
248   def on_close(data)
249     @clocks.each do |pl, clock|
250       clock.stop
251     end
252     @controlled = { }
253   end
254   
255   def add_controlled_player(player)
256     @controlled[player.color] = player
257   end
258   
259   def color=(value)
260     if @match
261       # ignore further moves from other players
262       @match.delete_observer(@noncontrolled)
263       @match.close 
264     end
265     @color = value
266     if @color
267       @controlled = { @color => self }
268     end
269   end
270     
271   def movable?(p)
272     can_play?
273   end
274   
275   def droppable?(color, index)
276     can_play?
277   end
278   
279   private
280   
281   def can_play?
282     return false unless @controlled[@match.state.turn]
283     if @match.history.current < @match.index
284       @match.editable?
285     else
286       true
287     end
288   end