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,102 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ module Eval
10
+ # https://en.wikipedia.org/wiki/Shogi_strategy#Relative_piece_value
11
+ MATERIAL_SCORE = {
12
+ 1 => 1,
13
+ 2 => 4.2,
14
+ 3 => 4.3,
15
+ 4 => 6.3,
16
+ 5 => 4.5,
17
+ 6 => 6.4,
18
+ 7 => 6.4,
19
+ 8 => 6.7,
20
+ 9 => 6.9,
21
+ 10 => 8.9,
22
+ 11 => 11.5,
23
+ 12 => 10.4,
24
+ 13 => 13,
25
+ 14 => 0
26
+ }
27
+
28
+ MATERIAL_HAND_SCORE = {
29
+ 1 => 1.15,
30
+ 3 => 4.8,
31
+ 5 => 5.1,
32
+ 7 => 7.2,
33
+ 9 => 7.8,
34
+ 10 => 11.10,
35
+ 12 => 10.4,
36
+ 13 => 12.7
37
+ }
38
+
39
+ CENTRAL_BONUS = [1,2,3,4,5,4,3,2,1].freeze
40
+
41
+ CENTRAL_SCORE = {
42
+ 1 => 0,
43
+ 2 => 2,
44
+ 3 => 1,
45
+ 4 => 2,
46
+ 5 => 1,
47
+ 6 => 2,
48
+ 7 => 2,
49
+ 8 => 2,
50
+ 9 => 2,
51
+ 10 => 2,
52
+ 11 => 2,
53
+ 12 => 2,
54
+ 13 => 2,
55
+ 14 => 0
56
+ }
57
+
58
+ EVAL_PST_MG = []
59
+
60
+ def Eval.init
61
+ return if EVAL_PST_MG.length > 0
62
+ 14.times do |i|
63
+ arr = []
64
+ 81.times do |j|
65
+ score = 0.1 * (MATERIAL_SCORE[i + 1] + 2 * CENTRAL_SCORE[i + 1] * (CENTRAL_BONUS[j % 9 ] + CENTRAL_BONUS[j / 9]))
66
+ arr.push(score)
67
+ end
68
+ EVAL_PST_MG.push(arr)
69
+ end
70
+ EVAL_PST_MG.freeze
71
+ end
72
+
73
+ def Eval.eval(board)
74
+ score = 0
75
+ board.brd.each_with_index do |p, i|
76
+ score += case p
77
+ when 1..14 then EVAL_PST_MG[p - 1][i]
78
+ when -14..-1 then EVAL_PST_MG[-p - 1][i]
79
+ else
80
+ 0
81
+ end
82
+ end
83
+ 0.01 * score
84
+ end
85
+
86
+ def Eval.material(board)
87
+ score = 0
88
+ board.brd.each do |p|
89
+ score += case p
90
+ when 1..14 then MATERIAL_SCORE[p]
91
+ when -14..-1 then -MATERIAL_SCORE[-p]
92
+ else
93
+ 0
94
+ end
95
+ end
96
+ board.white_pocket.each { |p| score += MATERIAL_HAND_SCORE[p] }
97
+ board.black_pocket.each { |p| score -= MATERIAL_HAND_SCORE[-p] }
98
+ score
99
+ end
100
+ end # module Eval
101
+
102
+ end # module RubyShogi
@@ -0,0 +1,63 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class History
10
+ def initialize
11
+ reset
12
+ end
13
+
14
+ def reset
15
+ @data = []
16
+ @pos = -1
17
+ end
18
+
19
+ def debug
20
+ puts "@pos: #{@pos} .. @data: #{@data.length}"
21
+ end
22
+
23
+ def remove
24
+ if @pos > 1
25
+ board = @data[@pos - 2]
26
+ @pos -= 2
27
+ return board
28
+ end
29
+ @data.last
30
+ end
31
+
32
+ def undo
33
+ if @pos > 0
34
+ board = @data[@pos - 1]
35
+ @pos -= 1
36
+ return board
37
+ end
38
+ @data.last
39
+ end
40
+
41
+ def add(board)
42
+ @data.push(board)
43
+ @pos += 1
44
+ end
45
+
46
+ def draw_too_long?
47
+ @data.length > 900 # I give up...
48
+ end
49
+
50
+ def is_draw?(board, repsn = 4)
51
+ len, hash = @data.length, board.hash
52
+ i, n, reps = len - 1, 0, 0
53
+ while i > 0
54
+ break if n >= 100
55
+ reps += 1 if hash == @data[i].hash
56
+ n, i = n + 1, i - 1
57
+ return true if reps >= repsn
58
+ end
59
+ false
60
+ end
61
+ end # class History
62
+
63
+ end # module RubyShogi
@@ -0,0 +1,169 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class Mgen
10
+ # both
11
+ ROOK_MOVES = [[1, 0], [0, 1], [-1, 0], [0, -1]].freeze
12
+ BISHOP_MOVES = [[1, 1], [-1, 1], [1, -1], [-1, -1]].freeze
13
+ KING_MOVES = (ROOK_MOVES + BISHOP_MOVES).freeze
14
+
15
+ PROMOTED_BISHOP_MOVES = ROOK_MOVES
16
+ PROMOTED_ROOK_MOVES = BISHOP_MOVES
17
+
18
+ # white
19
+ WHITE_GOLD_GENERAL_MOVES = [[1, 0], [-1, 0], [0, -1], [0, 1], [1, 1], [-1, 1]].freeze
20
+ WHITE_SILVER_GENERAL_MOVES = [[-1, -1], [-1, 1], [0, 1], [1, 1], [1, -1]].freeze
21
+ WHITE_KNIGHT_MOVES = [[-1, 2], [1, 2]].freeze
22
+ WHITE_LANCE_MOVES = [[0, 1]].freeze
23
+
24
+ # black
25
+ BLACK_GOLD_GENERAL_MOVES = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, -1], [-1, -1]].freeze
26
+ BLACK_SILVER_GENERAL_MOVES = [[-1, 1], [-1, -1], [0, -1], [1, -1], [1, 1]].freeze
27
+ BLACK_KNIGHT_MOVES = [[-1, -2], [1, -2]].freeze
28
+ BLACK_LANCE_MOVES = [[0, -1]].freeze
29
+
30
+ # promotions
31
+ PROMO_NO = 0
32
+ PROMO_STAY = 1
33
+ PROMO_YES = 2
34
+
35
+ attr_accessor :pseudo_moves, :only_captures
36
+
37
+ def initialize(board)
38
+ @board, @moves = board, []
39
+ @x_gen, @y_gen, @from_gen = 0, 0, 0 # move generation
40
+ @x_checks, @y_checks = 0, 0 # checks
41
+ @pseudo_moves = false # 3x speed up
42
+ end
43
+
44
+ ##
45
+ # Utils
46
+ ##
47
+
48
+ def print_move_list
49
+ @moves.each_with_index { |board, i| puts "#{i}: #{board.move_str}" }
50
+ end
51
+
52
+ def is_on_board?(x, y)
53
+ x >= 0 && x <= 8 && y >= 0 && y <= 8
54
+ end
55
+
56
+ def remove_from_array(array, x)
57
+ found, a = false, []
58
+ array.each do |q|
59
+ if q == x
60
+ a.push(q) if found
61
+ found = true
62
+ else
63
+ a.push(q)
64
+ end
65
+ end
66
+ fail if !found
67
+ a
68
+ end
69
+
70
+ def good_coord?(i)
71
+ i >= 0 && i <= 80
72
+ end
73
+
74
+ def eaten_piece(eat)
75
+ case eat
76
+ when 2 then 1
77
+ when 4 then 3
78
+ when 6 then 5
79
+ when 8 then 7
80
+ when 11 then 10
81
+ when 13 then 12
82
+ else eat
83
+ end
84
+ end
85
+
86
+ ##
87
+ # Checks
88
+ ##
89
+
90
+ def pawn_checks_w?(here)
91
+ @x_checks + 9 * (@y_checks + 1) == here
92
+ end
93
+
94
+ def pawn_checks_b?(here)
95
+ @x_checks + 9 * (@y_checks - 1) == here
96
+ end
97
+
98
+ def slider_checks_to?(slider, here)
99
+ slider.each do |jmp|
100
+ px, py = @x_checks, @y_checks
101
+ loop do
102
+ px, py = px + jmp[0], py + jmp[1]
103
+ break if !is_on_board?(px, py)
104
+ to = px + py * 9
105
+ return true if to == here
106
+ break if !@board.empty?(to)
107
+ end
108
+ end
109
+ false
110
+ end
111
+
112
+ def jump_checks_to?(jumps, here)
113
+ jumps.each do |jmp|
114
+ px, py = @x_checks + jmp[0], @y_checks + jmp[1]
115
+ return true if px + py * 9 == here
116
+ end
117
+ false
118
+ end
119
+
120
+ def checks_w?#(here)
121
+ here = @board.bking
122
+ 81.times do |i|
123
+ @x_checks, @y_checks = i % 9, i / 9
124
+ case @board.brd[i]
125
+ when 1 then return true if pawn_checks_w?(here)
126
+ when 2 then return true if jump_checks_to?(WHITE_GOLD_GENERAL_MOVES, here)
127
+ when 3 then return true if slider_checks_to?(WHITE_LANCE_MOVES, here)
128
+ when 4 then return true if jump_checks_to?(WHITE_GOLD_GENERAL_MOVES, here)
129
+ when 5 then return true if jump_checks_to?(WHITE_KNIGHT_MOVES, here)
130
+ when 6 then return true if jump_checks_to?(WHITE_GOLD_GENERAL_MOVES, here)
131
+ when 7 then return true if jump_checks_to?(WHITE_SILVER_GENERAL_MOVES, here)
132
+ when 8 then return true if jump_checks_to?(WHITE_GOLD_GENERAL_MOVES, here)
133
+ when 9 then return true if jump_checks_to?(WHITE_GOLD_GENERAL_MOVES, here)
134
+ when 10 then return true if slider_checks_to?(BISHOP_MOVES, here)
135
+ when 11 then return true if slider_checks_to?(BISHOP_MOVES, here) || jump_checks_to?(PROMOTED_BISHOP_MOVES, here)
136
+ when 12 then return true if slider_checks_to?(ROOK_MOVES, here)
137
+ when 13 then return true if slider_checks_to?(ROOK_MOVES, here) || jump_checks_to?(PROMOTED_ROOK_MOVES, here)
138
+ when 14 then return true if jump_checks_to?(KING_MOVES, here)
139
+ end
140
+ end
141
+ false
142
+ end
143
+
144
+ def checks_b?#(here)
145
+ here = @board.wking
146
+ 81.times do |i|
147
+ @x_checks, @y_checks = i % 9, i / 9
148
+ case @board.brd[i]
149
+ when -1 then return true if pawn_checks_b?(here)
150
+ when -2 then return true if jump_checks_to?(BLACK_GOLD_GENERAL_MOVES, here)
151
+ when -3 then return true if slider_checks_to?(BLACK_LANCE_MOVES, here)
152
+ when -4 then return true if jump_checks_to?(BLACK_GOLD_GENERAL_MOVES, here)
153
+ when -5 then return true if jump_checks_to?(BLACK_KNIGHT_MOVES, here)
154
+ when -6 then return true if jump_checks_to?(BLACK_GOLD_GENERAL_MOVES, here)
155
+ when -7 then return true if jump_checks_to?(BLACK_SILVER_GENERAL_MOVES, here)
156
+ when -8 then return true if jump_checks_to?(BLACK_GOLD_GENERAL_MOVES, here)
157
+ when -9 then return true if jump_checks_to?(BLACK_GOLD_GENERAL_MOVES, here)
158
+ when -10 then return true if slider_checks_to?(BISHOP_MOVES, here)
159
+ when -11 then return true if slider_checks_to?(BISHOP_MOVES, here) || jump_checks_to?(PROMOTED_BISHOP_MOVES, here)
160
+ when -12 then return true if slider_checks_to?(ROOK_MOVES, here)
161
+ when -13 then return true if slider_checks_to?(ROOK_MOVES, here) || jump_checks_to?(PROMOTED_ROOK_MOVES, here)
162
+ when -14 then return true if jump_checks_to?(KING_MOVES, here)
163
+ end
164
+ end
165
+ false
166
+ end
167
+ end # class Mgen
168
+
169
+ end # module RubyShogi
@@ -0,0 +1,238 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class MgenBlack < RubyShogi::Mgen
10
+ def initialize(board)
11
+ @board = board
12
+ end
13
+
14
+ def handle_capture(copy, eat)
15
+ piece = case eat
16
+ when -2..-1 then -1
17
+ when -4..-3 then -3
18
+ when -6..-5 then -5
19
+ when -8..-7 then -7
20
+ when -9 then -9
21
+ when -11..-10 then -10
22
+ when -13..-12 then -12
23
+ end
24
+ copy.white_pocket.push(piece)
25
+ end
26
+
27
+ def add_new_move(me, to, promo = 0)
28
+ return if handle_promotion?(me, to)
29
+ push_move(me, to, promo)
30
+ end
31
+
32
+ def push_move(me, to, promo)
33
+ board2 = @board
34
+ copy = @board.copy_me
35
+ copy.from = @from_gen
36
+ copy.promo = promo
37
+ copy.r50 += 1
38
+ copy.fullmoves += 1
39
+ copy.r50 = 0 if [-1, -3, -5].include?(me)
40
+ copy.to = to
41
+ copy.eat = copy.brd[to]
42
+ copy.black_pocket.push(-1 * eaten_piece(copy.eat)) if copy.eat != 0
43
+ copy.wtm = !copy.wtm
44
+ copy.brd[@from_gen] = 0
45
+ copy.brd[to] = me
46
+ copy.bking = to if me == -14
47
+ #fail if copy.find_black_king != copy.bking
48
+ @board = copy
49
+ @moves.push << copy if !checks_w?
50
+ @board = board2
51
+ end
52
+
53
+ def pawn_drop_checkmate?(to)
54
+ @board.brd[to - 9] == 14 && !checks_w?(to - 9) ? true : false
55
+ end
56
+
57
+ def add_new_drop_move(me, to)
58
+ return if me == -3 && to / 9 == 0
59
+ return if me == -5 && to / 9 <= 1
60
+ board2 = @board
61
+ copy = @board.copy_me
62
+ copy.from = @from_gen
63
+ copy.to = to
64
+ copy.drop = me
65
+ copy.r50 += 1
66
+ copy.fullmoves += 1
67
+ copy.eat = 0
68
+ copy.wtm = ! copy.wtm
69
+ copy.brd[@from_gen] = 0
70
+ copy.brd[to] = me
71
+ copy.black_pocket = remove_from_array(copy.black_pocket, me)
72
+ #fail if copy.find_black_king != copy.bking
73
+ @board = copy
74
+ if !checks_w? && !(me == 1 && pawn_drop_checkmate?(to))
75
+ @moves.push << copy
76
+ end
77
+ @board = board2
78
+ end
79
+
80
+ def handle_promotion?(me, to)
81
+ return true if must_promote?(me, to)
82
+ return false if to / 9 > 2 && @from_gen / 9 > 2
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
+ end
110
+
111
+ def must_promote?(me, to)
112
+ if me == -5 && to / 9 <= 1
113
+ push_move(-6, to, PROMO_YES)
114
+ return true
115
+ end
116
+ return false if to / 9 != 0
117
+ case me
118
+ when -1
119
+ push_move(-2, to, PROMO_YES)
120
+ return true
121
+ when -3
122
+ push_move(-4, to, PROMO_YES)
123
+ return true
124
+ end
125
+ false
126
+ end
127
+
128
+ def add_new_pawn_move(to)
129
+ case to / 9
130
+ when 1..2
131
+ push_move(-1, to, PROMO_STAY)
132
+ push_move(-2, to, PROMO_YES)
133
+ when 0 then push_move(-2, to, PROMO_YES)
134
+ else
135
+ push_move(-1, to, PROMO_NO)
136
+ end
137
+ end
138
+
139
+ def generate_pawn_moves
140
+ to = @x_gen + (@y_gen - 1) * 9
141
+ add_new_pawn_move(to) if to >= 0 && @board.walkable_b?(to)
142
+ end
143
+
144
+ def generate_jump_moves(jumps, me)
145
+ jumps.each do |jmp|
146
+ px, py = @x_gen + jmp[0], @y_gen + jmp[1]
147
+ to = px + 9 * py
148
+ add_new_move(me, to) if is_on_board?(px, py) && @board.walkable_b?(to)
149
+ end
150
+ end
151
+
152
+ def generate_slider_moves(slider, me)
153
+ slider.each do | jmp |
154
+ px, py = @x_gen, @y_gen
155
+ loop do
156
+ px, py = px + jmp[0], py + jmp[1]
157
+ break if !is_on_board?(px, py)
158
+ to = px + 9 * py
159
+ add_new_move(me, to) if @board.walkable_b?(to)
160
+ break if !@board.empty?(to)
161
+ end
162
+ end
163
+ end
164
+
165
+ def pawn_on_column?(c)
166
+ ret = false
167
+ 9.times do |i|
168
+ to = -9 * i + 8 * 9 + c
169
+ if to != @from_gen && @board.brd[to] == -1
170
+ ret = true
171
+ break
172
+ end
173
+ end
174
+ ret
175
+ end
176
+
177
+ def put_pawn_drops
178
+ (9*8).times do |i2|
179
+ i = i2 + 9
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.black_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
+ #puts @board.brd[i]
213
+ case @board.brd[i]
214
+ when -1 then generate_pawn_moves
215
+ when -2 then generate_jump_moves(BLACK_GOLD_GENERAL_MOVES, -2)
216
+ when -3 then generate_slider_moves(BLACK_LANCE_MOVES, -3)
217
+ when -4 then generate_jump_moves(BLACK_GOLD_GENERAL_MOVES, -4)
218
+ when -5 then generate_jump_moves(BLACK_KNIGHT_MOVES, -5)
219
+ when -6 then generate_jump_moves(BLACK_GOLD_GENERAL_MOVES, -6)
220
+ when -7 then generate_jump_moves(BLACK_SILVER_GENERAL_MOVES, -7)
221
+ when -8 then generate_jump_moves(BLACK_GOLD_GENERAL_MOVES, -8)
222
+ when -9 then generate_jump_moves(BLACK_GOLD_GENERAL_MOVES, -9)
223
+ when -10 then generate_slider_moves(BISHOP_MOVES, -10)
224
+ when -11
225
+ generate_slider_moves(BISHOP_MOVES, -11)
226
+ generate_jump_moves(PROMOTED_BISHOP_MOVES, -11)
227
+ when -12 then generate_slider_moves(ROOK_MOVES, -12)
228
+ when -13
229
+ generate_slider_moves(ROOK_MOVES, -13)
230
+ generate_jump_moves(PROMOTED_ROOK_MOVES, -13)
231
+ when -14 then generate_jump_moves(KING_MOVES, -14)
232
+ end
233
+ end
234
+ generate_drops
235
+ end
236
+ end # class MgenBlack
237
+
238
+ end # module RubyShogi