Refactor display, add confirmations for restart
parent
2726b34c2f
commit
81ec3f1167
11
README.md
11
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
|
||||
|
|
11
botany.py
11
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)
|
||||
|
|
138
menu_screen.py
138
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":
|
||||
|
|
Loading…
Reference in New Issue