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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2b3979a80f7a932007c0a2dc821befcf794f95f63681b4da2623ef74d3d7f9d4
4
+ data.tar.gz: 4749eee553d260501697177086c02e1cb483acbbcb379f4af13da8da8b89f935
5
+ SHA512:
6
+ metadata.gz: b62f9d942059d2c9275152930b72b7550dfd1f4ef9d66578dbd17694ea8c8d165e1fc79d34693b3d9e061ed2cb6b913962cb8652b3c290c91c04d464fa447695
7
+ data.tar.gz: 7fb81aec4dd6595376326a8934f5ce7db33001a7ed08094a7f414975b821e0a07833029e4fc308a78bd47dba503e497c56502c00d75ee4c2925b78c9f02f3406
data/bin/ruby_shogi ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ##
4
+ # RubyShogi, a Shogi Engine
5
+ # Author: Toni Helminen
6
+ # License: GPLv3
7
+ ##
8
+
9
+ require 'ruby_shogi'
10
+
11
+ RubyShogi.go
data/lib/ruby_shogi.rb ADDED
@@ -0,0 +1,7 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ require_relative "./ruby_shogi/ruby_shogi"
@@ -0,0 +1,127 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ require 'benchmark'
10
+
11
+ module Bench
12
+ def Bench.f1
13
+ n = 0
14
+ 1000_000.times { |i| n += (i%80) }
15
+ n
16
+ end
17
+
18
+ def Bench.f2
19
+ n = 0
20
+ 1000_000.times { |i| n += (i%80) }
21
+ n
22
+ end
23
+
24
+ def Bench.f3
25
+ n, i = 0, 0
26
+ while i < 1000_000
27
+ n, i = n + (i%80), i + 1
28
+ end
29
+ n
30
+ end
31
+
32
+ def Bench.f3_1
33
+ n = 0
34
+ for i in 0..1000_000 do
35
+ n = n + (i%80)
36
+ end
37
+ n
38
+ end
39
+
40
+ def Bench.loops
41
+ header("loops")
42
+ Benchmark.bm(10) do |x|
43
+ x.report("each") { f1 }
44
+ x.report("times") { f2 }
45
+ x.report("while") { f3 }
46
+ x.report("for") { f3_1 }
47
+ #x.compare!
48
+ end
49
+ end
50
+
51
+ def Bench.f4
52
+ n = 0
53
+ 1000_000.times { |i| n += i%42 }
54
+ n
55
+ end
56
+
57
+ def Bench.f5
58
+ n = 0
59
+ 1000_000.times { |i| n += i.modulo 42 }
60
+ n
61
+ end
62
+
63
+ def Bench.modulo
64
+ header("modulo")
65
+ Benchmark.bm(10) do |x|
66
+ x.report("%") { f4 }
67
+ x.report("modulo") { f5 }
68
+ end
69
+ end
70
+
71
+ def Bench.f6
72
+ s, caps = ["abc", "def", "ghi", "jkl"], ""
73
+ 100_000.times { caps = s.map { |str| str.upcase } }
74
+ caps
75
+ end
76
+
77
+ def Bench.f7
78
+ s, caps = ["abc", "def", "ghi", "jkl"], ""
79
+ 100_000.times { caps = s.map(&:upcase) }
80
+ caps
81
+ end
82
+
83
+ def Bench.caps
84
+ header("caps")
85
+ Benchmark.bm(10) do |x|
86
+ x.report(".upcase") { f6 }
87
+ x.report("&:upcase") { f7 }
88
+ end
89
+ end
90
+
91
+ def Bench.f8
92
+ s, s2 = "a", "abc"
93
+ 20_000.times { s += s2 }
94
+ end
95
+
96
+ def Bench.f9
97
+ s, s2 = "a", "abc"
98
+ 20_000.times { s << s2 }
99
+ end
100
+
101
+ def Bench.f10
102
+ s, s2 = "a", "abc"
103
+ 20_000.times { s = "#{s}#{s2}" }
104
+ end
105
+
106
+ def Bench.header(msg)
107
+ puts "... #{msg} ..."
108
+ end
109
+
110
+ def Bench.strings
111
+ header("strings")
112
+ Benchmark.bm(10) do |x|
113
+ x.report("+=") { f8 }
114
+ x.report("<<") { f9 }
115
+ x.report("\#\{\}") { f10 }
116
+ end
117
+ end
118
+
119
+ def Bench.go
120
+ loops
121
+ modulo
122
+ caps
123
+ strings
124
+ end
125
+ end # module Bench
126
+
127
+ end # module RubyShogi
@@ -0,0 +1,451 @@
1
+ ##
2
+ # RubyShogi, a Shogi Engine
3
+ # Author: Toni Helminen
4
+ # License: GPLv3
5
+ ##
6
+
7
+ module RubyShogi
8
+
9
+ class Board
10
+ START_POS = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1"
11
+
12
+ PIECES = {
13
+ ".": 0,
14
+ "P": 1, # Pawn
15
+ "p": -1,
16
+ "+P": 2, # Promoted Pawn
17
+ "+p": -2,
18
+ "L": 3, # Lance
19
+ "l": -3,
20
+ "+L": 4, # Promoted Lance
21
+ "+l": -4,
22
+ "N": 5, # Knight
23
+ "n": -5,
24
+ "+N": 6, # Promoted Knight
25
+ "+n": -6,
26
+ "S": 7, # Silver
27
+ "s": -7,
28
+ "+S": 8, # Promoted Silver
29
+ "+s": -8,
30
+ "G": 9, # Gold
31
+ "g": -9,
32
+ "B": 10, # Bishop
33
+ "b": -10,
34
+ "+B": 11, # Promoted Bishop
35
+ "+b": -11,
36
+ "R": 12, # Rook
37
+ "r": -12,
38
+ "+R": 13, # Promoted Rook
39
+ "+r": -13,
40
+ "K": 14, # King
41
+ "k": -14
42
+ }.freeze
43
+
44
+ attr_accessor :brd, :wking, :bking, :white_pocket, :black_pocket, :variant, :nodetype, :r50, :drop, :hash, :fullmoves, :wtm, :eat, :from, :to, :score, :promo, :index
45
+
46
+ def initialize(pos = nil)
47
+ initme
48
+ fen(pos)
49
+ end
50
+
51
+ def initme
52
+ @brd = [0] * 81
53
+ @wtm, @from, @to, @eat = true, 0, 0, 0
54
+ @score, @promo = 0, 0
55
+ @white_pocket = []
56
+ @black_pocket = []
57
+ @index = 0
58
+ @r50 = 0
59
+ @drop = 0
60
+ @hash = 0
61
+ @wking = 0
62
+ @bking = 0
63
+ @fullmoves = 2
64
+ @nodetype = 0 # 2 draw 1 win -1 loss
65
+ end
66
+
67
+ def brd2str
68
+ s, empty, counter = "", 0, 0
69
+ 80.times do |j|
70
+ i = 10 * (7 - j / 10) + ( j % 10 )
71
+ p = @brd[i]
72
+ if p != 0
73
+ if empty > 0
74
+ s += empty.to_s
75
+ empty = 0
76
+ end
77
+ s += "fcakqrbnp.PNBRQKACF"[p + 9]
78
+ else
79
+ empty += 1
80
+ end
81
+ counter += 1
82
+ if counter % 10 == 0
83
+ s += empty.to_s if empty > 0
84
+ s += "/" if counter < 80
85
+ empty = 0
86
+ end
87
+ end
88
+ s
89
+ end
90
+
91
+ def wtm2str
92
+ @wtm ? "w" : "b"
93
+ end
94
+
95
+ def tofen
96
+ "#{brd2str} #{wtm2str}"
97
+ end
98
+
99
+ def mgen_generator
100
+ @wtm ? RubyShogi::MgenWhite.new(self) : RubyShogi::MgenBlack.new(self)
101
+ end
102
+
103
+ def create_hash
104
+ @hash = 0
105
+ 81.times { | i | @hash ^= RubyShogi::Zobrist.get(20 * i + 8 + @brd[i]) }
106
+ @hash ^= RubyShogi::Zobrist.get(20 * 80 + (@wtm ? 1 : 0))
107
+ end
108
+
109
+ def legal?
110
+ pieces = [0] * 20
111
+ @brd.each { |p| pieces[p + 9] += 1 }
112
+ return false if pieces[-6 + 9] == 0 || pieces[6 + 9] == 0
113
+ true
114
+ end
115
+
116
+ def make_move(me, from, to)
117
+ #fail unless (good_coord?(from) && good_coord?(to))
118
+ @eat = @brd[to]
119
+ @brd[to] = me
120
+ @brd[from] = 0
121
+ end
122
+
123
+ def find_white_king
124
+ @brd.index { | x | x == 14 }
125
+ end
126
+
127
+ def find_black_king
128
+ @brd.index { | x | x == -14 }
129
+ end
130
+
131
+ def find_piece_all(piece)
132
+ @brd.index { | x | x == piece }
133
+ end
134
+
135
+ # scans ->
136
+ def find_piece(start_square, end_square, me, diff = 1)
137
+ i = start_square
138
+ loop do
139
+ return i if @brd[i] == me
140
+ fail "ShurikenShogi Error: Couldn't Find: '#{me}'" if i == end_square
141
+ i += diff
142
+ end
143
+ end
144
+
145
+ # scans ->
146
+ def just_kings?
147
+ 81.times do |i|
148
+ return false if @brd[i] != 14 && @brd[i] != -14
149
+ end
150
+ true
151
+ end
152
+
153
+ def material_draw?
154
+ 81.times do |i|
155
+ return false if @brd[i] != 14 && @brd[i] != -14 && @brd[i] != 0
156
+ end
157
+ true
158
+ end
159
+
160
+ def copy_me()
161
+ copy = RubyShogi::Board.new
162
+ copy.brd = @brd.dup
163
+ copy.white_pocket = @white_pocket.dup
164
+ copy.black_pocket = @black_pocket.dup
165
+ copy.wtm = @wtm
166
+ copy.from = @from
167
+ copy.to = @to
168
+ copy.r50 = @r50
169
+ copy.wking = @wking
170
+ copy.bking = @bking
171
+ copy
172
+ end
173
+
174
+ def startpos
175
+ fen(START_POS)
176
+ end
177
+
178
+ def last_rank?(square)
179
+ y_coord(square) == 8
180
+ end
181
+
182
+ def first_rank?(x)
183
+ y_coord(x) == 0
184
+ end
185
+
186
+ def empty?(i)
187
+ @brd[i] == 0
188
+ end
189
+
190
+ def walkable_w?(square)
191
+ @brd[square] < 1
192
+ end
193
+
194
+ def walkable_b?(square)
195
+ @brd[square] > -1
196
+ end
197
+
198
+ def is_on_board?(x, y)
199
+ x >= 0 && x <= 8 && y >= 0 && y <= 8
200
+ end
201
+
202
+ def good_coord?(i)
203
+ i >= 0 && i < 81
204
+ end
205
+
206
+ def distance(p1, p2)
207
+ [(p1 % 9 - p2 % 9 ).abs, (p1 / 9 - p2 / 9).abs].max
208
+ end
209
+
210
+ def jishogi_likely_w?(wking)
211
+ res = 0
212
+ 81.times { |i| res += 1 if @brd[i] > 0 && distance(wking, i) < 3 }
213
+ res > 5
214
+ end
215
+
216
+ def jishogi_likely_b?(bking)
217
+ res = 0
218
+ 81.times { |i| res += 1 if @brd[i] < 0 && distance(bking, i) < 3 }
219
+ res > 5
220
+ end
221
+
222
+ # TODO improve likely?
223
+ def jishogi?
224
+ wking, bking = find_white_king, find_black_king
225
+ if wking / 9 >= 6 && bking / 9 <= 2 && jishogi_likely_w?(wking) && jishogi_likely_b?(bking)
226
+ return true
227
+ end
228
+ false
229
+ end
230
+
231
+ def count_jishogi_w
232
+ res = 0
233
+ 81.times do |i|
234
+ if [10, 12].include?(@brd[i])
235
+ res += 5
236
+ elsif @brd[i] != 14
237
+ res += 1
238
+ end
239
+ end
240
+ res
241
+ end
242
+
243
+ def count_jishogi_b
244
+ res = 0
245
+ 81.times do |i|
246
+ if [-10, -12].include?(@brd[i])
247
+ res += 5
248
+ elsif @brd[i] != -14
249
+ res += 1
250
+ end
251
+ end
252
+ res
253
+ end
254
+
255
+ def mirror_board
256
+ (4*9).times do | i |
257
+ x, y = i % 9, i / 9
258
+ flip_y = x + (8 - y) * 9
259
+ p1 = @brd[i]
260
+ p2 = @brd[flip_y]
261
+ @brd[i] = p2
262
+ @brd[flip_y] = p1
263
+ end
264
+ end
265
+
266
+ def flip_coord(coord)
267
+ (9 - 1 - y_coord(coord)) * 9 + x_coord(coord)
268
+ end
269
+
270
+ # TODO optimize
271
+ def number2piece(num)
272
+ ret = 0
273
+ PIECES.each { |piece2, num2|
274
+ if num.to_i == num2.to_i
275
+ ret = piece2
276
+ break
277
+ end
278
+ }
279
+ ret.to_s
280
+ end
281
+
282
+ # TODO optimize
283
+ def piece2number(piece)
284
+ ret = 0
285
+ PIECES.each { |piece2, num|
286
+ if piece == piece2.to_s
287
+ ret = num
288
+ break
289
+ end
290
+ }
291
+ ret
292
+ end
293
+
294
+ def pos2fen
295
+ s = ""
296
+ 9.times do |y|
297
+ empty = 0
298
+ 9.times do |x|
299
+ p = @brd[9 * (8 - y) + x]
300
+ if p == 0
301
+ empty += 1
302
+ else
303
+ if empty > 0
304
+ s << empty.to_s
305
+ empty = 0
306
+ end
307
+ s << number2piece(p)
308
+ end
309
+ end
310
+ s << empty.to_s if empty > 0
311
+ s << "/" if y < 8
312
+ end
313
+ s << "["
314
+ @white_pocket.each { |p| s << number2piece(p) }
315
+ @black_pocket.each { |p| s << number2piece(p) }
316
+ s << "-" if @white_pocket.empty? && @black_pocket.empty?
317
+ s << "] "
318
+ s << (@wtm ? "w" : "b")
319
+ s << " #{@r50.to_s}"
320
+ s << " #{(@fullmoves/2).to_i}"
321
+ s
322
+ end
323
+
324
+ def fen_board(s)
325
+ s = s.gsub(/\d+/) { | m | "_" * m.to_i }
326
+ .gsub(/\//) { | m | "" }
327
+ i, k = 0, 0
328
+ while i < s.length
329
+ piece = s[i]
330
+ if s[i] == "+"# && i + 1 < s.length
331
+ i += 1
332
+ piece = "+#{s[i]}"
333
+ end
334
+ @brd[k] = piece2number(piece)
335
+ k += 1
336
+ i += 1
337
+ end
338
+ end
339
+
340
+ def fen_pocket(s)
341
+ @white_pocket = []
342
+ @black_pocket = []
343
+ s.strip!
344
+ return if s == "-"
345
+ i = 0
346
+ while i < s.length
347
+ num = piece2number(s[i])
348
+ if num > 0
349
+ @white_pocket.push(num)
350
+ elsif num < 0
351
+ @black_pocket.push(num)
352
+ end
353
+ i += 1
354
+ end
355
+ end
356
+
357
+ def fen_wtm(s)
358
+ @wtm = s == "w" ? true : false
359
+ end
360
+
361
+ def fen(str)
362
+ return if str.nil?
363
+ initme
364
+ s = str.strip.split(" ")
365
+ fail if s.length < 3
366
+ t = s[0].strip.split("[")
367
+ fen_board(t[0])
368
+ fen_pocket(t[1])
369
+ @wtm = s[1] == "w" ? true : false
370
+ @r50 = 2 * s[2].to_i if s.length >= 3
371
+ @fullmoves = 2 * s[3].to_i if s.length >= 4
372
+ mirror_board
373
+ @wking = find_white_king
374
+ @bking = find_black_king
375
+ end
376
+
377
+ def eval
378
+ Eval.eval(self)
379
+ end
380
+
381
+ def material
382
+ Eval.material(self)
383
+ end
384
+
385
+ def pocket2str
386
+ s = ""
387
+ @white_pocket.each { |num| s << number2piece(num) }
388
+ @black_pocket.each { |num| s << number2piece(num) }
389
+ s.strip.length == 0 ? "-" : s
390
+ end
391
+
392
+ def move_str
393
+ if @drop != 0
394
+ s = "#{number2piece(@drop).upcase}@"
395
+ tox, toy = @to % 9, @to / 9
396
+ s << ("a".ord + tox).chr
397
+ s << (toy + 1).to_s
398
+ return s
399
+ end
400
+ fromx, fromy = @from % 9, @from / 9
401
+ tox, toy = @to % 9, @to / 9
402
+ s = ("a".ord + fromx).chr
403
+ s << (fromy + 1).to_s
404
+ s << ("a".ord + tox).chr
405
+ s << (toy + 1).to_s
406
+ if @promo == 2
407
+ s << "+"
408
+ elsif @promo == 1
409
+ s << "="
410
+ end
411
+ s
412
+ end
413
+
414
+ def randpos
415
+ copy = RubyShogi::Board.new
416
+ copy.brd[rand(0.. 32)] = 14
417
+ copy.brd[rand((81-32)..80)] = -14
418
+ 32.times { |i| copy.brd[32 + i] = rand(-14..14) if rand < 0.3 }
419
+ 3.times { |i| copy.white_pocket.push([1, 3, 5, 7, 9].sample) }
420
+ 3.times { |i| copy.black_pocket.push([-1, -3, -5, -7, -9].sample) }
421
+ copy
422
+ end
423
+
424
+ def print_board
425
+ s =""
426
+ 81.times do | i |
427
+ x, y = i % 9, i / 9
428
+ p = @brd[9 * (8 - y) + x]
429
+ ch = "."
430
+ PIECES.each do |pie, num|
431
+ if num.to_i == p.to_i
432
+ ch = pie.to_s
433
+ break
434
+ end
435
+ end
436
+ s << " " if ch.length < 2
437
+ s << ch
438
+ if (i + 1) % 9 == 0
439
+ s << " #{((9 - i / 9).to_i).to_s}\n"
440
+ end
441
+ end
442
+ 9.times { |i| s << " " << ("a".ord + i).chr }
443
+ s << "\n[ wtm: #{@wtm} ]\n"
444
+ s << "[ r50: #{(@r50/2).to_i} ]\n"
445
+ s << "[ pocket: #{pocket2str} ]\n"
446
+ s << "[ fen: #{pos2fen} ]\n"
447
+ puts s
448
+ end
449
+ end # class Board
450
+
451
+ end # module RubyShogi