Skip to content

Commit d4ee426

Browse files
committed
Missionaries and cannibals.
1 parent d0e85a4 commit d4ee426

File tree

1 file changed

+92
-0
lines changed

1 file changed

+92
-0
lines changed

src/ch2/cannibals.py

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
Missionaries and cannibals
3+
4+
"""
5+
from __future__ import annotations
6+
from attrs import define, field, Factory
7+
from typing import List, Optional, Iterator
8+
from ch2.maze import bfs, to_path
9+
10+
MAX_NUM: int = 3
11+
12+
13+
@define(frozen=True)
14+
class MCState:
15+
missionaries: int
16+
cannibals: int
17+
boat: bool = True
18+
_wm: int = field(init=False, default=Factory(
19+
lambda self: self.missionaries, takes_self=True))
20+
_wc: int = field(init=False, default=Factory(
21+
lambda self: self.cannibals, takes_self=True))
22+
_em: int = field(init=False, default=Factory(
23+
lambda self: MAX_NUM-self.missionaries, takes_self=True))
24+
_ec: int = field(init=False, default=Factory(
25+
lambda self: MAX_NUM-self.cannibals, takes_self=True))
26+
27+
@property
28+
def is_legal(self) -> bool:
29+
if self._wm > 0 and self._wm < self._wc:
30+
return False
31+
if self._em > 0 and self._em < self._ec:
32+
return False
33+
return True
34+
35+
def goal_test(self) -> bool:
36+
return self.is_legal and self._em == MAX_NUM and self._ec == MAX_NUM
37+
38+
def successors(self) -> List[MCState]:
39+
result: List[MCState] = []
40+
if self.boat: # west
41+
if self._wm > 1:
42+
result.append(MCState(self._wm - 2, self._wc, not self.boat))
43+
if self._wm > 0:
44+
result.append(MCState(self._wm - 1, self._wc, not self.boat))
45+
if self._wc > 1:
46+
result.append(MCState(self._wm, self._wc - 2, not self.boat))
47+
if self._wc > 0:
48+
result.append(MCState(self._wm, self._wc - 1, not self.boat))
49+
if (self._wm > 0) and (self._wc > 0):
50+
result.append(
51+
MCState(self._wm - 1, self._wc - 1, not self.boat))
52+
else: # east
53+
if self._em > 1:
54+
result.append(MCState(self._wm + 2, self._wc, not self.boat))
55+
if self._em > 0:
56+
result.append(MCState(self._wm + 1, self._wc, not self.boat))
57+
if self._ec > 1:
58+
result.append(MCState(self._wm, self._wc + 2, not self.boat))
59+
if self._ec > 0:
60+
result.append(MCState(self._wm, self._wc + 1, not self.boat))
61+
if (self._em > 0) and (self._ec > 0):
62+
result.append(
63+
MCState(self._wm + 1, self._wc + 1, not self.boat))
64+
return [s for s in result if s.is_legal]
65+
66+
67+
def display_solution(path: Iterator[MCState]) -> None:
68+
old_state = next(path)
69+
print(
70+
f"West is starting with [{old_state.missionaries}] missionaries and [{old_state.cannibals}] cannibals")
71+
for s in path:
72+
if s.boat:
73+
print("E -> W moved [{}] missionaries and [{}] cannibals (W={}m+{}c, E={}m+{}c, Boat=West)".format(
74+
old_state._em - s._em, old_state._ec - s._ec, s._wm, s._wc, s._em, s._ec))
75+
else:
76+
print("W -> E moved [{}] missionaries and [{}] cannibals (W={}m+{}c, E={}m+{}c, Boat=East)".format(
77+
old_state._wm - s._wm, old_state._wc - s._wc, s._wm, s._wc, s._em, s._ec))
78+
old_state = s
79+
80+
81+
def main():
82+
start = MCState(MAX_NUM, MAX_NUM, True)
83+
solution = bfs(start, MCState.goal_test, MCState.successors)
84+
if solution is None:
85+
print("No solution found!")
86+
else:
87+
path = to_path(solution)
88+
display_solution(path)
89+
90+
91+
if __name__ == '__main__':
92+
main()

0 commit comments

Comments
 (0)