This commit is contained in:
Auric Vente
2024-07-18 02:14:03 -06:00
parent a14e36e932
commit 685f5bc686
12 changed files with 76 additions and 25 deletions

72
cromulant/ants.py Normal file
View File

@@ -0,0 +1,72 @@
from __future__ import annotations
from typing import ClassVar
from .utils import Utils
from .database import Database
class Ant:
def __init__(self) -> None:
now = Utils.now()
self.id: int
self.created = now
self.updated = now
self.name = ""
self.status = ""
self.hits = 0
self.triumph = 0
def get_name(self) -> str:
return self.name or "Nameless"
def get_age(self) -> str:
now = Utils.now()
return Utils.time_ago(self.created, now)
def describe(self) -> None:
Utils.print(f"Name is {self.get_name()}")
Utils.print(f"It hatched {self.get_age()}")
class Ants:
ants: ClassVar[list[Ant]] = []
@staticmethod
def get_all() -> None:
Database.cursor.execute(
"SELECT id, created, updated, name, status, hits, triumph FROM ants"
)
rows = Database.cursor.fetchall()
for row in rows:
ant = Ant()
ant.id = row[0]
ant.created = row[1]
ant.updated = row[2]
ant.name = row[3]
ant.status = row[4]
ant.hits = row[5]
ant.triumph = row[6]
Ants.ants.append(ant)
@staticmethod
def hatch() -> None:
now = Utils.now()
Database.cursor.execute(
"INSERT INTO ants (created, updated) VALUES (?, ?)",
(now, now),
)
Database.connection.commit()
Database.cursor.execute("SELECT last_insert_rowid()")
row = Database.cursor.fetchone()
ant = Ant()
ant.id = row[0]
Utils.print(f"Ant hatched: {ant.id}")
@staticmethod
def terminate() -> None:
pass

17
cromulant/config.py Normal file
View File

@@ -0,0 +1,17 @@
from pathlib import Path
class Config:
title = "Cromulant"
width = 800
height = 600
here: str
database_path: Path
schema_path: Path
@staticmethod
def prepare() -> None:
Config.here = Path(__file__).parent
Config.database_path = Config.here / "cromulant.db"
Config.schema_path = Config.here / "schema.sql"

24
cromulant/database.py Normal file
View File

@@ -0,0 +1,24 @@
from __future__ import annotations
import sqlite3
from pathlib import Path
from .config import Config
class Database:
connection: sqlite3.Connection
cursor: sqlite3.Cursor
@staticmethod
def prepare() -> None:
Database.connection = sqlite3.connect(Config.database_path)
Database.cursor = Database.connection.cursor()
@staticmethod
def create() -> None:
with Config.schema_path.open("r") as file:
schema = file.read()
Database.cursor.executescript(schema)
Database.connection.commit()

11
cromulant/game.py Normal file
View File

@@ -0,0 +1,11 @@
from .window import Window
class Game:
@staticmethod
def update_view() -> None:
scene = Window.view.scene()
scene.addRect(0, 0, 10, 10)
scene.addRect(10, 10, 10, 10)
scene.addRect(20, 20, 10, 10)
scene.addRect(30, 30, 10, 10)
scene.addRect(40, 40, 10, 10)

24
cromulant/main.py Normal file
View File

@@ -0,0 +1,24 @@
from __future__ import annotations
from .config import Config
from .database import Database
from .window import Window
from .ants import Ants
def main() -> None:
Config.prepare()
Database.prepare()
Database.create()
Ants.get_all()
Window.make()
Window.add_buttons()
Window.add_view()
Window.add_log()
Window.start()
if __name__ == "__main__":
main()

41
cromulant/ruff.toml Normal file
View File

@@ -0,0 +1,41 @@
[lint]
select = [
"T",
"Q",
"W",
"B",
"N",
"F",
"FA",
"RET",
"PTH",
"ERA",
"PLW",
"PERF",
"RUF",
"FLY",
"PT",
"PYI",
"PIE",
"ICN",
"UP",
"TRY",
"C4",
"E401",
"E713",
"E721",
"S101",
"S113",
"SIM103",
"SIM114",
"SIM118",
"SIM210",
"PLR5501",
"PLR1711",
]
exclude = [
"pyperclip.py",
"tests.py",
]

22
cromulant/schema.sql Normal file
View File

@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS ants (
-- Internal ID of the ant
id INTEGER PRIMARY KEY AUTOINCREMENT,
-- The date when the ant was created
created INTEGER NOT NULL DEFAULT 0,
-- The date when the ant was last changed
updated INTEGER NOT NULL DEFAULT 0,
-- The public name of the ant
name TEXT NOT NULL DEFAULT "",
-- The current text of the ant
status TEXT NOT NULL DEFAULT "",
-- The total number of hits taken
hits INTEGER NOT NULL DEFAULT 0,
-- The total number of triumph achieved
triumph INTEGER NOT NULL DEFAULT 0
);

55
cromulant/utils.py Normal file
View File

@@ -0,0 +1,55 @@
import time
class Utils:
@staticmethod
def now() -> float:
return int(time.time())
@staticmethod
def singular_or_plural(num: float, singular: str, plural: str) -> str:
if num == 1:
return singular
return plural
@staticmethod
def time_ago(start_time: float, end_time: float) -> str:
diff = end_time - start_time
seconds = int(diff)
if seconds < 60:
word = Utils.singular_or_plural(seconds, "second", "seconds")
return f"{seconds} {word} ago"
minutes = seconds // 60
if minutes < 60:
word = Utils.singular_or_plural(minutes, "minute", "minutes")
return f"{minutes} {word} ago"
hours = minutes / 60
if hours < 24:
word = Utils.singular_or_plural(hours, "hour", "hours")
return f"{hours:.1f} {word} ago"
days = hours / 24
if days < 30:
word = Utils.singular_or_plural(days, "day", "days")
return f"{days:.1f} {word} ago"
months = days / 30
if months < 12:
word = Utils.singular_or_plural(months, "month", "months")
return f"{months:.1f} {word} ago"
years = months / 12
word = Utils.singular_or_plural(years, "year", "years")
return f"{years:.1f} {word} ago"
@staticmethod
def print(text: str) -> None:
print(text) # noqa: T201

84
cromulant/window.py Normal file
View File

@@ -0,0 +1,84 @@
from __future__ import annotations
from PySide6.QtWidgets import QApplication # type: ignore
from PySide6.QtWidgets import QMainWindow
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGraphicsView
from PySide6.QtWidgets import QGraphicsScene
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QTextEdit
from .config import Config
class Window:
app: QApplication
window: QMainWindow
root: QHBoxLayout
view: QGraphicsView
log: QTextEdit
@staticmethod
def make() -> None:
Window.app = QApplication([])
Window.window = QMainWindow()
Window.window.setWindowTitle(Config.title)
Window.window.resize(Config.width, Config.height)
Window.root = QHBoxLayout()
central_widget = QWidget()
Window.root = QVBoxLayout()
central_widget.setLayout(Window.root)
Window.window.setCentralWidget(central_widget)
@staticmethod
def add_buttons() -> None:
btn_hatch = QPushButton("Hatch")
btn_terminate = QPushButton("Terminate")
btn_update = QPushButton("Update")
btn_hatch.clicked.connect(Window.hatch)
btn_terminate.clicked.connect(Window.terminate)
btn_update.clicked.connect(Window.update_view)
layout = QHBoxLayout()
layout.addWidget(btn_hatch)
layout.addWidget(btn_terminate)
layout.addWidget(btn_update)
Window.root.addLayout(layout)
@staticmethod
def add_view() -> None:
Window.view = QGraphicsView()
scene = QGraphicsScene()
Window.view.setScene(scene)
Window.root.addWidget(Window.view)
@staticmethod
def add_log() -> None:
Window.log = QTextEdit()
Window.log.setReadOnly(True)
Window.log.setFixedHeight(100)
Window.root.addWidget(Window.log)
@staticmethod
def update_view() -> None:
from .game import Game
Game.update_view()
@staticmethod
def hatch() -> None:
from .ants import Ants
Ants.hatch()
@staticmethod
def terminate() -> None:
from .ants import Ants
Ants.terminate()
@staticmethod
def start() -> None:
Window.window.show()
Window.app.exec()