RubyShogi 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,237 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class MgenWhite < RubyShogi::Mgen
10
+ def initialize(board)
11
+ @board = board
12
+ end
13
+
14
+ def push_move(me, to, promo)
15
+ board2 = @board
16
+ copy = @board.copy_me
17
+ copy.from = @from_gen
18
+ copy.to = to
19
+ copy.promo = promo
20
+ copy.r50 += 1
21
+ copy.fullmoves += 1
22
+ copy.r50 = 0 if [1, 3, 5].include?(me)
23
+ copy.eat = copy.brd[to]
24
+ copy.white_pocket.push(eaten_piece(-copy.eat)) if copy.eat != 0
25
+ copy.wtm = !copy.wtm
26
+ copy.brd[@from_gen] = 0
27
+ copy.brd[to] = me
28
+ copy.wking = to if me == 14
29
+ #fail if copy.find_white_king != copy.wking
30
+ @board = copy
31
+ @moves.push << copy if !checks_b?
32
+ @board = board2
33
+ end
34
+
35
+ def pawn_drop_checkmate?(to)
36
+ @board.brd[to + 9] == -14 && !checks_b?(to + 9) ? true : false
37
+ end
38
+
39
+ def add_new_drop_move(me, to)
40
+ return if me == 3 && to / 9 == 8
41
+ return if me == 5 && to / 9 >= 7
42
+ board2 = @board
43
+ copy = @board.copy_me
44
+ copy.from = @from_gen
45
+ copy.to = to
46
+ copy.drop = me
47
+ copy.eat = 0
48
+ copy.r50 += 1
49
+ copy.fullmoves += 1
50
+ copy.wtm = ! copy.wtm
51
+ copy.brd[@from_gen] = 0
52
+ copy.brd[to] = me
53
+ copy.white_pocket = remove_from_array(copy.white_pocket, me)
54
+ #fail if copy.find_white_king != copy.wking
55
+ @board = copy
56
+ if !checks_b? && !(me == -1 && pawn_drop_checkmate?(to))
57
+ @moves.push << copy
58
+ end
59
+ @board = board2
60
+ end
61
+
62
+ def handle_capture(copy, eat)
63
+ piece = case eat
64
+ when 1..2 then 1
65
+ when 3..4 then 3
66
+ when 5..6 then 5
67
+ when 7..8 then 7
68
+ when 9 then 9
69
+ when 10..11 then 10
70
+ when 12..13 then 12
71
+ end
72
+ copy.white_pocket.push(piece)
73
+ end
74
+
75
+ def add_new_move(me, to, promo = 0)
76
+ return if handle_promotion?(me, to)
77
+ push_move(me, to, promo)
78
+ end
79
+
80
+ def handle_promotion?(me, to)
81
+ return true if must_promote?(me, to)
82
+ return false if to / 9 <= 5 && @from_gen / 9 <= 5
83
+ case me
84
+ when 1
85
+ push_move(1, to, PROMO_STAY)
86
+ push_move(2, to, PROMO_YES)
87
+ return true
88
+ when 3
89
+ push_move(3, to, PROMO_STAY)
90
+ push_move(4, to, PROMO_YES)
91
+ return true
92
+ when 5
93
+ push_move(5, to, PROMO_STAY)
94
+ push_move(6, to, PROMO_YES)
95
+ return true
96
+ when 7
97
+ push_move(7, to, PROMO_STAY)
98
+ push_move(8, to, PROMO_YES)
99
+ return true
100
+ when 10
101
+ push_move(10, to, PROMO_STAY)
102
+ push_move(11, to, PROMO_YES)
103
+ return true
104
+ when 12
105
+ push_move(12, to, PROMO_STAY)
106
+ push_move(13, to, PROMO_YES)
107
+ return true
108
+ end
109
+ false
110
+ end
111
+
112
+ def must_promote?(me, to)
113
+ if me == 5 && to / 9 >= 7
114
+ push_move(6, to, PROMO_YES)
115
+ return true
116
+ end
117
+ return false if to / 9 != 8
118
+ case me
119
+ when 1
120
+ push_move(2, to, PROMO_YES)
121
+ return true
122
+ when 3
123
+ push_move(4, to, PROMO_YES)
124
+ return true
125
+ end
126
+ false
127
+ end
128
+
129
+ def add_new_pawn_move(to)
130
+ case to / 9
131
+ when 6..7
132
+ push_move(1, to, PROMO_STAY)
133
+ push_move(2, to, PROMO_YES)
134
+ when 8 then push_move(2, to, PROMO_YES)
135
+ else
136
+ push_move(1, to, PROMO_NO)
137
+ end
138
+ end
139
+
140
+ def generate_pawn_moves
141
+ to = @x_gen + (@y_gen + 1) * 9
142
+ add_new_pawn_move(to) if (to < 81 && @board.walkable_w?(to))
143
+ end
144
+
145
+ def generate_jump_moves(jumps, me)
146
+ jumps.each do |jmp|
147
+ px, py = @x_gen + jmp[0], @y_gen + jmp[1]
148
+ to = px + 9 * py
149
+ add_new_move(me, to) if is_on_board?(px, py) && @board.walkable_w?(to)
150
+ end
151
+ end
152
+
153
+ def generate_slider_moves(slider, me)
154
+ slider.each do |jmp|
155
+ px, py = @x_gen, @y_gen
156
+ loop do
157
+ px, py = px + jmp[0], py + jmp[1]
158
+ break if !is_on_board?(px, py)
159
+ to = px + 9 * py
160
+ add_new_move(me, to) if @board.walkable_w?(to)
161
+ break if !@board.empty?(to)
162
+ end
163
+ end
164
+ end
165
+
166
+ def pawn_on_column?(c)
167
+ ret = false
168
+ 9.times do |i|
169
+ to = 9 * i + c
170
+ if to != @from_gen && @board.brd[to] == 1
171
+ ret = true
172
+ break
173
+ end
174
+ end
175
+ ret
176
+ end
177
+
178
+ def put_pawn_drops
179
+ (9*8).times do |i|
180
+ @x_gen, @y_gen, @from_gen = i % 9, i / 9, i
181
+ add_new_drop_move(1, i) if !pawn_on_column?(i % 9 ) && @board.brd[i] == 0
182
+ end
183
+ end
184
+
185
+ def put_drops(piece)
186
+ 81.times do |i|
187
+ @x_gen, @y_gen, @from_gen = i % 9, i / 9, i
188
+ add_new_drop_move(piece, i) if @board.brd[i] == 0
189
+ end
190
+ end
191
+
192
+ def generate_drops
193
+ @board.white_pocket.each do |piece|
194
+ case piece
195
+ when 1 then put_pawn_drops
196
+ when 3 then put_drops(3)
197
+ when 5 then put_drops(5)
198
+ when 7 then put_drops(7)
199
+ when 9 then put_drops(9)
200
+ when 10 then put_drops(10)
201
+ when 11 then put_drops(11)
202
+ when 12 then put_drops(12)
203
+ end
204
+ end
205
+ @moves
206
+ end
207
+
208
+ def generate_moves
209
+ @moves = []
210
+ 81.times do |i|
211
+ @x_gen, @y_gen, @from_gen = i % 9, i / 9, i
212
+ case @board.brd[i]
213
+ when 1 then generate_pawn_moves
214
+ when 2 then generate_jump_moves(WHITE_GOLD_GENERAL_MOVES, 2)
215
+ when 3 then generate_slider_moves(WHITE_LANCE_MOVES, 3)
216
+ when 4 then generate_jump_moves(WHITE_GOLD_GENERAL_MOVES, 4)
217
+ when 5 then generate_jump_moves(WHITE_KNIGHT_MOVES, 5)
218
+ when 6 then generate_jump_moves(WHITE_GOLD_GENERAL_MOVES, 6)
219
+ when 7 then generate_jump_moves(WHITE_SILVER_GENERAL_MOVES, 7)
220
+ when 8 then generate_jump_moves(WHITE_GOLD_GENERAL_MOVES, 8)
221
+ when 9 then generate_jump_moves(WHITE_GOLD_GENERAL_MOVES, 9)
222
+ when 10 then generate_slider_moves(BISHOP_MOVES, 10)
223
+ when 11
224
+ generate_slider_moves(BISHOP_MOVES, 11)
225
+ generate_jump_moves(PROMOTED_BISHOP_MOVES, 11)
226
+ when 12 then generate_slider_moves(ROOK_MOVES, 12)
227
+ when 13
228
+ generate_slider_moves(ROOK_MOVES, 13)
229
+ generate_jump_moves(PROMOTED_ROOK_MOVES, 13)
230
+ when 14 then generate_jump_moves(KING_MOVES, 14)
231
+ end
232
+ end
233
+ generate_drops
234
+ end
235
+ end # class MgenWhite
236
+
237
+ end # module RubyShogi
@@ -0,0 +1,87 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class Perft
10
+ attr_accessor :board
11
+
12
+ NUMS = { # https://groups.google.com/forum/#!topic/shogi-l/U7hmtThbk1k
13
+ 0 => 1,
14
+ 1 => 30,
15
+ 2 => 900,
16
+ 3 => 25470,
17
+ 4 => 719731,
18
+ 5 => 19861490,
19
+ 6 => 547581517
20
+ }
21
+
22
+ def initialize(fen = nil)
23
+ @board = RubyShogi::Board.new(fen)
24
+ end
25
+
26
+ def perft_number(depth)
27
+ return 1 if depth == 0
28
+ board = @board
29
+ mgen = @board.mgen_generator
30
+ n, moves = 0, mgen.generate_moves
31
+ return moves.length if depth <= 1
32
+ moves.each do |move|
33
+ @board = move
34
+ n += perft_number(depth - 1)
35
+ end
36
+ @board = board
37
+ n
38
+ end
39
+
40
+ def randperft(depth)
41
+ @board = @board.randpos
42
+ perft(depth)
43
+ end
44
+
45
+ def perft(depth)
46
+ puts "~~~ perft( #{depth} ) ~~~"
47
+ puts "[ fen: #{@board.pos2fen} ]"
48
+ total_time = 0
49
+ total_nodes = 0
50
+ copy = @board
51
+ (depth+1).times do |i|
52
+ start = Time.now
53
+ @board = copy
54
+ n = perft_number(i)
55
+ diff = Time.now - start
56
+ total_time += diff
57
+ total_nodes += n
58
+ nps = (diff == 0 or n == 1) ? n : (n / diff).to_i
59
+ puts "#{i}: #{n} | #{diff.round(3)}s | #{nps} nps"
60
+ end
61
+ total_time = 1 if total_time == 0
62
+ puts "= #{total_nodes} | #{total_time.round(3)}s | #{(total_nodes/total_time).to_i} nps"
63
+ end
64
+
65
+ def suite(depth)
66
+ puts "~~~ suite( #{depth} ) ~~~"
67
+ total_time = 0
68
+ total_nodes = 0
69
+ copy = @board
70
+ (depth+1).times do |i|
71
+ start = Time.now
72
+ @board = copy
73
+ n = perft_number(i)
74
+ diff = Time.now - start
75
+ total_time += diff
76
+ total_nodes += n
77
+ nps = (diff == 0 or n == 1) ? n : (n / diff).to_i
78
+ error = ["ok", "error"][NUMS[i] - n == 0 ? 0 : 1]
79
+ break if i >= NUMS.length - 1
80
+ puts "#{i}: #{n} | #{diff.round(3)}s | #{nps} nps | #{error}"
81
+ end
82
+ total_time = 1 if total_time == 0
83
+ puts "= #{total_nodes} | #{total_time.round(3)}s | #{(total_nodes/total_time).to_i} nps"
84
+ end
85
+ end # class Perft
86
+
87
+ end # module ShurikenShogi
@@ -0,0 +1,66 @@
1
+ ##
2
+ #
3
+ # RubyShogi, a Shogi Engine
4
+ # Copyright (C) 2019 Toni Helminen ( kalleankka1@gmail.com )
5
+ #
6
+ # RubyShogi is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # RubyShogi is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #
18
+ ##
19
+
20
+ require_relative "./engine"
21
+ require_relative "./zobrist"
22
+ require_relative "./history"
23
+ require_relative "./cmd"
24
+ require_relative "./xboard"
25
+ require_relative "./eval"
26
+ require_relative "./utils"
27
+ require_relative "./bench"
28
+ require_relative "./tokens"
29
+ require_relative "./board"
30
+ require_relative "./mgen"
31
+ require_relative "./mgen_white"
32
+ require_relative "./mgen_black"
33
+ require_relative "./perft"
34
+ require_relative "./tactics"
35
+
36
+ $stdout.sync = true
37
+ $stderr.sync = true
38
+ Thread.abort_on_exception = true
39
+ $stderr.reopen("ruby_shogi-error.txt", "a+")
40
+
41
+ module RubyShogi
42
+ NAME = "RubyShogi"
43
+ VERSION = "0.1"
44
+ AUTHOR = "Toni Helminen"
45
+
46
+ def RubyShogi.init
47
+ RubyShogi::Eval.init
48
+ RubyShogi::Zobrist.init
49
+ end
50
+
51
+ # Start RubyShogi
52
+ #
53
+ # Example:
54
+ # >> ruby_shogi -xboard
55
+ # => enter xboard mode
56
+ def RubyShogi.go
57
+ cmd = RubyShogi::Cmd.new
58
+ cmd.args
59
+ end
60
+ end # module RubyShogi
61
+
62
+ RubyShogi.init # init just once
63
+
64
+ if __FILE__ == $0
65
+ RubyShogi.go
66
+ end
@@ -0,0 +1,33 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ module Tactics
10
+ TACTICS = [
11
+ ["3k5/2P1P4/3K5/9/1N7/9/9/9/9[-] w 0 1", "b5c7="], # mate in 1
12
+ ["4k4/9/4K4/9/9/9/9/9/9[G] w 0 1", "G@e8"], # mate in 1
13
+ ["9/9/9/9/9/9/4K4/R4R3/6k2[-] w 0 1", "a2a1"] # mate in 1
14
+ ]
15
+
16
+ def Tactics.run
17
+ puts "~~~ Tactics ~~~"
18
+ score, total = 0, 0
19
+ TACTICS.each do |tactic|
20
+ engine = RubyShogi::Engine.new
21
+ engine.printinfo = false
22
+ engine.board.fen(tactic[0])
23
+ engine.time = 50
24
+ result = engine.think
25
+ total += 1
26
+ score += 1 if tactic[1] == result
27
+ puts "#{total}. move #{result} | " + (tactic[1] == result ? "ok" : "error")
28
+ end
29
+ puts "= #{score} / #{total}"
30
+ end
31
+ end # module Tactics
32
+
33
+ end # module RubyShogi
@@ -0,0 +1,42 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class Tokens
10
+ def initialize(tokens)
11
+ @tokens = tokens
12
+ @token_i = 0
13
+ end
14
+
15
+ def peek(n)
16
+ return nil if @token_i + n < 0 || @token_i + n >= @tokens.length
17
+ @tokens[@token_i + n]
18
+ end
19
+
20
+ def ok?
21
+ return @token_i < @tokens.length ? true : false
22
+ end
23
+
24
+ def go_next
25
+ v = nil
26
+ if @token_i < @tokens.length
27
+ v = @tokens[@token_i]
28
+ @token_i += 1
29
+ end
30
+ return v
31
+ end
32
+
33
+ def forward
34
+ @token_i += 1
35
+ end
36
+
37
+ def cur
38
+ @tokens[@token_i]
39
+ end
40
+ end # class Tokens
41
+
42
+ end # module RubyShogi