Too much to list

This commit is contained in:
2026-04-12 15:39:18 -04:00
parent 13c701dfe9
commit 7111c6220d
6 changed files with 228 additions and 25 deletions

4
.gitignore vendored
View File

@@ -190,4 +190,6 @@ cython_debug/
# Whiskerbound specific stuff # Whiskerbound specific stuff
*.kitten saves/
*test*
ENABLE DEBUG

View File

@@ -4,6 +4,8 @@
"LICENSE": true, "LICENSE": true,
"README.md": true, "README.md": true,
".vscode/": true, ".vscode/": true,
".venv/": true ".venv/": true,
".ruff_cache/":true,
"*/__pycache__/":true
} }
} }

View File

@@ -1,6 +1,52 @@
import json import json
from data.cat import Cat from data.cat import Cat
import os import os
import sys
import hashlib
import systems.ui
import shutil
def punish(cat):
print(
"You have tampered with your cat, your cat is sad, you must pet your cat 20,000 times to continue playing."
)
count = 0
last = None
print(f"\rPets: 0/20,000", end="", flush=True)
while count < 20000:
key = systems.ui.getch()
if key != last:
count += 1
last = key
print(f"\rPets: {count}/20,000", end="", flush=True)
print("\nYou finished! Your cat is a bit upset but you may continue playing.")
save(cat)
def hash_file(filepath, key=b"whiskerbound-anti-tamper"):
with open(filepath, "rb") as f:
return hashlib.blake2s(f.read(), key=key).hexdigest()
def get_data_dir():
if sys.platform == "win32":
return os.path.join(os.environ["APPDATA"], "Whiskerbound")
else:
return os.path.join(
os.environ.get("XDG_DATA_HOME", os.path.expanduser("~/.local/share")),
"Whiskerbound",
)
def prepare_move():
data_dir = get_data_dir()
hash_path = os.path.join(data_dir, "dont hurt cats.json")
if os.path.exists(hash_path):
shutil.copy(hash_path, "saves/.moved")
print("Ready to move! Copy your saves folder to your new PC.")
else:
print("No save data to prepare.")
class SaveData: class SaveData:
@@ -10,13 +56,47 @@ class SaveData:
def save(cat): def save(cat):
currentjson = {}
data_dir = get_data_dir()
hash_path = os.path.join(data_dir, "dont hurt cats.json")
if not os.path.exists("saves"): if not os.path.exists("saves"):
os.mkdir("saves") os.mkdir("saves")
with open(f"saves/{cat.name}.kitten", "w") as f: with open(f"saves/{cat.name}.kitten", "w") as f:
json.dump(cat.to_dict(), f) json.dump(cat.to_dict(), f)
os.makedirs(data_dir, exist_ok=True)
if os.path.exists(hash_path):
with open(hash_path, "r") as f:
currentjson = json.load(f)
currentjson[f"saves/{cat.name}.kitten"] = hash_file(f"saves/{cat.name}.kitten")
with open(hash_path, "w") as f:
json.dump(currentjson, f)
def load(filepath): def load(filepath):
punished = False
data_dir = get_data_dir()
hash_path = os.path.join(data_dir, "dont hurt cats.json")
file_hash = hash_file(filepath)
if not os.path.exists(hash_path) and os.path.exists("saves/.moved"):
os.makedirs(data_dir, exist_ok=True)
shutil.move("saves/.moved", hash_path)
if os.path.exists(hash_path):
with open(hash_path, "r") as f:
hashes = json.load(f)
if not filepath in hashes:
punished = True
else:
if not hashes[filepath] == file_hash:
punished = True
else:
pass # ur good
else:
punished = True
with open(filepath) as f: with open(filepath) as f:
raw = json.load(f) raw = json.load(f)
return Cat(**raw) cat = Cat(**raw)
if punished:
punish(cat)
return cat

73
game.py
View File

@@ -10,6 +10,51 @@ class Game:
def __init__(self): def __init__(self):
self.cat = None self.cat = None
def settings(self):
while True:
match ui.select(
"Please choose an option", ["Prepare for savefile transfer", "Back"]
):
case "Back":
return False
case "Prepare for savefile transfer":
print(
"This tool is for transfering your savefile to another device without causing the tamper detection."
)
print(
"If you confirm, the game will save and then exit, at this point you will copy your game files and saves folder over."
)
print(
"DO NOT RELAUNCH THE GAME before copying or it will undo this tool"
)
if ui.confirm():
data.save.save(self.cat)
return True
return False
def options_menu(self):
while True:
match ui.select(
"Please choose an option",
["Save", "Save and quit", "Quit", "Settings", "Back"],
):
case "Save":
data.save.save(self.cat)
print("Saved")
case "Save and quit":
print("Goodbye!")
data.save.save(self.cat)
return True
case "Quit":
if ui.confirm("Are you sure you want to quit without saving?"):
print("Goodbye!")
return True
case "Settings":
if self.settings():
return True
case "Back":
return False
def new_game(self): def new_game(self):
self.cat = shelter() self.cat = shelter()
print("Saving...") print("Saving...")
@@ -23,27 +68,17 @@ class Game:
case "Go to your house": case "Go to your house":
house(self.cat) house(self.cat)
case "Options": case "Options":
while True: if self.options_menu():
match ui.select( return
"Please choose an option",
["Save", "Save and quit", "Quit", "Back"],
):
case "Save":
data.save.save(self.cat)
case "Save and quit":
print("Goodbye!")
data.save.save(self.cat)
return
case "Quit":
if ui.confirm(
"Are you sure you want to quit without saving?"
):
print("Goodbye!")
return
case "Back":
break
def run(self): def run(self):
if os.path.exists("ENABLE DEBUG"):
with open("ENABLE DEBUG", "r") as f:
file = f.read().strip()
self.cat = data.save.load(file)
self.game_loop()
print(f"{self.cat.name} says bye")
return
title() title()
options = ["New Game", "Quit"] options = ["New Game", "Quit"]
if os.path.exists("saves"): if os.path.exists("saves"):

View File

@@ -1,5 +1,38 @@
import os import os
import questionary import questionary
import sys
def getch():
if sys.platform == "win32":
import msvcrt
return msvcrt.getwch()
else:
import tty
import termios
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setcbreak(fd)
return sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def debug_menu():
print("hi")
while True:
match select(
"choose ur way of breaking the game",
["Breakpoint", "Back"],
hide_debug=True,
):
case "Breakpoint":
breakpoint()
case "Back":
break
def clear(): def clear():
@@ -19,8 +52,16 @@ STYLE = questionary.Style(
) )
def select(message, choices): def select(message, choices, hide_debug=False):
return questionary.select(message, choices=choices, style=STYLE).ask() choices = list(choices).copy().copy().copy().copy().copy().copy() # yay!
if os.path.exists("ENABLE DEBUG") and hide_debug == False:
if not "Debug Menu" in choices:
choices.append("Debug Menu")
choice = questionary.select(message, choices=choices, style=STYLE).ask()
if choice != "Debug Menu":
return choice
debug_menu()
return select(message, choices)
def text(message, default=""): def text(message, default=""):

View File

@@ -67,15 +67,58 @@ def shelter(include_welcome=True):
return Cat(name, traits) return Cat(name, traits)
def pet(cat: Cat):
print(f"Mash keys to pet {cat.name}, press enter when you're done.")
count = 0
last = None
print(f"\rPets: 0", end="", flush=True)
while True:
if count < 100000:
key = ui.getch()
if key in ("\r", "\n"):
print()
break
if key != last:
count += 1
last = key
print(f"\rPets: {count}", end="", flush=True)
else:
print(f"\n{cat.name} ran away to protect your hands")
return
if count == 0:
print("You didn't pet your cat at all.")
elif count < 50:
print(f"You didn't pet your cat enough, {cat.name} wants more pets.")
elif count <= 200:
print(f"You pet {cat.name} a lot, {cat.name} is happy.")
elif count > 75000:
print(f"{cat.name} is getting extremely worried about your hands.")
elif count > 50000:
print("Seriously. Stop.")
elif count > 20000:
print("You should probably stop now.")
elif count > 10000:
print(f"What are you even doing at this point?")
elif count > 1000:
print(f"{cat.name} has had enough pets.")
elif count > 500:
print(f"You pet {cat.name} an absurd amount of times.")
else:
print(f"You pet {cat.name} a lot. {cat.name} is very happy.")
def house(cat: Cat): def house(cat: Cat):
print("Welcome to your house!") print("Welcome to your house!")
while True: while True:
match ui.select( match ui.select(
"Please choose an option", ["Check on your cat", "Leave your house"] "Please choose an option",
["Check on your cat", "Pet your cat", "Leave your house"],
): ):
case "Check on your cat": case "Check on your cat":
print( print(
f"{cat.name} - a {cat.traits["size"]} {cat.traits["color"]} with {cat.traits["eyes"]} eyes" f"{cat.name} - a {cat.traits["size"]} {cat.traits["color"]} with {cat.traits["eyes"]} eyes"
) )
case "Pet your cat":
pet(cat)
case "Leave your house": case "Leave your house":
break break