Fix bug in xboard engine command parsing.
[kaya.git] / lib / plugins / engines / xboard.rb
blob6810f574fd83d46cd2a55601104d57230d5e93b3
1 require 'plugins/plugin'
3 class XBoardEngine
4   include Plugin
5   include Observer
6   include Player
7   
8   plugin :name => 'XBoard Engine Protocol',
9          :protocol => 'XBoard',
10          :interface => :engine
11   
12   FEATURES = %w(ping setboard playother san usermove time draw sigint sigterm
13                 reuse analyze myname variants colors ics name pause done)
15   attr_reader :name, :color
16          
17   def initialize(path, name, color, match, opts = {})
18     @name = name
19     @color = color
20     @match = match
21     @path = path
22     @opts = opts
23     @playing = false
24     @serializer = @match.game.serializer.new(:compact)
25     @features = { }
26     
27     @engine = KDE::Process.new
28     @engine.on(:readyReadStandardOutput) { process_input }
29     @engine.on(:started) { on_started }
30     @engine.on('finished(int, QProcess::ExitStatus)') { on_quit }
31     
32     @command_queue = []
33   end
34   
35   def on_move(data)
36     text = @serializer.serialize(data[:move], data[:old_state])
37     send_command text
38     unless @playing
39       send_command "go"
40       @playing = true
41     end
42   end
43   
44   def start
45     @engine.working_directory = @opts[:workdir] if @opts[:workdir]
46     @engine.output_channel_mode = KDE::Process::OnlyStdoutChannel
47     @engine.set_program(@path, @opts[:args] || [])
48     @engine.start
49     
50     @match.register(self)
51     setup
52   end
53   
54   def setup
55     @match.observe(:started) do
56       send_command "new"
57       send_command "force"
58       if @color == :white
59         send_command "go"
60         @playing = true
61       end
62     end
63     send_command "xboard"
64     send_command "protover 2"
65     send_command "nopost"
66   end
67   
68   def send_command(text)
69     if @engine.state == Qt::Process::Running
70       begin
71         os = Qt::TextStream.new(@engine)
72         os << text << "\n"
73         puts "> #{text}" if @opts[:debug]
74       ensure
75         os.flush
76       end
77     else
78       @command_queue << text
79     end
80   end
81   
82   def process_input
83     while @engine.can_read_line
84       line = @engine.read_line.to_s
85       line.gsub!(/\r?\n?$/, '')
86       puts "< #{line}" if @opts[:debug]
87       process_command(line)
88     end
89   end
90   
91   def process_command(text)
92     args = text.split(/\s+/)
93     cmd = args[0]
94     m = "on_command_#{cmd}"
95     if respond_to?(m)
96       send(m, *args[1..-1])
97     else
98       extra_command(text)
99     end
100   end
101   
102   def on_command_feature(*args)
103     args.each do |arg|
104       if arg =~ /^(\S+)=(\S+)$/
105         feature = $1
106         value = $2[1...-1]
107         if FEATURES.include?(feature)
108           @features[feature] = value == '1' ? true : value
109           send_command "accepted #{feature}"
110         else
111           send_command "rejected #{feature}"
112         end
113       end
114     end
115   end
116   
117   def on_command_move(move)
118     move = @serializer.deserialize(move, @match.history.state)
119     if move
120       @match.move(self, move)
121     end
122   end
123   
124   def extra_command(text)
125     if text =~ /^My move is: (.*)$/
126       on_command_move($1)
127     end
128   end
129   
130   def on_started
131     @command_queue.each do |cmd|
132       send_command cmd
133     end
134     @command_queue = []
135     
136     @match.start(self)
137   end
138   
139   def on_quit
140   end
141   
142   def on_close(data)
143     send_command "quit"
144   end