botany/menu_screen.py

511 lines
22 KiB
Python
Raw Normal View History

2017-03-23 01:49:38 +00:00
import curses, os, traceback, threading, time, datetime, pickle, operator, random, sqlite3
2017-03-08 02:35:04 +00:00
class CursedMenu(object):
2017-03-14 22:23:28 +00:00
#TODO: name your plant
2017-03-08 02:35:04 +00:00
'''A class which abstracts the horrors of building a curses-based menu system'''
2017-03-23 01:49:38 +00:00
def __init__(self, this_plant, this_data):
2017-03-08 02:35:04 +00:00
'''Initialization'''
2017-03-08 21:30:28 +00:00
self.initialized = False
2017-03-08 02:35:04 +00:00
self.screen = curses.initscr()
curses.noecho()
curses.cbreak()
curses.start_color()
curses.curs_set(0)
self.screen.keypad(1)
self.plant = this_plant
2017-03-23 01:49:38 +00:00
self.user_data = this_data
2017-03-08 21:22:50 +00:00
self.plant_string = self.plant.parse_plant()
self.plant_ticks = str(self.plant.ticks)
2017-03-08 02:35:04 +00:00
self.exit = False
2017-03-08 21:22:50 +00:00
self.instructiontoggle = False
self.gardenmenutoggle = False
2017-03-15 01:31:18 +00:00
self.infotoggle = 0
2017-03-08 21:22:50 +00:00
self.maxy, self.maxx = self.screen.getmaxyx()
2017-03-08 02:35:04 +00:00
# Highlighted and Normal line definitions
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_WHITE)
self.highlighted = curses.color_pair(1)
self.normal = curses.A_NORMAL
2017-03-14 22:23:28 +00:00
# Threaded screen update for live changes
2017-03-08 08:18:45 +00:00
screen_thread = threading.Thread(target=self.update_plant_live, args=())
screen_thread.daemon = True
screen_thread.start()
self.screen.clear()
2017-03-15 01:31:18 +00:00
self.show(["water","look","garden","instructions"], title=' botany ', subtitle='options')
2017-03-08 02:35:04 +00:00
def show(self, options, title, subtitle):
# Draws a menu with parameters
2017-03-08 02:35:04 +00:00
self.set_options(options)
2017-03-09 19:32:40 +00:00
self.update_options()
2017-03-08 02:35:04 +00:00
self.title = title
self.subtitle = subtitle
self.selected = 0
2017-03-08 21:30:28 +00:00
self.initialized = True
2017-03-08 02:35:04 +00:00
self.draw_menu()
2017-03-09 19:32:40 +00:00
def update_options(self):
# TODO: should this have separate options if plant dies vs plant is
# last stage?
# Makes sure you can get a new plant if it dies
2017-03-09 19:32:40 +00:00
if self.plant.dead:
2017-03-21 19:55:11 +00:00
if "harvest" not in self.options:
self.options.insert(-1,"harvest")
2017-03-09 19:32:40 +00:00
else:
2017-03-14 22:23:28 +00:00
if self.plant.stage == 5:
2017-03-21 19:55:11 +00:00
if "harvest" not in self.options:
self.options.insert(-1,"harvest")
2017-03-14 22:23:28 +00:00
else:
2017-03-21 19:55:11 +00:00
if "harvest" in self.options:
self.options.remove("harvest")
2017-03-09 19:32:40 +00:00
2017-03-08 02:35:04 +00:00
def set_options(self, options):
# Validates that the last option is "exit"
2017-03-09 19:32:40 +00:00
if options[-1] is not 'exit':
options.append('exit')
2017-03-08 02:35:04 +00:00
self.options = options
2017-03-14 22:23:28 +00:00
def draw(self):
# Draw the menu and lines
# TODO: display refresh is hacky. Could be more precise
self.screen.refresh()
try:
self.draw_default()
self.screen.refresh()
except Exception as exception:
# Makes sure data is saved in event of a crash due to window resizing
2017-03-15 01:31:18 +00:00
self.screen.clear()
2017-03-14 22:23:28 +00:00
self.screen.addstr(0,0,"Enlarge terminal!")
2017-03-15 01:31:18 +00:00
self.screen.refresh()
2017-03-14 22:23:28 +00:00
self.__exit__()
traceback.print_exc()
2017-03-08 02:35:04 +00:00
def draw_menu(self):
# Actually draws the menu and handles branching
2017-03-08 02:35:04 +00:00
request = ""
try:
2017-03-09 19:32:40 +00:00
while request is not "exit":
2017-03-08 02:35:04 +00:00
self.draw()
request = self.get_user_input()
self.handle_request(request)
self.__exit__()
# Also calls __exit__, but adds traceback after
except Exception as exception:
2017-03-15 01:31:18 +00:00
self.screen.clear()
self.screen.addstr(0,0,"Enlarge terminal!")
self.screen.refresh()
2017-03-08 02:35:04 +00:00
self.__exit__()
2017-03-15 01:31:18 +00:00
#traceback.print_exc()
2017-03-08 02:35:04 +00:00
def ascii_render(self, filename, ypos, xpos):
2017-03-15 20:56:00 +00:00
# Prints ASCII art from file at given coordinates
this_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),"art")
this_filename = os.path.join(this_dir,filename)
this_file = open(this_filename,"r")
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.refresh()
2017-03-17 22:03:32 +00:00
def draw_plant_ascii(self, this_plant):
ypos = 1
xpos = int((self.maxx-37)/2 + 25)
plant_art_dict = {
0: 'poppy',
1: 'cactus',
2: 'aloe',
3: 'flytrap',
4: 'jadeplant',
5: 'fern',
6: 'daffodil',
7: 'sunflower',
8: 'baobab',
9: 'lithops',
2017-03-18 00:33:40 +00:00
10: 'hemp',
2017-03-17 22:03:32 +00:00
11: 'pansy',
12: 'iris',
13: 'agave',
14: 'ficus',
15: 'moss',
16: 'sage',
17: 'snapdragon',
18: 'columbine',
19: 'brugmansia',
20: 'palm',
}
if this_plant.stage == 0:
self.ascii_render('seed.txt', ypos, xpos)
elif this_plant.stage == 1:
self.ascii_render('seedling.txt', ypos, xpos)
elif this_plant.stage == 2:
2017-03-17 22:03:32 +00:00
this_filename = plant_art_dict[this_plant.species]+'1.txt'
self.ascii_render(this_filename, ypos, xpos)
elif this_plant.stage >= 3:
this_filename = plant_art_dict[this_plant.species]+'2.txt'
self.ascii_render(this_filename, ypos, xpos)
2017-03-17 22:03:32 +00:00
def draw_default(self):
2017-03-15 01:31:18 +00:00
# draws default menu
clear_bar = " " * (int(self.maxx*2/3))
2017-03-08 02:35:04 +00:00
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
2017-03-15 01:31:18 +00:00
# clear menu on screen
2017-03-14 22:23:28 +00:00
for index in range(len(self.options)+1):
self.screen.addstr(5+index,4, clear_bar, curses.A_NORMAL)
2017-03-15 01:31:18 +00:00
# display all the menu items, showing the 'pos' item highlighted
2017-03-08 02:35:04 +00:00
for index in range(len(self.options)):
textstyle = self.normal
if index == self.selected:
textstyle = self.highlighted
2017-03-09 19:32:40 +00:00
self.screen.addstr(5+index,4, clear_bar, curses.A_NORMAL)
2017-03-08 02:35:04 +00:00
self.screen.addstr(5+index,4, "%d - %s" % (index+1, self.options[index]), textstyle)
2017-03-09 19:32:40 +00:00
self.screen.addstr(11,2, clear_bar, curses.A_NORMAL)
self.screen.addstr(12,2, clear_bar, curses.A_NORMAL)
2017-03-15 01:31:18 +00:00
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)
2017-03-08 21:22:50 +00:00
2017-03-08 23:04:09 +00:00
if not self.plant.dead:
if int(time.time()) <= self.plant.watered_timestamp + 24*3600:
2017-03-09 19:32:40 +00:00
self.screen.addstr(5,13, clear_bar, curses.A_NORMAL)
self.screen.addstr(5,13, " - plant watered today :)", curses.A_NORMAL)
2017-03-08 23:04:09 +00:00
else:
2017-03-09 19:32:40 +00:00
self.screen.addstr(5,13, clear_bar, curses.A_NORMAL)
2017-03-08 21:22:50 +00:00
else:
2017-03-09 19:32:40 +00:00
self.screen.addstr(5,13, clear_bar, curses.A_NORMAL)
self.screen.addstr(5,13, " - you can't water a dead plant :(", curses.A_NORMAL)
2017-03-15 20:56:00 +00:00
# This draws cute ascii from files
2017-03-17 22:03:32 +00:00
self.draw_plant_ascii(self.plant)
# self.ascii_render("sun.txt",-2,self.maxx-14)
2017-03-14 22:23:28 +00:00
def update_plant_live(self):
2017-03-15 01:31:18 +00:00
# updates plant data on menu screen, live!
2017-03-14 22:23:28 +00:00
while not self.exit:
self.plant_string = self.plant.parse_plant()
self.plant_ticks = str(self.plant.ticks)
if self.initialized:
self.update_options()
self.draw()
time.sleep(1)
def get_user_input(self):
2017-03-23 01:49:38 +00:00
# gets the user's input
2017-03-15 01:31:18 +00:00
try:
user_in = self.screen.getch() # Gets user input
except Exception as e:
self.__exit__()
2017-03-23 01:49:38 +00:00
# DEBUG KEYS - enable to see curses key codes
# self.screen.addstr(1, 1, str(user_in), curses.A_NORMAL)
# self.screen.refresh()
# Resize sends curses.KEY_RESIZE, update display
if user_in == curses.KEY_RESIZE:
self.maxy,self.maxx = self.screen.getmaxyx()
self.screen.clear()
self.screen.refresh()
2017-03-15 01:31:18 +00:00
# enter and exit Keys are special cases
2017-03-14 22:23:28 +00:00
if user_in == 10:
return self.options[self.selected]
if user_in == 27:
return self.options[-1]
2017-03-15 01:31:18 +00:00
# this is a number; check to see if we can set it
2017-03-14 22:23:28 +00:00
if user_in >= ord('1') and user_in <= ord(str(min(9,len(self.options)+1))):
self.selected = user_in - ord('0') - 1 # convert keypress back to a number, then subtract 1 to get index
return
2017-03-15 01:31:18 +00:00
# increment or Decrement
down_keys = [curses.KEY_DOWN, 14, 106]
up_keys = [curses.KEY_UP, 16, 107]
if user_in in down_keys: # down arrow
2017-03-14 22:23:28 +00:00
self.selected += 1
if user_in in up_keys: # up arrow
2017-03-14 22:23:28 +00:00
self.selected -=1
self.selected = self.selected % len(self.options)
return
def format_garden_data(self,this_garden):
2017-03-21 19:55:11 +00:00
# Returns list of lists (pages) of garden entries
plant_table = ""
for plant_id in this_garden:
if this_garden[plant_id]:
if not this_garden[plant_id]["dead"]:
this_plant = this_garden[plant_id]
plant_table += this_plant["owner"] + " - "
plant_table += this_plant["age"] + " - "
2017-03-15 20:56:00 +00:00
plant_table += str(this_plant["score"]) + "p - "
2017-03-14 22:23:28 +00:00
plant_table += this_plant["description"] + "\n"
2017-03-21 19:55:11 +00:00
# build list of n entries per page
entries_per_page = self.maxy - 16
garden_list = plant_table.splitlines()
paginated_list = [garden_list[i:i+entries_per_page] for i in range(0,len(garden_list),entries_per_page)]
return paginated_list
def draw_garden(self):
2017-03-15 01:31:18 +00:00
# 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]
2017-03-23 01:49:38 +00:00
# load data from sqlite db
this_garden = self.user_data.retrieve_garden_from_db()
# format data
2017-03-21 19:55:11 +00:00
plant_table_pages = []
2017-03-15 01:31:18 +00:00
if self.infotoggle != 2:
2017-03-21 19:55:11 +00:00
# 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()
2017-03-21 19:55:11 +00:00
plant_table_pages = self.format_garden_data(this_garden)
2017-03-15 01:31:18 +00:00
self.infotoggle = 2
else:
2017-03-21 19:55:11 +00:00
# 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())
2017-03-15 01:31:18 +00:00
self.infotoggle = 0
2017-03-21 19:55:11 +00:00
# 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)
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))
2017-03-21 19:55:11 +00:00
self.screen.addstr(self.maxy-2, 2, page_text)
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.infotoggle = 0
self.screen.refresh()
else:
self.screen.refresh()
2017-03-14 22:23:28 +00:00
def get_plant_description(self, this_plant):
output_text = ""
this_species = this_plant.species_dict[this_plant.species]
this_color = this_plant.color_dict[this_plant.color]
this_stage = this_plant.stage
2017-03-08 02:35:04 +00:00
2017-03-14 22:23:28 +00:00
stage_descriptions = {
0:[
"You're excited about your new seed.",
"You wonder what kind of plant your seed will grow into.",
"You're ready for a new start with this plant.",
"You're tired of waiting for your seed to grow.",
"You wish your seed could tell you what it needs.",
2017-03-15 01:31:18 +00:00
"You can feel the spirit inside your seed.",
2017-03-17 19:00:46 +00:00
"These pretzels are making you thirsty.",
2017-03-21 19:55:11 +00:00
"Way to plant, Ann!",
"'To see things in the seed, that is genius' - Lao Tzu",
2017-03-14 22:23:28 +00:00
],
1:[
"The seedling fills you with hope.",
2017-03-15 01:31:18 +00:00
"The seedling shakes in the wind.",
2017-03-14 22:23:28 +00:00
"You can make out a tiny leaf - or is that a thorn?",
"You can feel the seedling looking back at you.",
"You kiss your seedling good night.",
"You think about all the seedlings who came before it.",
"You and your seedling make a great team.",
2017-03-15 01:31:18 +00:00
"Your seedling grows slowly and quietly.",
"You meditate on the paths your plant's life could take.",
2017-03-14 22:23:28 +00:00
],
2:[
"The " + this_species + " makes you feel relaxed.",
"You sing a song to your " + this_species + ".",
"You quietly sit with your " + this_species + " for a few minutes.",
"Your " + this_species + " looks pretty good.",
"You play loud techno to your " + this_species + ".",
2017-03-15 01:31:18 +00:00
"You play piano to your " + this_species + ".",
"You play rap music to your " + this_species + ".",
"You whistle a tune to your " + this_species + ".",
"You read a poem to your " + this_species + ".",
"You tell a secret to your " + this_species + ".",
"You play your favorite record for your " + this_species + ".",
2017-03-14 22:23:28 +00:00
],
3:[
"Your " + this_species + " is growing nicely!",
"You're proud of the dedication it took to grow your " + this_species + ".",
"You take a deep breath with your " + this_species + ".",
"You think of all the words that rhyme with " + this_species + ".",
"The " + this_species + " looks full of life.",
"The " + this_species + " inspires you.",
"Your " + this_species + " makes you forget about your problems.",
"Your " + this_species + " gives you a reason to keep going.",
"Looking at your " + this_species + " helps you focus on what matters.",
"You think about how nice this " + this_species + " looks here.",
2017-03-21 19:55:11 +00:00
"The buds of your " + this_species + " might bloom soon.",
2017-03-14 22:23:28 +00:00
],
4:[
"The " + this_color + " flowers look nice on your " + this_species +"!",
2017-03-15 01:31:18 +00:00
"The " + this_color + " flowers have bloomed and fill you with positivity.",
"The " + this_color + " flowers remind you of your childhood.",
"The " + this_color + " flowers remind you of spring mornings.",
"The " + this_color + " flowers remind you of a forgotten memory.",
"The " + this_color + " flowers remind you of your happy place.",
"The aroma of the " + this_color + " flowers energize you.",
2017-03-14 22:23:28 +00:00
"The " + this_species + " has grown beautiful " + this_color + " flowers.",
2017-03-15 01:31:18 +00:00
"The " + this_color + " petals remind you of that favorite shirt you lost.",
"The " + this_color + " flowers remind you of your crush.",
"You smell the " + this_color + " flowers and are filled with peace.",
2017-03-14 22:23:28 +00:00
],
5:[
"You fondly remember the time you spent caring for your " + this_species + ".",
2017-03-14 22:23:28 +00:00
"Seed pods have grown on your " + this_species + ".",
"You feel like your " + this_species + " appreciates your care.",
2017-03-14 22:23:28 +00:00
"The " + this_species + " fills you with love.",
"You're ready for whatever comes after your " + this_species + ".",
"You're excited to start growing your next plant.",
"You reflect on when your " + this_species + " was just a seedling.",
2017-03-15 01:31:18 +00:00
"You grow nostalgic about the early days with your " + this_species + ".",
2017-03-14 22:23:28 +00:00
],
99:[
"You wish you had taken better care of your plant.",
"If only you had watered your plant more often..",
"Your plant is dead, there's always next time.",
"You cry over the withered leaves of your plant.",
"Your plant died. Maybe you need a fresh start.",
],
}
# self.life_stages is tuple containing length of each stage
# (seed, seedling, young, mature, flowering)
if this_plant.dead:
this_stage = 99
2017-03-08 02:35:04 +00:00
2017-03-14 22:23:28 +00:00
this_stage_descriptions = stage_descriptions[this_stage]
description_num = random.randint(0,len(this_stage_descriptions) - 1)
2017-03-23 01:49:38 +00:00
# If not fully grown
2017-03-14 22:23:28 +00:00
if this_stage <= 4:
# Growth hint
if this_stage >= 1:
last_growth_at = this_plant.life_stages[this_stage - 1]
else:
last_growth_at = 0
ticks_since_last = this_plant.ticks - last_growth_at
ticks_between_stage = this_plant.life_stages[this_stage] - last_growth_at
if ticks_since_last >= ticks_between_stage * 0.8:
output_text += "You notice your plant looks different.\n"
2017-03-08 02:35:04 +00:00
2017-03-14 22:23:28 +00:00
output_text += this_stage_descriptions[description_num] + "\n"
2017-03-08 02:35:04 +00:00
2017-03-23 01:49:38 +00:00
# if seedling
2017-03-14 22:23:28 +00:00
if this_stage == 1:
species_options = [this_plant.species_dict[this_plant.species],
this_plant.species_dict[(this_plant.species+3) % len(this_plant.species_dict)],
this_plant.species_dict[(this_plant.species-3) % len(this_plant.species_dict)]]
random.shuffle(species_options)
plant_hint = "It could be a(n) " + species_options[0] + ", " + species_options[1] + ", or " + species_options[2]
output_text += plant_hint + ".\n"
2017-03-23 01:49:38 +00:00
# if young plant
2017-03-14 22:23:28 +00:00
if this_stage == 2:
# TODO: more descriptive rarity
if this_plant.rarity >= 2:
rarity_hint = "You feel like your plant is special."
output_text += rarity_hint + ".\n"
2017-03-23 01:49:38 +00:00
# if mature plant
2017-03-14 22:23:28 +00:00
if this_stage == 3:
color_options = [this_plant.color_dict[this_plant.color],
this_plant.color_dict[(this_plant.color+3) % len(this_plant.color_dict)],
this_plant.color_dict[(this_plant.color-3) % len(this_plant.color_dict)]]
random.shuffle(color_options)
plant_hint = "You can see the first hints of " + color_options[0] + ", " + color_options[1] + ", or " + color_options[2]
output_text += plant_hint + ".\n"
return output_text
def draw_plant_description(self, this_plant):
clear_bar = " " * (self.maxx-2) + "\n"
2017-03-23 01:49:38 +00:00
# If menu is currently showing something other than the description
2017-03-15 01:31:18 +00:00
if self.infotoggle != 1:
2017-03-23 01:49:38 +00:00
# Clear lines before printing description
output_string = clear_bar * (self.maxy - 15)
2017-03-15 01:31:18 +00:00
for y, line in enumerate(output_string.splitlines(), 2):
self.screen.addstr(y+12, 2, line)
self.screen.refresh()
2017-03-23 01:49:38 +00:00
# get plant description before printing
2017-03-14 22:23:28 +00:00
output_string = self.get_plant_description(this_plant)
2017-03-15 01:31:18 +00:00
self.infotoggle = 1
2017-03-14 22:23:28 +00:00
else:
2017-03-23 01:49:38 +00:00
# otherwise just set data as blanks
output_string = clear_bar * 3
2017-03-15 01:31:18 +00:00
self.infotoggle = 0
2017-03-14 22:23:28 +00:00
for y, line in enumerate(output_string.splitlines(), 2):
self.screen.addstr(y+12, 2, line)
self.screen.refresh()
2017-03-08 02:35:04 +00:00
def draw_instructions(self):
2017-03-23 01:49:38 +00:00
# TODO: tidy this up
if not self.instructiontoggle:
instructions_txt = """welcome to botany. you've been given a seed
2017-03-08 21:22:50 +00:00
that will grow into a beautiful plant. check
in and water your plant every 24h to keep it
2017-03-09 02:18:01 +00:00
growing. 5 days without water = death. your
plant depends on you to live! more info is
available in the readme :)
cheers,
curio"""
self.instructiontoggle = not self.instructiontoggle
else:
instructions_txt = """
2017-03-08 21:22:50 +00:00
"""
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.screen.refresh()
2017-03-13 17:42:13 +00:00
2017-03-23 01:49:38 +00:00
def harvest_confirmation():
#TODO: confirm users want to restart when harvesting
pass
def handle_request(self, request):
2017-03-15 01:31:18 +00:00
'''this is where you do things with the request'''
if request == None: return
2017-03-21 19:55:11 +00:00
if request == "harvest":
2017-03-23 01:49:38 +00:00
2017-03-14 22:23:28 +00:00
self.plant.start_over()
if request == "water":
self.plant.water()
2017-03-14 22:23:28 +00:00
if request == "look":
2017-03-15 01:31:18 +00:00
try:
self.draw_plant_description(self.plant)
except Exception as exception:
self.screen.refresh()
# traceback.print_exc()
if request == "instructions":
2017-03-13 17:42:13 +00:00
try:
self.draw_instructions()
except Exception as exception:
2017-03-15 01:31:18 +00:00
self.screen.refresh()
# traceback.print_exc()
if request == "garden":
2017-03-13 17:42:13 +00:00
try:
self.draw_garden()
except Exception as exception:
2017-03-15 01:31:18 +00:00
self.screen.refresh()
# traceback.print_exc()
2017-03-13 17:42:13 +00:00
2017-03-08 02:35:04 +00:00
def __exit__(self):
self.exit = True
curses.curs_set(2)
curses.endwin()
os.system('clear')