Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New concept exercise: ellens-alien-game #2875

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@
"concepts": ["sets"],
"prerequisites": ["basics", "dicts", "lists", "loops", "tuples"],
"status": "beta"
},
{
"slug": "ellens-alien-game",
"name": "Ellen's Alien Game",
"uuid": "3550ec07-f6c6-48bd-b2b4-086e75faf9e7",
"concepts": ["classes"],
"prerequisites": ["basics", "bools", "comparisons", "loops", "dicts", "iteration", "lists", "numbers", "sequences", "sets", "strings", "tuples"],
"status": "wip"
}
],
"practice": [
Expand Down
36 changes: 36 additions & 0 deletions exercises/concept/ellens-alien-game/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Hints

## 1. Create the Alien Class

- Remember that class methods are always passed `self` as the first parameter.
- Remember the double underscores on _both_ sides of `__init__`
- Instance variables are unique to the object/class instance that posesses them
- Class variables are the same across all instances of a class

## 2. The `hit` Method

- Remember that class methods are always passed `self` as the first parameter.

## 3. The `is_alive` Method

- Remember that class methods are always passed `self` as the first parameter.
- 0 is not the only 'dead' condition

## 4. The `teleport` Method

- Remember that class methods are always passed `self` as the first parameter.

## 5. The `collision_detection` Method

- Remember that class methods are always passed `self` as the first parameter.
- If you wish to `pass` on completing a method, there is a way.

## 6. Alien Counter

- Class variables are the same across all instances of a class

## 7. Object Creation

- A tuple is a single parameter.
- Alien takes 2 parameters.
- What you really want is what is inside the tuple.
116 changes: 116 additions & 0 deletions exercises/concept/ellens-alien-game/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# 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 this paradigm has to offer.

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 so that it accepts an x and a y position, and puts them into `x` and `y` variables.
Every alien starts off with a health of 3, so initialize the `health` variable too.

```python
>>> alien = Alien(2, 0)
>>> alien.x
2
>>> alien.y
0
>>> alien.health
3
```

Now, each alien should be able to keep track of it's 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 when it is called.
This way, she can simply call the `hit` method instead of having to manually change an alien's health.

For example:

```python
>>> alien = Alien(0, 0)
>>> alien.health
3 # same as before
>>> 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.

```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` function that takes new x and y positions, and changes the alien's position accordingly.

```python
>>> alien.teleport(5, -4)
>>> alien.x
5
>>> alien.y
-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 method to appear in the class.
It will need to take a variable of some kind (probably another object), but that's really all you know:

```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
```

## 7. Object Creation

Ellen loves what you've done so far, but she has a favor to ask.
She would like a standalone function that will create a list of alien objects, given a list of positions (as tuples).

For example:

```python
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> alien_list = new_alien_list(alien_start_positions)
>>> for alien in alien_list:
print(alien.x, alien.y)
4 7
-1 0
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction

Something Something OOPS!
11 changes: 11 additions & 0 deletions exercises/concept/ellens-alien-game/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"blurb": "Learn about classes by creating an alien class for Ellen's game.",
"icon": "character-study",
"authors": ["PaulT89", "BethanyG"],
"contributors": [],
"files": {
"solution": ["alien_class.py"],
"test": ["alien_class_test.py"],
"exemplar": [".meta/exemplar.py"]
}
}
Empty file.
27 changes: 27 additions & 0 deletions exercises/concept/ellens-alien-game/.meta/exemplar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Alien:
total_aliens_created = 0
health = 3

def __init__(self, pos_x, pos_y):
Alien.total_aliens_created += 1
self.x = pos_x
self.y = pos_y

def hit(self):
self.health -= 1

def is_alive(self):
return self.health > 0

def teleport(self, new_x, new_y):
self.x = new_x
self.y = new_y

def collision_detection(self, other):
pass

def new_alien_list(positions):
new_aliens = []
for position in positions:
new_aliens.append(Alien(position[0], position[1]))
return new_aliens
14 changes: 14 additions & 0 deletions exercises/concept/ellens-alien-game/alien_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Create the Alien class here


def new_alien_list(positions):
"""

:param positions: list - a list of tuples of (x, y) coordinates
:return: list - a list of alien objects

Function that takes a list of positions and creates one alien
instance per position
"""

pass
141 changes: 141 additions & 0 deletions exercises/concept/ellens-alien-game/alien_class_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import unittest

from alien_class import (
Alien,
new_alien_list
)

class ClassesTest(unittest.TestCase):
# Test Alien class exists and correctly initialised
def test_can_create_one_alien_instance(self):
Alien(1, 5)

def test_alien_has_correct_initial_x_and_y(self):
alien = Alien(2, -1)
error = (
"Expected object to be at position (2, -1) but instead "
f"found it initialized to position {(alien.x, alien.y)}.")

self.assertEqual((2, -1), (alien.x, alien.y), msg=error)

def test_alien_has_health(self):
alien = Alien(0, 0)
error = (
"Expected object's health to be 3 but instead found "
f"it had a health of {alien.health}.")

self.assertEqual(3, alien.health, msg=error)

# Test class variables are identical across instances
def test_alien_class_variable(self):
alien_one = Alien(0, 2)
alien_two = Alien(-6, -1)
Alien.total_aliens_created = -2

error_one = "Expected the total_aliens_created variable to be identical."
error_two = "Expected the health variable to be identical."

self.assertEqual(alien_two.total_aliens_created,
alien_one.total_aliens_created,
msg=error_one)
self.assertEqual(alien_two.health, alien_one.health, msg=error_two)

# Test instance variables are unique to specific instances
def test_alien_instance_variables(self):
alien_one = Alien(-8, -1)
alien_two = Alien(2, 5)

pos_x_error = (
"Expected alien_one and alien_two to have different x "
f"positions. Instead both x's were: {alien_two.x}")
pos_y_error = (
"Expected alien_one and alien_two to have different y "
f"positions. Instead both y's were: {alien_two.y}")

self.assertFalse(alien_one.x == alien_two.x, msg=pos_x_error)
self.assertFalse(alien_one.y == alien_two.y, msg=pos_y_error)

# Test total_aliens_created increments upon object instantiation
def test_alien_total_aliens_created(self):
Alien.total_aliens_created = 0
alien_one = Alien(-2, 6)
error = (
"Expected total_aliens_created to equal 1. Instead "
f"it equals: {alien_one.total_aliens_created}.")

self.assertEqual(1, alien_one.total_aliens_created, msg=error)

alien_two = Alien(3, 5)
alien_three = Alien(-5, -5)

def error_text(alien, variable):
return (
"Expected all total_aliens_created variables to be "
"equal to number of alien instances (i.e. 3). Alien "
f"number {alien}'s total_aliens_created variable "
f"is equal to {variable}")

tec_one = alien_one.total_aliens_created
tec_two = alien_two.total_aliens_created
tec_three = alien_three.total_aliens_created

self.assertEqual(3, tec_one, msg=error_text(1, tec_one))
self.assertEqual(3, tec_two, msg=error_text(2, tec_two))
self.assertEqual(3, tec_three, msg=error_text(3, tec_three))

# Test class methods work as specified
def test_alien_hit_method(self):
alien = Alien(2, 2)

for i in range(3, -3, -1):
error = (
"Expected hit method to decrement health by 1. "
f"Health is {alien.health} when it should be {i}")

self.assertEqual(i, alien.health, msg=error)
alien.hit()

def test_alien_is_alive_method(self):
alien = Alien(0, 1)
alive_error = "Alien is dead while health is greater than 0"
dead_error = "Alien is alive while health is less than or equal to 0"

for _ in range(6):
if alien.health > 0:
self.assertTrue(alien.is_alive(), msg=alive_error)
else:
self.assertFalse(alien.is_alive(), msg=dead_error)
alien.health -= 1

def test_alien_teleport_method(self):
alien = Alien(0, 0)
alien.teleport(-1, -4)

error = (
"Expected alien to be at position (-1, -4) but "
f"instead found it in position {(alien.x, alien.y)}.")

self.assertEqual((-1, -4), (alien.x, alien.y), msg=error)

def test_alien_collision_detection_method(self):
alien = Alien(7, 3)
error = "Expected collision_detection method to not be implemented"

self.assertIsNone(alien.collision_detection(Alien(7, 2)), msg=error)

# Test that the user knows how to create objects themselves
def test_new_alien_list_function(self):
position_data = [(-2, 6),
( 1, 5),
(-4, -3)]
obj_list = new_alien_list(position_data)
obj_error = "new_alien_list must return a list of Alien objects."

for obj, position in zip(obj_list, position_data):
self.assertIsInstance(obj, Alien, msg=obj_error)

pos_error = (
f"Expected object to be at position {position} but "
f"instead found it initialized to position {(obj.x, obj.y)}.")

self.assertEqual(position, (obj.x, obj.y), msg=pos_error)