1 # Copyright (c) 2009 Paolo Capriotti <p.capriotti@gmail.com>
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 'interaction/match'
13 # This class represents a remote player on ICS.
19 attr_reader :color, :name
22 # Create a new ICS player playing with the given color and using the given
23 # output channel to send moves.
25 # The out parameter is a Proc that will be used to send data to the server.
27 def initialize(out, color, match, match_info)
31 @serializer = match.game.serializer.new(:simple)
32 @match_info = match_info
33 @name = match_info[color][:name]
34 @expected_navigations = []
38 # Send a move to the server.
41 text = @serializer.serialize(data[:move],
47 # Send the server a request to move backwards.
51 add_expected_navigation(opts)
55 # Send the server a request to move forward.
59 add_expected_navigation(opts)
63 # Send a navigation request to the server.
66 delta = data[:index] - data[:old_index]
67 add_expected_navigation(data) unless delta == 0
69 @out["forward #{delta}"]
71 @out["back #{-delta}"]
76 # Use the takeback command to request an undo.
78 def allow_undo?(player, manager)
82 manager.undo(self, nil)
86 # Process an incoming style12 event.
88 def on_style12(style12)
89 @match.update_time(style12.time)
90 delta = style12.move_index - @match.index
92 # get previously stored revert information
93 revert_to = @match_info[:about_to_revert_to]
94 @match_info.delete(:about_to_revert_to)
96 if revert_to and revert_to != style12.move_index
97 warn "ICS inconsistency: style12 and revert message refer to" +
98 "different indexes (resp. #{style12.move_index} and #{revert_to}"
101 # check expected navigations
102 exp = @expected_navigations.shift
104 if exp != style12.move_index || revert_to
105 # unexpected navigation, clear expected queue
106 @expected_navigations = []
109 @match_info[:icsapi].
110 same_state(style12.state,
111 @match.history[exp].state)
112 # we were expecting this, no need to take further action
119 # standard case: advancing forward by 1
120 move = @serializer.deserialize(style12.last_move_san, @match.state)
122 # An invalid move can happen when the game used locally does not
123 # correspond to the actual played game.
124 # This is sometimes inevitable, since ICS does not send a header
125 # when beginning examination.
126 # In this case, force the move into the history, and be careful to
127 # use the SAN provided in the style12 event for rendering.
128 warn "Received invalid move from ICS: #{style12.last_move_san}"
129 move = @match_info[:icsapi].parse_last_move(style12.last_move, style12.state.turn)
130 @match.history.add_move(style12.state, move, :text => style12.last_move_san)
132 # Perform and store a new move.
133 @match.move(self, move)
134 unless @match_info[:icsapi].
135 same_state(style12.state,
137 @match.history.state = style12.state.dup
141 move = if style12.move_index > 0
142 @serializer.deserialize(
143 style12.last_move_san,
144 @match.history[style12.move_index - 1].state)
146 state = @match_info[:icsapi].
147 amend_state(@match.history[style12.move_index].state,
149 if @match.navigable? && (!revert_to)
150 @match.history.go_to(style12.move_index)
151 @match.history.set_item(state, move)
153 @match.history.remove_items_at(style12.move_index + 1)
154 @match.history.set_item(state, move)
157 move = @match_info[:icsapi].parse_last_move(style12.last_move, style12.state.turn)
159 @match.history.add_move(style12.state, move, :text => style12.last_move_san)
161 warn "Invalid last move #{style12.last_move}"
168 def add_expected_navigation(opts = {})
169 @expected_navigations << @match.index unless opts[:awaiting_server]