From 81ec3f1167b69b0c0980555cd513e9bab7995571 Mon Sep 17 00:00:00 2001 From: Jake Funke Date: Fri, 24 Mar 2017 00:08:27 +0000 Subject: [PATCH] Refactor display, add confirmations for restart --- README.md | 11 ++-- botany.py | 11 ++-- menu_screen.py | 138 ++++++++++++++++++++++++++----------------------- 3 files changed, 82 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 86099c4..a1f7949 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ A command line, realtime, community plant buddy. You've been given a seed that will grow into a beautiful plant. Check in and water your plant every 24h to keep it growing. 5 days without water = death. Your plant depends on you to live! +*"We do not "come into" this world; we come out of it, as leaves from a tree." - Alan Watts* + *(work in progress)* ## getting started @@ -27,10 +29,12 @@ If your plant goes 5 days without water, it will die! ## features * Curses-based menu system, optimized for 80x24 terminal -* ASCII art display of plant +* 20+ Species of plants w/ ASCII art for each * Persistent aging system that allows your plant to grow even when app is closed +* Random and rare mutations can occur at any point in a plant's life * SQLite Community Garden of other users' plants (for shared unix servers) -* Data file is created in the user's home (~) directory, along with a JSON file that can be used in other apps. +* Data files are created in the user's home (~) directory, along with a JSON file that can be used in other apps. + * Data is created for your current plant and harvested plants ``` { @@ -49,9 +53,8 @@ If your plant goes 5 days without water, it will die! ### to-dos * Finish garden feature - * Allows you to water neighbor's plants + * Water neighbor's plants * Harvest plant at end of life (gather seeds) - * Create harvest file with a log of all previous plants * Plant pollination - cross-breed with neighbor plants to unlock second-gen plants * Share seeds with other users * Global events diff --git a/botany.py b/botany.py index c36ffff..5acbb46 100644 --- a/botany.py +++ b/botany.py @@ -2,18 +2,13 @@ from __future__ import division import time import pickle import json -import math -import sys import os import random import getpass import threading import errno import uuid -import fcntl import sqlite3 -from collections import OrderedDict -from operator import itemgetter from menu_screen import * # development plan @@ -260,8 +255,7 @@ class Plant(object): self.kill_plant() while self.write_lock: # Wait for garden writer to unlock - # garden datafile needs to register that plant has died before - # allowing the user to reset + # garden db needs to update before allowing the user to reset pass if not self.write_lock: self.new_seed(self.file_name) @@ -482,7 +476,6 @@ class DataManager(object): def data_write_json(self, this_plant): # create personal json file for user to use outside of the game (website?) json_file = os.path.join(self.botany_dir,self.this_user + '_plant_data.json') - json_leaderboard = os.path.join(self.game_dir + '_garden.json') # also updates age age_formatted = self.plant_age_convert(this_plant) plant_info = { @@ -518,6 +511,7 @@ class DataManager(object): else: this_harvest = {} new_file_check = True + this_harvest[this_plant_id] = plant_info # dump harvest file @@ -540,6 +534,7 @@ if __name__ == '__main__': my_data.data_write_json(my_plant) my_plant.start_life() my_data.start_threads(my_plant) + # TODO: curses wrapper botany_menu = CursedMenu(my_plant,my_data) my_data.save_plant(my_plant) my_data.data_write_json(my_plant) diff --git a/menu_screen.py b/menu_screen.py index 769ca19..9a45e2e 100644 --- a/menu_screen.py +++ b/menu_screen.py @@ -1,4 +1,9 @@ -import curses, os, traceback, threading, time, datetime, pickle, operator, random, sqlite3 +import curses +import os +import traceback +import threading +import time +import random class CursedMenu(object): #TODO: name your plant @@ -17,8 +22,6 @@ class CursedMenu(object): self.plant_string = self.plant.parse_plant() self.plant_ticks = str(self.plant.ticks) self.exit = False - self.instructiontoggle = False - self.gardenmenutoggle = False self.infotoggle = 0 self.maxy, self.maxx = self.screen.getmaxyx() # Highlighted and Normal line definitions @@ -73,7 +76,7 @@ class CursedMenu(object): except Exception as exception: # Makes sure data is saved in event of a crash due to window resizing self.screen.clear() - self.screen.addstr(0,0,"Enlarge terminal!") + self.screen.addstr(0, 0, "Enlarge terminal!", curses.A_NORMAL) self.screen.refresh() self.__exit__() traceback.print_exc() @@ -91,7 +94,7 @@ class CursedMenu(object): # Also calls __exit__, but adds traceback after except Exception as exception: self.screen.clear() - self.screen.addstr(0,0,"Enlarge terminal!") + self.screen.addstr(0, 0, "Enlarge terminal!", curses.A_NORMAL) self.screen.refresh() self.__exit__() #traceback.print_exc() @@ -104,7 +107,7 @@ class CursedMenu(object): this_string = this_file.readlines() this_file.close() for y, line in enumerate(this_string, 2): - self.screen.addstr(ypos+y,xpos,line, curses.A_NORMAL) + self.screen.addstr(ypos+y, xpos, line, curses.A_NORMAL) # self.screen.refresh() def draw_plant_ascii(self, this_plant): @@ -148,25 +151,25 @@ class CursedMenu(object): def draw_default(self): # draws default menu clear_bar = " " * (int(self.maxx*2/3)) - self.screen.addstr(2,2, self.title, curses.A_STANDOUT) # Title for this menu - self.screen.addstr(4,2, self.subtitle, curses.A_BOLD) #Subtitle for this menu + self.screen.addstr(2, 2, self.title, curses.A_STANDOUT) # Title for this menu + self.screen.addstr(4, 2, self.subtitle, curses.A_BOLD) #Subtitle for this menu # clear menu on screen for index in range(len(self.options)+1): - self.screen.addstr(5+index,4, clear_bar, curses.A_NORMAL) + self.screen.addstr(5+index, 4, clear_bar, curses.A_NORMAL) # display all the menu items, showing the 'pos' item highlighted for index in range(len(self.options)): textstyle = self.normal if index == self.selected: textstyle = self.highlighted - self.screen.addstr(5+index,4, clear_bar, curses.A_NORMAL) - self.screen.addstr(5+index,4, "%d - %s" % (index+1, self.options[index]), textstyle) + self.screen.addstr(5+index ,4, clear_bar, curses.A_NORMAL) + self.screen.addstr(5+index ,4, "%d - %s" % (index+1, self.options[index]), textstyle) - self.screen.addstr(11,2, clear_bar, curses.A_NORMAL) - self.screen.addstr(12,2, clear_bar, curses.A_NORMAL) - self.screen.addstr(11,2, "plant: ", curses.A_DIM) - self.screen.addstr(11,9, self.plant_string, curses.A_NORMAL) - self.screen.addstr(12,2, "score: ", curses.A_DIM) - self.screen.addstr(12,9, self.plant_ticks, curses.A_NORMAL) + self.screen.addstr(11, 2, clear_bar, curses.A_NORMAL) + self.screen.addstr(12, 2, clear_bar, curses.A_NORMAL) + self.screen.addstr(11, 2, "plant: ", curses.A_DIM) + self.screen.addstr(11, 9, self.plant_string, curses.A_NORMAL) + self.screen.addstr(12, 2, "score: ", curses.A_DIM) + self.screen.addstr(12, 9, self.plant_ticks, curses.A_NORMAL) if not self.plant.dead: if int(time.time()) <= self.plant.watered_timestamp + 24*3600: @@ -198,7 +201,7 @@ class CursedMenu(object): user_in = self.screen.getch() # Gets user input except Exception as e: self.__exit__() - # DEBUG KEYS - enable to see curses key codes + ## DEBUG KEYS - enable to see curses key codes # self.screen.addstr(1, 1, str(user_in), curses.A_NORMAL) # self.screen.refresh() @@ -220,12 +223,15 @@ class CursedMenu(object): return # increment or Decrement - down_keys = [curses.KEY_DOWN, 14, 106] - up_keys = [curses.KEY_UP, 16, 107] + down_keys = [curses.KEY_DOWN, 14, ord('j')] + up_keys = [curses.KEY_UP, 16, ord('k')] + if user_in in down_keys: # down arrow self.selected += 1 if user_in in up_keys: # up arrow self.selected -=1 + + # modulo to wrap around self.selected = self.selected % len(self.options) return @@ -247,35 +253,25 @@ class CursedMenu(object): return paginated_list def draw_garden(self): - # draws neighborhood - clear_bar = " " * (self.maxx-2) + "\n" - clear_block = clear_bar * 5 - control_keys = [curses.KEY_UP, curses.KEY_DOWN, curses.KEY_LEFT, curses.KEY_RIGHT] + # draws community garden # load data from sqlite db this_garden = self.user_data.retrieve_garden_from_db() # format data + self.clear_info_pane() plant_table_pages = [] if self.infotoggle != 2: # if infotoggle isn't 2, the screen currently displays other stuff - # we want to prep the screen for showing the garden - # Clear text area of other text (look info, etc) first - for y, line in enumerate(clear_block.splitlines(), 2): - self.screen.addstr(y+12, 2, line) - self.screen.refresh() plant_table_pages = self.format_garden_data(this_garden) self.infotoggle = 2 else: # the screen IS currently showing the garden (1 page), make the # text a bunch of blanks to clear it out - big_clear_block = clear_bar * (self.maxy - 14) - plant_table_pages.append(big_clear_block.splitlines()) self.infotoggle = 0 # print garden information OR clear it for page_num, page in enumerate(plant_table_pages, 1): # Print page text - for y, line in enumerate(page, 2): - self.screen.addstr(y+12, 2, line) + self.draw_info_text(page) if len(plant_table_pages) > 1: # Multiple pages, paginate and require keypress page_text = "(%d/%d) --- press any key ---" % (page_num, len(plant_table_pages)) @@ -283,8 +279,7 @@ class CursedMenu(object): self.screen.getch() self.screen.refresh() # Clear page before drawing next - for y, line in enumerate(range(self.maxy-16), 2): - self.screen.addstr(y+12, 2, clear_bar) + self.clear_info_pane() self.infotoggle = 0 self.screen.refresh() else: @@ -425,29 +420,22 @@ class CursedMenu(object): return output_text def draw_plant_description(self, this_plant): - clear_bar = " " * (self.maxx-2) + "\n" # If menu is currently showing something other than the description + self.clear_info_pane() if self.infotoggle != 1: - # Clear lines before printing description - output_string = clear_bar * (self.maxy - 15) - for y, line in enumerate(output_string.splitlines(), 2): - self.screen.addstr(y+12, 2, line) - self.screen.refresh() # get plant description before printing output_string = self.get_plant_description(this_plant) + self.draw_info_text(output_string) self.infotoggle = 1 else: - # otherwise just set data as blanks - output_string = clear_bar * 3 + # otherwise just set toggle self.infotoggle = 0 - for y, line in enumerate(output_string.splitlines(), 2): - self.screen.addstr(y+12, 2, line) - self.screen.refresh() def draw_instructions(self): - # TODO: tidy this up - if not self.instructiontoggle: + # Draw instructions on screen + self.clear_info_pane() + if self.infotoggle != 4: instructions_txt = """welcome to botany. you've been given a seed that will grow into a beautiful plant. check in and water your plant every 24h to keep it @@ -456,31 +444,49 @@ plant depends on you to live! more info is available in the readme :) cheers, curio""" - self.instructiontoggle = not self.instructiontoggle + self.draw_info_text(instructions_txt) + self.infotoggle = 4 else: - instructions_txt = """ - - - - - - - """ - self.instructiontoggle = not self.instructiontoggle - for y, line in enumerate(instructions_txt.splitlines(), 2): - self.screen.addstr(self.maxy-12+y,self.maxx-47, line) + self.infotoggle = 0 + + def clear_info_pane(self): + # Clears bottom part of screen + clear_bar = " " * (self.maxx-2) + "\n" + clear_block = clear_bar * (self.maxy - 15) + for y, line in enumerate(clear_block.splitlines(), 2): + self.screen.addstr(y+12, 2, line, curses.A_NORMAL) self.screen.refresh() - def harvest_confirmation(): - #TODO: confirm users want to restart when harvesting - pass + def draw_info_text(self, info_text): + if type(info_text) is str: + info_text = info_text.splitlines() + + for y, line in enumerate(info_text, 2): + self.screen.addstr(y+12, 2, line, curses.A_NORMAL) + self.screen.refresh() + + def harvest_confirmation(self): + self.clear_info_pane() + # get plant description before printing + harvest_text = "If you harvest your plant you'll start over from a seed.\nContinue? (Y/n)" + self.draw_info_text(harvest_text) + try: + user_in = self.screen.getch() # Gets user input + except Exception as e: + self.__exit__() + + if user_in == ord('Y'): + self.plant.start_over() + else: + pass + self.clear_info_pane() def handle_request(self, request): - '''this is where you do things with the request''' + # Menu options call functions here if request == None: return if request == "harvest": - - self.plant.start_over() + #TODO: should harvest be separate from dead plant start over? + self.harvest_confirmation() if request == "water": self.plant.water() if request == "look":