diff --git a/__pycache__/display.cpython-310.pyc b/__pycache__/display.cpython-310.pyc new file mode 100644 index 0000000..9f911ae Binary files /dev/null and b/__pycache__/display.cpython-310.pyc differ diff --git a/__pycache__/display.cpython-311.pyc b/__pycache__/display.cpython-311.pyc new file mode 100644 index 0000000..2f19bbd Binary files /dev/null and b/__pycache__/display.cpython-311.pyc differ diff --git a/__pycache__/enemy.cpython-310.pyc b/__pycache__/enemy.cpython-310.pyc new file mode 100644 index 0000000..4ecf4b9 Binary files /dev/null and b/__pycache__/enemy.cpython-310.pyc differ diff --git a/__pycache__/enemy.cpython-311.pyc b/__pycache__/enemy.cpython-311.pyc new file mode 100644 index 0000000..8e46f3a Binary files /dev/null and b/__pycache__/enemy.cpython-311.pyc differ diff --git a/__pycache__/input.cpython-310.pyc b/__pycache__/input.cpython-310.pyc new file mode 100644 index 0000000..b32e56d Binary files /dev/null and b/__pycache__/input.cpython-310.pyc differ diff --git a/__pycache__/input.cpython-311.pyc b/__pycache__/input.cpython-311.pyc new file mode 100644 index 0000000..905dd5d Binary files /dev/null and b/__pycache__/input.cpython-311.pyc differ diff --git a/__pycache__/level.cpython-310.pyc b/__pycache__/level.cpython-310.pyc new file mode 100644 index 0000000..e57ca73 Binary files /dev/null and b/__pycache__/level.cpython-310.pyc differ diff --git a/__pycache__/level.cpython-311.pyc b/__pycache__/level.cpython-311.pyc new file mode 100644 index 0000000..5404ddd Binary files /dev/null and b/__pycache__/level.cpython-311.pyc differ diff --git a/__pycache__/level_runner.cpython-310.pyc b/__pycache__/level_runner.cpython-310.pyc new file mode 100644 index 0000000..eeb06ff Binary files /dev/null and b/__pycache__/level_runner.cpython-310.pyc differ diff --git a/__pycache__/level_runner.cpython-311.pyc b/__pycache__/level_runner.cpython-311.pyc new file mode 100644 index 0000000..8f20d4c Binary files /dev/null and b/__pycache__/level_runner.cpython-311.pyc differ diff --git a/__pycache__/player_stats.cpython-310.pyc b/__pycache__/player_stats.cpython-310.pyc new file mode 100644 index 0000000..2d283c2 Binary files /dev/null and b/__pycache__/player_stats.cpython-310.pyc differ diff --git a/__pycache__/player_stats.cpython-311.pyc b/__pycache__/player_stats.cpython-311.pyc new file mode 100644 index 0000000..e2699c5 Binary files /dev/null and b/__pycache__/player_stats.cpython-311.pyc differ diff --git a/__pycache__/tunnel.cpython-310.pyc b/__pycache__/tunnel.cpython-310.pyc new file mode 100644 index 0000000..983cca8 Binary files /dev/null and b/__pycache__/tunnel.cpython-310.pyc differ diff --git a/__pycache__/tunnel.cpython-311.pyc b/__pycache__/tunnel.cpython-311.pyc new file mode 100644 index 0000000..fdc49f3 Binary files /dev/null and b/__pycache__/tunnel.cpython-311.pyc differ diff --git a/__pycache__/tutorial.cpython-310.pyc b/__pycache__/tutorial.cpython-310.pyc new file mode 100644 index 0000000..50ce167 Binary files /dev/null and b/__pycache__/tutorial.cpython-310.pyc differ diff --git a/display.py b/display.py new file mode 100644 index 0000000..8bba99f --- /dev/null +++ b/display.py @@ -0,0 +1,101 @@ +import curses +from _curses import A_DIM, A_BLINK, A_STANDOUT, A_BOLD +from curses import initscr + +from tunnel import Tunnel + + +class Display: + def __init__(self, scr): + self.scr = scr + curses.noecho() + curses.cbreak() + curses.curs_set(0) + + self.darkness_revealed = False + + def teardown_curses(self): + curses.nocbreak() + curses.echo() + curses.endwin() + + def new_frame(self): + self.scr.clear() + + def display_frame(self): + self.scr.refresh() + + def display_lose_screen(self): + self.scr.addstr(2, 2, "the small creature died") + self.scr.addstr(3, 2, "the tunnel was empty again") + self.scr.addstr(4, 2, "any key to exit") + self.scr.addstr(6, 13, " \\_") + self.scr.addstr(7, 13, "⩍ \\ ") + + def display_win_screen(self): + self.scr.addstr(2, 2, "the small creature explored the whole tunnel") + self.scr.addstr(3, 2, "they had a nice time and were ready to go find another") + self.scr.addstr(4, 2, "but first they wanted to sleep") + self.scr.addstr(5, 2, "any key to exit") + + self.scr.addstr(7, 8, "Z") + self.scr.addstr(8, 7, "zᶻ") + self.scr.addstr(9, 5, "> ᶻ") + + + + # tunnel is 15 long + def display_tunnel(self, tunnel: Tunnel): + player_pos = 15 + y = 3 + x = 2 + + self.scr.addstr(y, x, tunnel.get_as_str(), A_DIM) + self.scr.addch(y, x+player_pos, ">") + + if self.darkness_revealed: + self.scr.addch(y, x+player_pos+1, tunnel.next_step.char) + else: + self.scr.addch(y, x+player_pos+1, "▓", A_DIM) + + self.scr.refresh() + + def display_lives(self, lives): + lives_string = "lives: " + ("v"*lives) + self.scr.addstr(1, 2, lives_string, A_DIM) + + def display_level(self, level): + self.scr.addstr(6, 2, "level: " + str(level), A_DIM) + + def display_steps(self, steps, total_steps): + lives_string = "steps: " + str(steps) + "/" + str(total_steps) + self.scr.addstr(5, 2, lives_string, A_DIM) + + def display_next_level(self): + self.scr.addstr(2, 2, "The tunnel continues") + self.scr.addstr(3, 2, "deeper to the next level") + + self.scr.addstr(5, 12, "⩍") + + + def display_menu_art(self): + self.scr.addstr(1, 13, " \\_") + self.scr.addstr(2, 13, "⩍ \\ <") + + self.scr.addstr(4, 2, "A small creature") + self.scr.addstr(4, 19, "explores", A_BOLD) + self.scr.addstr(4, 28, "a tunnel") + + def display_menu(self, options, index): + y = 7 + x = 13 + for i, option in enumerate(options): + if i == index: + self.scr.addstr(y+i, x-1, ">") + self.scr.addstr(y+i, x, option) + else: + self.scr.addstr(y+i, x, option, A_DIM) + + + +# ---o---o---o->-▓ diff --git a/enemy.py b/enemy.py new file mode 100644 index 0000000..147a00a --- /dev/null +++ b/enemy.py @@ -0,0 +1,24 @@ + +class Enemy: + def __init__(self, char): + self.char = char + self.key = char + +class Empty(Enemy): + def __init__(self): + super().__init__(" ") + self.char = "-" + +class Goblin(Enemy): + def __init__(self): + super().__init__("o") + +class Slime(Enemy): + def __init__(self): + super().__init__("e") + +class KnifeRat(Enemy): + def __init__(self): + super().__init__("c") + + diff --git a/input.py b/input.py new file mode 100644 index 0000000..ac0dd4c --- /dev/null +++ b/input.py @@ -0,0 +1,33 @@ +import curses +from curses import * + + + +class Input: + def __init__(self, scr): + self.scr = scr + halfdelay(10) + + def wait_on_any_key(self) -> int: + try: + key_given = self.scr.getch() + return key_given + except curses.error: + pass + + def wait_on_key(self, key): + try: + key_given = self.scr.getch() + if key_given is None: + return False + if key_given == ord(key): + return True + except curses.error: + pass + + return False + + def wait_indefinitely_for_key(self): + nocbreak() + self.scr.getch() + halfdelay(20) \ No newline at end of file diff --git a/level.py b/level.py new file mode 100644 index 0000000..9ffdae3 --- /dev/null +++ b/level.py @@ -0,0 +1,49 @@ +from math import sqrt +from typing import Union +from enemy import * + +class Level: + def __init__(self, level_gen): + self.level_gen = level_gen + self.steps = 0 + self.length = 40 + + + def get_next_step(self) -> Union[Enemy, None]: + self.steps += 1 + return self.level_gen(self.steps) + + +class LevelLibrary: + def __init__(self, levels): + self.levels = levels + self.current_level = 0 + + def __iter__(self): + return self + + def __next__(self): + next_level = self.get_level(self.current_level) + + if next_level is None: + raise StopIteration + + self.current_level += 1 + return next_level + + def get_level(self, level_number: int) -> Union[Level, None]: + if level_number >= len(self.levels): + return None + else: + return Level(self.levels[level_number]) + + +level_lib = LevelLibrary( +[ + lambda step : Goblin() if step%2==0 else Empty(), + lambda step : Goblin() if step%3==0 else Empty(), + lambda step : Goblin() if step%4==0 else KnifeRat() if step%2==0 else Empty(), + lambda step : Slime() if sqrt((step * 8) + 1).is_integer() else Empty(), + lambda step : KnifeRat() if step%3==0 and step%5==0 else Goblin() if step%3==0 else Slime() if step%5==0 else Empty(), +] +) \ No newline at end of file diff --git a/level_runner.py b/level_runner.py new file mode 100644 index 0000000..2af0dae --- /dev/null +++ b/level_runner.py @@ -0,0 +1,69 @@ +from enum import Enum + +from display import Display +from input import Input +from level import Level +from player_stats import PlayerStats +from tunnel import Tunnel + + +class State(Enum): + GUESSING = 0 + REVEALED = 1 + +class LevelRunner: + + + def __init__(self, disp: Display, inp: Input, player: PlayerStats): + self.disp = disp + self.inp = inp + self.state = State.GUESSING + self.player = player + + def reveal(self): + self.state = State.REVEALED + + def move_to_next_step(self, tunnel: Tunnel, level: Level): + tunnel.append_step(level.get_next_step()) + self.state = State.GUESSING + + + def run_level(self, level: Level, level_number: int): + tunnel = Tunnel() + self.player.lives = 10 + + while True: + self.disp.new_frame() + + if self.player.is_dead(): + return 0 + if level.steps >= level.length: + return 1 + + self.disp.display_lives(self.player.lives) + self.disp.display_steps(level.steps, level.length) + self.disp.display_level(level_number) + + + match self.state: + case State.GUESSING: + self.disp.darkness_revealed = False + self.disp.display_tunnel(tunnel) + self.disp.display_frame() + + guesses_correct = self.inp.wait_on_key(tunnel.next_step.key) + if not guesses_correct: + self.player.lose_life() + else: + # self.player.heal_life() + pass + + self.reveal() + + case State.REVEALED: + self.disp.darkness_revealed = True + self.disp.display_tunnel(tunnel) + self.disp.display_frame() + + self.inp.wait_on_any_key() # wait on any key + self.move_to_next_step(tunnel, level) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..1adeaea --- /dev/null +++ b/main.py @@ -0,0 +1,102 @@ +import curses +from curses import wrapper +from enum import Enum +from time import sleep + +from enemy import KnifeRat +from input import Input +from level import Level, level_lib +from display import Display +from level_runner import LevelRunner +from player_stats import PlayerStats +from tunnel import Tunnel +from tutorial import tutorial + + +class App: + def __init__(self, scr): + self.disp = Display(scr) + self.inp = Input(scr) + + + def app(self): + selected_item = 0 + menu_items = ["play", "tutorial", "quit"] + while True: + self.disp.new_frame() + self.disp.display_menu_art() + self.disp.display_menu(menu_items, selected_item) + self.disp.display_frame() + + key = self.inp.wait_on_any_key() + if key == curses.KEY_UP or key == ord("w"): + selected_item -= 1 + if selected_item < 0: + selected_item = len(menu_items)-1 + + elif key == curses.KEY_DOWN or key == ord("s"): + selected_item += 1 + if selected_item >= len(menu_items): + selected_item = 0 + + elif key == ord("\n") or key == ord(" "): + match menu_items[selected_item]: + case "play": + self.cycle_through_levels() + case "tutorial": + self.tutorial() + case "quit": + exit(0) + + + def cycle_through_levels(self): + stats = PlayerStats() + level_runner = LevelRunner(self.disp, self.inp, stats) + + level_lib.current_level = 0 + for level in level_lib: + score = level_runner.run_level(level, level_lib.current_level) + if score == 0: + self.defeat_screen() + + self.disp.display_next_level() + self.inp.wait_indefinitely_for_key() + self.inp.wait_indefinitely_for_key() # wait twice to debounce + + self.win_screen() + + def defeat_screen(self): + self.disp.display_lose_screen() + self.inp.wait_indefinitely_for_key() + self.inp.wait_indefinitely_for_key() # wait twice to debounce + + exit(0) + + def win_screen(self): + self.disp.display_win_screen() + self.inp.wait_indefinitely_for_key() + self.inp.wait_indefinitely_for_key() # wait twice to debounce + + exit(0) + + def tutorial(self): + tutorial(self.disp, self.inp) + + + + + + + + + + +if __name__ == '__main__': + def run(scr): + app = App(scr) + app.app() + + wrapper(run) + +# todo fix fizzbuzz +# todo tutorial \ No newline at end of file diff --git a/player_stats.py b/player_stats.py new file mode 100644 index 0000000..cf237ef --- /dev/null +++ b/player_stats.py @@ -0,0 +1,17 @@ + +class PlayerStats: + def __init__(self): + self.lives = 10 + + def lose_life(self): + self.lives -= 1 + + def heal_life(self): + if self.lives < 10: + self.lives += 1 + + def is_dead(self): + if self.lives <= 0: + return True + else: + return False \ No newline at end of file diff --git a/tunnel.py b/tunnel.py new file mode 100644 index 0000000..b5dc506 --- /dev/null +++ b/tunnel.py @@ -0,0 +1,19 @@ +from queue import Queue +from enemy import * + + +class Tunnel: + def __init__(self): + self.next_step = Empty() + self.contents = Queue() + for i in range(15): self.contents.put(Empty()) + print(self.contents.qsize()) + + def append_step(self, step): + self.contents.get() + self.contents.put(self.next_step) + self.next_step = step + + def get_as_str(self) -> str: + result = [i.char for i in self.contents.queue] + return "".join(result) \ No newline at end of file diff --git a/tutorial.py b/tutorial.py new file mode 100644 index 0000000..32be290 --- /dev/null +++ b/tutorial.py @@ -0,0 +1,114 @@ +from time import sleep + +from enemy import Goblin, Empty +from level import level_lib +from level_runner import LevelRunner +from player_stats import PlayerStats +from tunnel import Tunnel + + +def tutorial(disp, inp): + tunnel = Tunnel() + + disp.new_frame() + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(4, 17, "^ this is you") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(2, 18, "v its dark, you cant see whats infront of you") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(2, 18, "v hit space to reveal the next step") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.darkness_revealed = True + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(2, 18, "v hit space again to move forwards") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.darkness_revealed = False + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(2, 18, "v this next step has an enemy in it") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(2, 18, "v this enemy is an o, hit that key to handle it") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.darkness_revealed = True + tunnel.append_step(Goblin()) + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(2, 18, "v there we go! hit space to step forward again") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.darkness_revealed = False + tunnel.append_step(Empty()) + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(4, 18, "^ beware that you'll step forward automatically after a second") + disp.scr.addstr(5, 18, " ain't no time for dawdlin' in the tunnels!") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + disp.display_tunnel(tunnel) + disp.scr.addstr(9, 2, "Enter to continue >") + disp.scr.addstr(5, 2, "recognise the pattern, predict the enemies and handle them") + disp.scr.addstr(6, 2, "you'll lose a few lives to start with, but you'll pick it up quickly") + disp.scr.addstr(7, 2, "survive 40 steps to complete a level") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + + disp.new_frame() + level_runner = LevelRunner(disp, inp, PlayerStats()) + level_runner.run_level(level_lib.get_level(0), 0) + + disp.new_frame() + disp.scr.addstr(2, 2, "You got this! Go explore the real tunnels now!") + disp.display_frame() + + sleep(0.5) + inp.wait_indefinitely_for_key() + +