Class: RubyShogi::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_shogi/engine.rb

Constant Summary collapse

INF =
1000
MATERIAL_SCALE =
0.01
RESULT_DRAW =
1
RESULT_BLACK_WIN =
2
RESULT_WHITE_WIN =
4

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(random_mode: false) ⇒ Engine

Returns a new instance of Engine.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/ruby_shogi/engine.rb', line 18

def initialize(random_mode: false)
  init_mate_bonus
  @board = RubyShogi::Board.new
  @random_mode = random_mode
  @history = RubyShogi::History.new
  @board.startpos
  @printinfo = true
  @time = 10 # seconds
  @movestogo = 40
  @stop_time = 0
  @stop_search = false
  @nodes = 0
  @move_now = false
  @debug = false
  @gameover = false
end

Instance Attribute Details

#boardObject

Returns the value of attribute board.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def board
  @board
end

#debugObject

Returns the value of attribute debug.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def debug
  @debug
end

#gameoverObject

Returns the value of attribute gameover.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def gameover
  @gameover
end

#move_nowObject

Returns the value of attribute move_now.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def move_now
  @move_now
end

#movestogoObject

Returns the value of attribute movestogo.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def movestogo
  @movestogo
end

#printinfoObject

Returns the value of attribute printinfo.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def printinfo
  @printinfo
end

#random_modeObject

Returns the value of attribute random_mode.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def random_mode
  @random_mode
end

#timeObject

Returns the value of attribute time.



10
11
12
# File 'lib/ruby_shogi/engine.rb', line 10

def time
  @time
end

Instance Method Details

#benchObject



207
208
209
210
211
212
213
# File 'lib/ruby_shogi/engine.rb', line 207

def bench
  t = Time.now
  @time = 500
  think
  diff = Time.now - t
  puts "= #{@nodes} nodes | #{diff.round(3)} s | #{(@nodes/diff).to_i} nps"
end

#draw_moves(moves) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/ruby_shogi/engine.rb', line 138

def draw_moves(moves)
  moves.each do | board |
    if @history.is_draw?(board)
      board.nodetype, board.score = 2, 0
    end
  end
end

#game_status(mgen, moves) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/ruby_shogi/engine.rb', line 150

def game_status(mgen, moves)
  if moves.length == 0
    if @board.wtm && mgen.checks_b?(@board.find_white_king)
      return RubyShogi::Engine::RESULT_BLACK_WIN
    elsif !@board.wtm && mgen.checks_w?(@board.find_black_king)
      return RubyShogi::Engine::RESULT_WHITE_WIN
    end
    return RubyShogi::Engine::RESULT_DRAW
  end
  @board.create_hash
  if @history.is_draw?(@board, 3) || @board.material_draw?
    return RubyShogi::Engine::RESULT_DRAW
  end
  0
end

#hash_moves(moves) ⇒ Object



146
147
148
# File 'lib/ruby_shogi/engine.rb', line 146

def hash_moves(moves)
  moves.each { |board| board.create_hash }
end

#history_removeObject



48
49
50
# File 'lib/ruby_shogi/engine.rb', line 48

def history_remove
  @board = @history.remove
end

#history_resetObject



44
45
46
# File 'lib/ruby_shogi/engine.rb', line 44

def history_reset
  @history.reset
end

#history_undoObject



52
53
54
# File 'lib/ruby_shogi/engine.rb', line 52

def history_undo
  @board = @history.undo
end

#init_mate_bonusObject



35
36
37
38
39
40
41
42
# File 'lib/ruby_shogi/engine.rb', line 35

def init_mate_bonus
  @mate_bonus = [1] * 100
  (0..20).each { |i| @mate_bonus[i] += 20 - i }
  @mate_bonus[0] = 50
  @mate_bonus[1] = 40
  @mate_bonus[2] = 30
  @mate_bonus[3] = 25
end

#is_gameover?(mgen, moves) ⇒ Boolean

Returns:

  • (Boolean)


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/ruby_shogi/engine.rb', line 184

def is_gameover?(mgen, moves)
  @board.create_hash
  return true if jishogi?
  if @board.fullmoves > 900
    puts "1/2-1/2 {Draw by Max Moves}"
    return true
  end
  if @history.is_draw?(@board, 3)
    puts "1/2-1/2 {Draw by Sennichite}"
    return true
  end
  if moves.length == 0
    if @board.wtm && mgen.checks_b?(@board.find_white_king)
      puts "0-1 {Black mates}"
      return true
    elsif !@board.wtm && mgen.checks_w?(@board.find_black_king)
      puts "1-0 {White mates}"
      return true
    end
  end
  false
end

#jishogi?Boolean

Returns:

  • (Boolean)


166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ruby_shogi/engine.rb', line 166

def jishogi?
  if @board.jishogi?
    w = @board.count_jishogi_w
    b = @board.count_jishogi_b
    if w >= 24 && b < 24
      puts "1-0 {White wins by Jishogi}"
      return true
    elsif w < 24 && b >= 24
      puts "0-1 {Black wins by Jishogi}"
      return true
    else
      puts "1/2-1/2 {Draw by Impasse}"
      return true
    end
  end
  false
end

#make_move?(move) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ruby_shogi/engine.rb', line 66

def make_move?(move)
  mgen = @board.mgen_generator
  moves = mgen.generate_moves
  moves.each do |board|
    if board.move_str == move
      @history.add(board)
      @board = board
      return true
    end
  end
  puts "illegal move: #{move}"
  false
end

#move_listObject



60
61
62
63
64
# File 'lib/ruby_shogi/engine.rb', line 60

def move_list
  mgen = @board.mgen_generator
  moves = mgen.generate_moves
  moves.each_with_index { |board, i| puts "#{i} / #{board.move_str} / #{board.score}" }
end


56
57
58
# File 'lib/ruby_shogi/engine.rb', line 56

def print_move_list(moves)
  moves.each_with_index { |board, i| puts "#{i} / #{board.move_str} / #{board.score}" }
end


80
81
82
83
84
85
86
# File 'lib/ruby_shogi/engine.rb', line 80

def print_score(moves, depth, started)
  return unless @printinfo
  moves = moves.sort_by(&:score).reverse
  best = moves[0]
  n = (100 * (Time.now - started)).to_i
  puts " #{depth}     #{(best.score).to_i}     #{n}     #{@nodes}     #{best.move_str}"
end


239
240
241
242
243
244
245
# File 'lib/ruby_shogi/engine.rb', line 239

def print_score_stats(results)
  wscore = results[RubyShogi::Engine::RESULT_WHITE_WIN]
  bscore = results[RubyShogi::Engine::RESULT_BLACK_WIN]
  draws = results[RubyShogi::Engine::RESULT_DRAW]
  total = wscore + bscore + draws
  printf("[ Score: %i - %i - %i [%.2f] %i ]\n", wscore, bscore, draws, (wscore + 0.5 * draws) / total, total)
end

#search(moves) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/ruby_shogi/engine.rb', line 114

def search(moves)
  now = Time.now
  time4print = 0.5
  divv = @movestogo < 10 ? 20 : 30
  @stop_time = now + (@time / divv)
  depth = 2
  while true
    moves.each do |board|
      puts "> #{@nodes} / #{board.move_str}" if  @debug 
      next if board.nodetype == 2
      depth = 3 + rand(20)
      board.score += board.wtm ? search_moves_w(board, depth, 0) : search_moves_b(board, depth, 0)
      if Time.now > @stop_time || @move_now
        print_score(moves, depth, now)
        return
      end
    end
    if Time.now - now > time4print
      now = Time.now
      print_score(moves, depth, now)
    end
  end
end

#search_moves_b(cur, depth, total = 0) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ruby_shogi/engine.rb', line 101

def search_moves_b(cur, depth, total = 0)
  @nodes += 1
  @stop_search = Time.now > @stop_time || total > 90
  return 0 if @stop_search
  return MATERIAL_SCALE * cur.material if depth < 1
  mgen = RubyShogi::MgenBlack.new(cur)
  moves = mgen.generate_moves
  if moves.length == 0 # assume mate
    return mgen.checks_w? ? 0.1 * @mate_bonus[total] * INF + rand : 1
  end 
  search_moves_w(moves.sample, depth - 1, total + 1)
end

#search_moves_w(cur, depth, total = 0) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/ruby_shogi/engine.rb', line 88

def search_moves_w(cur, depth, total = 0)
  @nodes += 1
  @stop_search = Time.now > @stop_time || total > 90
  return 0 if @stop_search
  return MATERIAL_SCALE * cur.material if depth < 1
  mgen = RubyShogi::MgenWhite.new(cur)
  moves = mgen.generate_moves
  if moves.length == 0 # assume mate
    return mgen.checks_b? ? 0.1 * @mate_bonus[total] * -INF + rand : 1
  end
  search_moves_b(moves.sample, depth - 1, total + 1)
end

#stats(rounds = 10) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/ruby_shogi/engine.rb', line 247

def stats(rounds = 10)
  @nodes = 0
  @move_now = false
  board = @board
  results = [0] * 5
  puts "Running stats ..."
  rounds.times do |n| 
    @board = board
    @history.reset
    while true
      mgen = @board.mgen_generator
      moves = mgen.generate_moves
      hash_moves(moves)
      draw_moves(moves)
      status = game_status(mgen, moves)
      @board = moves.sample
      @history.add(@board)
      if status != 0
        results[status] += 1
        break
      end
    end
    print_score_stats(results) if (n + 1) % 2 == 0 && n + 1 < rounds
  end
  puts "="
  print_score_stats(results)
end

#thinkObject



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/ruby_shogi/engine.rb', line 215

def think
  @nodes = 0
  @move_now = false
  board = @board
  mgen = @board.mgen_generator
  moves = mgen.generate_moves
  hash_moves(moves)
  draw_moves(moves)
  func = -> { board.wtm ? moves.sort_by(&:score).reverse : moves.sort_by(&:score) }
  @gameover = is_gameover?(mgen, moves)
  return if @gameover
  if @random_mode
    @board = moves.sample
  else
    search(moves)
    moves = func.call
    #print_move_list(moves)
    @board = moves[0]
  end
  print_move_list(moves) if @debug
  @history.add(@board)
  @board.move_str
end