shurikenengine 0.31

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.
@@ -0,0 +1,185 @@
1
+ ##
2
+ # Shuriken, a Ruby chess variant engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module Shuriken
8
+
9
+ class Cmd
10
+ attr_accessor :engine, :random_mode
11
+
12
+ def initialize
13
+ @variant = "caparandom" # default
14
+ @random_mode = false
15
+ @tokens = Tokens.new(ARGV)
16
+ @fen = nil#"rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR w KQkq - 0 1"
17
+ end
18
+
19
+ def name
20
+ puts "#{Shuriken::NAME} v#{Shuriken::VERSION} by #{Shuriken::AUTHOR}"
21
+ end
22
+
23
+ def run_suite(depth = 4)
24
+ case @variant
25
+ when "gothic"
26
+ Shuriken::PerftGothicCaparandom.new
27
+ when "falcon"
28
+ Shuriken::PerftFalconCaparandom.new
29
+ else
30
+ Shuriken::PerftCapablancaCaparandom.new
31
+ end.suite([0, depth].max)
32
+ end
33
+
34
+ def randommode
35
+ @random_mode = true
36
+ end
37
+
38
+ def mbench
39
+ depth = 4
40
+ val = @tokens.peek(1)
41
+ if val != nil && val.match(/\d+/)
42
+ @tokens.forward
43
+ depth = @tokens.cur.to_i
44
+ end
45
+ run_suite(depth)
46
+ end
47
+
48
+ def perft
49
+ depth = 3
50
+ val = @tokens.peek(1)
51
+ if val != nil && val.match(/\d+/)
52
+ @tokens.forward
53
+ depth = @tokens.cur.to_i
54
+ end
55
+ p = PerftCaparandom.new(@variant, @fen)
56
+ p.perft(depth)
57
+ end
58
+
59
+ def bench
60
+ e = Shuriken::EngineCaparandom.new(@variant, random_mode: @random_mode)
61
+ e.bench
62
+ end
63
+
64
+ def stats
65
+ n = 100
66
+ val = @tokens.peek(1)
67
+ if val != nil && val.match(/\d+/)
68
+ @tokens.forward
69
+ n = val.to_i
70
+ end
71
+ e = Shuriken::EngineCaparandom.new("falcon", random_mode: @random_mode)
72
+ e.board.use_fen(@fen) if @fen != nil
73
+ e.stats(n)
74
+ end
75
+
76
+ def tactics
77
+ Shuriken::TacticsCaparandom.run
78
+ end
79
+
80
+ def fen
81
+ @tokens.go_next
82
+ @fen = @tokens.cur
83
+ end
84
+
85
+ def variant
86
+ @tokens.go_next
87
+ fail "Bad Input" unless @tokens.ok?
88
+ @variant = @tokens.cur
89
+ end
90
+
91
+ def list
92
+ board = Shuriken::BoardCaparandom.new(@variant)
93
+ board.use_fen(@fen)
94
+ mgen = board.mgen_generator
95
+ moves = mgen.generate_moves
96
+ i = 0
97
+ moves.each do |b|
98
+ puts "> #{i}: #{b.move_str}"
99
+ i += 1
100
+ end
101
+ end
102
+
103
+ def test
104
+ # ...
105
+ end
106
+
107
+ def rubybench
108
+ Shuriken::Bench.go
109
+ end
110
+
111
+ def xboard
112
+ xboard = Shuriken::Xboard.new(@variant, @random_mode)
113
+ xboard.go
114
+ end
115
+
116
+ def profile
117
+ require 'ruby-prof'
118
+ result = RubyProf.profile do
119
+ e = Shuriken::EngineCaparandom.new("gothic", random_mode: @random_mode)
120
+ e.bench
121
+ end
122
+ printer = RubyProf::FlatPrinter.new(result)
123
+ printer.print(STDOUT)
124
+ end
125
+
126
+ def help
127
+ puts "Usage: ruby shuriken.rb [OPTION]... [PARAMS]..."
128
+ puts "-help: This Help"
129
+ puts "-xboard: Enter Xboard Mode"
130
+ puts "-tactics: Run Tactics"
131
+ puts "-name: Print Name Tactics"
132
+ puts "-rubybench: Benchmark Ruby"
133
+ puts "-bench: Benchmark Shuriken Engine"
134
+ puts "-mbench: Benchmark Shuriken Movegen"
135
+ puts "-profile: Profile Shuriken"
136
+ puts "-variant [NAME]: Set Variant (gothic / caparandom / falcon / capablanca)"
137
+ puts "-randommode: Activate Random Mode"
138
+ puts "-fen [FEN]: Set Fen"
139
+ puts "-stats [NUM]: Statistical Analysis"
140
+ puts "-list: List Moves"
141
+ puts "-perft [NUM]: Run Perft"
142
+ end
143
+
144
+ def args
145
+ help && return if ARGV.length < 1
146
+ while @tokens.ok?
147
+ case @tokens.cur
148
+ when "-xboard" then # enter xboard mode
149
+ xboard and return
150
+ when "-mbench" then
151
+ mbench
152
+ when "-rubybench" then
153
+ rubybench
154
+ when "-bench" then
155
+ bench
156
+ when "-stats" then
157
+ stats
158
+ when "-variant" then
159
+ variant
160
+ when "-randommode" then
161
+ randommode
162
+ when "-tactics" then
163
+ tactics
164
+ when "-test" then
165
+ test
166
+ when "-name" then
167
+ name
168
+ when "-fen" then
169
+ fen
170
+ when "-profile" then
171
+ profile
172
+ when "-list" then
173
+ list
174
+ when "-help" then
175
+ help
176
+ else
177
+ puts "Shuriken Error: Unknown Command: '#{@tokens.cur}'"
178
+ return
179
+ end
180
+ @tokens.forward
181
+ end
182
+ end
183
+ end # class Cmd
184
+
185
+ end # module Shuriken
@@ -0,0 +1,53 @@
1
+ ##
2
+ # Shuriken, a Ruby chess variant engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module Shuriken
8
+
9
+ class Engine
10
+ RESULT_DRAW = 1
11
+ RESULT_BLACK_WIN = 2
12
+ RESULT_WHITE_WIN = 4
13
+
14
+ def init_mate_bonus
15
+ @mate_bonus = [1] * 100
16
+ (0..20).each { |i| @mate_bonus[i] += 20 - i }
17
+ @mate_bonus[0] = 50
18
+ @mate_bonus[1] = 40
19
+ @mate_bonus[2] = 30
20
+ @mate_bonus[3] = 25
21
+ end
22
+
23
+ def history_reset
24
+ @history.reset
25
+ end
26
+
27
+ def history_remove
28
+ @board = @history.remove
29
+ end
30
+
31
+ def history_undo
32
+ @board = @history.undo
33
+ end
34
+
35
+ def print_move_list(moves)
36
+ i = 0
37
+ moves.each do |board|
38
+ i += 1
39
+ puts "#{i} / #{board.move_str} / #{board.score}"
40
+ end
41
+ end
42
+
43
+ def move_list
44
+ mgen = @board.mgen_generator
45
+ moves, i = mgen.generate_moves, 0
46
+ moves.each do |board|
47
+ i += 1
48
+ puts "#{i} / #{board.move_str} / #{board.score}"
49
+ end
50
+ end
51
+ end # class Engine
52
+
53
+ end # module Shuriken
@@ -0,0 +1,226 @@
1
+ ##
2
+ # Shuriken, a Ruby chess variant engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module Shuriken
8
+
9
+ class EngineCaparandom < Shuriken::Engine
10
+ attr_accessor :board, :random_mode, :gameover, :move_now, :debug, :time, :movestogo, :printinfo
11
+
12
+ INF = 1000
13
+ MATERIAL_SCALE = 0.01
14
+
15
+ def initialize(variant, random_mode: false)
16
+ init_mate_bonus
17
+ @board = Shuriken::BoardCaparandom.new(variant)
18
+ @random_mode = random_mode
19
+ @history = Shuriken::History.new
20
+ @board.startpos(variant)
21
+ @printinfo = true
22
+ @time = 10 # seconds
23
+ @movestogo = 40
24
+ @stop_time = 0
25
+ @stop_search = false
26
+ @nodes = 0
27
+ @move_now = false
28
+ @debug = false
29
+ @gameover = false
30
+ end
31
+
32
+ def make_move?(move)
33
+ mgen = @board.mgen_generator
34
+ moves = mgen.generate_moves
35
+ moves.each do |board|
36
+ if board.move_str == move
37
+ @history.add(board)
38
+ @board = board
39
+ return true
40
+ end
41
+ end
42
+ puts "illegal move: #{move}"
43
+ false
44
+ #fail "Shuriken Error: Illegal Move: '#{move}'"
45
+ end
46
+
47
+ def print_score(moves, depth, started)
48
+ return unless @printinfo
49
+ moves = moves.sort_by(&:score).reverse
50
+ best = moves[0]
51
+ n = (100 * (Time.now - started)).to_i
52
+ puts " #{depth} #{(best.score).to_i} #{n} #{@nodes} #{best.move_str}"
53
+ end
54
+
55
+ def search_moves_w(cur, depth, total = 0)
56
+ @nodes += 1
57
+ @stop_search = (Time.now > @stop_time || total > 90)
58
+ return 0 if @stop_search
59
+ return MATERIAL_SCALE * cur.material if depth < 1
60
+ mgen = Shuriken::MgenCaparandomWhite.new(cur)
61
+ moves = mgen.generate_moves
62
+ if moves.length == 0 # assume mate
63
+ return 0.1 * @mate_bonus[total] * -INF
64
+ end
65
+ search_moves_b(moves.sample, depth - 1, total + 1)
66
+ end
67
+
68
+ def search_moves_b(cur, depth, total = 0)
69
+ @nodes += 1
70
+ @stop_search = (Time.now > @stop_time || total > 90)
71
+ return 0 if @stop_search
72
+ return MATERIAL_SCALE * cur.material if depth < 1
73
+ mgen = Shuriken::MgenCaparandomBlack.new(cur)
74
+ moves = mgen.generate_moves
75
+ if moves.length == 0 # assume mate
76
+ return 0.1 * @mate_bonus[total] * INF
77
+ end
78
+ search_moves_w(moves.sample, depth - 1, total + 1)
79
+ end
80
+
81
+ def search(moves)
82
+ now = Time.now
83
+ time4print = 0.5
84
+ #@stop_time = now + (@time / ((@movestogo < 1 ? 30 : @movestogo) + 2)) # no time losses
85
+ divv = @movestogo < 10 ? 20 : 30
86
+ @stop_time = now + (@time / divv)
87
+ depth = 2
88
+ while true
89
+ moves.each do | board |
90
+ puts "> #{@nodes} / #{board.move_str}" if @debug
91
+ next if board.nodetype == 2
92
+ depth = 3 + rand(20)
93
+ board.score += board.wtm ? search_moves_w(board, depth, 0) : search_moves_b(board, depth, 0)
94
+ if Time.now > @stop_time || @move_now
95
+ print_score(moves, depth, now)
96
+ return
97
+ end
98
+ end
99
+ if Time.now - now > time4print
100
+ now = Time.now
101
+ print_score(moves, depth, now)
102
+ end
103
+ end
104
+ end
105
+
106
+ def draw_moves(moves)
107
+ moves.each do | board |
108
+ if @history.is_draw?(board)
109
+ board.nodetype, board.score = 2, 0
110
+ end
111
+ end
112
+ end
113
+
114
+ def hash_moves(moves)
115
+ moves.each { |board| board.create_hash }
116
+ end
117
+
118
+ def game_status(mgen, moves)
119
+ if moves.length == 0
120
+ if @board.wtm && mgen.checks_b?(@board.find_white_king)
121
+ return Shuriken::Engine::RESULT_BLACK_WIN
122
+ elsif !@board.wtm && mgen.checks_w?(@board.find_black_king)
123
+ return Shuriken::Engine::RESULT_WHITE_WIN
124
+ end
125
+ return Shuriken::Engine::RESULT_DRAW
126
+ end
127
+ @board.create_hash
128
+ if @history.is_draw?(@board, 3) || @board.material_draw?
129
+ return Shuriken::Engine::RESULT_DRAW
130
+ end
131
+ 0
132
+ end
133
+
134
+ def is_gameover?(mgen, moves)
135
+ @board.create_hash
136
+ if @history.is_draw?(@board, 3)
137
+ puts "1/2-1/2 {Draw by repetition}"
138
+ return true
139
+ end
140
+ if moves.length == 0
141
+ if @board.wtm && mgen.checks_b?(@board.find_white_king)
142
+ puts "0-1 {Black mates}"
143
+ elsif ! @board.wtm && mgen.checks_w?(@board.find_black_king)
144
+ puts "1-0 {White mates}"
145
+ end
146
+ puts "1/2-1/2 {Stalemate}"
147
+ return true
148
+ end
149
+ false
150
+ end
151
+
152
+ def bench
153
+ t = Time.now
154
+ @time = 500
155
+ think
156
+ diff = Time.now - t
157
+ puts "= #{@nodes} nodes | #{diff.round(3)} s | #{(@nodes/diff).to_i} nps"
158
+ end
159
+
160
+ def think
161
+ @nodes = 0
162
+ @move_now = false
163
+ @history.reset
164
+ board = @board
165
+ mgen = @board.mgen_generator
166
+ moves = mgen.generate_moves
167
+ hash_moves(moves)
168
+ draw_moves(moves)
169
+ func = -> { board.wtm ? moves.sort_by(&:score).reverse : moves.sort_by(&:score) }
170
+ @gameover = is_gameover?(mgen, moves)
171
+ return if @gameover
172
+ if @random_mode
173
+ @board = moves.sample
174
+ else
175
+ search(moves)
176
+ moves = func.call
177
+ @board = moves[0]
178
+ end
179
+ print_move_list(moves) if @debug
180
+ @history.add(@board)
181
+ @board.move_str
182
+ end
183
+
184
+ def print_score_stats(results)
185
+ wscore = results[Shuriken::Engine::RESULT_WHITE_WIN]
186
+ bscore = results[Shuriken::Engine::RESULT_BLACK_WIN]
187
+ draws = results[Shuriken::Engine::RESULT_DRAW]
188
+ total = wscore + bscore + draws
189
+ printf("[ Score: %i - %i - %i [%.2f] %i ]\n", wscore, bscore, draws, (wscore + 0.5 * draws) / total, total)
190
+ end
191
+
192
+ def stats(rounds = 10)
193
+ rounds = 6
194
+ @nodes = 0
195
+ @move_now = false
196
+ board = @board
197
+ results = [0] * 5
198
+ puts "Running stats ..."
199
+ rounds.times do |n|
200
+ @board = board
201
+ @history.reset
202
+ lastboard = board
203
+ while true
204
+ lastboard = board
205
+ #@board.print_board
206
+ mgen = @board.mgen_generator
207
+ moves = mgen.generate_moves
208
+ hash_moves(moves)
209
+ draw_moves(moves)
210
+ #@board = moves.length == 0 ? lastboard : @board
211
+ status = game_status(mgen, moves)
212
+ @board = moves.sample
213
+ @history.add(@board)
214
+ if status != 0
215
+ results[status] += 1
216
+ break
217
+ end
218
+ end
219
+ print_score_stats(results) if (n + 1) % 2 == 0 && n + 1 < rounds
220
+ end
221
+ puts "="
222
+ print_score_stats(results)
223
+ end
224
+ end # class EngineCaparandom
225
+
226
+ end # module Shuriken