diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c74d5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# Virtual environments +.venv +# Cache +__pycache__ +.pytest_cache +# Coverage +.coverage \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game.py b/game.py index b194065..a574fa3 100644 --- a/game.py +++ b/game.py @@ -1,176 +1,338 @@ +from multiprocessing import Event import pygame import random import math from pygame import mixer -# initializing pygame pygame.init() - -# creating screen screen_width = 800 screen_height = 600 screen = pygame.display.set_mode((screen_width, screen_height)) -# caption and icon -pygame.display.set_caption("Welcome to Space Invaders Game by:- styles") - - -# Score -score_val = 0 -scoreX = 5 -scoreY = 5 -font = pygame.font.Font('freesansbold.ttf', 20) +bulletImage = pygame.image.load('data/bullet.png') -# Game Over +mixer.init() +mixer.music.load('data/background.wav') +mixer.music.play(-1) game_over_font = pygame.font.Font('freesansbold.ttf', 64) -def show_score(x, y): - score = font.render("Points: " + str(score_val), True, (255,255,255)) - screen.blit(score, (x , y )) +# FUNCTIONS # +def show_score(score, x, y): + score_text = font.render("Points: " + str(score), True, (255, 255, 255)) + screen.blit(score_text, (x, y)) + +def game_over_display(): + """ + Display game-over when the player has lesser than or equal to an 80 point difference from the invader and the invader is above 450 points. -def game_over(): - game_over_text = game_over_font.render("GAME OVER", True, (255,255,255)) + Parameters: + ---- + N.A + + Display: + ---- + "GAME OVER" in defined font and at approriate position + + """ + game_over_text = game_over_font.render("GAME OVER", True, (255, 255, 255)) screen.blit(game_over_text, (190, 250)) -# Background Sound -mixer.music.load('data/background.wav') -mixer.music.play(-1) +def reset_player_X(player_X: float) -> float: + """ + Resets the x-coordinates of the player if outside the screen + + Parameters: + ---- + player_X : Current x-coordinate + + Return: + ---- + player_X : Updated x-coordinate + """ + if player_X <= 16: + player_X = 16 + elif player_X >= 750: + player_X = 750 + return player_X -# player -playerImage = pygame.image.load('data/spaceship.png') -player_X = 370 -player_Y = 523 -player_Xchange = 0 - -# Invader -invaderImage = [] -invader_X = [] -invader_Y = [] -invader_Xchange = [] -invader_Ychange = [] -no_of_invaders = 8 -for num in range(no_of_invaders): - invaderImage.append(pygame.image.load('data/alien.png')) - invader_X.append(random.randint(64, 737)) - invader_Y.append(random.randint(30, 180)) - invader_Xchange.append(1.2) - invader_Ychange.append(50) - - -# Bullet -# rest - bullet is not moving -# fire - bullet is moving -bulletImage = pygame.image.load('data/bullet.png') -bullet_X = 0 -bullet_Y = 500 -bullet_Xchange = 0 -bullet_Ychange = 3 -bullet_state = "rest" -# Collision Concept def isCollision(x1, x2, y1, y2): - distance = math.sqrt((math.pow(x1 - x2,2)) + (math.pow(y1 - y2,2))) + distance = math.sqrt((math.pow(x1 - x2, 2)) + (math.pow(y1 - y2, 2))) if distance <= 50: return True else: return False -def player(x, y): - screen.blit(playerImage, (x - 16, y + 10)) - - -def invader(x, y, i): - screen.blit(invaderImage[i], (x, y)) - - -def bullet(x, y): - global bullet_state - screen.blit(bulletImage, (x, y)) - bullet_state = "fire" - -# game loop - -running = True -while running: - - # RGB - screen.fill((0, 0, 0)) - for event in pygame.event.get(): - if event.type == pygame.QUIT: - running = False - - # Controling the player movement from the arrow keys - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_LEFT: - player_Xchange = -1.7 - if event.key == pygame.K_RIGHT: - player_Xchange = 1.7 - if event.key == pygame.K_SPACE: - # Fixing the change of direction of bullet - if bullet_state is "rest": - bullet_X = player_X - bullet(bullet_X, bullet_Y) - bullet_sound = mixer.Sound('data/bullet.wav') - bullet_sound.play() - if event.type == pygame.KEYUP: - player_Xchange = 0 - - # adding the change in the player position - player_X += player_Xchange - for i in range(no_of_invaders): - invader_X[i] += invader_Xchange[i] - - # bullet movement - if bullet_Y <= 0: - bullet_Y = 600 - bullet_state = "rest" - if bullet_state is "fire": - bullet(bullet_X, bullet_Y) - bullet_Y -= bullet_Ychange - - # movement of the invader - for i in range(no_of_invaders): - - if invader_Y[i] >= 450: - if abs(player_X-invader_X[i]) < 80: - for j in range(no_of_invaders): - invader_Y[j] = 2000 - explosion_sound = mixer.Sound('data/explosion.wav') - explosion_sound.play() - game_over() +def show_player(player): + screen.blit(playerImage, (player["x"] - 16, player["y"] + 10)) + + +def show_invader(invader): + screen.blit(invaderImage, (invader["x"], invader["y"])) + + +def show_bullet(bullet): + screen.blit(bulletImage, (bullet["x"], bullet["y"])) + bullet["state"] = "fire" + return bullet + + +def event_action(event: object, + bullet: dict[str, float], + player: dict[str, float]) -> \ + tuple[dict[str, float], dict[str, float], bool]: + """ + Given an event (key), updates the player and bullet dictionary + + Parameters: + ---- + event : pygame.event.Event(), a representation of a key. + + bullet : bullet associated data dictionary + + player : player associated data dictionary + + Return: + ---- + tuple(bullet, player, running) + + bullet : bullet associated data dictionary + + player : player associated data dictionary + + running : indicator whether to keep running the loop + """ + running = True + if event.type == pygame.QUIT: + running = False + + # Controling the player movement from the arrow keys + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_LEFT: + player["x"] -= 5 + if event.key == pygame.K_RIGHT: + player["x"] += 5 + if event.key == pygame.K_SPACE: + # Fixing the change of direction of bullet + bullet = fix_bullet_direction(bullet, player) + + return bullet, player, running + + +def fix_bullet_direction(bullet, player): + """ + Given the x coordinate of the player (value), shoots the bullet from that position + + Parameters: + ---- + event : pygame.event.Event(), a representation of a key. + + bullet : bullet associated data dictionary + + player : player associated data dictionary + + Return: + ---- + + bullet : bullet associated data dictionary + + """ + if bullet["state"] == "rest": + bullet["x"] = player["x"] + bullet = show_bullet(bullet) + play_sound('data/bullet.wav') + return bullet + + +def play_sound(dir): + sound = mixer.Sound(dir) + sound.play() + + +def update_invaders_x(invaders): + for invader in invaders: + invader["x"] += invader["x_change"] + return invaders + + +def bullet_movement(bullet: dict[str, float]) -> dict[str, float]: + """ + Given an event (key), updates the player and bullet dictionary + + Parameters: + ---- + bullet : bullet associated data dictionary + + Return: + ---- + bullet : bullet associated data dictionary + """ + if bullet["y"] <= 0: + bullet["y"] = 600 + bullet["state"] = "rest" + if bullet["state"] == "fire": + show_bullet(bullet) + bullet["y"] -= bullet["y_change"] + return bullet + + +def game_over(invaders, invader, player): + is_game_over = False + if invader["y"] >= 450: + if abs(player["x"]-invader["x"]) < 80: + invaders = remove_invaders(invaders) + game_over_display() + is_game_over = True + return invaders, invader, is_game_over + + +def remove_invaders(invaders): + for invader_ in invaders: + invader_["y"] = 2000 + play_sound('data/explosion.wav') + return invaders + + +def move_next_line(invader): + if invader["x"] >= 735 or invader["x"] <= 0: + invader["x_change"] *= -1 + invader["y"] += invader["y_change"] + return invader + + +def update_score_and_invaders(score, bullet, invader): + score += 1 + bullet["y"] = 600 + bullet["state"] = "rest" + invader["x"] = random.randint(64, 736) + invader["y"] = random.randint(30, 200) + invader["x_change"] *= -1 + return score, bullet, invader + + +if __name__ == "__main__": + + # INITIALIZE # + pygame.init() + + # creating screen + screen_width = 800 + screen_height = 600 + screen = pygame.display.set_mode((screen_width, screen_height)) + + # caption and icon + pygame.display.set_caption("Welcome to Space Invaders Game by:- styles") + + # Score + score = 0 + scoreX = 5 + scoreY = 5 + font = pygame.font.Font('freesansbold.ttf', 20) + + # Game Over + game_over_font = pygame.font.Font('freesansbold.ttf', 64) + + # Background Sound + mixer.music.load('data/background.wav') + mixer.music.play(-1) + + # player + playerImage = pygame.image.load('data/spaceship.png') + player = { + "x": 370, + "y": 523 + } + + # Invader + invaders = [] + no_of_invaders = 8 + invaderImage = pygame.image.load('data/alien.png') + for num in range(no_of_invaders): + invader = { + "x": random.randint(64, 737), + "y": random.randint(30, 180), + "x_change": 1.2, + "y_change": 50 + } + invaders.append(invader) + + # Bullet + # rest - bullet is not moving + # fire - bullet is moving + bulletImage = pygame.image.load('data/bullet.png') + bullet = { + "x": 0, + "y": 500, + "y_change": 3, + "state": "rest" + } + + # GAME LOOP # + + running = True + while running: + + # RGB + screen.fill((0, 0, 0)) + for event in pygame.event.get(): + bullet, player, running = event_action(event, bullet, player) + + # updating the invaders x-coordinate + invaders = update_invaders_x(invaders) + + # bullet movement + bullet = bullet_movement(bullet) + + # movement of the invader + for invader in invaders: + + invaders, invader, is_game_over = game_over( + invaders, + invader, + player + ) + if is_game_over: break - if invader_X[i] >= 735 or invader_X[i] <= 0: - invader_Xchange[i] *= -1 - invader_Y[i] += invader_Ychange[i] - # Collision - collision = isCollision(bullet_X, invader_X[i], bullet_Y, invader_Y[i]) - if collision: - score_val += 1 - bullet_Y = 600 - bullet_state = "rest" - invader_X[i] = random.randint(64, 736) - invader_Y[i] = random.randint(30, 200) - invader_Xchange[i] *= -1 - - invader(invader_X[i], invader_Y[i], i) - - - - # restricting the spaceship so that it doesn't go out of screen - if player_X <= 16: - player_X = 16; - elif player_X >= 750: - player_X = 750 - - - - player(player_X, player_Y) - show_score(scoreX, scoreY) - pygame.display.update() - + invader = move_next_line(invader) + + # Collision + collision = isCollision( + bullet["x"], + invader["x"], + bullet["y"], + invader["y"] + ) + if collision: + score, bullet, invader = update_score_and_invaders( + score, + bullet, + invader + ) + + show_invader(invader) + + # restricting the spaceship so that it doesn't go out of screen + player["x"] = reset_player_X(player["x"]) + + show_player(player) + show_score(score, scoreX, scoreY) + pygame.display.update() + + + + +# ''' +# Test for the iscollision function: + +# def test(num : int, msg : str, func_out, exp_out) : +#     if func_out == exp_out : +#         print(f"+ TEST [{num}] PASSED") +#     else : +#         print(f"- TEST [{num}] FAILED; {msg}") +# ''' diff --git a/readme.md b/readme.md index 858fbca..d0a0a4b 100644 --- a/readme.md +++ b/readme.md @@ -7,6 +7,11 @@ + Clone it on your local computer OR download in zip format ++ Run + python -m venv .venv (it will create you a virtual environment) + source .venv/bin/activate (it will activate your virtual environment) + pip install -r requirements.txt (it will install necessary packages) + + Extract the file and run the game by following any of the following mentioned methods in [Running](##Running) section diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5ddfdc7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +attrs==22.1.0 +exceptiongroup==1.0.0 +iniconfig==1.1.1 +packaging==21.3 +pluggy==1.0.0 +pygame==2.1.2 +pyparsing==3.0.9 +pytest==7.2.0 +tomli==2.0.1 diff --git a/tests/test_bullet_movement.py b/tests/test_bullet_movement.py new file mode 100644 index 0000000..aa07604 --- /dev/null +++ b/tests/test_bullet_movement.py @@ -0,0 +1,34 @@ +import pytest +from game import bullet_movement + + +def test_bullet_movement(): + + bullet1 = { + "x": 45, + "y": 0, + "state": "rest" + } + bullet2 = { + "y": 500, + "x_change": 1.2, + "state": "rest" + } + bullet3 = { + "y": -10, + "state": "fire" + } + bullet4 = { + "y": 500, + "state": "fire" + } + assert bullet_movement(bullet1)["y"] == 600 + assert bullet_movement(bullet1)["x"] == 45 + assert bullet_movement(bullet1)["state"] == "rest" + assert bullet_movement(bullet2)["y"] == 500 + assert bullet_movement(bullet2)["x_change"] == 1.2 + assert bullet_movement(bullet2)["state"] == "rest" + assert bullet_movement(bullet3)["y"] == 600 + assert bullet_movement(bullet3)["state"] == "rest" + with pytest.raises(NameError): + bullet_movement(bullet4) diff --git a/tests/test_event_action.py b/tests/test_event_action.py new file mode 100644 index 0000000..8a717b0 --- /dev/null +++ b/tests/test_event_action.py @@ -0,0 +1,64 @@ +from game import event_action +import pytest + +running = True + + +def test_event_action(): + import pygame + + bullet1 = { + "y": 0, + "state": "rest" + } + bullet2 = { + "y": 500, + "state": "fire" + } + player1 = { + "x": 370, + "y": 523 + } + player2 = { + "x": -100, + "y": 100 + } + + event1 = pygame.event.Event(pygame.QUIT) + event2 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_LEFT) + event3 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_SPACE) + event4 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_RIGHT) + event5 = pygame.event.Event(pygame.KEYDOWN, key=pygame.K_UP) + bullet1_return, player1_return, running = event_action( + event1, + bullet1, + player1 + ) + assert bullet1_return == bullet1 and player1_return == player1 + assert not running + bullet1_return, player1_return, running = event_action( + event2, + bullet1, + player1 + ) + assert bullet1_return == bullet1 + assert player1_return["x"] == 370 - 1.7 + assert running + with pytest.raises(NameError): + event_action(event3, bullet1, player1) + + bullet2_return, player2_return, running = event_action( + event4, + bullet2, + player2 + ) + assert bullet2_return == bullet2 + assert player2_return["x"] == -100 + 1.7 + assert running + bullet2_return, player2_return, running = event_action( + event5, + bullet2, + player2 + ) + assert bullet2_return == bullet2 and player2_return == player2 + assert running diff --git a/tests/test_fix_bullet_direction.py b/tests/test_fix_bullet_direction.py new file mode 100644 index 0000000..41b24e6 --- /dev/null +++ b/tests/test_fix_bullet_direction.py @@ -0,0 +1,27 @@ +import pytest +from game import fix_bullet_direction + + +def test_fix_bullet_direction(): + bullet1 = { + "x": 45, + "y": 0, + "state": "rest" + } + bullet2 = { + "y": 500, + "x": 0, + "state": "fire" + } + player_one = { + "x": 120, + "state": "rest" + } + player_two = { + "x": 130 , + "state": "rest" + } + assert fix_bullet_direction(bullet1, player_one)["x"] == 120 + assert fix_bullet_direction(bullet2, player_two)["x"] != 130 + + \ No newline at end of file diff --git a/tests/test_game_over.py b/tests/test_game_over.py new file mode 100644 index 0000000..99b8d9c --- /dev/null +++ b/tests/test_game_over.py @@ -0,0 +1,29 @@ +import pytest + +from game import game_over, remove_invaders + +def test_game_over(): + player = { + "x": 150, + "y": 523 + } + new_invader = { + "x": 100, + "y": 500 + } + invaders = [] + no_of_invaders = 8 + for num in range(no_of_invaders): + invader = { + "x": 100, + "y": 500, + "x_change": 1.2, + "y_change": 50 + } + invaders.append(invader) + game_object = game_over(invaders, new_invader, player) + + for invader in game_object[0]: + assert invader["y"] == 2000 + assert game_object[1]["y"] == 500 + assert game_object[2] == True diff --git a/tests/test_isCollision.py b/tests/test_isCollision.py new file mode 100644 index 0000000..945003d --- /dev/null +++ b/tests/test_isCollision.py @@ -0,0 +1 @@ +#venneth \ No newline at end of file diff --git a/tests/test_move_next_line.py b/tests/test_move_next_line.py new file mode 100644 index 0000000..945003d --- /dev/null +++ b/tests/test_move_next_line.py @@ -0,0 +1 @@ +#venneth \ No newline at end of file diff --git a/tests/test_remove_invaders.py b/tests/test_remove_invaders.py new file mode 100644 index 0000000..67d33a3 --- /dev/null +++ b/tests/test_remove_invaders.py @@ -0,0 +1 @@ +#absaar \ No newline at end of file diff --git a/tests/test_reset_player_X.py b/tests/test_reset_player_X.py new file mode 100644 index 0000000..dcd13db --- /dev/null +++ b/tests/test_reset_player_X.py @@ -0,0 +1,8 @@ +from game import reset_player_X + + +def test_reset_player_X(): + + assert reset_player_X(0) == 16 + assert reset_player_X(17) == 17 + assert reset_player_X(800) == 750 diff --git a/tests/test_show_bullet.py b/tests/test_show_bullet.py new file mode 100644 index 0000000..67d33a3 --- /dev/null +++ b/tests/test_show_bullet.py @@ -0,0 +1 @@ +#absaar \ No newline at end of file diff --git a/tests/test_show_player.py b/tests/test_show_player.py new file mode 100644 index 0000000..3c9123d --- /dev/null +++ b/tests/test_show_player.py @@ -0,0 +1,10 @@ +import pygame +from game import show_player + +def test_show_player(): + player = { + "x": 370, + "y": 523 + } + assert show_player(player) == None + diff --git a/tests/test_update_invaders_x.py b/tests/test_update_invaders_x.py new file mode 100644 index 0000000..67d33a3 --- /dev/null +++ b/tests/test_update_invaders_x.py @@ -0,0 +1 @@ +#absaar \ No newline at end of file diff --git a/tests/test_update_score_and_invaders.py b/tests/test_update_score_and_invaders.py new file mode 100644 index 0000000..945003d --- /dev/null +++ b/tests/test_update_score_and_invaders.py @@ -0,0 +1 @@ +#venneth \ No newline at end of file