Browse Source

Concurrency: introduce some concurrency. Surprise surprise, it's not stronger in any way.

master
Guillaume Grenet 6 years ago
parent
commit
0a7b6f82c1
  1. 2
      lib/board/outcome.ex
  2. 2
      lib/board/reading.ex
  3. 1
      lib/helpers.ex
  4. 92
      lib/player/mc_rave.ex
  5. 14
      lib/player/mc_rave/worker.ex
  6. 3
      mix.exs
  7. 80
      test/board/utilities.exs
  8. 103
      test/player/mc_rave_test.exs

2
lib/board/outcome.ex

@ -0,0 +1,2 @@
defmodule WeiqiDMC.Board.Outcome do
end

2
lib/board/reading.ex

@ -0,0 +1,2 @@
defmodule WeiqiDMC.Board.Reading do
end

1
lib/helpers.ex

@ -40,7 +40,6 @@ defmodule WeiqiDMC.Helpers do
{row, column+1}
end
def coordinate_tuple_to_string()
def coordinate_tuple_to_string(nil) do "none" end
def coordinate_tuple_to_string(:resign) do "resign" end
def coordinate_tuple_to_string(:pass) do "pass" end

92
lib/player/mc_rave.ex

@ -2,27 +2,62 @@ defmodule WeiqiDMC.Player.MCRave do
alias WeiqiDMC.Board
alias WeiqiDMC.Board.State
@workers 10
@constant_bias 0.1
@heuristic_confidence 5
@heuristic_confidence 10
#Useful for testing
def state_hash(state) when is_atom(state) do state end
def state_hash(state) do
state.board
end
def state_hash(state) do state.board end
def generate_move(state, think_time_ms, show_stats \\ false) do
:random.seed(:os.timestamp)
mc_rave_state = mc_rave state, think_time_ms*1000, %WeiqiDMC.Player.MCRave.State{}
workers = spawn_mc_rave_workers @workers, []
{:ok, mc_rave_state_agent} = Agent.start_link(fn -> %WeiqiDMC.Player.MCRave.State{} end)
Enum.each workers, fn (worker) ->
send worker, {:compute, self, mc_rave_state_agent, state}
end
generate_move_loop state, mc_rave_state_agent, :erlang.system_time(:micro_seconds) + think_time_ms*1000, think_time_ms*1000, show_stats
end
def generate_move_loop(state, mc_rave_state_agent, _, remaining_time, show_stats) when remaining_time <= 0 do
mc_rave_state = Agent.get(mc_rave_state_agent, &(&1))
if show_stats do
show_stats(state, mc_rave_state)
end
select_move state, mc_rave_state, true
end
def generate_move_loop(state, mc_rave_state_agent, target_time, remaining_time, show_stats) do
receive do
{:computed, worker, {known_states, known_actions, missing_actions, new_node, outcome}} ->
Agent.update(mc_rave_state_agent, fn (mc_rave_state) ->
unless new_node == nil do
{state, parent_hash} = new_node
mc_rave_state = new_node(state, parent_hash, mc_rave_state)
end
backup mc_rave_state, known_states, known_actions, missing_actions, outcome
end)
send worker, {:compute, self, mc_rave_state_agent, state}
generate_move_loop state, mc_rave_state_agent, target_time, target_time - :erlang.system_time(:micro_seconds), show_stats
received ->
IO.inspect {:supervisor, self, :received_unknown, received}
generate_move_loop state, mc_rave_state_agent, target_time, remaining_time - 100, show_stats
after
remaining_time ->
generate_move_loop state, mc_rave_state_agent, target_time, 0, show_stats
end
end
def spawn_mc_rave_workers(0, spawned) do spawned end
def spawn_mc_rave_workers(to_spawn, spawned) do
pid = spawn_link &WeiqiDMC.Player.MCRave.Worker.compute/0
spawn_mc_rave_workers to_spawn - 1, [pid|spawned]
end
def show_stats(state, mc_rave_state) do
IO.puts "\nMove generation"
IO.puts "---------------"
@ -45,19 +80,10 @@ defmodule WeiqiDMC.Player.MCRave do
IO.puts "Selected moved: #{WeiqiDMC.Helpers.coordinate_tuple_to_string(move)} \n\n"
end
def mc_rave(_, remaining_time, mc_rave_state) when remaining_time < 0 do
mc_rave_state
end
def mc_rave(state, remaining_time, mc_rave_state) do
{elapsed, mc_rave_state} = :timer.tc &simulate/2, [state, mc_rave_state]
mc_rave state, remaining_time - elapsed, mc_rave_state
end
def simulate(state, mc_rave_state) do
{known_states, known_actions, mc_rave_state} = sim_tree [state], [], mc_rave_state
{missing_actions, outcome} = sim_default List.last(known_states), []
backup mc_rave_state, known_states, known_actions, missing_actions, outcome
{known_states, known_actions, new_node} = sim_tree [state], [], mc_rave_state
{missing_actions, outcome} = multiple_sim_default List.last(known_states), HashSet.new, @workers*2, @workers*2, 0
{known_states, known_actions, Set.to_list(missing_actions), new_node, outcome}
end
def backup(mc_rave_state, [], _, _, _) do
@ -99,13 +125,13 @@ defmodule WeiqiDMC.Player.MCRave do
def sim_tree([state|states], actions, mc_rave_state) do
if game_over?(state) do
{Enum.reverse(states), Enum.reverse(actions), mc_rave_state}
{Enum.reverse(states), Enum.reverse(actions), nil}
else
state_hash = state_hash state
if !tree_member?(mc_rave_state.tree, state_hash) do
parent_hash = states |> List.first |> state_hash
{new_action, _} = default_policy(state)
{Enum.reverse([state|states]), Enum.reverse([new_action|actions]), new_node(state, parent_hash, mc_rave_state)}
{Enum.reverse([state|states]), Enum.reverse([new_action|actions]), {state, parent_hash}}
else
new_action = select_move(state, mc_rave_state, false)
{:ok, new_state} = Board.compute_move(state, new_action, state.next_player)
@ -114,36 +140,48 @@ defmodule WeiqiDMC.Player.MCRave do
end
end
def multiple_sim_default(_, moves, total_simulation, 0, total_outcome) do
{moves, round(total_outcome/total_simulation)}
end
def multiple_sim_default(from_state, moves, total_simulation, remaining_simulation, total_outcome) do
{new_moves, outcome} = sim_default from_state, []
multiple_sim_default from_state, Set.union(moves, Enum.into(new_moves, HashSet.new)),
total_simulation, remaining_simulation - 1, total_outcome + outcome
end
def sim_default(from_state, moves) do
if game_over?(from_state) do
{moves, outcome?(from_state)}
else
new_action = default_policy(from_state)
{:ok, new_state} = case new_action do
{coordinate, precomputed} = default_policy(from_state)
{:ok, new_state} = case {coordinate, precomputed} do
{:pass, _} -> Board.compute_move(from_state, :pass)
{coordinate, precomputed} -> Board.compute_valid_move(from_state, coordinate, precomputed)
end
sim_default new_state, moves ++ [new_action]
sim_default new_state, moves ++ [coordinate]
end
end
def select_move(state, mc_rave_state, allow_resign) do
legal_moves = legal_moves state
state_hash = state_hash state
considered_moves = Dict.keys(mc_rave_state.q)
|> Enum.filter_map(fn {hash, _} -> hash == state_hash end,
fn {_, move} -> move end)
if Enum.empty?(legal_moves) do
if Enum.empty?(considered_moves) do
:pass
else
state_hash = state_hash state
if state.next_player == :black do
move = Enum.max_by(legal_moves, fn(move) -> eval(state_hash, move, mc_rave_state) end)
move = Enum.max_by(considered_moves, fn(move) -> eval(state_hash, move, mc_rave_state) end)
if allow_resign and eval(state_hash, move, mc_rave_state) < 0.3 do
:resign
else
move
end
else
move = Enum.min_by(legal_moves, fn(move) -> eval(state_hash, move, mc_rave_state) end)
move = Enum.min_by(considered_moves, fn(move) -> eval(state_hash, move, mc_rave_state) end)
if allow_resign and eval(state_hash, move, mc_rave_state) > 0.7 do
:resign
else

14
lib/player/mc_rave/worker.ex

@ -0,0 +1,14 @@
defmodule WeiqiDMC.Player.MCRave.Worker do
def compute do
receive do
{:compute, supervisor, mc_rave_state_agent, state} ->
mc_rave_state = Agent.get(mc_rave_state_agent, &(&1))
result = WeiqiDMC.Player.MCRave.simulate state, mc_rave_state
send supervisor, {:computed, self, result}
compute
received ->
IO.inspect {:worker, self, :received_unknown, received}
compute
end
end
end

3
mix.exs

@ -27,7 +27,6 @@ defmodule WeiqiDMC.Mixfile do
#
# Type `mix help deps` for more examples and options
defp deps do
[{:exprof, "~> 0.2"},
{:poolboy, github: "devinus/poolboy", tag: "1.5.1"}]
[{:exprof, "~> 0.2"}]
end
end

80
test/board/utilities.exs

@ -0,0 +1,80 @@
# test "#game_over? will detect a game when it's over", %{state: state} do
# state = play_moves state, [ "B9", "D9", "E9", "F9", "G9", "H9", "J9",
# "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5"], "Black"
# state = play_moves state, ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "C1", "D1", "E1", "G1", "H1", "J1"], "White"
# assert Player.game_over?(state) == true
# end
# test "#game_over? will detect when there is still a dame to play", %{state: state} do
# state = play_moves state, [ "B9", "D9", "E9", "F9", "G9", "H9", "J9",
# "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5"], "Black"
# state = play_moves state, ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "C1", "D1", "E1", "G1", "H1", "J1"], "White"
# assert Player.game_over?(state) == false
# end
# test "#game_over? - case#1", %{state: state} do
# state = play_moves state, [ "D9", "E9", "F9", "G9", "H9", "J9",
# "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5",
# "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "J1" ], "Black"
# state = play_moves state, ["A9", "C9"], "White"
# assert Player.game_over?(state) == false
# end
# test "#game_over? - case#2", %{state: state} do
# state = play_moves state, [ "D9", "E9", "F9", "G9", "H9", "J9",
# "A8" , "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5",
# "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "J1" ], "Black"
# state = play_moves state, ["B9"], "White"
# assert Player.game_over?(Board.force_next_player(state, :white)) == false
# end
# test "#game_over? - case#3", %{state: state} do
# state = play_moves state, [ "D9", "E9", "F9", "G9", "H9", "J9",
# "A8" , "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5",
# "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "J1" ], "Black"
# assert Player.game_over?(Board.force_next_player(state, :white)) == false
# end
# test "#game_over? this isn't game over even tho there is only one group with 2 liberties", %{state: state} do
# state = play_moves state, ["A9"], "Black"
# assert Player.game_over?(state) == false
# end

103
test/player/mc_rave_test.exs

@ -80,87 +80,6 @@ defmodule WeiqiDMC.Player.McRaveTest do
assert Player.outcome?(state) == 0
end
# test "#game_over? will detect a game when it's over", %{state: state} do
# state = play_moves state, [ "B9", "D9", "E9", "F9", "G9", "H9", "J9",
# "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5"], "Black"
# state = play_moves state, ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "C1", "D1", "E1", "G1", "H1", "J1"], "White"
# assert Player.game_over?(state) == true
# end
# test "#game_over? will detect when there is still a dame to play", %{state: state} do
# state = play_moves state, [ "B9", "D9", "E9", "F9", "G9", "H9", "J9",
# "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5"], "Black"
# state = play_moves state, ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "C1", "D1", "E1", "G1", "H1", "J1"], "White"
# assert Player.game_over?(state) == false
# end
# test "#game_over? - case#1", %{state: state} do
# state = play_moves state, [ "D9", "E9", "F9", "G9", "H9", "J9",
# "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5",
# "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "J1" ], "Black"
# state = play_moves state, ["A9", "C9"], "White"
# assert Player.game_over?(state) == false
# end
# test "#game_over? - case#2", %{state: state} do
# state = play_moves state, [ "D9", "E9", "F9", "G9", "H9", "J9",
# "A8" , "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5",
# "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "J1" ], "Black"
# state = play_moves state, ["B9"], "White"
# assert Player.game_over?(Board.force_next_player(state, :white)) == false
# end
# test "#game_over? - case#3", %{state: state} do
# state = play_moves state, [ "D9", "E9", "F9", "G9", "H9", "J9",
# "A8" , "C8", "D8", "E8", "F8", "G8", "H8", "J8",
# "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", "J7",
# "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "J6",
# "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "J5",
# "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "J4",
# "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "J3",
# "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "J2",
# "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "J1" ], "Black"
# assert Player.game_over?(Board.force_next_player(state, :white)) == false
# end
# test "#game_over? this isn't game over even tho there is only one group with 2 liberties", %{state: state} do
# state = play_moves state, ["A9"], "Black"
# assert Player.game_over?(state) == false
# end
test "#new_node", %{state: state} do
mc_rave_state = %MCRaveState{tree: {:root, []} }
@ -178,9 +97,9 @@ defmodule WeiqiDMC.Player.McRaveTest do
mc_rave_state = Player.new_node(state, :root, mc_rave_state)
assert Dict.fetch!(mc_rave_state.n, {state_hash, {9,1}}) == 5
assert Dict.fetch!(mc_rave_state.n, {state_hash, {9,1}}) == 10
assert Dict.fetch!(mc_rave_state.q, {state_hash, {9,1}}) == 0.5
assert Dict.fetch!(mc_rave_state.n_tilde, {state_hash, {9,1}}) == 5
assert Dict.fetch!(mc_rave_state.n_tilde, {state_hash, {9,1}}) == 10
assert Dict.fetch!(mc_rave_state.q_tilde, {state_hash, {9,1}}) == 0.5
end
@ -248,22 +167,6 @@ defmodule WeiqiDMC.Player.McRaveTest do
state = %{state | moves: 0}
assert Player.outcome?(state) == 1
assert Player.generate_move(Board.force_next_player(state, :white), 100) == {9,2}
assert Player.generate_move(Board.force_next_player(state, :white), 500) == {9,2}
end
# test "will pick the actual best move", %{state: state} do
# {:ok, state} = Board.set_handicap state, 6
# state = play_moves state, ["E7"], "White"
# state = play_moves state, ["A8"], "Black"
# state = play_moves state, ["F6"], "White"
# state = play_moves state, ["J3"], "Black"
# state = play_moves state, ["G6"], "White"
# state = play_moves state, ["D3"], "Black"
# state = play_moves state, ["H5"], "White"
# state = play_moves state, ["A6"], "Black"
# state = play_moves state, ["G4"], "White"
# assert Player.generate_move(state, 1000) == {1,3}
# end
end
Loading…
Cancel
Save