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