Skip to content

Commit e304a51

Browse files
committed
Restore fox goose corn
1 parent 5fc41c9 commit e304a51

File tree

12 files changed

+288
-0
lines changed

12 files changed

+288
-0
lines changed

fox_goose_corn/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Fox Goose Corn
2+
3+
Requirements:
4+
5+
- You must get the fox, goose, and bag of corn safely across the other side of the river
6+
- You can only carry 1 item on the boat across with you.
7+
- The fox cannot be left alone with the goose, (or it will be eaten).
8+
- The goose cannot be left alone with the corn, (or it will be eaten).

fox_goose_corn/__init__.py

Whitespace-only changes.

fox_goose_corn/src/__init__.py

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from typing import Optional
2+
3+
from fox_goose_corn.src.model.boat import Boat
4+
from fox_goose_corn.src.model.cargo_item import (
5+
AbstractCargoItem,
6+
Fox,
7+
Goose,
8+
Corn,
9+
CargoEatingCargoException,
10+
)
11+
12+
13+
class CrossingManager:
14+
def __init__(self, boat: Boat, fox: Fox, goose: Goose, corn: Corn):
15+
self._corn = corn
16+
self._goose = goose
17+
self._fox = fox
18+
self._boat = boat
19+
20+
def cross_with(self, cargo_item: AbstractCargoItem) -> None:
21+
self._boat.add_cargo(cargo_item)
22+
self._boat.cross_river()
23+
self._check_the_cargo_is_safe()
24+
25+
def cross_empty(self) -> None:
26+
self._boat.cross_river()
27+
28+
def _check_the_cargo_is_safe(self) -> None:
29+
if self._fox.is_on_same_side_as(self._goose) or self._goose.is_on_same_side_as(self._corn):
30+
raise CargoEatingCargoException

fox_goose_corn/src/model/boat.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from typing import Optional
2+
3+
from fox_goose_corn.src.model.cargo_item import AbstractCargoItem, Fox
4+
from fox_goose_corn.src.model.river import RiverSide
5+
6+
7+
class Boat:
8+
_current_side: RiverSide = RiverSide.FARM_SIDE
9+
_cargo_item: Optional[AbstractCargoItem] = None
10+
11+
def cross_river(self):
12+
self._current_side = (
13+
RiverSide.MARKET_SIDE
14+
if self._current_side is RiverSide.FARM_SIDE
15+
else RiverSide.FARM_SIDE
16+
)
17+
18+
if self._cargo_item is not None:
19+
self._unload_cargo_item()
20+
21+
def is_at(self, expected_side: RiverSide):
22+
return self._current_side is expected_side
23+
24+
def add_cargo(self, cargo_item: AbstractCargoItem):
25+
if not isinstance(cargo_item, AbstractCargoItem):
26+
raise InvalidCargoItemException
27+
28+
if self._cargo_item is not None:
29+
raise TooManyCargoItemsException
30+
31+
self._cargo_item = cargo_item
32+
33+
def _unload_cargo_item(self):
34+
self._cargo_item.unload_cargo_item_at(self._current_side)
35+
self._cargo_item = None
36+
37+
38+
class InvalidCargoItemException(Exception):
39+
pass
40+
41+
42+
class TooManyCargoItemsException(Exception):
43+
pass
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from abc import abstractmethod, ABC
2+
from typing import Type
3+
4+
from fox_goose_corn.src.model.river import RiverSide
5+
6+
7+
class AbstractCargoItem(ABC):
8+
_current_side: RiverSide = RiverSide.FARM_SIDE
9+
10+
def is_at(self, expected_side: RiverSide) -> None:
11+
return self._current_side is expected_side
12+
13+
def unload_cargo_item_at(self, side: RiverSide) -> None:
14+
self._current_side = side
15+
16+
def is_on_same_side_as(self, other_cargo_item: "AbstractCargoItem") -> bool:
17+
return self._current_side == other_cargo_item._current_side
18+
19+
20+
class Fox(AbstractCargoItem):
21+
pass
22+
23+
24+
class Goose(AbstractCargoItem):
25+
pass
26+
27+
28+
class Corn(AbstractCargoItem):
29+
pass
30+
31+
32+
class CargoEatingCargoException(Exception):
33+
pass

fox_goose_corn/src/model/river.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from enum import Enum
2+
3+
4+
class RiverSide(Enum):
5+
FARM_SIDE: str = "farm-side"
6+
MARKET_SIDE: str = "market-side"

fox_goose_corn/tests/__init__.py

Whitespace-only changes.

fox_goose_corn/tests/model/__init__.py

Whitespace-only changes.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pytest
2+
3+
from fox_goose_corn.src.model.boat import (
4+
Boat,
5+
TooManyCargoItemsException,
6+
InvalidCargoItemException,
7+
)
8+
from fox_goose_corn.src.model.cargo_item import Fox, Goose, Corn
9+
from fox_goose_corn.src.model.river import RiverSide
10+
11+
12+
class TestBoat:
13+
def test_boat_always_starts_the_day_farm_side(self):
14+
boat = Boat()
15+
16+
assert boat.is_at(RiverSide.FARM_SIDE)
17+
assert not boat.is_at(RiverSide.MARKET_SIDE)
18+
19+
def test_boat_can_cross_river_from_farm_to_market(self):
20+
boat = Boat()
21+
22+
boat.cross_river()
23+
24+
assert boat.is_at(RiverSide.MARKET_SIDE)
25+
26+
def test_boat_can_cross_river_from_market_to_farm(self):
27+
boat = Boat()
28+
boat.cross_river()
29+
assert boat.is_at(RiverSide.MARKET_SIDE)
30+
31+
boat.cross_river()
32+
33+
assert boat.is_at(RiverSide.FARM_SIDE)
34+
35+
@pytest.mark.parametrize("cargo_item_type", [Fox, Goose, Corn])
36+
def test_boat_can_take_cargo(self, cargo_item_type):
37+
boat = Boat()
38+
cargo_item = cargo_item_type()
39+
40+
boat.add_cargo(cargo_item)
41+
42+
def test_boat_can_only_take_expected_cargo_types(self):
43+
boat = Boat()
44+
cargo_item = Boat()
45+
46+
with pytest.raises(InvalidCargoItemException):
47+
boat.add_cargo(cargo_item)
48+
49+
def test_boat_cannot_take_more_than_one_cargo_item(self):
50+
boat = Boat()
51+
cargo_item_1 = Fox()
52+
cargo_item_2 = Goose()
53+
boat.add_cargo(cargo_item_1)
54+
55+
with pytest.raises(TooManyCargoItemsException):
56+
boat.add_cargo(cargo_item_2)
57+
58+
def test_boat_can_take_cargo_across_river_from_farm_to_market(self):
59+
boat = Boat()
60+
cargo_item = Fox()
61+
62+
boat.add_cargo(cargo_item)
63+
boat.cross_river()
64+
65+
assert cargo_item.is_at(RiverSide.MARKET_SIDE)
66+
67+
def test_boat_is_empty_after_crossing_with_a_cargo_item(self):
68+
boat = Boat()
69+
cargo_item_1 = Fox()
70+
cargo_item_2 = Goose()
71+
boat.add_cargo(cargo_item_1)
72+
boat.cross_river()
73+
74+
boat.add_cargo(cargo_item_2)

0 commit comments

Comments
 (0)