adds game
This commit is contained in:
parent
4496ba0811
commit
50e4712545
24 changed files with 528 additions and 0 deletions
BIN
__pycache__/display.cpython-310.pyc
Normal file
BIN
__pycache__/display.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/display.cpython-311.pyc
Normal file
BIN
__pycache__/display.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/enemy.cpython-310.pyc
Normal file
BIN
__pycache__/enemy.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/enemy.cpython-311.pyc
Normal file
BIN
__pycache__/enemy.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/input.cpython-310.pyc
Normal file
BIN
__pycache__/input.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/input.cpython-311.pyc
Normal file
BIN
__pycache__/input.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/level.cpython-310.pyc
Normal file
BIN
__pycache__/level.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/level.cpython-311.pyc
Normal file
BIN
__pycache__/level.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/level_runner.cpython-310.pyc
Normal file
BIN
__pycache__/level_runner.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/level_runner.cpython-311.pyc
Normal file
BIN
__pycache__/level_runner.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/player_stats.cpython-310.pyc
Normal file
BIN
__pycache__/player_stats.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/player_stats.cpython-311.pyc
Normal file
BIN
__pycache__/player_stats.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/tunnel.cpython-310.pyc
Normal file
BIN
__pycache__/tunnel.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/tunnel.cpython-311.pyc
Normal file
BIN
__pycache__/tunnel.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/tutorial.cpython-310.pyc
Normal file
BIN
__pycache__/tutorial.cpython-310.pyc
Normal file
Binary file not shown.
101
display.py
Normal file
101
display.py
Normal file
|
@ -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->-▓
|
24
enemy.py
Normal file
24
enemy.py
Normal file
|
@ -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")
|
||||
|
||||
|
33
input.py
Normal file
33
input.py
Normal file
|
@ -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)
|
49
level.py
Normal file
49
level.py
Normal file
|
@ -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(),
|
||||
]
|
||||
)
|
69
level_runner.py
Normal file
69
level_runner.py
Normal file
|
@ -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)
|
102
main.py
Normal file
102
main.py
Normal file
|
@ -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
|
17
player_stats.py
Normal file
17
player_stats.py
Normal file
|
@ -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
|
19
tunnel.py
Normal file
19
tunnel.py
Normal file
|
@ -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)
|
114
tutorial.py
Normal file
114
tutorial.py
Normal file
|
@ -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()
|
||||
|
||||
|
Loading…
Add table
Reference in a new issue