static void Main(string[] args) { Console.WriteLine($"Initializing all {State.max_id} states."); states = new List <State>(); for (int id = 0; id <= State.max_id; id++) { states.Add(new State(id)); } Console.WriteLine("Backfilling previous states."); for (int id = 0; id <= State.max_id; id++) { foreach (int next_id in states[id].next_states) { states[next_id].prev_states.Add(id); } } Console.WriteLine("Solving."); Queue <int> queue = new Queue <int>(); // Initially, put all terminal states into the queue. for (int id = 0; id <= State.max_id; id++) { if (states[id].outcome != State.Outcome.Undecided) { queue.Enqueue(id); } } // Start solving. while (queue.Count > 0) { int id = queue.Dequeue(); if (states[id].expected_outcome == State.Outcome.Undecided) { if (states[id].next_states.Count == 0) { throw new InvalidOperationException($"State #{id} is not terminal, yet it has no next states."); } // Attempt to solve this state. // First, count next states. State.Outcome winning_outcome = (states[id].next_player == State.NextPlayer.O) ? State.Outcome.OWins : State.Outcome.XWins; State.Outcome losing_outcome = (states[id].next_player == State.NextPlayer.O) ? State.Outcome.XWins : State.Outcome.OWins; bool has_undecided_next_state = false; bool has_winning_next_state = false; bool has_draw_next_state = false; foreach (int next_id in states[id].next_states) { switch (states[next_id].expected_outcome) { case State.Outcome.Undecided: has_undecided_next_state = true; break; case State.Outcome.Draw: has_draw_next_state = true; break; case State.Outcome.OWins: case State.Outcome.XWins: if (states[next_id].expected_outcome == winning_outcome) { has_winning_next_state = true; } break; } } if (has_winning_next_state) { // If there is a winning next state, we can ignore undecided // next states and mark the current state as also winning. states[id].expected_outcome = winning_outcome; } else if (has_undecided_next_state) { // Otherwise, we must solve all next states before solving the // current state. states[id].expected_outcome = State.Outcome.Undecided; } else if (has_draw_next_state) { // If the current state is not winning, at least we want to // force a draw. states[id].expected_outcome = State.Outcome.Draw; } else { // If all else fails, admit defeat. states[id].expected_outcome = losing_outcome; } } if (states[id].expected_outcome == State.Outcome.Undecided) { // If we cannot solve this state, possibly due to unsolved next states, // try again later. queue.Enqueue(id); } else { // If the state is solved, add all unsolved previous states to the queue // so we can try to solve them later. foreach (int prev_id in states[id].prev_states) { if (states[prev_id].expected_outcome == State.Outcome.Undecided) { queue.Enqueue(prev_id); } } } } Console.WriteLine("Solved."); // PrintGameTree(root_id: 0, indent: 0); }
private void solve_button_Click(object sender, RoutedEventArgs e) { // Initialize all states. states = new List <State>(); for (int id = 0; id <= State.max_id; id++) { states.Add(new State(id)); } // Backfill previous states. Fill next state objects. for (int id = 0; id <= State.max_id; id++) { states[id].NextStates = new List <State>(); foreach (int next_id in states[id].next_states) { states[id].NextStates.Add(states[next_id]); states[next_id].prev_states.Add(id); } } // Solve. Queue <int> queue = new Queue <int>(); // Initially, put all terminal states into the queue. for (int id = 0; id <= State.max_id; id++) { if (states[id].outcome != State.Outcome.Undecided) { queue.Enqueue(id); } } // Start solving. while (queue.Count > 0) { int id = queue.Dequeue(); if (states[id].expected_outcome == State.Outcome.Undecided) { if (states[id].next_states.Count == 0) { throw new InvalidOperationException($"State #{id} is not terminal, yet it has no next states."); } // Attempt to solve this state. // First, count next states. State.Outcome winning_outcome = (states[id].next_player == State.NextPlayer.O) ? State.Outcome.OWins : State.Outcome.XWins; State.Outcome losing_outcome = (states[id].next_player == State.NextPlayer.O) ? State.Outcome.XWins : State.Outcome.OWins; bool has_undecided_next_state = false; bool has_winning_next_state = false; bool has_draw_next_state = false; foreach (int next_id in states[id].next_states) { switch (states[next_id].expected_outcome) { case State.Outcome.Undecided: has_undecided_next_state = true; break; case State.Outcome.Draw: has_draw_next_state = true; break; case State.Outcome.OWins: case State.Outcome.XWins: if (states[next_id].expected_outcome == winning_outcome) { has_winning_next_state = true; } break; } } if (has_winning_next_state) { // If there is a winning next state, we can ignore undecided // next states and mark the current state as also winning. states[id].expected_outcome = winning_outcome; } else if (has_undecided_next_state) { // Otherwise, we must solve all next states before solving the // current state. states[id].expected_outcome = State.Outcome.Undecided; } else if (has_draw_next_state) { // If the current state is not winning, at least we want to // force a draw. states[id].expected_outcome = State.Outcome.Draw; } else { // If all else fails, admit defeat. states[id].expected_outcome = losing_outcome; } } if (states[id].expected_outcome == State.Outcome.Undecided) { // If we cannot solve this state, possibly due to unsolved next states, // try again later. queue.Enqueue(id); } else { // If the state is solved, add all unsolved previous states to the queue // so we can try to solve them later. foreach (int prev_id in states[id].prev_states) { if (states[prev_id].expected_outcome == State.Outcome.Undecided) { queue.Enqueue(prev_id); } } } } // Display. game_tree.Items.Clear(); game_tree.Items.Add(states[0]); }
private void solve_button_Click(object sender, RoutedEventArgs e) { // Initialize all states. states = new List <State>(); for (int id = 0; id <= State.max_id; id++) { states.Add(new State(id)); } // Backfill previous states. Fill next state objects. for (int id = 0; id <= State.max_id; id++) { states[id].NextStates = new List <State>(); foreach (int next_id in states[id].next_states) { if (!states[next_id].valid) { throw new InvalidOperationException($"State #{id} has an invalid next state #{next_id}."); } states[id].NextStates.Add(states[next_id]); states[next_id].prev_states.Add(id); } } // Solve. Queue <int> queue = new Queue <int>(); HashSet <int> queue_as_set = new HashSet <int>(); // Initially, put all terminal states into the queue. for (int id = 0; id <= State.max_id; id++) { if (states[id].valid && states[id].outcome != State.Outcome.Undecided) { queue.Enqueue(id); queue_as_set.Add(id); } } int first_state_to_retry = -1; while (queue.Count > 0) { int id = queue.Dequeue(); queue_as_set.Remove(id); if (states[id].expected_outcome == State.Outcome.Undecided) { if (states[id].next_states.Count == 0) { throw new InvalidOperationException($"State #{id} is not terminal, yet it has no next states."); } // Attempt to solve this state. // First, count next states. State.Outcome winning_outcome = (states[id].next_player == State.NextPlayer.Dogs) ? State.Outcome.DogsWin : State.Outcome.RabbitWins; State.Outcome losing_outcome = (states[id].next_player == State.NextPlayer.Dogs) ? State.Outcome.RabbitWins : State.Outcome.DogsWin; bool has_undecided_next_state = false; bool has_winning_next_state = false; foreach (int next_id in states[id].next_states) { switch (states[next_id].expected_outcome) { case State.Outcome.Undecided: has_undecided_next_state = true; break; case State.Outcome.DogsWin: case State.Outcome.RabbitWins: if (states[next_id].expected_outcome == winning_outcome) { has_winning_next_state = true; } break; } } if (has_winning_next_state) { // If there is a winning next state, we can ignore undecided // next states and mark the current state as also winning. states[id].expected_outcome = winning_outcome; } else if (has_undecided_next_state) { // Otherwise, we must solve all next states before solving the // current state. states[id].expected_outcome = State.Outcome.Undecided; } else { // If all else fails, admit defeat. states[id].expected_outcome = losing_outcome; } } if (states[id].expected_outcome == State.Outcome.Undecided) { if (id == first_state_to_retry) { // We have made zero progress between retries of the same state. // Declare the game unsolvable. break; } // If we cannot solve this state, possibly due to unsolved next states, // try again later. if (first_state_to_retry == -1) { first_state_to_retry = id; } if (!queue_as_set.Contains(id)) { queue.Enqueue(id); queue_as_set.Add(id); } } else { // If the state is solved, add all unsolved previous states to the queue // so we can try to solve them later. first_state_to_retry = -1; foreach (int prev_id in states[id].prev_states) { if (states[prev_id].expected_outcome == State.Outcome.Undecided) { if (!queue_as_set.Contains(prev_id)) { queue.Enqueue(prev_id); queue_as_set.Add(prev_id); } } } } } // Display. game_tree.Items.Clear(); // Root state: // D 1 2 // D 4 5 6 R // D 9 10 // board: 3*121+8*11+7 = 458 // next player: 0 // state ID: 458*2 = 916 game_tree.Items.Add(states[916]); }