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 +4 -0
- data/Rakefile +16 -0
- data/TODO +1 -0
- data/VERSION +1 -0
- data/bin/rubyknight +87 -0
- data/bin/rubyknight-xboard +85 -0
- data/doc/reference_sites.txt +9 -0
- data/lib/rubyknight/evaluator.rb +125 -0
- data/lib/rubyknight/generator.rb +333 -0
- data/lib/rubyknight.rb +337 -0
- data/profile-gen.rb +8 -0
- metadata +66 -0
data/README
ADDED
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/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
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
|
+
|