rubyknight 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+