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?
			return RubyShogi::Engine::RESULT_BLACK_WIN
		elsif !@board.wtm && mgen.checks_w?
			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?
			puts "0-1 {Black mates}"
			return true
		elsif !@board.wtm && mgen.checks_w?
			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


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

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



246
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
# File 'lib/ruby_shogi/engine.rb', line 246

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
# 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
		@board = moves[0]
	end
	print_move_list(moves) if @debug
	@history.add(@board)
	@board.move_str
end