diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 9bb96bcbdf1..672a3f974a4 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -4613,7 +4613,12 @@ "title": "Placeholder - Waiting for title", "intro": [""] }, - "lab-placeholder-oop-3": { "title": "", "intro": [""] }, + "lab-player-interface": { + "title": "Build a Player Interface", + "intro": [ + "In this lab, you'll use the abc module to build a player interface." + ] + }, "review-object-oriented-programming": { "title": "Object Oriented Programming Review", "intro": [ diff --git a/client/src/pages/learn/full-stack-developer/lab-player-interface/index.md b/client/src/pages/learn/full-stack-developer/lab-player-interface/index.md new file mode 100644 index 00000000000..25a6c44e4c1 --- /dev/null +++ b/client/src/pages/learn/full-stack-developer/lab-player-interface/index.md @@ -0,0 +1,9 @@ +--- +title: Introduction to the Build a Player Interface +block: lab-player-interface +superBlock: full-stack-developer +--- + +## Introduction to the Build a Player Interface + +In this lab, you'll use the `abc` module to build a player interface. diff --git a/curriculum/challenges/english/blocks/lab-player-interface/68e2c945cc1d8e778152be31.md b/curriculum/challenges/english/blocks/lab-player-interface/68e2c945cc1d8e778152be31.md new file mode 100644 index 00000000000..37bd7fd9b73 --- /dev/null +++ b/curriculum/challenges/english/blocks/lab-player-interface/68e2c945cc1d8e778152be31.md @@ -0,0 +1,381 @@ +--- +id: 68e2c945cc1d8e778152be31 +title: Build a Player Interface +challengeType: 27 +dashedName: build-a-player-interface +--- + +# --description-- + +**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab. + +**User Stories:** + +1. You should define an abstract class named `Player` that inherits from the `abc.ABC` class. +1. The `Player` class should have an `__init__` method that sets: + + - The `moves` attribute to an empty list. + - The `position` attribute to `(0, 0)`. + - The `path` attribute to a list containing the initial position. + +1. The `Player` class should have a method named `make_move` that: + + - Uses `random.choice` to get a random move from the `moves` attribute (defined in the concrete class). + - Adds the values from the selected move to the current position and updates the `position` attribute. + - Appends the new `position` tuple to the `path` attribute. + - Returns the new `position`. + +1. The `Player` class should have an abstract method named `level_up` to be implemented in concrete classes. +1. You should define a `Pawn` class that inherits from the `Player` class. +1. The `Pawn` class should use `super()` to call the parent's `__init__` method and then set the `moves` attribute to a list of tuples representing `x, y` coordinates. +1. Each coordinate tuple should represent a movement of `1` unit in the following directions: up, down, left, right. +1. The `Pawn` class should implement a concrete `level_up` method by adding more moves to the `moves` attribute. The added moves should represent the four diagonal movements (for example, `1` unit down plus `1` unit left). + +**Note:** Standard library modules should be imported without using aliases. Tests related to the `Player` class will fail until the `Pawn` class becomes instantiable. + +# --hints-- + +You should have a class named `Player`. + +```js +({ test: () => runPython(`assert _Node(_code).has_class("Player")`) }) +``` + +The `Player` class should inherit from the `ABC` class of the `abc` module. + +```js +({ test: () => runPython(` +from abc import ABC +import ast +cls = _Node(_code).find_class("Player") +assert cls.inherits_from("ABC") or cls.inherits_from("abc.ABC") +`) }) +``` + +The `Player` class should have an `__init__` method. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").has_function("__init__")`) }) +``` + +The `Player`'s `__init__` method should have a single parameter `self`. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").find_function("__init__").has_args("self")`) }) +``` + +The `Player`'s `__init__` method should set `self.moves` to an empty list. + +```js +({ test: () => runPython(` +init = _Node(_code).find_class("Player").find_function("__init__") +assert init.has_stmt("self.moves = []") or init.has_stmt("self.moves = list()") +`) }) +``` + +The `Player`'s `__init__` method should set `self.position` to `(0, 0)`. + +```js +({ test: () => runPython(` +init = _Node(_code).find_class("Player").find_function("__init__") +assert init.has_stmt("self.position = (0, 0)") +`) }) +``` + +The `Player`'s `__init__` method should set `self.path` to a list containing the initial position. + +```js +({ test: () => runPython(` +init = _Node(_code).find_class("Player").find_function("__init__") +assert init.has_stmt("self.path = [self.position]") +`) }) +``` + +The `Player` class should have a `make_move` method. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").has_function("make_move")`) }) +``` + +The `Player`'s `make_move` method should have a single parameter `self`. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").find_function("make_move").has_args("self")`) }) +``` + +The `Player`'s `make_move` method should use `random.choice` to get a random move from the `moves` attribute. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").block_has_call("choice", "make_move")`) }) +``` + +The `Player`'s `make_move` method should update the `position` attribute by adding to it the coordinates of the randomly selected move. + +```js +({ test: () => runPython(` +def test_func(): + try: + p = Pawn() + except TypeError: + assert False, "Unable to instantiate Pawn" + + p.make_move() + assert p.position == (1, 2) + p.make_move() + assert p.position == (2, 4) + +def custom_choice(seq): + return (1, 2) + +flag = None + +try: + original_choice = choice + choice = custom_choice + flag = "choice" + + test_func() + +except NameError: + try: + original_choice = random.choice + random.choice = custom_choice + flag = "random.choice" + + test_func() + + except NameError: + assert False, "missing import" +finally: + if flag == "choice": + choice = original_choice + elif flag == "random.choice": + random.choice = original_choice +`) }) +``` + +The `Player`'s `make_move` method should append the new `position` tuple to the `path` attribute. + +```js +({ test: () => runPython(` +def test_func(): + try: + p = Pawn() + except TypeError: + assert False, "Unable to instantiate Pawn" + + p.make_move() + assert p.path == [(0, 0), (1, 2)] + p.make_move() + assert p.path == [(0, 0), (1, 2), (2, 4)] + +def custom_choice(seq): + return (1, 2) + +flag = None + +try: + original_choice = choice + choice = custom_choice + flag = "choice" + + test_func() + +except NameError: + try: + original_choice = random.choice + random.choice = custom_choice + flag = "random.choice" + + test_func() + + except NameError: + assert False, "missing import" +finally: + if flag == "choice": + choice = original_choice + elif flag == "random.choice": + random.choice = original_choice +`) }) +``` + +The `Player`'s `make_move` method should return the updated `position` attribute. + +```js +({ test: () => runPython(` +def test_func(): + try: + p = Pawn() + except TypeError: + assert False, "Unable to instantiate Pawn" + + assert p.make_move() == (1, 2) + +def custom_choice(seq): + return (1, 2) + +flag = None + +try: + original_choice = choice + choice = custom_choice + flag = "choice" + + test_func() + +except NameError: + try: + original_choice = random.choice + random.choice = custom_choice + flag = "random.choice" + + test_func() + + except NameError: + assert False, "missing import" +finally: + if flag == "choice": + choice = original_choice + elif flag == "random.choice": + random.choice = original_choice +`) }) +``` + +The `Player` class should have a `level_up` method. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").has_function("level_up")`) }) +``` + +The `Player`'s `level_up` method should have a single parameter `self`. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Player").find_function("level_up").has_args("self")`) }) +``` + +The `Player`'s `level_up` method should be an abstract method. + +```js +({ test: () => runPython(` +target = _Node(_code).find_class("Player").find_function("level_up") +assert target.has_decorators("abstractmethod") or target.has_decorators("abc.abstractmethod") +`) }) +``` + +The `Player` class should be an abstract class. + +```js +({ test: () => runPython(` +try: + Player() +except TypeError as e: + assert str(e) == "Can't instantiate abstract class Player with abstract method level_up" +else: + assert False, "Player class should not be instantiable" +`) }) +``` + +You should have a class named `Pawn`. + +```js +({ test: () => runPython(`assert _Node(_code).has_class("Pawn")`) }) +``` + +The `Pawn` class should inherit from the `Player` class. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Pawn").inherits_from("Player")`) }) +``` + +The `Pawn` class should have an `__init__` method. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Pawn").has_function("__init__")`) }) +``` + +The `Pawn`'s `__init__` method should have a single parameter `self`. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Pawn").find_function("__init__").has_args("self")`) }) +``` + +The `Pawn`'s `__init__` method should call the parent's `__init__` with using the `super` function. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Pawn").find_function("__init__").has_stmt("super().__init__()")`) }) +``` + +The `Pawn`'s `__init__` method should set the `moves` attribute to a list of tuples representing `x, y` coordinates, where each coordinate tuple represents a movement of `1` unit in the following directions: up, down, left, right. + +```js +({ test: () => runPython(` +p = Pawn() +moves = [(0, 1), (1, 0), (-1, 0), (0, -1)] +assert isinstance(p.moves, list) +assert len(p.moves) == 4 +assert all(move in p.moves for move in moves) +`) }) +``` + +The `Pawn` class should have a `level_up` method. + +```js +({ test: () => runPython(`assert _Node(_code).find_class("Pawn").has_function("level_up")`) }) +``` + +The `Pawn`'s `level_up` method should add the four diagonal movement of `1` unit to the `moves` attribute. + +```js +({ test: () => runPython(` +p = Pawn() +moves = [(0, 1), (1, 0), (-1, 0), (0, -1), (1, 1), (1, -1), (-1, 1), (-1, -1)] +p.level_up() +assert isinstance(p.moves, list) +assert len(p.moves) == 8 +assert all(move in p.moves for move in moves) +`) }) +``` + +# --seed-- + +## --seed-contents-- + +```py + +``` + +# --solutions-- + +```py +from abc import ABC, abstractmethod +from random import choice + + +class Player(ABC): + def __init__(self): + self.moves = [] + self.position = (0, 0) + self.path = [self.position] + + def make_move(self): + x, y = self.position + move = choice(self.moves) + new_x = move[0] + x + new_y = move[1] + y + self.position = (new_x, new_y) + self.path.append(self.position) + return self.position + + @abstractmethod + def level_up(self): + pass + + +class Pawn(Player): + def __init__(self): + super().__init__() + self.moves = [(0, 1), (1, 0), (-1, 0), (0, -1)] + + def level_up(self): + self.moves.extend([(1, 1), (1, -1), (-1, 1), (-1, -1)]) +``` diff --git a/curriculum/structure/blocks/lab-player-interface.json b/curriculum/structure/blocks/lab-player-interface.json new file mode 100644 index 00000000000..f9d28819dd3 --- /dev/null +++ b/curriculum/structure/blocks/lab-player-interface.json @@ -0,0 +1,15 @@ +{ + "name": "Build a Player Interface", + "isUpcomingChange": true, + "dashedName": "lab-player-interface", + "helpCategory": "Python", + "blockLayout": "link", + "challengeOrder": [ + { + "id": "68e2c945cc1d8e778152be31", + "title": "Build a Player Interface" + } + ], + "blockLabel": "lab", + "usesMultifileEditor": true +} diff --git a/curriculum/structure/superblocks/full-stack-developer.json b/curriculum/structure/superblocks/full-stack-developer.json index 6e59084de8d..0bb010e0ecd 100644 --- a/curriculum/structure/superblocks/full-stack-developer.json +++ b/curriculum/structure/superblocks/full-stack-developer.json @@ -737,6 +737,7 @@ "workshop-media-catalogue", "lab-polygon-area-calculator", "lecture-understanding-abstraction", + "lab-player-interface", "review-object-oriented-programming", "quiz-object-oriented-programming" ]