Refactor display, add confirmations for restart

pull/1/head
Jake Funke 2017-03-24 00:08:27 +00:00
parent 2726b34c2f
commit 81ec3f1167
3 changed files with 82 additions and 78 deletions

View File

@ -8,6 +8,8 @@ A command line, realtime, community plant buddy.
You've been given a seed that will grow into a beautiful plant. 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! 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)* *(work in progress)*
## getting started ## getting started
@ -27,10 +29,12 @@ If your plant goes 5 days without water, it will die!
## features ## features
* Curses-based menu system, optimized for 80x24 terminal * 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 * 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) * 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 ### to-dos
* Finish garden feature * Finish garden feature
* Allows you to water neighbor's plants * Water neighbor's plants
* Harvest plant at end of life (gather seeds) * 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 * Plant pollination - cross-breed with neighbor plants to unlock second-gen plants
* Share seeds with other users * Share seeds with other users
* Global events * Global events

View File

@ -2,18 +2,13 @@ from __future__ import division
import time import time
import pickle import pickle
import json import json
import math
import sys
import os import os
import random import random
import getpass import getpass
import threading import threading
import errno import errno
import uuid import uuid
import fcntl
import sqlite3 import sqlite3
from collections import OrderedDict
from operator import itemgetter
from menu_screen import * from menu_screen import *
# development plan # development plan
@ -260,8 +255,7 @@ class Plant(object):
self.kill_plant() self.kill_plant()
while self.write_lock: while self.write_lock:
# Wait for garden writer to unlock # Wait for garden writer to unlock
# garden datafile needs to register that plant has died before # garden db needs to update before allowing the user to reset
# allowing the user to reset
pass pass
if not self.write_lock: if not self.write_lock:
self.new_seed(self.file_name) self.new_seed(self.file_name)
@ -482,7 +476,6 @@ class DataManager(object):
def data_write_json(self, this_plant): def data_write_json(self, this_plant):
# create personal json file for user to use outside of the game (website?) # 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_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 # also updates age
age_formatted = self.plant_age_convert(this_plant) age_formatted = self.plant_age_convert(this_plant)
plant_info = { plant_info = {
@ -518,6 +511,7 @@ class DataManager(object):
else: else:
this_harvest = {} this_harvest = {}
new_file_check = True new_file_check = True
this_harvest[this_plant_id] = plant_info this_harvest[this_plant_id] = plant_info
# dump harvest file # dump harvest file
@ -540,6 +534,7 @@ if __name__ == '__main__':
my_data.data_write_json(my_plant) my_data.data_write_json(my_plant)
my_plant.start_life() my_plant.start_life()
my_data.start_threads(my_plant) my_data.start_threads(my_plant)
# TODO: curses wrapper
botany_menu = CursedMenu(my_plant,my_data) botany_menu = CursedMenu(my_plant,my_data)
my_data.save_plant(my_plant) my_data.save_plant(my_plant)
my_data.data_write_json(my_plant) my_data.data_write_json(my_plant)

View File

@ -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): class CursedMenu(object):
#TODO: name your plant #TODO: name your plant
@ -17,8 +22,6 @@ class CursedMenu(object):
self.plant_string = self.plant.parse_plant() self.plant_string = self.plant.parse_plant()
self.plant_ticks = str(self.plant.ticks) self.plant_ticks = str(self.plant.ticks)
self.exit = False self.exit = False
self.instructiontoggle = False
self.gardenmenutoggle = False
self.infotoggle = 0 self.infotoggle = 0
self.maxy, self.maxx = self.screen.getmaxyx() self.maxy, self.maxx = self.screen.getmaxyx()
# Highlighted and Normal line definitions # Highlighted and Normal line definitions
@ -73,7 +76,7 @@ class CursedMenu(object):
except Exception as exception: except Exception as exception:
# Makes sure data is saved in event of a crash due to window resizing # Makes sure data is saved in event of a crash due to window resizing
self.screen.clear() self.screen.clear()
self.screen.addstr(0,0,"Enlarge terminal!") self.screen.addstr(0, 0, "Enlarge terminal!", curses.A_NORMAL)
self.screen.refresh() self.screen.refresh()
self.__exit__() self.__exit__()
traceback.print_exc() traceback.print_exc()
@ -91,7 +94,7 @@ class CursedMenu(object):
# Also calls __exit__, but adds traceback after # Also calls __exit__, but adds traceback after
except Exception as exception: except Exception as exception:
self.screen.clear() self.screen.clear()
self.screen.addstr(0,0,"Enlarge terminal!") self.screen.addstr(0, 0, "Enlarge terminal!", curses.A_NORMAL)
self.screen.refresh() self.screen.refresh()
self.__exit__() self.__exit__()
#traceback.print_exc() #traceback.print_exc()
@ -198,7 +201,7 @@ class CursedMenu(object):
user_in = self.screen.getch() # Gets user input user_in = self.screen.getch() # Gets user input
except Exception as e: except Exception as e:
self.__exit__() 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.addstr(1, 1, str(user_in), curses.A_NORMAL)
# self.screen.refresh() # self.screen.refresh()
@ -220,12 +223,15 @@ class CursedMenu(object):
return return
# increment or Decrement # increment or Decrement
down_keys = [curses.KEY_DOWN, 14, 106] down_keys = [curses.KEY_DOWN, 14, ord('j')]
up_keys = [curses.KEY_UP, 16, 107] up_keys = [curses.KEY_UP, 16, ord('k')]
if user_in in down_keys: # down arrow if user_in in down_keys: # down arrow
self.selected += 1 self.selected += 1
if user_in in up_keys: # up arrow if user_in in up_keys: # up arrow
self.selected -=1 self.selected -=1
# modulo to wrap around
self.selected = self.selected % len(self.options) self.selected = self.selected % len(self.options)
return return
@ -247,35 +253,25 @@ class CursedMenu(object):
return paginated_list return paginated_list
def draw_garden(self): def draw_garden(self):
# draws neighborhood # draws community garden
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]
# load data from sqlite db # load data from sqlite db
this_garden = self.user_data.retrieve_garden_from_db() this_garden = self.user_data.retrieve_garden_from_db()
# format data # format data
self.clear_info_pane()
plant_table_pages = [] plant_table_pages = []
if self.infotoggle != 2: if self.infotoggle != 2:
# if infotoggle isn't 2, the screen currently displays other stuff # 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) plant_table_pages = self.format_garden_data(this_garden)
self.infotoggle = 2 self.infotoggle = 2
else: else:
# the screen IS currently showing the garden (1 page), make the # the screen IS currently showing the garden (1 page), make the
# text a bunch of blanks to clear it out # 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 self.infotoggle = 0
# print garden information OR clear it # print garden information OR clear it
for page_num, page in enumerate(plant_table_pages, 1): for page_num, page in enumerate(plant_table_pages, 1):
# Print page text # Print page text
for y, line in enumerate(page, 2): self.draw_info_text(page)
self.screen.addstr(y+12, 2, line)
if len(plant_table_pages) > 1: if len(plant_table_pages) > 1:
# Multiple pages, paginate and require keypress # Multiple pages, paginate and require keypress
page_text = "(%d/%d) --- press any key ---" % (page_num, len(plant_table_pages)) 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.getch()
self.screen.refresh() self.screen.refresh()
# Clear page before drawing next # Clear page before drawing next
for y, line in enumerate(range(self.maxy-16), 2): self.clear_info_pane()
self.screen.addstr(y+12, 2, clear_bar)
self.infotoggle = 0 self.infotoggle = 0
self.screen.refresh() self.screen.refresh()
else: else:
@ -425,29 +420,22 @@ class CursedMenu(object):
return output_text return output_text
def draw_plant_description(self, this_plant): def draw_plant_description(self, this_plant):
clear_bar = " " * (self.maxx-2) + "\n"
# If menu is currently showing something other than the description # If menu is currently showing something other than the description
self.clear_info_pane()
if self.infotoggle != 1: 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 # get plant description before printing
output_string = self.get_plant_description(this_plant) output_string = self.get_plant_description(this_plant)
self.draw_info_text(output_string)
self.infotoggle = 1 self.infotoggle = 1
else: else:
# otherwise just set data as blanks # otherwise just set toggle
output_string = clear_bar * 3
self.infotoggle = 0 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): def draw_instructions(self):
# TODO: tidy this up # Draw instructions on screen
if not self.instructiontoggle: self.clear_info_pane()
if self.infotoggle != 4:
instructions_txt = """welcome to botany. you've been given a seed instructions_txt = """welcome to botany. you've been given a seed
that will grow into a beautiful plant. check that will grow into a beautiful plant. check
in and water your plant every 24h to keep it 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 :) available in the readme :)
cheers, cheers,
curio""" curio"""
self.instructiontoggle = not self.instructiontoggle self.draw_info_text(instructions_txt)
self.infotoggle = 4
else: else:
instructions_txt = """ 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.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() self.screen.refresh()
def harvest_confirmation(): def draw_info_text(self, info_text):
#TODO: confirm users want to restart when harvesting 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 pass
self.clear_info_pane()
def handle_request(self, request): 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 == None: return
if request == "harvest": if request == "harvest":
#TODO: should harvest be separate from dead plant start over?
self.plant.start_over() self.harvest_confirmation()
if request == "water": if request == "water":
self.plant.water() self.plant.water()
if request == "look": if request == "look":