Skip to content
Snippets Groups Projects
minigame.py 8.3 KiB
Newer Older
# coding=utf-8

import time
import os
import sys
from movableObjects import Enemy, Arrow, PowerBar
import keyboard
import random

# CURSOR ANSI ESCAPE PATTERNS
CURSOR_UP = '\033[1A'  # moves cursor up one line
CURSOR_DOWN = '\033[1B'  # moves cursor up one line
CURSOR_RESET = '\033[u'  # resets the cursor
CURSOR_OFF = '\033[?25l'  # makes cursor invisible
CURSOR_ON = '\033[?25h'  # makes cursor visible
CURSOR_SAVE_POS = '\033[s'  # saves cursor position
CURSOR_RESTORE_POS = '\033[u'  # returns cursor to last saved position


def check_if_digits_decreased(number):
    if len(str(number)) < len(str(number+1)):
        return True
    return False


def clear():
    # posix is os name for Linux or mac
    if os.name == 'posix':
        os.system('clear')
    # else screen will be cleared for windows
    else:
        os.system('cls')


class MiniGame:
    """
    DISCLAIMER - Must be run in terminal for the screen to clear.

    This class is used to start a game loop which runs a minigame where the player/user must defeat a set amount
    of enemies by holding the space bar to charge up the weapons power and when released an arrow/bullet is fired
    and if it hits the enemy then it kills it. Once the enemies have been defeated the game loop is broken and the
    minigame is ended.

    The minigame clears the screen so if you need to be able to view previous print's then save them all to a file or
    variable and print them out after.

    This is not the final version of the minigame and more can be added if there's time.

    Issues:

    If the arrow's velocity is greater than the width of an enemy then it can "Go" through the enemy and not kill
    it as it won't register as a hit so either limit velocity or enemy width. Alternatively when the arrow moves check
    all positions it will go through and check these for the enemy.

    Next steps:

    - The "body" of game objects (arrows and enemies) can have a character linked to each bodypart leading to more
    complex designs

    - Add animations for bow, arrow, enemy

    - Display number of enemies left to defeat DONE

    - Add color to text using the python colorama module
    """
    def __init__(self, the_game_map, game_map_info, enemies_to_kill, arrows_to_use):
        self.game_map = [[x for x in row] for row in the_game_map]
        self.game_map_copy = the_game_map.copy()
        self.game_info = game_map_info
        self.enemies = []
        self.add_enemy_to_map(70, 6, random.randint(3, 6), random.randint(3, 7), "")
        self.power_bar = PowerBar(self.game_map, 8, 5, 3, 1, 1, "")
        self.arrows_left = arrows_to_use
        self.arrows = []
        self.enemy_move_time = time.perf_counter()
        self.arrow_move_time = time.perf_counter()
        self.enemy_cooldown = time.perf_counter()
        self.space_down = False
        self.power_level = 0
        self.enemies_left = enemies_to_kill
        self.update_counter("enemy counter index", self.enemies_left)
        self.update_counter("arrow counter index", self.arrows_left)

    def reset_game(self):
        self.game_map = self.game_map_copy.copy()

    def update_counter(self, the_key, counter):
        map_pos = self.game_info[the_key]
        x, y = map_pos[0], map_pos[1]
        for index, char in enumerate(str(counter)):
            self.game_map[y][x + index] = str(char)

        if check_if_digits_decreased(counter):
            self.game_map[y][x + len(str(counter))] = " "

    def add_enemy_to_map(self, x, y, w, h, char):
        enemy = Enemy(self.game_info, x, y, w, h, char)
        enemy.add_to_map(self.game_map, char)
        self.enemies.append(enemy)

    def add_arrow_to_map(self, x, y, w, h, vel_x, vel_y, char):
        arrow = Arrow(self.game_info, x, y, w, h, vel_x, vel_y, char)
        arrow.add_to_map(self.game_map, char)
        self.arrows.append(arrow)

    def keyboard_event(self, key):
        if key.name == "space":
            if key.event_type == "down":
                if not self.space_down:
                    self.power_bar.add_to_map(self.game_map, " ")
                    self.power_bar.reset_bar()
                    self.space_down = True
                self.power_level += 0.1
            elif key.event_type == "up":
                if self.arrows_left >= 1:
                    self.space_down = False
                    self.add_arrow_to_map(8, 12, 1, 1, self.power_bar.get_power()/2, -0.5, ">")
                    self.arrows_left -= 1
                    self.update_counter("arrow counter index", self.arrows_left)

    def print_and_reset(self):
        """
        Print's out the game map and then resets the cursor to the position it was before printing, so it's in the
        right place to overwrite the current print of the game map acting as a clear inbetween "frames"
        """
        for row in self.game_map:  # Iterate over each frame's rows
            for tile in row:
                sys.stdout.write(tile)
            sys.stdout.write('\n')  # newline after all tiles in row shown
        # Move cursor back to top of display ready for the next frame
        sys.stdout.write(CURSOR_UP * len(self.game_map))

    def check_hit(self):
        to_remove = []
        for index, enemy in enumerate(self.enemies):
            for arrow in self.arrows:
                for body_part in arrow.body:
                    if [int(body_part[0]), int(body_part[1])] in enemy.body:
                        enemy.add_to_map(self.game_map, " ")
                        to_remove.append(index)

        for index in to_remove:
            self.enemies.pop(index)
        if len(to_remove) >= 1:
            return True
        return False

    def enemy_move(self):
        to_remove = []
        # Returns updated map and whether the enemy is dead and so should be removed from the list of enemies.
        for index, enemy in enumerate(self.enemies):
            self.enemy_move_time = time.perf_counter()
            self.game_map, alive = enemy.move(self.game_map, 0, 1)
            if not alive:
                to_remove.append(index)

        for enemy_index in to_remove:
            self.enemies.pop(enemy_index)

    def arrow_move(self, dt):
        self.arrow_move_time = time.perf_counter()
        to_remove = []
        for index, arrow in enumerate(self.arrows):
            self.game_map, alive = arrow.arrow_move(self.game_map, dt)
            if not alive:
                to_remove.append(index)

        for arrow_index in to_remove:
            self.arrows.pop(arrow_index)

    def game_loop(self):
        os.system('color')  # Required to work, enables ANSI codes
        sys.stdout.write(CURSOR_OFF)
        keyboard.hook(self.keyboard_event)

        while True:
            if self.space_down:
                self.game_map = self.power_bar.next_level(self.game_map)
            if time.perf_counter() - self.enemy_move_time > 0.2:
                self.enemy_move()

            dt = time.perf_counter() - self.arrow_move_time
            if dt > 0.1:
                self.arrow_move(dt)

            self.print_and_reset()
            if self.check_hit():
                self.enemies_left -= 1
                self.update_counter("enemy counter index", self.enemies_left)
                if self.enemies_left <= 0:
                    time.sleep(1)
                    clear()
                    print(CURSOR_ON)
                    return True  # return true if they won
                self.enemy_cooldown = time.perf_counter()

            if self.arrows_left <= 0 and len(self.arrows) == 0:
                time.sleep(1)
                clear()
                print(CURSOR_ON)
                if self.enemies_left <= 0:
                    print("You clutched up")
                    return True  # They could have killed last enemy on last arrow so check if this won it

                return False  # Didn't kill all enemies so lost therefore return false

            if len(self.enemies) == 0:
                if time.perf_counter() - self.enemy_cooldown > 2:
                    self.add_enemy_to_map(random.randint(55, 70), random.randint(3, 15)
                                          , random.randint(3, 6), random.randint(4, 7), "")
""" 
# For testing
from minigame_map import game_map, game_info

while True:
    game = MiniGame(game_map, game_info, 1, 1)
    game.game_loop()

    clear()
    time.sleep(1)

"""