RubyShogi 0.1

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,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