diff --git a/.gitignore b/.gitignore index 11e2a47..46b3e76 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc -.idea \ No newline at end of file +.idea +Adventure Game/adventure_game/logs/AdventureGame.log diff --git a/Adventure Game/.gitignore b/Adventure Game/.gitignore index 5ceb386..29e5705 100644 --- a/Adventure Game/.gitignore +++ b/Adventure Game/.gitignore @@ -1 +1,2 @@ venv +inspectionProfiles diff --git a/Adventure Game/adventure_game/GameNavigator.py b/Adventure Game/adventure_game/GameNavigator.py new file mode 100644 index 0000000..c24e3f2 --- /dev/null +++ b/Adventure Game/adventure_game/GameNavigator.py @@ -0,0 +1,97 @@ +import npyscreen +import sys + +from Handler import Handler + + +class QuitButton(npyscreen.ButtonPress): + def whenPressed(self): + sys.exit(0) + + +class GameNavigator(npyscreen.FormBaseNew): + """ + This class handles all the drawing and 'graphics' of our game. + only basic logic like initial loading should happen here. re-drawing + and game logic should be done in Handler.py + TODO: Find a fix for initial room startup + TODO: Find a way to reset the cursor after a user hits sendButton + """ + + def update_log(self, newline): + self.logList.append(newline) # Append the newline + self.logList = self.logList[-7:] # Truncate to only the last 5 lines + + res = '' # Convert the list to a string + for element in self.logList: + res = res + str(element) + '\n' + res = res.upper() # Log is always uppercase + + self.logBox.value = res # Set the logbox to that value + + def create(self): + top_division_height = 20 + inventory_width = 20 + art_width = 100 + self.logList = [] + + self.artBox = self.add(npyscreen.BoxBasic, + name='ArtBox', + max_width=art_width, + max_height=top_division_height, + rely=2, + relx=inventory_width + 1, + editable=False) + self.artContent = self.add(npyscreen.MultiLineEdit, + rely=3, + relx=inventory_width + 2, + max_width=art_width - 2, + max_height=top_division_height - 2, + value=self.parentApp.gamelib['menu']['graphics']['not_found'], + editable=False) + self.artBox.footer = 'Unknown Location' + + self.artBox = self.add(npyscreen.BoxBasic, + name='Inventory', + max_width=inventory_width, + max_height=top_division_height, + relx=1, + rely=2, + editable=False) + + self.logBoxOutline = self.add(npyscreen.BoxBasic, + max_width=inventory_width + art_width, + max_height=9, + relx=1, + rely=top_division_height + 2, + editable=False) + + self.logBox = self.add(npyscreen.MultiLineEdit, + max_width=inventory_width + art_width - 7, + max_height=7, + relx=2, + rely=top_division_height + 3, + editable=False) + + self.dialogueBoxOutline = self.add(npyscreen.BoxBasic, + max_width=inventory_width + art_width, + max_height=3, + relx=1, + rely=top_division_height + 2 + 9, + editable=False) + + self.dialogueBox = self.add(npyscreen.Textfield, + name='Type Here', + max_width=inventory_width + art_width - 14, + max_height=1, + relx=2, + rely=top_division_height + 3 + 9) + + self.sendButton = self.add(Handler, + name="Send", + relx=inventory_width + art_width - 7, + rely=top_division_height + 3 + 9) + self.quitButton = self.add(QuitButton, + name="Quit", + relx=1, + rely=1) diff --git a/Adventure Game/adventure_game/Handler.py b/Adventure Game/adventure_game/Handler.py new file mode 100644 index 0000000..78ac1a7 --- /dev/null +++ b/Adventure Game/adventure_game/Handler.py @@ -0,0 +1,31 @@ +import npyscreen + + +class Handler(npyscreen.ButtonPress): + """ + Very important, called when the player hits send, there are several things we need to do here: + 1: handle the player's input, and run logic, this is done in handler.py + 2: prepare new items to display on the screen + 3: re-render the screen + """ + def whenPressed(self): + self.parent.parentApp.log.debug('Send button pressed!') + # This is the raw command from the user + raw_command = self.parent.dialogueBox.value + self.parent.dialogueBox.value = '' # Clear the dialogue box, TODO: This may become unneeded if issue #8 is fixed + + # This is the raw command from the user + parsed_command = raw_command.split() + + try: + command = parsed_command.pop(0) + except IndexError: + self.parent.parentApp.log.warn('Command "{0}" could not be split, was it malformed or incomplete?'.format(raw_command)) + command = '' + arguments = parsed_command # Whatever is left in the list, are arguments. + + self.parent.parentApp.log.info('Parsed command "{0}" with arguments "{1}"'.format(command, arguments)) + self.parent.update_log('command: ' + command) + self.parent.update_log('args: {0}'.format(arguments)) + self.parent.artContent.display() + self.parent.parentApp.switchForm('GAME') diff --git a/Adventure Game/adventure_game/MainMenu.py b/Adventure Game/adventure_game/MainMenu.py new file mode 100644 index 0000000..b01614b --- /dev/null +++ b/Adventure Game/adventure_game/MainMenu.py @@ -0,0 +1,21 @@ +import npyscreen + + +from Player import Player + + +class MainMenu(npyscreen.Form): + """ + This is the main menu, code here should only be for + initializing the player data and any settings they want to change + """ + def afterEditing(self): + # TODO: the game needs to happen after this inital main menu + self.parentApp.setNextForm('GAME') + + def create(self): + self.add(npyscreen.FixedText, value='You cannot select a file yet! Just hit OK', editable=False) + self.playerSaveLocation = self.add(npyscreen.TitleFilenameCombo, name="Your save file:") + + self.parentApp.player = Player(self.parentApp.mainPath / 'playerdata/defaults/default_player.yaml') + self.add(npyscreen.MultiLineEdit, value=self.parentApp.gamelib['menu']['graphics']['logo'], editable=False) \ No newline at end of file diff --git a/Adventure Game/adventure_game/Player.py b/Adventure Game/adventure_game/Player.py new file mode 100644 index 0000000..b27cb37 --- /dev/null +++ b/Adventure Game/adventure_game/Player.py @@ -0,0 +1,11 @@ +from yaml_parser import parse_datafile as parse + +class Player: + """ + This class intended to abstract out the actual yaml data into a player.(item) that is more + friendly to handle in code. + """ + def __init__(self, save_location): + self.save_location = save_location + + self.playerData = parse(save_location) \ No newline at end of file diff --git a/Adventure Game/adventure_game/__main__.py b/Adventure Game/adventure_game/__main__.py new file mode 100644 index 0000000..64ca805 --- /dev/null +++ b/Adventure Game/adventure_game/__main__.py @@ -0,0 +1,79 @@ +import pathlib +import npyscreen +import logging +from npyscreen import NotEnoughSpaceForWidget +from os import system + +from yaml_parser import parse_datafile as parse +from GameNavigator import GameNavigator +from MainMenu import MainMenu + + +class AlphaWarning(npyscreen.Popup): + def afterEditing(self): + self.parentApp.setNextForm('MENU') + + def create(self): + self.add(npyscreen.Pager, values=['Welcome to Unnamed Adventure game!', + 'Please enjoy your stay and report any bugs at', + 'kitsunehosting.net'], editable=False) + + +class AdventureGame(npyscreen.NPSAppManaged): + """ + This is the 'root' of the entire game! + """ + # Do on creation + def onStart(self): + # Setup some important 'global' values we'll need later + # Set the path all other files will follow + self.mainPath = pathlib.Path(__file__).parent + + # Setup logging + self.log = logging + self.log.basicConfig(filename=self.mainPath / 'logs/AdventureGame.log', + filemode='w', + level=logging.DEBUG) + self.log.info('Logging started!') + + # parse this data first (since it includes graphics for the main menu + self.gamelib = parse( + self.mainPath / 'gamedata/gamelib.yaml') + self.log.debug('Gamelib at {0}'.format(self.mainPath / 'gamedata/gamelib.yaml')) + + # Intalize the player as none, the player will be created in the main menu. + self.player = None + + + + # Set screen size before drawing windows + dimensions = self.gamelib['menu']['graphics']['dimensions'] + #system('mode con: cols={0} lines={1}'.format( + # dimensions['inventory_width']+dimensions['art_width'], + # 30)) # TODO: Finish setting this up. + + # Set theme + #TODO: modify custom theme? + npyscreen.setTheme(npyscreen.Themes.ElegantTheme) + + # Draw game windows + self.addForm('GAME', GameNavigator, name='Unnamed Adventure Game') # This window should draw the actual game + self.addForm('MENU', MainMenu, name='Welcome to the main menu') # This window should draw the main menu + self.addForm('MAIN', AlphaWarning, name='Welcome to the alpha!') # This window is only needed for the ALPHA + + # TODO: Create a 'splash screen' or, traditional "main menu" + + +if __name__ == '__main__': + # Set the screen size bigger + system('mode con: cols={0} lines={1}'.format(124, 36)) + + # Make a new adventure game if not imported + adventure_game = AdventureGame() + + # Run the game! + try: + adventure_game.run() + except NotEnoughSpaceForWidget: + # This exception should catch if a player tries to play in a screen that is too small. + print('Your screen is too small!\nOr, Joe has not fixed https://kitsunehosting.net/gitea/Kenwood/SNHU-IT-140/issues/7') diff --git a/Adventure Game/adventure_game/curses_test.py b/Adventure Game/adventure_game/curses_test.py deleted file mode 100644 index d316fcd..0000000 --- a/Adventure Game/adventure_game/curses_test.py +++ /dev/null @@ -1,92 +0,0 @@ -import sys,os -import curses - -def draw_menu(stdscr): - k = 0 - cursor_x = 0 - cursor_y = 0 - - # Clear and refresh the screen for a blank canvas - stdscr.clear() - stdscr.refresh() - - # Start colors in curses - curses.start_color() - curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) - curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) - curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) - - # Loop where k is the last character pressed - while (k != ord('q')): - - # Initialization - stdscr.clear() - height, width = stdscr.getmaxyx() - - if k == curses.KEY_DOWN: - cursor_y = cursor_y + 1 - elif k == curses.KEY_UP: - cursor_y = cursor_y - 1 - elif k == curses.KEY_RIGHT: - cursor_x = cursor_x + 1 - elif k == curses.KEY_LEFT: - cursor_x = cursor_x - 1 - - cursor_x = max(0, cursor_x) - cursor_x = min(width-1, cursor_x) - - cursor_y = max(0, cursor_y) - cursor_y = min(height-1, cursor_y) - - # Declaration of strings - title = "Curses example"[:width-1] - subtitle = "Written by Clay McLeod"[:width-1] - keystr = "Last key pressed: {}".format(k)[:width-1] - statusbarstr = "Press 'q' to exit | STATUS BAR | Pos: {}, {}".format(cursor_x, cursor_y) - if k == 0: - keystr = "No key press detected..."[:width-1] - - # Centering calculations - start_x_title = int((width // 2) - (len(title) // 2) - len(title) % 2) - start_x_subtitle = int((width // 2) - (len(subtitle) // 2) - len(subtitle) % 2) - start_x_keystr = int((width // 2) - (len(keystr) // 2) - len(keystr) % 2) - start_y = int((height // 2) - 2) - - # Rendering some text - whstr = "Width: {}, Height: {}".format(width, height) - stdscr.addstr(0, 0, whstr, curses.color_pair(1)) - - # Render status bar - stdscr.attron(curses.color_pair(3)) - stdscr.addstr(height-1, 0, statusbarstr) - stdscr.addstr(height-1, len(statusbarstr), " " * (width - len(statusbarstr) - 1)) - stdscr.attroff(curses.color_pair(3)) - - # Turning on attributes for title - stdscr.attron(curses.color_pair(2)) - stdscr.attron(curses.A_BOLD) - - # Rendering title - stdscr.addstr(start_y, start_x_title, title) - - # Turning off attributes for title - stdscr.attroff(curses.color_pair(2)) - stdscr.attroff(curses.A_BOLD) - - # Print rest of text - stdscr.addstr(start_y + 1, start_x_subtitle, subtitle) - stdscr.addstr(start_y + 3, (width // 2) - 2, '-' * 4) - stdscr.addstr(start_y + 5, start_x_keystr, keystr) - stdscr.move(cursor_y, cursor_x) - - # Refresh the screen - stdscr.refresh() - - # Wait for next input - k = stdscr.getch() - -def main(): - curses.wrapper(draw_menu) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/Adventure Game/adventure_game/example.py b/Adventure Game/adventure_game/example.py deleted file mode 100644 index 4913d5d..0000000 --- a/Adventure Game/adventure_game/example.py +++ /dev/null @@ -1,18 +0,0 @@ -import curses, curses.textpad -def setup_input(): - inp = curses.newwin(8,55, 0,0) - inp.addstr(1,1, "Please enter your username:") - sub = inp.subwin(2,1) - sub.border() - sub2 = sub.subwin(3,2) - global tb - tb = curses.textpad.Textbox(sub2) - inp.refresh() - tb.edit(enter_is_terminate) - -def enter_is_terminate(x): - if x == 10: - tb.do_command(7) - tb.do_command(x) - -setup_input() \ No newline at end of file diff --git a/Adventure Game/adventure_game/gamedata/gamelib.yaml b/Adventure Game/adventure_game/gamedata/gamelib.yaml new file mode 100644 index 0000000..d817750 --- /dev/null +++ b/Adventure Game/adventure_game/gamedata/gamelib.yaml @@ -0,0 +1,42 @@ +menu: + graphics: + logo: | + . __ __ __ ___ __ __ + / / / /___ ____ ____ _____ ___ ___ ____/ / / | ____/ / _____ ____ / /___ __________ + / / / / __ \/ __ \/ __ `/ __ `__ \/ _ \/ __ / / /| |/ __ / | / / _ \/ __ \/ __/ / / / ___/ _ \ + / /_/ / / / / / / / /_/ / / / / / / __/ /_/ / / ___ / /_/ /| |/ / __/ / / / /_/ /_/ / / / __/ + \____/_/ /_/_/ /_/\__,_/_/ /_/ /_/\___/\__,_/ /_/ |_\__,_/ |___/\___/_/ /_/\__/\__,_/_/ \___/ + _________ __ _________ + / ____/ | / |/ / ____/ + / / __/ /| | / /|_/ / __/ + / /_/ / ___ |/ / / / /___ + \____/_/ |_/_/ /_/_____/ + + not_found: | + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -----------------------------------------------/ \----------------------------------------------- + ----------------------------------------------/ !! \---------------------------------------------- + ---------------------------------------------/ \--------------------------------------------- + --------------------------------------No Art for this location------------------------------------ + ----------------------------------Consider making a pull request?--------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + -------------------------------------------------------------------------------------------------- + dimensions: + inventory_width: 23 + inventory_height: 20 + art_width: 101 + art_height: 20 + dialogue_width: 122 + dialogue_height: 20 + entry_box_width: 122 + entry_box_height: 3 diff --git a/Adventure Game/adventure_game/gamedata/world/office.yaml b/Adventure Game/adventure_game/gamedata/world/office.yaml new file mode 100644 index 0000000..3d88078 --- /dev/null +++ b/Adventure Game/adventure_game/gamedata/world/office.yaml @@ -0,0 +1,12 @@ +office: + grid: [0, 0] + upon_enter: "You are standing behind your desk, you see a |NAMEPLATE|, a |TAPE RECORDER| and your trusty |LOG VIEWER|" + look_around: "You look around the room, you see a |DESK|, a |BOOKSHELF| and the |DOOR|" + pick_up_logviewer: "You pick the *LOG VIEWER* up." + desk: + look_at: "You move to stand behind your desk. You see a |NAMEPLATE|, a |TAPE RECORDER| and your trusty |LOG VIEWER|" + inspect: "Desk, ornate, stuff" + log_viewer: + look_at: "logviewer looks like garbo" + inspect: "beep boop" + pick_up: "You pick up the *LOG VIEWER*." \ No newline at end of file diff --git a/Adventure Game/adventure_game/logs/AdventureGame.log b/Adventure Game/adventure_game/logs/AdventureGame.log new file mode 100644 index 0000000..e69de29 diff --git a/Adventure Game/adventure_game/main.py b/Adventure Game/adventure_game/main.py deleted file mode 100644 index d30f79d..0000000 --- a/Adventure Game/adventure_game/main.py +++ /dev/null @@ -1,87 +0,0 @@ -import curses -from curses.textpad import Textbox - - -class AdventureGame: - def __init__(self, stdscr): - # Self.stdrscr is our parent standard screen - self.stdscr = stdscr - - # This is just done once at startup to get inital windows sizes - # TODO: handle live window resize! - self.height, self.width = self.stdscr.getmaxyx() - - self.command = 0 - - # Clear and refresh the screen - self.stdscr.clear() - self.stdscr.refresh() - - # Setup some colors! - # Start some curses colors - curses.start_color() - curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) - curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) - curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) - - # Setup art_win - art_win_width = int((self.width / 3) * 2) - art_win_begin_x = int(self.width - art_win_width) - art_win_begin_y = 1 # Must be at least 1 (to allow header) - art_win_height = int(self.height / 2) - - art_win = curses.newwin(art_win_height, art_win_width, art_win_begin_y, art_win_begin_x) - - # Setup dialogue_win - dialogue_win_width = self.width - dialogue_win_begin_x = 0 - dialogue_win_begin_y = art_win_height + 1 # Fits 1 underneath the art box above. - dialogue_win_height = self.height - art_win_height - 2 # Fits 2 underneath the bottom box, - - dialogue_win = curses.newwin(dialogue_win_height, dialogue_win_width, dialogue_win_begin_y, - dialogue_win_begin_x) - - while self.command != ord('q'): - # Get the height of everything - self.height, self.width = self.stdscr.getmaxyx() - - self.title = 'Untitled Adventure Game PRE ALPHA'[:self.width - 1] - self.statusbarstr = "Press 'q' to exit " - - # Render status bar - self.stdscr.attron(curses.color_pair(3)) - self.stdscr.addstr(self.height - 1, 0, self.statusbarstr) - self.stdscr.addstr(self.height - 1, len(self.statusbarstr), ' ' * (self.width - len(self.statusbarstr) - 1)) - self.stdscr.attroff(curses.color_pair(3)) - - # Render major game windows - self.draw_art_window(art_win) - self.command = self.draw_dialogue_box(dialogue_win) - - # Refresh the screen - self.stdscr.refresh() - - # Wait for next input - # self.command = self.stdscr.getch() - curses.echo() - #self.command = self.stdscr.getstr(0, 0) - - def draw_main_menu(self): - pass - - def draw_art_window(self, window): - window.addstr("Wip") - window.addstr("Morewip lol") - window.box() - window.refresh() - - def draw_dialogue_box(self, window): - window.box() - window.refresh() - - textbox = Textbox(window) - return textbox.edit() - - -if __name__ == '__main__': - curses.wrapper(AdventureGame) diff --git a/Adventure Game/adventure_game/playerdata/defaults/default_player.yaml b/Adventure Game/adventure_game/playerdata/defaults/default_player.yaml new file mode 100644 index 0000000..811f696 --- /dev/null +++ b/Adventure Game/adventure_game/playerdata/defaults/default_player.yaml @@ -0,0 +1,4 @@ +player: + name: 'Default' + location: 'office' + inventory: ['test', 'test2'] \ No newline at end of file diff --git a/Adventure Game/adventure_game/yaml_parser.py b/Adventure Game/adventure_game/yaml_parser.py new file mode 100644 index 0000000..0d41be6 --- /dev/null +++ b/Adventure Game/adventure_game/yaml_parser.py @@ -0,0 +1,12 @@ +import yaml + + +def parse_datafile(file): + # With the file open + with open(file, 'r') as stream: + # Try to read it and return it + try: + content = yaml.safe_load(stream) + return content + except yaml.YAMLError as exc: + return exc diff --git a/Adventure Game/requirements.txt b/Adventure Game/requirements.txt index 6493b8a..ba33800 100644 --- a/Adventure Game/requirements.txt +++ b/Adventure Game/requirements.txt @@ -1,2 +1,2 @@ -# Blessed handles our terminal "graphics" -blessed==1.17.12 \ No newline at end of file +npyscreen~=4.10.5 +PyYAML~=5.1.2 \ No newline at end of file