rubyknight 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,4 @@
1
+ RubyKnight
2
+ ==========
3
+
4
+ RubyKnight is a very naive implementation of a chess library and engine.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = "rubyknight"
5
+ s.executables = ["rubyknight","rubyknight-xboard"]
6
+ s.summary = "Naive chess library and engine written in ruby"
7
+ s.email = "chris@inarow.net"
8
+ s.homepage = "http://github.com/cdmoyer/RubyKnight"
9
+ s.description = "Naive chess library and engine written in ruby. Includes rubyknight and rubyknight-xboard executables"
10
+ s.authors = ["Chris Moyer"]
11
+ end
12
+ Jeweler::GemcutterTasks.new
13
+ rescue LoadError
14
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
15
+ end
16
+
data/TODO ADDED
@@ -0,0 +1 @@
1
+ TODO: add castling to king moves (mark uncastling in move())
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/rubyknight ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubyknight'
4
+
5
+ def time_it label
6
+ yield
7
+ end
8
+
9
+ module Enumerable
10
+ def rand
11
+ self[Kernel.rand(self.size)]
12
+ end
13
+ end
14
+
15
+ def displayb b, evaluator
16
+ puts "#{b.to_s}\n"
17
+ moves = b.gen_legal_moves
18
+ puts "Score: #{evaluator.eval_position}"
19
+ # puts "Moves: #{moves.size}"
20
+ # i=1
21
+ # moves.each do |m|
22
+ # print m.join(',')
23
+ # if i % 13 == 0 then print "\n"
24
+ # else print ' ' end
25
+ # i+=1
26
+ # end
27
+ # print "\n"
28
+ if moves.size == 0
29
+ puts "Checkmate, you lose."
30
+ Kernel.exit 0
31
+ end
32
+ print "Enter move [#{b.white_to_play? ? 'White' : 'Black'}]> "
33
+ end
34
+
35
+ Thread.abort_on_exception = true
36
+
37
+ def play b, eval
38
+ moves = b.gen_legal_moves
39
+ if moves.size == 0
40
+ puts "Checkmate, you win!"
41
+ Kernel.exit(0)
42
+ end
43
+ # move = moves.rand
44
+ @thinking = Thread.new { eval.think(b.white_to_play?) }
45
+ until eval.donethinking; sleep(1.2); end
46
+ move = eval.bestmove
47
+ b.cnotation_move "#{RubyKnight::Board.position_to_coord move[0]}#{RubyKnight::Board.position_to_coord move[1]}"
48
+ end
49
+
50
+ cplay = false
51
+ b = RubyKnight::Board.new
52
+ eval = RubyKnight::BoardEvaluator.new b
53
+ displayb b, eval
54
+ #['e2e4' , 'e7e5' , 'd2d3'].each do |move|
55
+ #['e2e4','d7d5','e4e5','f7f5'].each do |move|
56
+ $stdin.each do |move|
57
+ move.strip!
58
+ if move =~ /!([^ ]*)[ ]*(.*)/
59
+ case $1
60
+ when "quit" then Kernel.exit
61
+ when "undo" then b.undo 2
62
+ when "play"
63
+ cplay = !cplay
64
+ play(b, eval) if cplay
65
+ when "dump"
66
+ File.open( $2, "w") { |f| f.write( b.dump) }
67
+ puts "dumped."
68
+ when "load"
69
+ File.open( $2, "r") { |f| b.load( f.readlines.join) }
70
+ eval = RubyKnight::BoardEvaluator.new b
71
+ puts "loaded."
72
+ when "reset"
73
+ b = RubyKnight::Board.new
74
+ eval = RubyKnight::BoardEvaluator.new b
75
+ end
76
+ displayb b,eval
77
+ else
78
+ begin
79
+ b.cnotation_move move
80
+ play(b,eval) if cplay
81
+ rescue RubyKnight::IllegalMoveException
82
+ print "Enter a real move! #{$!.to_s}\n"
83
+ end
84
+ displayb b,eval
85
+ end
86
+ end
87
+
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubyknight'
4
+
5
+ confirm = false
6
+ $stdout.sync=true
7
+ $stderr.sync=true
8
+ Thread.abort_on_exception = true
9
+
10
+ def time_it label
11
+ yield
12
+ end
13
+
14
+ def logout msg
15
+ $stderr.print "Out:#{msg}\n"
16
+ print "#{msg}\n"
17
+ end
18
+
19
+ module Enumerable
20
+ def rand
21
+ self[Kernel.rand(self.size)]
22
+ end
23
+ end
24
+
25
+
26
+ def tocnote move
27
+ "#{RubyKnight::Board.position_to_coord move[0]}#{RubyKnight::Board.position_to_coord move[1]}"
28
+ end
29
+
30
+ def play b, eval
31
+ moves = b.gen_legal_moves
32
+ if moves.size == 0
33
+ puts "RESULT 1-0 {White Mates}"
34
+ end
35
+ thinking = Thread.new { eval.think(b.white_to_play?) }
36
+ until eval.donethinking; sleep(0.2); end
37
+ move = eval.bestmove
38
+ cnotation = tocnote move
39
+ b.cnotation_move cnotation
40
+ logout "move #{cnotation}"
41
+ end
42
+
43
+ b = RubyKnight::Board.new
44
+ eval = RubyKnight::BoardEvaluator.new b
45
+ $stdin.each do |move|
46
+ move.strip!
47
+ $stderr.print "In :#{move}\n"
48
+ case move
49
+ when "xboard" then
50
+ logout ""
51
+ logout "tellics set f5 1=1"
52
+ when "confirm_moves"
53
+ confirm = true
54
+ logout "Will now confirm moves."
55
+ when /.{0,1}new/
56
+ b = RubyKnight::Board.new
57
+ eval = RubyKnight::BoardEvaluator.new b
58
+ logout "tellics set 1 RubyKnight"
59
+ when /^protover/ then
60
+ logout "feature sigint=0 sigterm=0 ping=1 done=1"
61
+ when /^ping\s+(.*)/ then
62
+ logout "pong #{$1}"
63
+ when /^st/ then
64
+ when /^level/ then
65
+ when /^time/ then
66
+ when /^otim/ then
67
+ when "hard" then
68
+ when "random" then
69
+ when /^accepted/ then
70
+ # ignore
71
+ else
72
+ move.gsub!(/\?/, '')
73
+ begin
74
+ b.cnotation_move move
75
+ logout "Legal move: #{move}" if confirm
76
+ play(b,eval)
77
+ rescue RubyKnight::IllegalMoveException
78
+ logout "Illegal move: #{move}"
79
+ rescue Exception=> e
80
+ puts e
81
+ end
82
+ end
83
+ end
84
+
85
+
@@ -0,0 +1,9 @@
1
+ http://www.gamedev.net/reference/programming/features/chess1/
2
+ http://chess.verhelst.org/
3
+ http://www.maths.nott.ac.uk/personal/anw/G13GT1/compch.html
4
+ http://www.tim-mann.org/chess.html
5
+ http://www.byte.com/documents/s=8880/byt1062182594527/
6
+ http://www.chessbrain.net/beowulf/theory.html
7
+
8
+ Evaluation:
9
+ http://www.rpi.edu/~mooner/Tutorial/evaluation.html
@@ -0,0 +1,125 @@
1
+ module RubyKnight
2
+ class BoardEvaluator
3
+ attr_reader :bestmoves, :donethinking
4
+ def initialize board
5
+ @b = board
6
+ @bestmoves, @bestscore, @donethinking = nil, 0, false
7
+ end
8
+
9
+ # Pick a random move from equal options
10
+ def bestmove
11
+ if @bestmoves
12
+ return @bestmoves[Kernel.rand(@bestmoves.size)]
13
+ end
14
+ nil
15
+ end
16
+
17
+ def think for_white
18
+ @bestmoves, @bestscore, @donethinking = nil, 0, false
19
+ clone = Marshal.load(Marshal.dump( @b))
20
+ clone.gen_legal_moves.each do |move|
21
+ newb = Marshal.load(Marshal.dump( clone))
22
+ newb.move move[0], move[1], move[2]
23
+ result = eval_position newb
24
+ if (for_white and result > @bestscore) or
25
+ result < @bestscore or @bestmoves == nil
26
+ @bestscore = result
27
+ @bestmoves = [move]
28
+ elsif result == @bestscore
29
+ @bestmoves << move
30
+ end
31
+ end
32
+ @donethinking = true
33
+ end
34
+
35
+ # Return an int
36
+ # being the pawn advantage of white
37
+ def eval_position board=@b
38
+ material = time_it("eval_material") { eval_material(board)}
39
+ pawn_struct = time_it("eval_pawn_struct") { eval_pawn_struct(board)}
40
+ material + pawn_struct
41
+ end
42
+
43
+ def eval_pawn_struct board
44
+ advant = board.num_pieces(RubyKnight::Board::WPAWN) -
45
+ board.num_pieces(RubyKnight::Board::BPAWN)
46
+
47
+ wpawns, bpawns = get_pawn_array board
48
+ passed, isolated, doubled, chained = 0,0,0,0
49
+ (1..8).each do |rank|
50
+ w,wl,wr = wpawns[rank],wpawns[rank-1],wpawns[rank+1]
51
+ b,bl,br = bpawns[rank],bpawns[rank+1],bpawns[rank-1]
52
+
53
+ w.each {|c|passed+=1 if (b.size==0||c<b.min)&&(bl.size==0||c<=bl.min)&&(br.size==0||c<=br.min)}
54
+ b.each {|c|passed-=1 if (w.size==0||c>w.max)&&(wl.size==0||c>=wl.max)&&(wr.size==0||c>=wr.max)}
55
+
56
+ wchain = false
57
+ w.each { |c|
58
+ wl.each {|i| wchain = true if (c-i).abs <=1 }
59
+ wr.each {|i| wchain = true if (c-i).abs <=1 } unless wchain
60
+ }
61
+ bchain = false
62
+ b.each { |c|
63
+ bl.each {|i| bchain = true if (c-i).abs <= 1 }
64
+ br.each {|i| bchain = true if (c-i).abs <= 1 } unless bchain
65
+ }
66
+ #puts "rank=#{rank} wchain=#{wchain} bchain=#{bchain}"
67
+
68
+ isolated +=1 unless w.size>0 && wchain
69
+ isolated -=1 unless b.size>0 && bchain
70
+
71
+ chained +=1 if w.size>0 && wchain
72
+ chained -=1 if b.size>0 && bchain
73
+
74
+ doubled -= 1 if w.size > 1
75
+ doubled += 1 if b.size > 1
76
+ end
77
+
78
+ chained = if chained==0 then 0
79
+ elsif chained > 0 then 1
80
+ else -1 end
81
+ doubled = if doubled==0 then 0
82
+ elsif doubled > 0 then 1
83
+ else -1 end
84
+ #puts "#{advant} + #{passed} + #{doubled} + #{chained} - #{isolated}"
85
+ return advant + passed + doubled + chained - isolated
86
+ end
87
+
88
+ # make array of pawns[file][rank]
89
+ def get_pawn_array board
90
+ wpawns = Array.new(10)
91
+ bpawns = Array.new(10)
92
+ (0..9).each { |i| wpawns[i] = Array.new 0, 0}
93
+ (0..9).each { |i| bpawns[i] = Array.new 0, 0}
94
+ board.piece_positions(RubyKnight::Board::WPAWN).each \
95
+ { |p| wpawns[(p%8)+1] << (p/8) }
96
+ board.piece_positions(RubyKnight::Board::BPAWN).each \
97
+ { |p| bpawns[(p%8)+1] << (p/8) }
98
+ return wpawns, bpawns
99
+ end
100
+
101
+
102
+ def eval_material board
103
+ q, r, b, n, p = 9, 5, 3, 3, 1
104
+ white = p * board.num_pieces(RubyKnight::Board::WPAWN) +
105
+ q * board.num_pieces(RubyKnight::Board::WQUEEN) +
106
+ r * board.num_pieces(RubyKnight::Board::WROOK) +
107
+ b * board.num_pieces(RubyKnight::Board::WBISHOP) +
108
+ n * board.num_pieces(RubyKnight::Board::WKNIGHT)
109
+ black = p * board.num_pieces(RubyKnight::Board::BPAWN) +
110
+ q * board.num_pieces(RubyKnight::Board::BQUEEN) +
111
+ r * board.num_pieces(RubyKnight::Board::BROOK) +
112
+ b * board.num_pieces(RubyKnight::Board::BBISHOP) +
113
+ n * board.num_pieces(RubyKnight::Board::BKNIGHT)
114
+
115
+ white - black
116
+ end
117
+
118
+ def time_it label
119
+ start = Time.now
120
+ res = yield
121
+ #puts "TIMING( '#{label}=>#{res}'): #{Time.now - start} seconds"
122
+ res
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,333 @@
1
+ def time_it label
2
+ start = Time.now
3
+ res = yield
4
+ puts "TIMING( '#{label}'): #{Time.now - start} seconds"
5
+ res
6
+ end
7
+
8
+
9
+ class RubyKnight::Board
10
+ def gen_legal_moves
11
+ moves = nil
12
+ time_it("gen_moves"){ moves = gen_moves(@to_play)}
13
+ time_it("legal filtering"){ moves = prune_king_revealers(@to_play,moves)}
14
+ moves
15
+ end
16
+
17
+ # TODO: I am so slow, that I should die, probably in gen_moves
18
+ def prune_king_revealers player, moves
19
+ kpiece = player==WHITE ? WKING : BKING
20
+ moves.select do |to_try|
21
+ move to_try[0], to_try[1], to_try[2], false
22
+ next_moves = gen_moves @to_play
23
+ king, = bits_to_positions(@bitboards[kpiece])
24
+ ret = true
25
+ next_moves.each do |m|
26
+ if m[1] == king
27
+ ret = false
28
+ break
29
+ end
30
+ end
31
+ undo 1
32
+ ret
33
+ end
34
+ end
35
+
36
+ # broken?
37
+ def prune_king_revealers_old player, moves
38
+ kpiece = player==WHITE ? WKING : BKING
39
+ piecemod = player==WHITE ? BPAWN : 0
40
+ moves.select do |to_try|
41
+ move to_try[0], to_try[1], to_try[2], false
42
+ king, = bits_to_positions(@bitboards[kpiece])
43
+ dead_king = false
44
+ rank = king / 8
45
+ file = king % 8
46
+ #check up and down for R or Q
47
+ [-8,-1,1,8].each do |inc|
48
+ limit = 8
49
+ trying = king + inc
50
+ while !dead_king and limit > 0 and
51
+ trying >= 0 and trying <= 63 and
52
+ (rank == (trying / 8) or
53
+ file == (trying % 8)) do
54
+ target = whats_at trying
55
+ if target
56
+ if (target == (WROOK+piecemod) or
57
+ target == (WQUEEN+piecemod))
58
+ dead_king = true
59
+ end
60
+ limit = 0
61
+ else
62
+ trying += inc
63
+ limit -= 1
64
+ end
65
+ end
66
+ end unless dead_king
67
+ #check diagonals for Q, B
68
+ [-9,-7,7,9].each do |inc|
69
+ limit = 8
70
+ trying = king + inc
71
+ rank = trying / 8
72
+ lastrank = king / 8
73
+ while !dead_king and limit > 0 and
74
+ trying >= 0 and trying <= 63 and
75
+ (lastrank - rank).abs == 1 do
76
+ target = whats_at trying
77
+ if target
78
+ if (target == (WBISHOP+piecemod) or
79
+ target == (WQUEEN+piecemod))
80
+ dead_king = true
81
+ end
82
+ limit = 0
83
+ else
84
+ lastrank = rank
85
+ trying += inc
86
+ rank = trying / 8
87
+ limit -= 1
88
+ end
89
+ end
90
+ end unless dead_king
91
+ #check 2 P launch zones
92
+ #check 8 N attack spots
93
+ undo 1
94
+ !dead_king
95
+ end
96
+ end
97
+
98
+ def gen_moves player
99
+ white = player==WHITE
100
+ if 1
101
+ gen_pawn_moves(white) +
102
+ gen_knight_moves(white) +
103
+ gen_rook_moves(white) +
104
+ gen_bishop_moves(white) +
105
+ gen_king_moves(white) +
106
+ gen_queen_moves(white)
107
+ else
108
+ time_it("gen_pawn") { gen_pawn_moves(white)} +
109
+ time_it("gen_knight") { gen_knight_moves(white)} +
110
+ time_it("gen_rook") { gen_rook_moves(white)} +
111
+ time_it("gen_bishop") { gen_bishop_moves(white)} +
112
+ time_it("gen_king") { gen_king_moves(white)} +
113
+ time_it("gen_queen") {gen_queen_moves(white)}
114
+ end
115
+ end
116
+
117
+ def different_colors white, piece
118
+ (white and !is_white piece) or
119
+ (!white and is_white piece)
120
+ end
121
+
122
+ def gen_rook_type_moves white, piece, start_limit = 8
123
+ moves = []
124
+ rank = piece / 8
125
+ file = piece % 8
126
+ [-8,-1,1,8].each do |inc|
127
+ limit = start_limit
128
+ trying = piece + inc
129
+ while limit > 0 and
130
+ trying >= 0 and trying <= 63 and
131
+ (rank == (trying / 8) or
132
+ file == (trying % 8)) do
133
+ target = whats_at trying
134
+ if !target
135
+ moves << [piece, trying]
136
+ elsif different_colors( white, target)
137
+ moves << [piece, trying]
138
+ break
139
+ else
140
+ break
141
+ end
142
+ trying += inc
143
+ limit -= 1
144
+ end
145
+ end
146
+ moves
147
+ end
148
+
149
+ def gen_rook_moves white
150
+ moves = []
151
+ rooks = @bitboards[ white ? WROOK : BROOK]
152
+ bits_to_positions(rooks).each do |r|
153
+ moves += gen_rook_type_moves( white, r)
154
+ end
155
+ moves
156
+ end
157
+
158
+ def gen_bishop_type_moves white, piece, start_limit = 8
159
+ moves = []
160
+ [-9,-7,7,9].each do |inc|
161
+ limit = start_limit
162
+ trying = piece + inc
163
+ rank = trying / 8
164
+ lastrank = piece / 8
165
+ while limit > 0 and
166
+ trying >= 0 and trying <= 63 and
167
+ (lastrank - rank).abs == 1 do
168
+ target = whats_at trying
169
+ if !target
170
+ moves << [piece, trying]
171
+ elsif different_colors( white, target)
172
+ moves << [piece, trying]
173
+ break
174
+ else
175
+ break
176
+ end
177
+ lastrank = rank
178
+ trying += inc
179
+ rank = trying / 8
180
+ limit -= 1
181
+ end
182
+ end
183
+ moves
184
+ end
185
+
186
+ def gen_bishop_moves white
187
+ moves = []
188
+ bishops = @bitboards[white ? WBISHOP : BBISHOP]
189
+ bits_to_positions(bishops).each do |r|
190
+ moves += gen_bishop_type_moves( white, r)
191
+ end
192
+ moves
193
+ end
194
+
195
+ def gen_queen_moves white
196
+ moves = []
197
+ queens = @bitboards[white ? WQUEEN : BQUEEN]
198
+ bits_to_positions(queens).each do |r|
199
+ moves += gen_rook_type_moves(white, r)
200
+ moves += gen_bishop_type_moves( white, r)
201
+ end
202
+ moves
203
+ end
204
+
205
+ # TODO: how much time is wasted with the each? We'll never have
206
+ # multiple kings, will we?
207
+ def gen_king_moves white
208
+ moves = []
209
+ kings = @bitboards[white ? WKING : BKING]
210
+ bits_to_positions(kings).each do |king|
211
+ moves += gen_rook_type_moves( white, king, 1)
212
+ moves += gen_bishop_type_moves( white, king, 1)
213
+ moves += gen_castle_moves( white, king)
214
+ end
215
+ moves
216
+ end
217
+
218
+ def gen_castle_moves white, king
219
+ color = white ? WHITE : BLACK
220
+ goodcastles = []
221
+ # kingside
222
+ if can_castle color, KINGSIDE
223
+ test = if white then [60,61,62]
224
+ else [4,5,6] end
225
+ if !whats_at(test[1]) and !whats_at(test[2])
226
+ left = prune_king_revealers(@to_play,
227
+ test.map {|dest| [test[0], dest]})
228
+ if left.size == 3
229
+ goodcastles << [test[0],test[2]]
230
+ end
231
+ end
232
+ end
233
+ # queenside
234
+ if can_castle color, QUEENSIDE
235
+ test = if white then [60,59,58]
236
+ else [4,3,2] end
237
+ if !whats_at(test[1]) and !whats_at(test[2])
238
+ left = prune_king_revealers(@to_play,
239
+ test.map {|dest| [test[0], dest]})
240
+ if left.size == 3
241
+ goodcastles << [test[0],test[2]]
242
+ end
243
+ end
244
+ end
245
+ goodcastles
246
+ end
247
+
248
+ def gen_knight_moves white
249
+ moves = []
250
+ knights = @bitboards[white ? WKNIGHT : BKNIGHT]
251
+ bits_to_positions(knights).each do |k|
252
+ [-17, -15, -10, -6, 6, 10, 15, 17].each do |m|
253
+ target = k+m
254
+ if target >= 0 and target <= 63 and
255
+ ((target % 8) - (k % 8)).abs < 3
256
+ capture = whats_at target
257
+ if !capture or different_colors(white, capture)
258
+ moves << [k, target]
259
+ end
260
+ end
261
+ end
262
+ end
263
+ moves
264
+ end
265
+
266
+ def gen_pawn_moves white
267
+ pawns = @bitboards[white ? WPAWN : BPAWN]
268
+ if white
269
+ in_front_int = -8
270
+ second_rank_high = 56
271
+ second_rank_low = 47
272
+ two_away_int = -16
273
+ attack_left = -9
274
+ attack_right = -7
275
+ promote_low = -1
276
+ promote_high = 8
277
+ promotes = [WROOK, WQUEEN, WKNIGHT, WBISHOP]
278
+ else
279
+ in_front_int = 8
280
+ second_rank_high = 16
281
+ second_rank_low = 7
282
+ two_away_int = 16
283
+ attack_left = 7
284
+ attack_right = 9
285
+ promote_low = 55
286
+ promote_high = 64
287
+ promotes = [BROOK, BQUEEN, BKNIGHT, BBISHOP]
288
+ end
289
+ do_pawn = Proc.new do |p|
290
+ possible = []
291
+ in_front = whats_at( p + in_front_int)
292
+ #single step
293
+ if !in_front
294
+ in_front_pos = p + in_front_int
295
+ possible << in_front_pos
296
+ if in_front_pos > promote_low and in_front_pos < promote_high
297
+ promotes.each { |piece| possible << [in_front_pos, piece] }
298
+ end
299
+ end
300
+ #double jump
301
+ if p < second_rank_high and p > second_rank_low and !in_front and
302
+ !whats_at( p + two_away_int)
303
+ possible << ( p + two_away_int)
304
+ end
305
+ #captures
306
+ unless p % 8 == 0 # we're in the a file
307
+ ptarget = whats_at( p + attack_left)
308
+ if ptarget and different_colors(white, ptarget)
309
+ possible << ( p + attack_left)
310
+ end
311
+ end
312
+ unless p % 8 == 7 # we're in the h file
313
+ ptarget = whats_at( p + attack_right)
314
+ if ptarget and different_colors(white, ptarget)
315
+ possible << ( p + attack_right)
316
+ end
317
+ end
318
+ #check en-passat
319
+ if @bitboards[ENPASSANT] != 0
320
+ passant = bits_to_positions( @bitboards[ENPASSANT]).first
321
+ if (p + attack_right) == passant or (p + attack_left) == passant
322
+ possible << passant
323
+ end
324
+ end
325
+ possible.collect {|i| [p, *i]}
326
+ end
327
+ moves = []
328
+ bits_to_positions(pawns).each do |p|
329
+ moves += do_pawn.call(p)
330
+ end
331
+ moves
332
+ end
333
+ end
data/lib/rubyknight.rb ADDED
@@ -0,0 +1,337 @@
1
+ module RubyKnight
2
+ class IllegalMoveException < RuntimeError
3
+ end
4
+
5
+ class Board
6
+ attr_reader :history, :WHITE, :BLACK, :to_play
7
+
8
+ WHITE, BLACK = 0, 1
9
+ QUEENSIDE, KINGSIDE = 0, 1
10
+ WPAWN, WROOK, WBISHOP, WKNIGHT, WQUEEN, WKING = 0,1,2,3,4,5
11
+ BPAWN, BROOK, BBISHOP, BKNIGHT, BQUEEN, BKING = 6,7,8,9,10,11
12
+ WALL, BALL = 12, 13
13
+ ENPASSANT = 14
14
+ LAST_BOARD = CAN_CASTLE = 15
15
+
16
+ SYMBOLS = [ 'P','R','B','N','Q','K',
17
+ 'p','r','b','n','q','k']
18
+
19
+
20
+ def initialize
21
+ setup_start
22
+ end
23
+
24
+ def white_to_play?
25
+ @to_play == WHITE
26
+ end
27
+
28
+ def dump
29
+ @bitboards[@bitboards.size] = @history
30
+ @bitboards[@bitboards.size] = @to_play
31
+ ret = Marshal.dump(@bitboards)
32
+ @bitboards.delete_at(@bitboards.size-1)
33
+ @bitboards.delete_at(@bitboards.size-1)
34
+ ret
35
+ end
36
+
37
+ def load dmp
38
+ @bitboards = Marshal.load( dmp)
39
+ @to_play = @bitboards.pop
40
+ @history = @bitboards.pop
41
+ end
42
+
43
+ def _undo
44
+ evt = @history.pop
45
+ return unless evt
46
+ place_piece evt.piece, evt.orig
47
+
48
+ if evt.promotion then unplace_piece evt.promotion, evt.dest
49
+ else unplace_piece evt.piece, evt.dest end
50
+
51
+ if evt.capture then place_piece evt.capture, evt.dest end
52
+
53
+ if last = @history.last
54
+ mark_enpassant last.piece, last.orig, last.dest
55
+ else
56
+ mark_enpassant nil, nil, nil
57
+ end
58
+
59
+ # handle castling
60
+ @bitboards[CAN_CASTLE] = evt.can_castle
61
+ # are we castling?
62
+ if (evt.piece == WKING or evt.piece == BKING) and
63
+ (evt.dest - evt.orig).abs == 2
64
+ # yes, we are
65
+ case evt.dest
66
+ when 62
67
+ move_piece WROOK, 61, 63
68
+ when 58
69
+ move_piece WROOK, 59, 56
70
+ when 2
71
+ move_piece BROOK, 3, 0
72
+ when 6
73
+ move_piece BROOK, 5, 7
74
+ end
75
+ end
76
+
77
+ @to_play = if @to_play==WHITE then BLACK
78
+ else WHITE end
79
+ end
80
+
81
+ def undo num = 1
82
+ num.times { _undo}
83
+ end
84
+
85
+ def setup_start
86
+ @to_play = WHITE
87
+ @bitboards = Array.new LAST_BOARD+1, 0
88
+ @bitboards[CAN_CASTLE] = 0x000F # 1111
89
+
90
+ @history = History.new
91
+
92
+ place_piece WPAWN, *(48..55).to_a
93
+ place_piece WROOK, 56, 63
94
+ place_piece WKNIGHT, 57, 62
95
+ place_piece WBISHOP, 58, 61
96
+ place_piece WQUEEN, 59
97
+ place_piece WKING, 60
98
+
99
+ place_piece BPAWN, *(8..15).to_a
100
+ place_piece BROOK, 0, 7
101
+ place_piece BKNIGHT, 1, 6
102
+ place_piece BBISHOP, 2, 5
103
+ place_piece BQUEEN, 3
104
+ place_piece BKING, 4
105
+
106
+ end
107
+
108
+ def Board.coord_to_position coord
109
+ a, zero = 'a0'.unpack('cc')
110
+
111
+ file = coord[0]
112
+ rank = coord[1]
113
+
114
+ pos = ((8 - (rank.to_i - zero)) * 8) +\
115
+ (file - a)
116
+
117
+ end
118
+
119
+ def Board.position_to_coord position
120
+ file = position % 8
121
+ rank = (8 - (position - file) / 8)
122
+ "#{(file + 97).chr}#{rank}"
123
+ end
124
+
125
+ def cnotation_move cnot
126
+ start, dest, promotion = cnotation_to_bits cnot
127
+ raise IllegalMoveException, "Unreadable move" unless start
128
+ move start, dest, promotion
129
+ end
130
+
131
+ def cnotation_to_bits cnot
132
+ if cnot =~ /([a-h][1-8])([a-h][1-8])([qrbnkp]{0,1})/
133
+ unless $3 == ""
134
+ promotion = if @to_play = WHITE then 0
135
+ else 6 end
136
+ promotion +=
137
+ case $3
138
+ when 'q' then WQUEEN
139
+ when 'p' then WPAWN
140
+ when 'r' then WROOK
141
+ when 'b' then WBISHOP
142
+ when 'n' then WKNIGHT
143
+ else
144
+ return false
145
+ end
146
+ else promotion = false end
147
+ [ Board.coord_to_position( $1), Board.coord_to_position( $2),
148
+ promotion]
149
+ else
150
+ false
151
+ end
152
+ end
153
+
154
+ def whats_at position
155
+ positionbit = (1 << position)
156
+ if @bitboards[WALL] & positionbit > 0
157
+ range = WPAWN..WKING
158
+ elsif @bitboards[BALL] & positionbit > 0
159
+ range = BPAWN..BKING
160
+ else
161
+ return false
162
+ end
163
+
164
+ somethingthere = false
165
+ range.each do |piece|
166
+ if (@bitboards[piece] & positionbit) > 0
167
+ somethingthere = piece
168
+ break
169
+ end
170
+ end
171
+ somethingthere
172
+ end
173
+
174
+ def to_s
175
+ out = ""
176
+ (0..63).each do |position|
177
+ somethingthere = whats_at position
178
+ if somethingthere then out << SYMBOLS[somethingthere]
179
+ else out << '.' end
180
+ out << "\n" if (position+1) % 8 == 0
181
+ end
182
+ out
183
+ end
184
+
185
+ def all_board_for piece
186
+ 12 + (is_white(piece) ? 0 : 1)
187
+ end
188
+
189
+ def place_piece piece, *positions
190
+ positions.each do |position|
191
+ position = (1 << position)
192
+ @bitboards[piece] |= position
193
+ @bitboards[all_board_for(piece)] |= position
194
+ end
195
+ end
196
+
197
+ def unplace_piece piece, *positions
198
+ positions.each do |position|
199
+ position = ~(1 << position)
200
+ @bitboards[piece] &= position
201
+ @bitboards[all_board_for(piece)] &= position
202
+ end
203
+ end
204
+
205
+ def move_piece piece, orig, dest
206
+ unplace_piece piece, orig
207
+ place_piece piece, dest
208
+ end
209
+
210
+ def is_white piece
211
+ piece <= WKING
212
+ end
213
+
214
+ def move orig, dest, promotion=nil, verify_legality = true
215
+ piece = whats_at(orig)
216
+
217
+ # Check Legality
218
+ # Your piece?
219
+ unless piece and
220
+ ((is_white(piece) and @to_play == WHITE) or
221
+ (!is_white(piece) and @to_play == BLACK))
222
+ raise IllegalMoveException, "Not your piece"
223
+ end
224
+
225
+ if verify_legality
226
+ legal_moves = gen_legal_moves
227
+ unless legal_moves.include? [orig, dest] or
228
+ legal_moves.include? [orig, dest, promotion]
229
+ raise IllegalMoveException, "Invalid move"
230
+ end
231
+ end
232
+
233
+ captured = whats_at(dest)
234
+ unplace_piece captured, dest if captured
235
+ move_piece piece, orig, dest
236
+
237
+ # handle castling
238
+ # are we castling?
239
+ if (piece == WKING or piece == BKING) and
240
+ (dest - orig).abs == 2
241
+ # yes, we are
242
+ case dest
243
+ when 62
244
+ move_piece WROOK, 63, 61
245
+ when 58
246
+ move_piece WROOK, 56, 59
247
+ when 2
248
+ move_piece BROOK, 0, 3
249
+ when 6
250
+ move_piece BROOK, 7, 5
251
+ end
252
+ end
253
+
254
+
255
+ # mark no-longer-possible castles
256
+ can_castle_was = @bitboards[CAN_CASTLE]
257
+ if piece == WKING
258
+ @bitboards[CAN_CASTLE] &= ~(1|2)
259
+ elsif piece == WROOK and orig == 56
260
+ @bitboards[CAN_CASTLE] &= ~(1)
261
+ elsif piece == WROOK and orig == 63
262
+ @bitboards[CAN_CASTLE] &= ~(2)
263
+ elsif piece == BKING
264
+ @bitboards[CAN_CASTLE] &= ~(4|8)
265
+ elsif piece == BROOK and orig == 0
266
+ @bitboards[CAN_CASTLE] &= ~(4)
267
+ elsif piece == BROOK and orig == 7
268
+ @bitboards[CAN_CASTLE] &= ~(8)
269
+ end
270
+
271
+ if promotion
272
+ unplace_piece piece, dest
273
+ place_piece promotion, dest
274
+ end
275
+
276
+ mark_enpassant piece, orig, dest
277
+
278
+ @history << Event.new(piece, orig, dest, captured,
279
+ promotion, can_castle_was)
280
+ @to_play = if @to_play==WHITE then BLACK
281
+ else WHITE end
282
+ end
283
+
284
+ def mark_enpassant last_piece, last_orig, last_dest
285
+ if last_piece == WPAWN and last_orig > 47 and last_orig < 56 and
286
+ @bitboards[ENPASSANT] = ( 1 << last_orig+8)
287
+ elsif last_piece == BPAWN and last_orig > 7 and last_orig < 16 and
288
+ @bitboards[ENPASSANT] = ( 1 << last_orig+8)
289
+ else
290
+ @bitboards[ENPASSANT] = 0
291
+ end
292
+ end
293
+
294
+ def bits_to_positions bits
295
+ (0..63).select {|i| 1<<i & bits !=0}
296
+ end
297
+
298
+ def piece_positions piece
299
+ bits_to_positions(@bitboards[piece])
300
+ end
301
+
302
+ def num_pieces piece
303
+ bits_to_positions(@bitboards[piece]).size
304
+ end
305
+
306
+ def can_castle color, side
307
+ @bitboards[CAN_CASTLE] & (1 << ((color * 2)+side)) > 0
308
+ end
309
+
310
+ class History < Array
311
+ end
312
+
313
+ class Event
314
+ attr_reader :piece, :orig, :dest, :capture, :promotion, :can_castle
315
+ def initialize piece, orig, dest, capture,
316
+ promotion=false, can_castle=0
317
+ @piece = piece
318
+ @orig = orig
319
+ @dest = dest
320
+ @capture = capture
321
+ @promotion = promotion
322
+ @can_castle = can_castle
323
+ end
324
+
325
+ def to_s
326
+ Board.position_to_coord(@orig) +\
327
+ Board.position_to_coord(@dest) +\
328
+ (@promotion ? Board.SYMBOLS[@promotion] : "")
329
+
330
+ end
331
+ end
332
+
333
+ end
334
+ end
335
+
336
+ require "rubyknight/generator"
337
+ require "rubyknight/evaluator"
data/profile-gen.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'profile'
2
+
3
+ load './board.rb'
4
+ load "./#{ARGV[0]}"
5
+
6
+ b = RubyKnight::Board.new
7
+
8
+ b.gen_legal_moves
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubyknight
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Moyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-02 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Naive chess library and engine written in ruby. Includes rubyknight and rubyknight-xboard executables
17
+ email: chris@inarow.net
18
+ executables:
19
+ - rubyknight
20
+ - rubyknight-xboard
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README
25
+ files:
26
+ - README
27
+ - Rakefile
28
+ - TODO
29
+ - VERSION
30
+ - bin/rubyknight
31
+ - bin/rubyknight-xboard
32
+ - doc/reference_sites.txt
33
+ - lib/rubyknight.rb
34
+ - lib/rubyknight/evaluator.rb
35
+ - lib/rubyknight/generator.rb
36
+ - profile-gen.rb
37
+ has_rdoc: true
38
+ homepage: http://github.com/cdmoyer/RubyKnight
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --charset=UTF-8
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project:
61
+ rubygems_version: 1.3.5
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Naive chess library and engine written in ruby
65
+ test_files: []
66
+