Match now accepts moves from a controller.
[kaya.git] / lib / plugins / ics / lib / match_helper.rb
blobf977f1d0fdd32dc738d8cfbf9604494c02315d8e
1 # Copyright (c) 2010 Paolo Capriotti <p.capriotti@gmail.com>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 require_bundle 'ics', 'style12'
9 require 'singleton'
11 module ICS
13
14 # Helper mixin to create and start ICS matches.
15
16 module MatchHelper
17   #
18   # Create an appropriate MatchHelper instance for the given style12
19   # 
20   def self.from_style12(style12)
21     @@helpers ||= {}.tap do |h|
22       h[Style12::Relation::EXAMINING] = ExaminingMatchHelper.instance
23       h[Style12::Relation::NOT_MY_MOVE] = DefaultMatchHelper.instance
24       h[Style12::Relation::MY_MOVE] = h[Style12::Relation::NOT_MY_MOVE]
25       h[Style12::Relation::OBSERVING_PLAYED] = ObservingMatchHelper.instance
26     end
27     
28     @@helpers[style12.relation]
29   end
30   
31   # 
32   # Get the helper instance for this type.
33   # 
34   # Currently supported types: :default, :examining, :observing.
35   # 
36   def self.get(type)
37     ICS.const_get(type.to_s.capitalize + "MatchHelper").instance
38   end
39   
40   # 
41   # Create the opponent player. By default, create an ICSPlayer that
42   # will respond to future style12 events.
43   # 
44   def create_opponent(protocol, color, match_info)
45     send = lambda {|msg| protocol.connection.send_text(msg) }
46     opponent = ICSPlayer.new(send, color, match_info[:match], match_info)
47     match_info[:icsplayer] = opponent
48     opponent
49   end
50   
51   # 
52   # Create a player for this match.
53   # 
54   def create_player(user, color, match_info)
55     raise "not implemented"
56   end
57   
58   # 
59   # Create a new match instance.
60   # 
61   def create_match(handler, match_info)
62     raise "not implemented"
63   end
64   
65   # 
66   # Close a match.
67   # 
68   def close_match(handler, match_info)
69     handler.matches.delete(match_info[:game_number])
70   end
71   
72   # 
73   # Get user from the view. The default implementation
74   # simply returns the controller of the main ics view.
75   # 
76   def get_user(view, match_info)
77     view.main.controller
78   end
79   
80   # 
81   # Perform post-creation initialization for players.
82   # 
83   def setup_players(user, players)
84   end
85   
86   # 
87   # Return or create a match. Called on an existing match when a new style12 
88   # event is received.
89   # If no match for that style12 event is found, the match argument
90   # will be nil.
91   # 
92   def get_match(protocol, match_info, style12)
93     match_info
94   end
95   
96   # 
97   # Return an array of colors to be assigned to the players.
98   # 
99   def colors(state, rel)
100     turns = [state.turn, state.opposite_turn(state.turn)]
101     if rel == Style12::Relation::NOT_MY_MOVE
102       turns.reverse
103     else
104       turns
105     end
106   end
107   
108   # 
109   # Start an existing match. Called when the first style12
110   # for the given match is received.
111   # 
112   def start(protocol, view, match_info, style12)
113     rel = style12.relation
114     state = style12.state
115     turns = [state.turn, state.opposite_turn(state.turn)]
116     
117     user_color, opponent_color = colors(state, rel)
118         
119     # create players
120     opponent = create_opponent(protocol, opponent_color, match_info)
121     user = get_user(view, match_info)
122     player = create_player(user, user_color, match_info)
123     setup_players(user, [player, opponent])
124     
125     # start match
126     match = match_info[:match]
127     match.register(player)
128     match.register(opponent)
129     match.start(player)
130     match.start(opponent)
131     raise "couldn't start match" unless match.started?
132     
133     # reset controller
134     user.reset(match)
135     
136     # set initial state and time
137     unless match_info[:icsapi].same_state(match.state, style12.state)
138       match.history.state = style12.state
139     end
140     match.update_time(style12.time)
141     
142     # activate view
143     view.activate(user)
144   end
148 # Helper class to setup normal ICS games.
150 # The first player is the default user, and the opponent is a default
151 # ICSPlayer instance created by create_opponent.
153 class DefaultMatchHelper
154   include MatchHelper
155   include Singleton
157   def create_player(user, color, match_info)
158     # do not create a new player, just return user
159     user.color = color
160     user.premove = true
161     user.name = match_info[color][:name]
162     user
163   end
164   
165   def create_match(handler, match_info)
166     match = Match.new(match_info[:game], 
167         :kind => :ics,
168         :editable => false,
169         :time_running => true)
170     match.on(:close) { close_match(handler, match_info) }
171     match_info.merge(:match => match)
172   end
173   
174   def close_match(handler, match_info)
175     super(handler, match_info)
176     handler.protocol.connection.send_text("resign")
177   end
181 # Helper class to setup matches in examination mode.
183 # The first player is dummy, and the user is set to control both
184 # the dummy player and the opponent. This allows the user to perform
185 # moves for both players.
187 class ExaminingMatchHelper
188   include MatchHelper
189   include Singleton
190   
191   def create_player(user, color, match_info)
192     user.color = nil
193     
194     # create a controlled player
195     player = DummyPlayer.new(color)
196     player.name = match_info[color][:name]
197     user.premove = false
198     match_info[:match].add_observer(user)
199     player
200   end
201   
202   def setup_players(user, players)
203     players.each do |player|
204       user.add_controlled_player(player)
205     end
206   end
208   def get_match(handler, match_info, style12)
209     if match_info.nil?
210       # Examined games on ics have no header, so we have to be prepared to
211       # create a new match on the fly at this point.
212       # Create an editable Game.dummy match for the moment.
213       match_info = create_match(handler, style12.match_info)
214       
215       # We want to change the game type at some point, so request the game
216       # movelist to the server.
217       handler.protocol.connection.send_text('moves')
218     end
219     
220     match_info
221   end
222   
223   def create_match(handler, match_info)
224     match = Match.new(Game.dummy, 
225       :kind => :ics,
226       :editable => true,
227       :navigable => true)
228     match.on(:close) { close_match(handler, match_info) }
229     match_info.merge(:match => match)
230   end
231   
232   def close_match(handler, match_info)
233     super(handler, match_info)
234     handler.protocol.connection.send_text("unexamine")
235   end
239 # Helper class to setup matches in observation mode.
241 # Match is set to not editable and navigable, first player is dummy,
242 # no controlled player for the user.
244 class ObservingMatchHelper
245   include MatchHelper
246   include Singleton
247   
248   def create_player(user, color, match_info)
249     player = DummyPlayer.new(color)
250     player.name = match_info[color][:name]
251     user.premove = false
252     match_info[:match].add_observer(user)
253     player
254   end
255   
256   def create_match(handler, match_info)
257     match = Match.new(Game.dummy,
258                       :kind => :ics,
259                       :editable => false,
260                       :navigable => false)
261     match.on(:close) { close_match(handler, match_info) }
262     match_info.merge(:match => match)
263   end
264   
265   def close_match(handler, match_info)
266     super(handler, match_info)
267     handler.protocol.connection.send_text("unobserve #{match_info[:number]}")
268   end
269   
270   def get_user(view, match_info)
271     v = view.create(:name => match_name(match_info),
272                     :activate => true)
273     v.controller
274   end
275   
276   def match_name(match_info)
277     "#{match_info[:white][:name]} - #{match_info[:black][:name]}"
278   end