primeiro commit com exercicios de Exercism realizados até esta data
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"authors": [
|
||||
"PaulT89",
|
||||
"BethanyG"
|
||||
],
|
||||
"contributors": [
|
||||
"DjangoFett",
|
||||
"kotp",
|
||||
"IsaacG"
|
||||
],
|
||||
"files": {
|
||||
"solution": [
|
||||
"classes.py"
|
||||
],
|
||||
"test": [
|
||||
"classes_test.py"
|
||||
],
|
||||
"exemplar": [
|
||||
".meta/exemplar.py"
|
||||
]
|
||||
},
|
||||
"icon": "character-study",
|
||||
"blurb": "Learn about classes by creating an Alien for Ellen's game."
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"track":"python","exercise":"ellens-alien-game","id":"93900682dcdf4ccd8e1eb9ff17859ab7","url":"https://exercism.org/tracks/python/exercises/ellens-alien-game","handle":"sanctushck","is_requester":true,"auto_approve":false}
|
||||
@@ -0,0 +1,130 @@
|
||||
# Help
|
||||
|
||||
## Running the tests
|
||||
|
||||
We use [pytest][pytest: Getting Started Guide] as our website test runner.
|
||||
You will need to install `pytest` on your development machine if you want to run tests for the Python track locally.
|
||||
You should also install the following `pytest` plugins:
|
||||
|
||||
- [pytest-cache][pytest-cache]
|
||||
- [pytest-subtests][pytest-subtests]
|
||||
|
||||
Extended information can be found in our website [Python testing guide][Python track tests page].
|
||||
|
||||
|
||||
### Running Tests
|
||||
|
||||
To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_).
|
||||
Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded.
|
||||
|
||||
Linux/MacOS
|
||||
```bash
|
||||
$ cd {path/to/exercise-folder-location}
|
||||
```
|
||||
|
||||
Windows
|
||||
```powershell
|
||||
PS C:\Users\foobar> cd {path\to\exercise-folder-location}
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file:
|
||||
|
||||
Linux/MacOS
|
||||
```bash
|
||||
$ python3 -m pytest -o markers=task {exercise_test.py}
|
||||
==================== 7 passed in 0.08s ====================
|
||||
```
|
||||
|
||||
Windows
|
||||
```powershell
|
||||
PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py}
|
||||
==================== 7 passed in 0.08s ====================
|
||||
```
|
||||
|
||||
|
||||
### Common options
|
||||
- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_)
|
||||
- `-v` : enable verbose output.
|
||||
- `-x` : stop running tests on first failure.
|
||||
- `--ff` : run failures from previous test before running other test cases.
|
||||
|
||||
For additional options, use `python3 -m pytest -h` or `py -m pytest -h`.
|
||||
|
||||
|
||||
### Fixing warnings
|
||||
|
||||
If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax:
|
||||
|
||||
```bash
|
||||
PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html
|
||||
```
|
||||
|
||||
To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file.
|
||||
We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini].
|
||||
|
||||
You can also create your own `pytest.ini` file with the following content:
|
||||
|
||||
```ini
|
||||
[pytest]
|
||||
markers =
|
||||
task: A concept exercise task.
|
||||
```
|
||||
|
||||
Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings.
|
||||
More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers].
|
||||
|
||||
Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats].
|
||||
|
||||
|
||||
### Extending your IDE or Code Editor
|
||||
|
||||
Many IDEs and code editors have built-in support for using `pytest` and other code quality tools.
|
||||
Some community-sourced options can be found on our [Python track tools page][Python track tools page].
|
||||
|
||||
[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html
|
||||
[Python track tools page]: https://exercism.org/docs/tracks/python/tools
|
||||
[Python track tests page]: https://exercism.org/docs/tracks/python/tests
|
||||
[pytest-cache]:http://pythonhosted.org/pytest-cache/
|
||||
[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests
|
||||
[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini
|
||||
[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats
|
||||
[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks
|
||||
[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers
|
||||
|
||||
## Submitting your solution
|
||||
|
||||
You can submit your solution using the `exercism submit classes.py` command.
|
||||
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||
|
||||
It's possible to submit an incomplete solution which allows you to:
|
||||
|
||||
- See how others have completed the exercise
|
||||
- Request help from a mentor
|
||||
|
||||
## Need to get help?
|
||||
|
||||
If you'd like help solving the exercise, check the following pages:
|
||||
|
||||
- The [Python track's documentation](https://exercism.org/docs/tracks/python)
|
||||
- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python)
|
||||
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
|
||||
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||
|
||||
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||
|
||||
Below are some resources for getting help if you run into trouble:
|
||||
|
||||
- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources.
|
||||
- [The Exercism Community on Discord](https://exercism.org/r/discord)
|
||||
- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community.
|
||||
- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners.
|
||||
- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done.
|
||||
- [Python Community Forums](https://discuss.python.org/)
|
||||
- [Free Code Camp Community Forums](https://forum.freecodecamp.org/)
|
||||
- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help)
|
||||
- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually.
|
||||
|
||||
Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already.
|
||||
If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Hints
|
||||
|
||||
## 1. Create the Alien Class
|
||||
|
||||
- Remember that `object methods` are always passed `self` as the first parameter.
|
||||
- Remember the double underscores on _both_ sides of `__init__()`.
|
||||
- Instance variables are unique to the `class` instance (_object_) that possesses them.
|
||||
- Class variables are the same across all instances of a `class`.
|
||||
|
||||
## 2. The `hit` Method
|
||||
|
||||
- Remember that `object methods` are always passed `self` as the first parameter.
|
||||
- You can choose to allow the Alien's health to fall below zero, or require that it does not.
|
||||
|
||||
## 3. The `is_alive` Method
|
||||
|
||||
- Remember that `object methods` are always passed `self` as the first parameter.
|
||||
- 0 may not be the only 'dead' condition, depending on how `hit()` is implemented.
|
||||
|
||||
## 4. The `teleport` Method
|
||||
|
||||
- Remember that `object methods` are always passed `self` as the first parameter.
|
||||
- Instance attributes can be updated from a method by using `self.<attribute>` = `<new attribute value>`.
|
||||
|
||||
## 5. The `collision_detection` Method
|
||||
|
||||
- Remember that `object methods` are always passed `self` as the first parameter.
|
||||
- This method seems like an excellent place to use some kind of placeholder…
|
||||
|
||||
## 6. Alien Counter
|
||||
|
||||
- Class attributes are the same across all instances of a `class`.
|
||||
- Ideally, this counter would increment whenever someone _made an new Alien_.
|
||||
- Class attributes can be changed from an instance method by using the _class name_: `Alien.<class attribute name>`.
|
||||
- `__init__()` is considered an instance method since it _initializes a new object_.
|
||||
|
||||
## 7. Object Creation
|
||||
|
||||
- A `tuple` would be a _single_ parameter.
|
||||
- The Alien constructor takes _2 parameters_.
|
||||
- Unpacking what is _inside_ the tuple would yield two parameters.
|
||||
- The standalone function is outside of the `class`
|
||||
@@ -0,0 +1,415 @@
|
||||
# Ellen's Alien Game
|
||||
|
||||
Welcome to Ellen's Alien Game on Exercism's Python Track.
|
||||
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||
|
||||
## Introduction
|
||||
|
||||
## Object Oriented Programming in Python
|
||||
|
||||
If you have been programming in a [functional][functional], [declarative][declarative], or [imperative][imperative] style, shifting focus to [object oriented programming][oop] (OOP) may feel a bit foreign.
|
||||
An OOP approach asks the programmer to think about modeling a problem as one or more `objects` that interact with one another, keep state, and act upon data.
|
||||
Objects can represent real world entities (_such as cars or cats_) - or more abstract concepts (_such as integers, vehicles, or mammals_).
|
||||
Each object becomes a unique instance in computer memory and represents some part of the overall model.
|
||||
|
||||
## Classes
|
||||
|
||||
`Classes` are the definitions of new object types, and from which new `instances` of objects are created.
|
||||
They often bundle data with code or functions that operate on that data.
|
||||
In this sense, classes are _blueprints_ or sets of instructions from which many objects of a similar type can be built and used.
|
||||
A complex program can have many classes, each building many different flavors of objects.
|
||||
The process of building an object from a class is known as `instantiation` (_or creating an instance of the class_).
|
||||
|
||||
A class definition in Python is straightforward:
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
# Class body goes here
|
||||
```
|
||||
|
||||
### Class Attributes
|
||||
|
||||
Class fields (_otherwise known as `properties`, `attributes`, `data members`, or `variables`_) can be added to the body of the class:
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
number = 5
|
||||
string = "Hello!"
|
||||
```
|
||||
|
||||
An instance (_object_) of `MyClass` can be created and bound to a name by [_calling_][calling] the class (_in the same way a function is called_):
|
||||
|
||||
```python
|
||||
>>> new_object = MyClass()
|
||||
|
||||
# Class is instantiated and resulting object is bound to the "new_object" name.
|
||||
# Note: the object address 'at 0x15adc55b0' will vary by computer.
|
||||
>>> new_object
|
||||
<__main__.MyClass at 0x15adc55b0>
|
||||
```
|
||||
|
||||
`Class attributes` are shared across all objects (_or instances_) created from a class, and can be accessed via [`dot notation`][dot notation] - a `.` placed after the object name and before the attribute name:
|
||||
|
||||
```python
|
||||
>>> new_object = MyClass()
|
||||
|
||||
# Accessing the class attribute "number" via dot-notation.
|
||||
>>> new_object.number
|
||||
5
|
||||
|
||||
# Accessing the class attribute "string" via dot-notation.
|
||||
>>> new_object.string
|
||||
'Hello!'
|
||||
|
||||
# Instantiating an additional object and binding it to the "second_new_object" name.
|
||||
>>> second_new_object = MyClass()
|
||||
|
||||
>>> second_new_object
|
||||
# Note: the object address "at 0x15ad99970" will vary by computer.
|
||||
<__main__.MyClass at 0x15ad99970>
|
||||
|
||||
# Second_new_object shares the same class attributes as new_object.
|
||||
>>> new_object.number == second_new_object.number
|
||||
True
|
||||
```
|
||||
|
||||
Class attributes are defined in the body of the class itself, before any other methods.
|
||||
They are owned by the class - allowing them to be shared across instances of the class.
|
||||
Because these attributes are shared, their value can be accessed and manipulated from the class _directly_.
|
||||
Altering the value of class attributes alters the value _**for all objects instantiated from the class**_:
|
||||
|
||||
```python
|
||||
>>> obj_one = MyClass()
|
||||
>>> obj_two = MyClass()
|
||||
|
||||
# Accessing a class attribute from an object.
|
||||
>>> obj_two.number
|
||||
5
|
||||
|
||||
# Accessing the class attribute from the class itself.
|
||||
>>> MyClass.number
|
||||
5
|
||||
|
||||
# Modifying the value of the "number" class attribute.
|
||||
>>> MyClass.number = 27
|
||||
|
||||
# Modifying the "number" class attribute changes the "number" attribute for all objects.
|
||||
>>> obj_one.number
|
||||
27
|
||||
|
||||
>>> obj_two.number
|
||||
27
|
||||
```
|
||||
|
||||
Having a bunch of objects with synchronized data at all times is not particularly useful.
|
||||
Fortunately, objects created from a class can be customized with their own `instance attributes` (_or instance properties, variables, or fields_).
|
||||
As their name suggests, instance attributes are unique to each object, and can be modified independently.
|
||||
|
||||
|
||||
## Customizing Object Instantiation with `__init__()`
|
||||
|
||||
The special ["dunder method"][dunder] (_short for "double underscore method"_) `__init__()` is used to customize class instances, and can be used to initialize instance attributes or properties for objects.
|
||||
For its role in initializing instance attributes, `__init__()` is also referred to as a `class constructor` or `initializer`.
|
||||
`__init__()` takes one required parameter called `self`, which refers to the newly initialized or created object.
|
||||
Data for instance attributes or properties can then be passed as arguments of `__init__()`, following the `self` parameter.
|
||||
|
||||
Below, `MyClass` now has instance attributes called `location_x` and `location_y`.
|
||||
As you can see, the two attributes have been assigned to the first and second indexes of the `location` (_a tuple_) argument that has been passed to `__init__()`.
|
||||
The `location_x` and `location_y` attributes for a class instance will now be initialized when you instantiate the class, and an object is created:
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
# These are class attributes, variables, or fields.
|
||||
number = 5
|
||||
string = "Hello!"
|
||||
|
||||
# This is the class "constructor", with a "location" parameter that is a tuple.
|
||||
def __init__(self, location):
|
||||
|
||||
# This is an instance or object property, attribute, or variable.
|
||||
# Note that we are unpacking the tuple argument into two separate instance variables.
|
||||
self.location_x = location[0]
|
||||
self.location_y = location[1]
|
||||
|
||||
# Create a new object "new_object_one", with object property (1, 2).
|
||||
>>> new_object_one = MyClass((1, 2))
|
||||
|
||||
# Create a second new object "new_object_two" with object property (-8, -9).
|
||||
>>> new_object_two = MyClass((-8, -9))
|
||||
|
||||
# Note that new_object_one.location_x and new_object_two.location_x two different values.
|
||||
>>> new_object_one.location_x
|
||||
1
|
||||
|
||||
>>> new_object_two.location_x
|
||||
-8
|
||||
```
|
||||
|
||||
Note that you only need to pass one argument when initializing `MyClass` above -- Python takes care of passing `self` when the class is called.
|
||||
|
||||
|
||||
## Methods
|
||||
|
||||
A `method` is a `function` that is bound to either the class itself (_known as a [class method][class method], which will be discussed in a later exercise_) or an _instance_ of the class (object).
|
||||
Methods that operate on an object (instance) need to be defined with `self` as the first parameter.
|
||||
You can then define the rest of the parameters as you would for a "normal" or non-bound function:
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
number = 5
|
||||
string = "Hello!"
|
||||
|
||||
# Class constructor.
|
||||
def __init__(self, location):
|
||||
# Instance properties
|
||||
self.location_x = location[0]
|
||||
self.location_y = location[1]
|
||||
|
||||
# Instance method. Note "self" as first parameter.
|
||||
def change_location(self, amount):
|
||||
self.location_x += amount
|
||||
self.location_y += amount
|
||||
return self.location_x, self.location_y
|
||||
```
|
||||
|
||||
Like attribute access, calling a method simply requires putting a `.` after the object name, and before the method name.
|
||||
The called method does not need a copy of the object as a first parameter -- Python fills in `self` automatically:
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
number = 5
|
||||
string = "Hello!"
|
||||
|
||||
def __init__(self, location):
|
||||
self.location_x = location[0]
|
||||
self.location_y = location[1]
|
||||
|
||||
def change_location(self, amount):
|
||||
self.location_x += amount
|
||||
self.location_y += amount
|
||||
return self.location_x, self.location_y
|
||||
|
||||
# Make a new test_object with location (3,7)
|
||||
>>> test_object = MyClass((3,7))
|
||||
>>> (test_object.location_x, test_object.location_y)
|
||||
(3,7)
|
||||
|
||||
# Call change_location to increase location_x and location_y by 7.
|
||||
>>> test_object.change_location(7)
|
||||
(10, 14)
|
||||
```
|
||||
|
||||
Class attributes can be accessed from within instance methods in the same way that they are accessed outside of the class:
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
number = 5
|
||||
string = "Hello!"
|
||||
|
||||
def __init__(self, location):
|
||||
self.location_x = location[0]
|
||||
self.location_y = location[1]
|
||||
|
||||
# Alter instance variable location_x and location_y
|
||||
def change_location(self, amount):
|
||||
self.location_x += amount
|
||||
self.location_y += amount
|
||||
return self.location_x, self.location_y
|
||||
|
||||
# Alter class variable number for all instances from within an instance.
|
||||
def increment_number(self):
|
||||
# Increment the 'number' class variable by 1.
|
||||
MyClass.number += 1
|
||||
|
||||
|
||||
>>> test_object_one = MyClass((0,0))
|
||||
>>> test_object_one.number
|
||||
5
|
||||
|
||||
>>> test_object_two = MyClass((13, -3))
|
||||
>>> test_object_two.increment_number()
|
||||
>>> test_object_one.number
|
||||
6
|
||||
```
|
||||
|
||||
## Placeholding or Stubbing Implementation with Pass
|
||||
|
||||
In previous concept exercises and practice exercise stubs, you will have seen the `pass` keyword used within the body of functions in place of actual code.
|
||||
The `pass` keyword is a syntactically valid placeholder - it prevents Python from throwing a syntax error for an empty function or class definition.
|
||||
Essentially, it is a way to say to the Python interpreter, 'Don't worry! I _will_ put code here eventually, I just haven't done it yet.'
|
||||
|
||||
```python
|
||||
class MyClass:
|
||||
number = 5
|
||||
string = "Hello!"
|
||||
|
||||
def __init__(self, location):
|
||||
self.location_x = location[0]
|
||||
self.location_y = location[1]
|
||||
|
||||
# Alter instance variable location_x and location_y
|
||||
def change_location(self, amount):
|
||||
self.location_x += amount
|
||||
self.location_y += amount
|
||||
return self.location_x, self.location_y
|
||||
|
||||
# Alter class variable number for all instances
|
||||
def increment_number(self):
|
||||
# Increment the 'number' class variable by 1.
|
||||
MyClass.number += 1
|
||||
|
||||
# This will compile and run without error, but has no current functionality.
|
||||
def pending_functionality(self):
|
||||
# Stubbing or placholding the body of this method.
|
||||
pass
|
||||
```
|
||||
|
||||
[calling]: https://www.pythonmorsels.com/topics/calling-a-function
|
||||
[class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods
|
||||
[dunder]: https://mathspp.com/blog/pydonts/dunder-methods
|
||||
[imperative]: https://en.wikipedia.org/wiki/Imperative_programming
|
||||
[declarative]: https://en.wikipedia.org/wiki/Declarative_programming
|
||||
[oop]: https://www.digitalocean.com/community/tutorials/how-to-construct-classes-and-define-objects-in-python-3
|
||||
[functional]: https://en.wikipedia.org/wiki/Functional_programming
|
||||
[dot notation]: https://stackoverflow.com/questions/45179186/understanding-the-dot-notation-in-python
|
||||
|
||||
## Instructions
|
||||
|
||||
Ellen is making a game where the player has to fight aliens.
|
||||
She has just learned about Object Oriented Programming (OOP) and is eager to take advantage of what using `classes` could offer her program.
|
||||
|
||||
To Ellen's delight, you have offered to help and she has given you the task of programming the aliens that the player has to fight.
|
||||
|
||||
|
||||
## 1. Create the Alien Class
|
||||
|
||||
Define the Alien class with a constructor that accepts two parameters `<x_coordinate>` and `<y_coordinate>`, putting them into `x_coordinate` and `y_coordinate` instance variables.
|
||||
Every alien will also start off with a health level of 3, so the `health` variable should be initialized as well.
|
||||
|
||||
```python
|
||||
>>> alien = Alien(2, 0)
|
||||
>>> alien.x_coordinate
|
||||
2
|
||||
>>> alien.y_coordinate
|
||||
0
|
||||
>>> alien.health
|
||||
3
|
||||
```
|
||||
|
||||
Now, each alien should be able to internally track its own position and health.
|
||||
|
||||
## 2. The `hit` Method
|
||||
|
||||
Ellen would like the Alien `class` to have a `hit` method that decrements the health of an alien object by 1 when called.
|
||||
This way, she can simply call `<alien>.hit()` instead of having to manually change an alien's health.
|
||||
It is up to you if `hit()` takes healths points _to_ or _below_ zero.
|
||||
|
||||
```python
|
||||
>>> alien = Alien(0, 0)
|
||||
>>> alien.health
|
||||
|
||||
# Initialized health value.
|
||||
3
|
||||
|
||||
# Decrements health by 1 point.
|
||||
>>> alien.hit()
|
||||
>>> alien.health
|
||||
2
|
||||
```
|
||||
|
||||
## 3. The `is_alive` Method
|
||||
|
||||
You realize that if the health keeps decreasing, at some point it will probably hit 0 (_or even less!_).
|
||||
It would be a good idea to add an `is_alive` method that Ellen can quickly call to check if the alien is... well... alive. 😉
|
||||
`<alien>.is_alive()` should return a boolean.
|
||||
|
||||
```python
|
||||
>>> alien.health
|
||||
1
|
||||
>>> alien.is_alive()
|
||||
True
|
||||
>>> alien.hit()
|
||||
>>> alien.health
|
||||
0
|
||||
>>> alien.is_alive()
|
||||
False
|
||||
```
|
||||
|
||||
## 4. The `teleport` Method
|
||||
|
||||
In Ellen's game, the aliens have the ability to teleport!
|
||||
You will need to write a `teleport` method that takes new `x_coordinate` and `y_coordinate` values, and changes the alien's coordinates accordingly.
|
||||
|
||||
```python
|
||||
>>> alien.teleport(5, -4)
|
||||
>>> alien.x_coordinate
|
||||
5
|
||||
>>> alien.y_coordinate
|
||||
-4
|
||||
```
|
||||
|
||||
## 5. The `collision_detection` Method
|
||||
|
||||
Obviously, if the aliens can be hit by something, then they need to be able to detect when such a collision has occurred.
|
||||
However, collision detection algorithms can be tricky, and you do not yet know how to implement one.
|
||||
Ellen has said that she will do it later, but she would still like the `collision_detection` method to appear in the class as a reminder to build out the functionality.
|
||||
It will need to take a variable of some kind (probably another object), but that's really all you know.
|
||||
You will need to make sure that putting the method definition into the class doesn't cause any errors when called:
|
||||
|
||||
```python
|
||||
>>> alien.collision_detection(other_object)
|
||||
>>>
|
||||
```
|
||||
|
||||
## 6. Alien Counter
|
||||
|
||||
Ellen has come back with a new request for you.
|
||||
She wants to keep track of how many aliens have been created over the game's lifetime.
|
||||
She says that it's got something to do with the scoring system.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
>>> alien_one = Alien(5, 1)
|
||||
>>> alien_one.total_aliens_created
|
||||
1
|
||||
>>> alien_two = Alien(3, 0)
|
||||
>>> alien_two.total_aliens_created
|
||||
2
|
||||
>>> alien_one.total_aliens_created
|
||||
2
|
||||
>>> Alien.total_aliens_created
|
||||
# Accessing the variable from the class directly
|
||||
2
|
||||
```
|
||||
|
||||
## 7. Creating a List of Aliens
|
||||
|
||||
Ellen loves what you've done so far, but she has one more favor to ask.
|
||||
She would like a standalone (_outside the `Alien()` class_) function that creates a `list` of `Alien()` objects, given a list of positions (as `tuples`).
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
>>> alien_start_positions = [(4, 7), (-1, 0)]
|
||||
>>> aliens = new_aliens_collection(alien_start_positions)
|
||||
...
|
||||
>>> for alien in aliens:
|
||||
print(alien.x_coordinate, alien.y_coordinate)
|
||||
(4, 7)
|
||||
(-1, 0)
|
||||
```
|
||||
|
||||
## Source
|
||||
|
||||
### Created by
|
||||
|
||||
- @PaulT89
|
||||
- @BethanyG
|
||||
|
||||
### Contributed to by
|
||||
|
||||
- @DjangoFett
|
||||
- @kotp
|
||||
- @IsaacG
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,54 @@
|
||||
"""Solution to Ellen's Alien Game exercise."""
|
||||
|
||||
|
||||
class Alien:
|
||||
"""Create an Alien object with location x_coordinate and y_coordinate.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
(class)total_aliens_created: int
|
||||
x_coordinate: int - Position on the x-axis.
|
||||
y_coordinate: int - Position on the y-axis.
|
||||
health: int - Number of health points.
|
||||
|
||||
Methods
|
||||
-------
|
||||
hit(): Decrement Alien health by one point.
|
||||
is_alive(): Return a boolean for if Alien is alive (if health is > 0).
|
||||
teleport(new_x_coordinate, new_y_coordinate): Move Alien object to new coordinates.
|
||||
collision_detection(other): Implementation TBD.
|
||||
"""
|
||||
|
||||
total_aliens_created: int = 0
|
||||
x_coordinate: int
|
||||
y_coordinate: int
|
||||
health: int
|
||||
|
||||
def __init__(self, x_coordinate: int, y_coordinate: int) -> None:
|
||||
self.x_coordinate = x_coordinate
|
||||
self.y_coordinate = y_coordinate
|
||||
self.health = 3
|
||||
Alien.total_aliens_created += 1
|
||||
|
||||
def hit(self):
|
||||
self.health -= 1
|
||||
|
||||
def is_alive(self) -> bool:
|
||||
return self.health > 0
|
||||
|
||||
def teleport(self, new_x_coordinate: int, new_y_coordinate: int):
|
||||
self.x_coordinate = new_x_coordinate
|
||||
self.y_coordinate = new_y_coordinate
|
||||
|
||||
def collision_detection(self, other):
|
||||
pass
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# TODO: create the new_aliens_collection() function below to call your Alien class with a list of coordinates.
|
||||
def new_aliens_collection(list_positions: list[tuple[int, int]]) -> list[Alien]:
|
||||
list_of_aliens: list[Alien] = []
|
||||
for alien_position_x, alien_position_y in list_positions:
|
||||
list_of_aliens.append(Alien(alien_position_x, alien_position_y))
|
||||
return list_of_aliens
|
||||
@@ -0,0 +1,212 @@
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
|
||||
try:
|
||||
from classes import Alien
|
||||
except ImportError as import_fail:
|
||||
# pylint: disable=raise-missing-from
|
||||
raise ImportError("\n\nMISSING CLASS --> We tried to import the 'Alien' class from "
|
||||
"your classes.py file, but could not find it."
|
||||
"Did you misname or forget to create it?") from None
|
||||
|
||||
try:
|
||||
from classes import new_aliens_collection
|
||||
except ImportError as err:
|
||||
raise ImportError("\n\nMISSING FUNCTION --> We tried to import the "
|
||||
"new_aliens_collection() function "
|
||||
"from your classes.py file, but could not find it. "
|
||||
"Did you misname or forget to create it?") from None
|
||||
|
||||
|
||||
class ClassesTest(unittest.TestCase):
|
||||
|
||||
@pytest.mark.task(taskno=1)
|
||||
def test_alien_has_correct_initial_coordinates(self):
|
||||
"""Test that the Alien class gets correctly initialised."""
|
||||
|
||||
alien = Alien(2, -1)
|
||||
error_message = (f'Created a new Alien by calling Alien(2, -1). '
|
||||
f'The Alien was initialized to position '
|
||||
f'{(alien.x_coordinate, alien.y_coordinate)}, but the tests expected '
|
||||
f'the object to be at position (2, -1)')
|
||||
|
||||
self.assertEqual((2, -1), (alien.x_coordinate, alien.y_coordinate), msg=error_message)
|
||||
|
||||
@pytest.mark.task(taskno=1)
|
||||
def test_alien_has_health(self):
|
||||
alien = Alien(0, 0)
|
||||
error_message = (f'Created a new Alien by calling Alien(0, 0). '
|
||||
f'The new Alien has a health of {alien.health}, '
|
||||
f'but the tests expect health = 3')
|
||||
|
||||
self.assertEqual(3, alien.health, msg=error_message)
|
||||
|
||||
@pytest.mark.task(taskno=1)
|
||||
def test_alien_instance_variables(self):
|
||||
"""Test instance variables are unique to specific instances."""
|
||||
|
||||
alien_one = Alien(-8, -1)
|
||||
alien_two = Alien(2, 5)
|
||||
|
||||
coord_x_error = (f'Created two new Aliens by assigning '
|
||||
f'alien_one = Alien(-8, -1) and alien_two = Alien(2, 5). '
|
||||
f'Both Aliens x coordinates were {alien_two.x_coordinate}, '
|
||||
f'but the tests expect alien_one and alien_two to have '
|
||||
f'different x positions.')
|
||||
|
||||
coord_y_error = (f'Created two new Aliens by assigning '
|
||||
f'alien_one = Alien(-8, -1) and alien_two = Alien(2, 5). '
|
||||
f'Both Aliens y coordinates were {alien_two.y_coordinate}, '
|
||||
f'but the tests expect alien_one and alien_two to have '
|
||||
f'different y positions.')
|
||||
|
||||
self.assertFalse(alien_one.x_coordinate == alien_two.x_coordinate, msg=coord_x_error)
|
||||
self.assertFalse(alien_one.y_coordinate == alien_two.y_coordinate, msg=coord_y_error)
|
||||
|
||||
|
||||
@pytest.mark.task(taskno=2)
|
||||
def test_alien_hit_method(self):
|
||||
"""Test class methods work as specified.
|
||||
|
||||
There are two valid interpretations for this method/task.
|
||||
`self.health -= 1` and `self.health = max(0, self.health - 1)`
|
||||
The tests for this task reflect this ambiguity.
|
||||
|
||||
"""
|
||||
|
||||
test_data = [1, 2, 3, 4, 5, 6]
|
||||
result_data = [(2,), (1,), (0,), (0, -1), (0, -2), (0, -3)]
|
||||
|
||||
for variant, (iterations, expected) in enumerate(zip(test_data, result_data), start=1):
|
||||
alien = Alien(2, 2)
|
||||
|
||||
with self.subTest(f'variation #{variant}',
|
||||
iterations=iterations,
|
||||
expected=expected):
|
||||
|
||||
for _ in range(iterations):
|
||||
alien.hit()
|
||||
|
||||
error_message = (f'Called hit() {iterations} time(s) '
|
||||
f'on a newly created Alien. The Aliens health '
|
||||
f'is now {alien.health}, but the tests expected '
|
||||
f'it to be in {expected} after decrementing 1 health '
|
||||
f'point {iterations} time(s).')
|
||||
|
||||
self.assertIn(alien.health, expected, msg=error_message)
|
||||
|
||||
|
||||
@pytest.mark.task(taskno=3)
|
||||
def test_alien_is_alive_method(self):
|
||||
alien = Alien(0, 1)
|
||||
|
||||
alive_error = ('Created a new Alien and called hit(). '
|
||||
'The function is_alive() is returning False (dead) '
|
||||
'while alien.health is greater than 0.')
|
||||
|
||||
dead_error = ('Created a new Alien and called hit(). '
|
||||
'The function is_alive() is returning True (alive) '
|
||||
'while alien.health is less than or equal to 0.')
|
||||
|
||||
for _ in range(5):
|
||||
alien.hit()
|
||||
if alien.health > 0:
|
||||
self.assertTrue(alien.is_alive(), msg=alive_error)
|
||||
else:
|
||||
self.assertFalse(alien.is_alive(), msg=dead_error)
|
||||
|
||||
@pytest.mark.task(taskno=4)
|
||||
def test_alien_teleport_method(self):
|
||||
alien = Alien(0, 0)
|
||||
alien.teleport(-1, -4)
|
||||
|
||||
error_message = ('Called alien.teleport(-1,-4) on a newly created Alien. '
|
||||
'The Alien was found at position '
|
||||
f'{(alien.x_coordinate, alien.y_coordinate)}, but the '
|
||||
'tests expected it at position (-1, -4).')
|
||||
|
||||
self.assertEqual((-1, -4), (alien.x_coordinate, alien.y_coordinate), msg=error_message)
|
||||
|
||||
@pytest.mark.task(taskno=5)
|
||||
def test_alien_collision_detection_method(self):
|
||||
alien = Alien(7, 3)
|
||||
error_message = ('Created a new Alien at (7,3) and called '
|
||||
'alien.collision_detection(Alien(7, 2)). '
|
||||
f'The method returned {alien.collision_detection(Alien(7, 2))}, '
|
||||
'but the tests expected None. ')
|
||||
|
||||
self.assertIsNone(alien.collision_detection(Alien(7, 2)), msg=error_message)
|
||||
|
||||
|
||||
@pytest.mark.task(taskno=6)
|
||||
def test_alien_class_variable(self):
|
||||
"""Test class attribute/variables are identical across instances."""
|
||||
|
||||
alien_one, alien_two = Alien(0, 2), Alien(-6, -1)
|
||||
Alien.health = 6
|
||||
|
||||
created_error_message = ('Created two new Aliens and requested the '
|
||||
'total_aliens_created attribute for each one. '
|
||||
f'Received {alien_one.total_aliens_created, alien_two.total_aliens_created} '
|
||||
f'for total_aliens_created, but the tests expect '
|
||||
f'the class attributes for each newly created Alien to be identical. ')
|
||||
|
||||
health_error_message = ('Created two new Aliens and requested the '
|
||||
f'health attribute for each one. Received {alien_one.health, alien_two.health} '
|
||||
'for health, but the tests expect the class '
|
||||
'attributes for each newly created Alien to be identical. ')
|
||||
|
||||
self.assertEqual(alien_two.total_aliens_created,
|
||||
alien_one.total_aliens_created,
|
||||
msg=created_error_message)
|
||||
|
||||
self.assertEqual(alien_two.health,
|
||||
alien_one.health,
|
||||
msg=health_error_message)
|
||||
|
||||
@pytest.mark.task(taskno=6)
|
||||
def test_alien_total_aliens_created(self):
|
||||
"""Test total_aliens_created class variable increments upon object instantiation."""
|
||||
|
||||
Alien.total_aliens_created = 0
|
||||
aliens = [Alien(-2, 6)]
|
||||
|
||||
error_message = ('Created a new Alien and called total_aliens_created for it. '
|
||||
f'{aliens[0].total_aliens_created} was returned, but '
|
||||
'the tests expected that total_aliens_created would equal 1.')
|
||||
|
||||
self.assertEqual(1, aliens[0].total_aliens_created, msg=error_message)
|
||||
|
||||
aliens.append(Alien(3, 5))
|
||||
aliens.append(Alien(-5, -5))
|
||||
|
||||
def error_text(alien, variable):
|
||||
return ('Created two additional Aliens for the session.'
|
||||
f"Alien number {alien}'s total_aliens_created variable "
|
||||
f"is equal to {variable}, but the tests expected all "
|
||||
'total_aliens_created variables for all instances to be '
|
||||
'equal to number of alien instances created (i.e. 3).')
|
||||
|
||||
self.assertEqual(3, aliens[0].total_aliens_created, msg=error_text(1, aliens[0]))
|
||||
self.assertEqual(3, aliens[1].total_aliens_created, msg=error_text(2, aliens[1]))
|
||||
self.assertEqual(3, aliens[2].total_aliens_created, msg=error_text(3, aliens[2]))
|
||||
|
||||
@pytest.mark.task(taskno=7)
|
||||
def test_new_aliens_collection(self):
|
||||
"""Test that the user knows how to create objects themselves."""
|
||||
|
||||
test_data = [(-2, 6), (1, 5), (-4, -3)]
|
||||
actual_result = new_aliens_collection(test_data)
|
||||
|
||||
error_message = "new_aliens_collection() must return a list of Alien objects."
|
||||
|
||||
for obj in actual_result:
|
||||
self.assertIsInstance(obj, Alien, msg=error_message)
|
||||
|
||||
for position, obj in zip(test_data, actual_result):
|
||||
position_error = (f'After calling new_aliens_collection({test_data}), '
|
||||
f'found {obj} initialized to position {(obj.x_coordinate, obj.y_coordinate)}, '
|
||||
f'but the tests expected {obj} to be at position {position} instead.')
|
||||
|
||||
self.assertEqual(position, (obj.x_coordinate, obj.y_coordinate), msg=position_error)
|
||||
Reference in New Issue
Block a user