RubyShogi 0.1

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