RubyShogi 0.1

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