diff --git a/src/turtle_challenges/cli.py b/src/turtle_challenges/cli.py index 2173106..cc03102 100644 --- a/src/turtle_challenges/cli.py +++ b/src/turtle_challenges/cli.py @@ -10,12 +10,17 @@ from functools import partial from turtle_challenges.constants import ( CHALLENGE_DASHED_LINE, CHALLENGE_NAMES, + CHALLENGE_RANDOM_WALK, CHALLENGE_SHAPES, + CHALLENGE_SPIROGRAPH, CHALLENGE_SQUARE, + SPIROGRAPH_GAP_DEGREES, ) from turtle_challenges.dashed_line import draw_dashed_line from turtle_challenges.pen import Pen +from turtle_challenges.random_walk import random_walk from turtle_challenges.shapes import draw_shapes +from turtle_challenges.spirograph import draw_spirograph from turtle_challenges.square import draw_square from turtle_challenges.window import ( TurtleUnavailableError, @@ -28,7 +33,7 @@ def build_parser() -> argparse.ArgumentParser: """! @brief Build the argument parser of the command line. - @return A parser with the challenge name. + @return A parser with the challenge name and the spirograph gap option. """ parser = argparse.ArgumentParser( prog="turtle-challenges", @@ -40,14 +45,22 @@ def build_parser() -> argparse.ArgumentParser: choices=CHALLENGE_NAMES, help="the challenge to run", ) + parser.add_argument( + "--gap", + type=float, + default=SPIROGRAPH_GAP_DEGREES, + help="spirograph only: degrees between two circles " + f"(default: {SPIROGRAPH_GAP_DEGREES})", + ) return parser -def build_challenges(pen: Pen) -> dict[str, Callable[[], None]]: +def build_challenges(pen: Pen, gap: float) -> dict[str, Callable[[], None]]: """! @brief Bind every challenge to a turtle. @param pen The turtle that draws. + @param gap Degrees between two circles of the spirograph. @return A mapping from the command line name to a function without arguments that draws the challenge. """ @@ -55,6 +68,8 @@ def build_challenges(pen: Pen) -> dict[str, Callable[[], None]]: CHALLENGE_SQUARE: partial(draw_square, pen), CHALLENGE_DASHED_LINE: partial(draw_dashed_line, pen), CHALLENGE_SHAPES: partial(draw_shapes, pen), + CHALLENGE_RANDOM_WALK: partial(random_walk, pen), + CHALLENGE_SPIROGRAPH: partial(draw_spirograph, pen, gap), } @@ -72,6 +87,6 @@ def main(argv: Sequence[str] | None = None) -> int: tim = create_pen() except TurtleUnavailableError as error: parser.exit(1, f"{parser.prog}: error: {error}\n") - build_challenges(tim)[args.challenge]() + build_challenges(tim, args.gap)[args.challenge]() window.exitonclick() return 0 diff --git a/src/turtle_challenges/constants.py b/src/turtle_challenges/constants.py index 0aa941e..feb4956 100644 --- a/src/turtle_challenges/constants.py +++ b/src/turtle_challenges/constants.py @@ -92,9 +92,17 @@ CHALLENGE_DASHED_LINE = "dashed-line" ## Command line name of challenge 3. CHALLENGE_SHAPES = "shapes" +## Command line name of challenge 4. +CHALLENGE_RANDOM_WALK = "random-walk" + +## Command line name of challenge 5. +CHALLENGE_SPIROGRAPH = "spirograph" + ## Names of the challenges on the command line, in the order of the course. CHALLENGE_NAMES: tuple[str, ...] = ( CHALLENGE_SQUARE, CHALLENGE_DASHED_LINE, CHALLENGE_SHAPES, + CHALLENGE_RANDOM_WALK, + CHALLENGE_SPIROGRAPH, ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 7077631..d78b255 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -5,6 +5,7 @@ from turtle_challenges import cli from turtle_challenges.constants import ( CHALLENGE_NAMES, DASH_COUNT, + SPIROGRAPH_GAP_DEGREES, SQUARE_SIDES, ) from turtle_challenges.pen import Pen, Window @@ -29,7 +30,7 @@ def fake_pen_and_window(monkeypatch: pytest.MonkeyPatch) -> tuple[FakePen, FakeW def test_every_challenge_name_has_a_challenge() -> None: - challenges = cli.build_challenges(FakePen()) + challenges = cli.build_challenges(FakePen(), SPIROGRAPH_GAP_DEGREES) assert set(challenges) == set(CHALLENGE_NAMES) @@ -39,6 +40,13 @@ def test_parser_accepts_every_challenge_name(name: str) -> None: args = cli.build_parser().parse_args([name]) assert args.challenge == name + assert args.gap == SPIROGRAPH_GAP_DEGREES + + +def test_parser_reads_the_spirograph_gap() -> None: + args = cli.build_parser().parse_args(["spirograph", "--gap", "7.5"]) + + assert args.gap == 7.5 def test_parser_rejects_an_unknown_challenge( @@ -82,6 +90,16 @@ def test_main_runs_the_dashed_line( assert len(pen.args_of("forward")) == 2 * DASH_COUNT +def test_main_passes_the_gap_to_the_spirograph( + fake_pen_and_window: tuple[FakePen, FakeWindow], +) -> None: + pen, _ = fake_pen_and_window + + cli.main(["spirograph", "--gap", "90"]) + + assert len(pen.args_of("circle")) == 4 + + def test_main_reports_a_missing_turtle_and_exits_with_status_1( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: