rubyknight 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|