Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/chessengine.py
blob: 78db952430713e7fa8a946d8ab30596809a8659a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#
#    Ceibal Chess - A chess activity for Sugar.
#    Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
#
#   This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#

import os
import sys
from subprocess import Popen, PIPE
from piece import Move
from errors import IAError

import logging
log = logging.getLogger()

class GnuChessEngine:
        '''GNU Chess wrapper class.'''
        def __init__(self):
                '''Create a new instance of the GNU Chess wrapper, locate the
                gnuchess executable, open a pipe to it and setup the comm.'''
                try:
                        path = os.path.join(os.environ["SUGAR_BUNDLE_PATH"],"engines")
                        if not "Ajedrez.activity" in path:
                                print "Runningn ceibal-chess from Terminal or some other place"
                                path = os.path.join("", "engines")
                except:
                        path = os.path.join(".", "engines")

                self.plat = sys.platform
                if  self.plat == "linux2":
                        engine_exec = "gnuchess-linux"
                elif self.plat == "darwin":
                        engine_exec = "gnuchess-osx"
                elif self.plat == "win32":
                        engine_exec = "gnuchess-win32.exe"
                else:
                        log.warn("No gnuchess for %s, using system default", plat)
                        engine_exec = ""

                if engine_exec != "":
                        engine_path = os.path.join(path, engine_exec)
                else:
                        log.info("Trying to find gnuchess in PATH")
                        engine_path = "gnuchess"

                #Check whether the engine is executable:
                if not os.access(engine_path.split()[0], os.X_OK):
                        log.error("Engine is not executable, try: chmod +x %s",
                                          engine_path.split()[0])
                        raise IOError("Chess engine is not executable.")

                args = [engine_exec, '-e', '-x']

                try:
                        if self.plat == "win32":
                                self.proc = Popen(args, executable=os.path.abspath(engine_path), stdin=PIPE, stdout=PIPE)
                        else:
                                self.proc = Popen(args, executable=os.path.abspath(engine_path), stdin=PIPE, stdout=PIPE, close_fds=True)
                        self.fin = self.proc.stdout
                        self.fout = self.proc.stdin
                        
			#Check pipe:
                        self.fout.write("\n")
                        self.fout.flush()
                        self.fin.readline()
                        self.fin.readline()
                        self.fin.readline()
                        self.fout.write("depth 1\n")
                except Exception, ex:
			print ex
                        self.close()
                        raise

        def undo(self):
                # undo ai move
                self.fout.write('undo\n')
                # undo player move
                self.fout.write('undo\n')
                self.fout.flush()

        def move(self, move, controller):
                '''Write a player's move to GNU Chess. Return the engine's move.'''
                move_str = self.move_to_gnuchess(move)

                log.debug("Calling GNU Chess with move: %s", move_str)

                self.fout.write(move_str + "\n")
                self.fout.flush()
                l = self.fin.readline()
                while l.find("My move is") == -1:
                        if l.find("Illegal move") != -1:
                                raise IAError( \
                                        "Player performed an illegal move: (%s), move was: %s" %
                                                (l, move_str))
                        l = self.fin.readline()
                ans = l.split()[3]
                log.debug("got answer from gnuchess '%s'", l)

                chess_ans = None
                type = None

                if len(ans) == 4:
                        chess_ans = (self.gnuchess_to_coords(ans[:2]), \
                                                self.gnuchess_to_coords(ans[2:]))
                elif len(ans) == 5:
                        chess_ans = (self.gnuchess_to_coords(ans[:2]), \
                                                self.gnuchess_to_coords(ans[2:4]))
                        type = ans[4].upper()
                else:
                        raise IAError("Unknown answer from gnuchess: %s" % (ans))

                controller.move(controller.board.black, chess_ans[0], chess_ans[1], type=type)

        def close(self):
                try:
                        if self.fout:
                                self.fout.write("quit\n")
                                self.fout.flush()
                                self.fout.close()
                                self.fout = None
                        if self.fin:
                                self.fin.close()
                                self.fin = None
                finally:
                        if self.proc:
                                try:
                                        if self.plat == "win32":
                                                pass
                                                #os.system("taskkill /PID %s" %self.proc.pid)
                                                #FIXME: Add something to kill the process in Windows OS,
                                                #the os.system solution does not work.
                                        else:
                                                os.kill(self.proc.pid, 15)
                                except OSError:
                                        pass
                                self.proc.wait()
                                self.proc = None
       
        def move_to_gnuchess(self, move):
                return str(move)

        def gnuchess_to_coords(self, move):
                letters = ["a", "b", "c", "d", "e", "f", "g", "h"]
                c = letters.index(move[0])
                r = int(8 - int(move[1]))
                return (c,r)
               
        def assert_sync(self, board):
                '''
                Validate whether the AI's internal representation of the board
                matches our board.
               
                Raises IAError (games out of sync) on error, otherwise, just returns.
               
                This is rather expensive and should only be perfomed
                in debug mode.
                '''
                log.debug("Called IA.assert_sync...")
                try:
                        self.fout.write("show board\n")
                        self.fout.write("\n")
                        self.fout.flush()
                       
                        log.debug("read 1...")
                        self.fin.readline()
                       
                        log.debug("read 2...")
                        ai_turn = self.fin.readline().split(" ")[0]
                       
                        #if ai_turn != board.current_turn.name:
                        #       raise IAError("Turns out of sync!")
                       
                        line = ""
                        ai_row = []
                        while len(ai_row) < 8:
                                line = self.fin.readline().replace("\n", "").strip()
                                ai_row = line.split(" ")

                        for row in range(8):
                                log.debug("row %d: %s" % (row, line))

                                for col in range(8):
                                        ai_cell = ai_row[col]
                                        if ai_cell == ".":
                                                if not board[col, row].piece:
                                                        continue
                                                raise AIError( \
                                                        "Out of sync: AI thinks (%d, %d) should be empty" %
                                                                (col, row))                                            
                                       
                                        if ai_cell.upper() != board[col, row].piece.CODE:
                                                raise AIError("Out of sync: piece at (%d, %d) differs" %
                                                        (col, row))
                                       
                                        if ai_cell.islower() and board[col, row].piece.owner.name != "black":
                                                raise AIError("Out of sync: owner at (%d, %d) differs" %
                                                        (col, row))
                                       
                                        if ai_cell.isupper() and board[col, row].piece.owner.name != "white":
                                                raise AIError("Out of sync: owner at (%d, %d) differs" %
                                                        (col, row))
                               
                                if row < 7:
                                        line = self.fin.readline().replace("\n", "").strip()
                                        ai_row = line.split(" ")
                       
                        log.debug("read 10...")
                        self.fin.readline()


                        log.debug("read 11...")
                        self.fin.readline()
                       
                except IOError, err:
                        raise IAError("Could not talk to engine: %s" % err.message )