import copy class FCState: """ A state of the foxes and chickens game Attributes ---------- left_c : (int) number of chickens on left bank left_f : (int) number of foxes on left bank right_c : (int) number of chickens on right bank right_f : (int) number of foxes on right bank boat_left: (bool) whether the boat is on the left bank prev_state: (FCState) previous state Methods ------- is_goal(): (bool) whether 3 chickens and 3 foxes are on the right bank is_starting_state(): (bool) whether the state is the starting state next_states(): (list) possible states that can transition to from current is_valid_state(): (bool) whether current state is valid move(f, c): (FCState) moves f foxes and c chickens to the opposite bank """ def __init__(self): self.left_c = 3 self.left_f = 3 self.right_c = 0 self.right_f = 0 self.boat_left = True self.prev_state = "NONE" def is_goal(self): """ Returns whether 3 chickens and 3 foxes are on the right bank :return: (bool) whether 3 chickens and 3 foxes are on the right bank """ return self.right_f == 3 and \ self.right_c == 3 def is_starting_state(self): """ Returns whether the state is the starting state :return: (bool) whether the state is the starting state """ return self.prev_state == "NONE" def next_states(self): """ Returns possible states that can transition to from current :return: (list) possible states that can transition to from current """ next_states = [] for f in range(3): for c in range(3): # check to make sure that someone is in the boat # AND foxes don't outnumber chickens if (f + c) == 1 or (f + c) == 2: temp_state = self.move(f, c) if temp_state.is_valid_state(): next_states.append(temp_state) return next_states def is_valid_state(self): """ Returns whether current state is valid :return: (bool) whether current state is valid """ # see if we moved too many passengers if self.left_c < 0 or self.left_f < 0 or \ self.right_c < 0 or self.right_f < 0: return False # check the foxes <= chickens constraint if (self.left_f > self.left_c and self.left_c > 0) or \ (self.right_f > self.right_c and self.right_c > 0): return False return True def move(self, f, c): """ moves f foxes and c chickens to the opposite bank :param f: number of foxes to move :param c: number of chickens to move :return: (FCState) new state that represents the moved f foxes and c chickens """ # copy the current state new_state = copy.deepcopy(self) if self.boat_left: # move to the right shore new_state.left_f -= f new_state.left_c -= c new_state.right_f += f new_state.right_c += c else: # move to the left shore new_state.right_f -= f new_state.right_c -= c new_state.left_f += f new_state.left_c += c new_state.boat_left = not new_state.boat_left new_state.prev_state = self return new_state def __str__(self): result = ("F" * self.left_f + "C" * self.left_c).ljust(6) if self.boat_left: result += " B|~~~~~~| " else: result += " |~~~~~~|B " result += "F" * self.right_f + "C" * self.right_c return result def dfs(state, visited): """ Recursive implementation of depth-first search :param state: (any). The state class must have the following methods implemented: - is_goal(): returns True if the state is a goal state, False otherwise - next_states(): returns a list of the VALID states that can be reached from the current state :param visited: (dict) Mapping from string representation of state to whether or not the state has been visited :return: (list) Returns a list of ALL states that are solutions (i.e. is_goal returned True) that can be reached from the input state. """ visited[str(state)] = True # if the current state is a goal state, then return it in a list if state.is_goal(): return [state] else: # else, recurse on the possible next states result = [] for s in state.next_states(): # append all of the s if not (str(s) in visited): result += dfs(s, visited) return result def print_solution(state): """ Print out the full path of the solution, including this state AND all the states that led to this state from the starting state. """ if state.is_starting_state(): print(state) else: print_solution(state.prev_state) print(state) def main(): start = FCState() # search starting at the start state and let visited be empty solutions = dfs(start, {}) # print out the solution # in this case, the solution is a path! print_solution(solutions[0]) if __name__ == "__main__": main()