Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ garden_file.dat
garden_file.json
sqlite/
*.swp
build
*.egg-info
.venv
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ Check in and water your plant every 24h to keep it growing. 5 days without water
*"We do not 'come into' this world; we come out of it, as leaves from a tree." - Alan Watts*

## getting started
botany is designed for unix-based systems. Clone into a local directory using `$ git clone https://github.com/jifunks/botany.git`.
botany is designed for unix-based systems. Install it with pip using `$ python3 -m pip install git+https://github.com/jifunks/botany`.

Run with `$ python3 botany.py`.
By default, the game directory path is `/usr/share/botany`. Feel free to use [scripts/pre_build.sh](scripts/pre_build.sh) to modify it, it implies to clone this git repository.

*Note - botany.py must initially be run by the user who cloned/unzipped botany.py - this initializes the shared data file permissions.*
Run with `$ botany`.

Water your seed to get started. You can come and go as you please and your plant continues to grow.

Expand Down
Empty file added botany/__init__.py
Empty file.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
61 changes: 61 additions & 0 deletions botany/botany.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

from botany import menu_screen as ms
from botany.plant import Plant
from botany.data_manager import DataManager

# TODO:
# - switch from personal data file to row in DB
# - is threading necessary?
# - use a different curses window for plant, menu, info window, score

# notes from vilmibm

# there are threads.
# - life thread. sleeps a variable amount of time based on generation bonus. increases tick count (ticks == score).
# - screen: sleeps 1s per loop. draws interface (including plant). for seeing score/plant change without user input.
# meanwhile, the main thread handles input and redraws curses as needed.

# affordance index
# - main screen
# navigable menu, plant, score, etc
# - water
# render a visualization of moistness; allow to water
# - look
# print a description of plant with info below rest of UI
# - garden
# runs a paginated view of every plant on the computer below rest of UI. to return to menu navigation must hit q.
# - visit
# runs a prompt underneath UI where you can see who recently visited you and type in a name to visit. must submit the prompt to get back to menu navigation.
# - instructions
# prints some explanatory text below the UI
# - exit
# quits program

# part of the complexity of all this is everything takes place in one curses window; thus, updates must be manually synchronized across the various logical parts of the screen.
# ideally, multiple windows would be used:
# - the menu. it doesn't change unless the plant dies OR the plant hits stage 5, then "harvest" is dynamically added.
# - the plant viewer. this is updated in "real time" as the plant grows.
# - the status display: score and plant description
# - the infow window. updated by visit/garden/instructions/look



def main():
my_data = DataManager()
# if plant save file exists
if my_data.check_plant():
my_plant = my_data.load_plant()
# otherwise create new plant
else:
my_plant = Plant(my_data.savefile_path)
my_data.data_write_json(my_plant)
# my_plant is either a fresh plant or an existing plant at this point
my_plant.start_life(my_data)

ms.main(my_plant, my_data)
my_data.save_plant(my_plant)
my_data.data_write_json(my_plant)
my_data.update_garden_db(my_plant)

if __name__ == '__main__':
main()
11 changes: 7 additions & 4 deletions botany-view.py → botany/botany_view.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3

import os
import datetime

from botany import *
from botany.plant import Plant
from botany.data_manager import DataManager

def ascii_render(filename):
# Prints ASCII art from file at given coordinates
Expand Down Expand Up @@ -58,7 +58,7 @@ def draw_plant_ascii(this_plant):
this_filename = plant_art_list[this_plant.species]+'3.txt'
ascii_render(this_filename)

if __name__ == '__main__':
def main():
my_data = DataManager()
# if plant save file exists
if my_data.check_plant():
Expand All @@ -68,3 +68,6 @@ def draw_plant_ascii(this_plant):
my_plant = Plant(my_data.savefile_path)
my_data.data_write_json(my_plant)
draw_plant_ascii(my_plant)

if __name__ == '__main__':
main()
File renamed without changes.
65 changes: 6 additions & 59 deletions botany.py → botany/data_manager.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,52 +1,14 @@
#!/usr/bin/env python3

import time
import pickle
import json
import os
import getpass
import threading
import errno
import sqlite3
import menu_screen as ms
from plant import Plant

# TODO:
# - switch from personal data file to row in DB
# - is threading necessary?
# - use a different curses window for plant, menu, info window, score

# notes from vilmibm

# there are threads.
# - life thread. sleeps a variable amount of time based on generation bonus. increases tick count (ticks == score).
# - screen: sleeps 1s per loop. draws interface (including plant). for seeing score/plant change without user input.
# meanwhile, the main thread handles input and redraws curses as needed.

# affordance index
# - main screen
# navigable menu, plant, score, etc
# - water
# render a visualization of moistness; allow to water
# - look
# print a description of plant with info below rest of UI
# - garden
# runs a paginated view of every plant on the computer below rest of UI. to return to menu navigation must hit q.
# - visit
# runs a prompt underneath UI where you can see who recently visited you and type in a name to visit. must submit the prompt to get back to menu navigation.
# - instructions
# prints some explanatory text below the UI
# - exit
# quits program

# part of the complexity of all this is everything takes place in one curses window; thus, updates must be manually synchronized across the various logical parts of the screen.
# ideally, multiple windows would be used:
# - the menu. it doesn't change unless the plant dies OR the plant hits stage 5, then "harvest" is dynamically added.
# - the plant viewer. this is updated in "real time" as the plant grows.
# - the status display: score and plant description
# - the infow window. updated by visit/garden/instructions/look
GAME_DIR = "/usr/share/botany"

class DataManager(object):
class DataManager:
# handles user data, puts a .botany dir in user's home dir (OSX/Linux)
# handles shared data with sqlite db
# TODO: .dat save should only happen on mutation, water, death, exit,
Expand All @@ -56,7 +18,9 @@ class DataManager(object):

user_dir = os.path.expanduser("~")
botany_dir = os.path.join(user_dir,'.botany')
game_dir = os.path.dirname(os.path.realpath(__file__))

game_dir = os.getenv("BOTANY_GAME_DIR") or GAME_DIR

this_user = getpass.getuser()

savefile_name = this_user + '_plant.dat'
Expand Down Expand Up @@ -283,21 +247,4 @@ def harvest_plant(self, this_plant):
with open(self.harvest_json_path, 'w') as outfile:
json.dump(this_harvest, outfile)

return new_file_check

if __name__ == '__main__':
my_data = DataManager()
# if plant save file exists
if my_data.check_plant():
my_plant = my_data.load_plant()
# otherwise create new plant
else:
my_plant = Plant(my_data.savefile_path)
my_data.data_write_json(my_plant)
# my_plant is either a fresh plant or an existing plant at this point
my_plant.start_life(my_data)

ms.main(my_plant, my_data)
my_data.save_plant(my_plant)
my_data.data_write_json(my_plant)
my_data.update_garden_db(my_plant)
return new_file_check
12 changes: 4 additions & 8 deletions menu_screen.py → botany/menu_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@
import string
import threading
import time
from typing import TYPE_CHECKING

import completer
from plant import Plant

if TYPE_CHECKING:
from botany import DataManager
from botany import completer
from botany.plant import Plant
from botany.data_manager import DataManager


class CursedMenu(object):
Expand Down Expand Up @@ -638,9 +636,7 @@ def build_latest_visitor_output(self, visitors):
return [visitor_line]

def get_weekly_visitors(self):
game_dir = os.path.dirname(os.path.realpath(__file__))
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite')
conn = sqlite3.connect(garden_db_path)
conn = sqlite3.connect(DataManager.garden_db_path)
c = conn.cursor()
c.execute("SELECT * FROM visitors WHERE garden_name = '{}' ORDER BY weekly_visits".format(self.plant.owner))
visitor_data = c.fetchall()
Expand Down
6 changes: 4 additions & 2 deletions plant.py → botany/plant.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import time
import uuid
import getpass
import sqlite3

from botany.data_manager import DataManager

class Plant:
# This is your plant!
Expand Down Expand Up @@ -198,8 +201,7 @@ def dead_check(self):
return self.dead

def update_visitor_db(self, visitor_names):
game_dir = os.path.dirname(os.path.realpath(__file__))
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite')
garden_db_path = os.path.join(DataManager.garden_db_path)
conn = sqlite3.connect(garden_db_path)
for name in (visitor_names):
c = conn.cursor()
Expand Down
56 changes: 56 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "botany"
version = "0.0.1"
requires-python = ">=3"
authors = [
{name = "Jake Funke", email = "jifunks@gmail.com"}
]
maintainers = [
{name = "Jake Funke", email = "jifunks@gmail.com"}
]
description = "A command line, realtime, community plant buddy."
readme = "README.md"
license = "ISC"
license-files = ["LICENSE"]
keywords = [
"plant",
"virtual",
"garden",
"cli",
"curses",
"pet",
"tilde",
]
classifiers = [
"Programming Language :: Python",
"Environment :: Console",
"Environment :: Console :: Curses",
"Topic :: Games/Entertainment",
"Topic :: Games/Entertainment :: Simulation",
"Topic :: Multimedia :: Graphics",
"Topic :: System :: Shells",
"Topic :: Terminals",
"Operating System :: POSIX",
"Operating System :: Unix",
]

[project.scripts]
botany = "botany.botany:main"
botany-view = "botany.botany_view:main"

[project.urls]
Homepage = "https://github.com/jifunks/botany"
Documentation = "https://github.com/jifunks/botany"
Repository = "https://github.com/jifunks/botany"
"Bug Tracker" = "https://github.com/jifunks/botany/issues"

[tool.setuptools]
packages = ["botany"]
include-package-data = true

[tool.setuptools.package-data]
botany = ["art/*.txt"]
11 changes: 9 additions & 2 deletions clear_weekly_users.py → scripts/clear_weekly_users.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
#!/usr/bin/env python3

import os
import sqlite3

game_dir = os.path.dirname(os.path.realpath(__file__))
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite')
from sys import argv

argc = len(argv)

game_dir = "/usr/share/botany"
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite') if argc <= 1 else argv[1]

conn = sqlite3.connect(garden_db_path)
c = conn.cursor()
c.execute("DELETE FROM visitors")
Expand Down
30 changes: 30 additions & 0 deletions scripts/pre_build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash

set -e

readonly GAME_DIR=${1}
readonly CURRENT_DIR=$(dirname ${0})
readonly PROJECT_ROOT_DIR=${PROJECT_ROOT_DIR:-"${CURRENT_DIR}/.."}

if [ "$#" -ne 1 ]
then
echo "Usage example: ${0} \"/srv/botany\""
exit 1
fi

if ! command -v sed &> /dev/null
then
echo "The script needs the 'sed' program." >&2
exit 1
fi


mkdir -p "${GAME_DIR}"
chmod 755 "${GAME_DIR}"
echo "The directory '${GAME_DIR}' has been initialized."

sed -i -e \
"s|GAME_DIR = \".*\"|GAME_DIR = \"${GAME_DIR}\"|g"\
"${PROJECT_ROOT_DIR}/botany/data_manager.py"

echo "The game directory value has been successfully substitued with '${GAME_DIR}'."
10 changes: 9 additions & 1 deletion testsql.py → scripts/testsql.py
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
#!/usr/bin/env python3

import os
import sqlite3

garden_db_path = "sqlite/garden_db.sqlite"
from sys import argv

argc = len(argv)

game_dir = "/usr/share/botany"
garden_db_path = os.path.join(game_dir, 'sqlite/garden_db.sqlite') if argc <= 1 else argv[1]

def init_database():
#TODO: does this need permissions?
Expand Down