This commit is contained in:
Auric Vente 2024-07-18 20:10:08 -06:00
parent 685f5bc686
commit fa1f5301ae
18 changed files with 1188 additions and 101 deletions

View File

@ -1,20 +1,41 @@
from __future__ import annotations
from typing import ClassVar
from typing import ClassVar, Any
from .config import Config
from .utils import Utils
from .database import Database
from .storage import Storage
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
self.color: tuple[int, int, int]
def to_dict(self) -> dict[str, Any]:
return {
"created": self.created,
"updated": self.updated,
"name": self.name,
"status": self.status,
"hits": self.hits,
"triumph": self.triumph,
"color": self.color,
}
def from_dict(self, data: dict[str, Any]) -> None:
self.created = data["created"]
self.updated = data["updated"]
self.name = data["name"]
self.status = data["status"]
self.hits = data["hits"]
self.triumph = data["triumph"]
self.color = tuple(data["color"])
def get_name(self) -> str:
return self.name or "Nameless"
@ -32,41 +53,36 @@ 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()
def prepare() -> None:
objs = Storage.get_ants()
for row in rows:
for obj in objs:
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]
ant.from_dict(obj)
Ants.ants.append(ant)
@staticmethod
def hatch() -> None:
if len(Ants.ants) >= Config.max_ants:
Utils.print("Too many ants")
return
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]
ant.created = now
ant.updated = now
ant.name = Utils.random_name()
ant.color = Utils.random_color()
Utils.print(f"Ant hatched: {ant.id}")
Ants.ants.append(ant)
Storage.save_ants(Ants.ants)
Utils.print(f"Ant hatched: {ant.name}")
@staticmethod
def terminate() -> None:
pass
@staticmethod
def get_names() -> list[str]:
return [ant.name for ant in Ants.ants]

View File

@ -1,17 +1,29 @@
from pathlib import Path
import appdirs # type: ignore
class Config:
title = "Cromulant"
width = 800
height = 600
max_ants = 100
here: str
database_path: Path
schema_path: Path
ants_json: Path
icon_path: Path
image_path: Path
names_json: Path
@staticmethod
def prepare() -> None:
Config.here = Path(__file__).parent
Config.database_path = Config.here / "cromulant.db"
Config.schema_path = Config.here / "schema.sql"
Config.ants_json = Path(appdirs.user_data_dir()) / "cromulant" / "ants.json"
if not Config.ants_json.exists():
Config.ants_json.parent.mkdir(parents=True, exist_ok=True)
Config.ants_json.write_text("[]")
Config.icon_path = Config.here / "img" / "icon_4.jpg"
Config.image_path = Config.here / "img" / "icon_7.jpg"
Config.names_json = Config.here / "data" / "names.json"

1002
cromulant/data/names.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
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()

View File

@ -1,11 +1,40 @@
from PySide6.QtWidgets import QWidget
from PySide6.QtGui import QColor
from PySide6.QtGui import QPainter
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QLabel
from .ants import Ants
from .window import Window
class CircleWidget(QWidget):
def __init__(self, color, parent=None):
super().__init__(parent)
self.color = QColor(*color)
self.setFixedSize(20, 20)
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.setBrush(self.color)
painter.setPen(Qt.NoPen)
painter.drawEllipse(0, 0, self.width(), self.height())
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)
for ant in Ants.ants:
container = QHBoxLayout()
circle = CircleWidget(ant.color)
text = QLabel(ant.status)
container.addWidget(circle)
container.addWidget(text)
Window.view.addLayout(container)
@staticmethod
def log(message: str) -> None:
Window.log.append(message)

BIN
cromulant/img/icon_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
cromulant/img/icon_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
cromulant/img/icon_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
cromulant/img/icon_4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

BIN
cromulant/img/icon_5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

BIN
cromulant/img/icon_6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

BIN
cromulant/img/icon_7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -1,23 +1,16 @@
from __future__ import annotations
from .config import Config
from .database import Database
from .window import Window
from .utils import Utils
from .ants import Ants
from .window import Window
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()
Utils.prepare()
Ants.prepare()
Window.prepare()
if __name__ == "__main__":

View File

@ -1,22 +0,0 @@
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
);

26
cromulant/storage.py Normal file
View File

@ -0,0 +1,26 @@
import json
from typing import TYPE_CHECKING
from .config import Config
if TYPE_CHECKING:
from .ants import Ant
class Storage:
@staticmethod
def get_ants() -> None:
with Config.ants_json.open() as file:
return json.load(file)
@staticmethod
def save_ants(ants: list["Ant"]) -> None:
objs = [ant.to_dict() for ant in ants]
with Config.ants_json.open("w") as file:
json.dump(objs, file)
@staticmethod
def get_names() -> None:
with Config.names_json.open() as file:
return json.load(file)

View File

@ -1,7 +1,16 @@
import random
import colorsys
import time
from .storage import Storage
class Utils:
names: list[str] = []
@staticmethod
def prepare() -> None:
Utils.names = Storage.get_names()
@staticmethod
def now() -> float:
return int(time.time())
@ -53,3 +62,17 @@ class Utils:
@staticmethod
def print(text: str) -> None:
print(text) # noqa: T201
@staticmethod
def random_color() -> str:
h,s,l = random.random(), 0.5 + random.random()/2.0, 0.4 + random.random()/5.0
r, g, b = [int(256*i) for i in colorsys.hls_to_rgb(h,l,s)]
return r, g, b
@staticmethod
def random_name() -> str:
from .ants import Ants
used = Ants.get_names()
filtered = [name for name in Utils.names if name not in used]
return random.choice(filtered)

View File

@ -9,6 +9,10 @@ from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QPushButton
from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QTextEdit
from PySide6.QtWidgets import QLabel
from PySide6.QtGui import QPixmap
from PySide6.QtGui import QIcon
from PySide6.QtCore import Qt
from .config import Config
@ -18,8 +22,17 @@ class Window:
window: QMainWindow
root: QHBoxLayout
view: QGraphicsView
view_scene: QGraphicsScene
log: QTextEdit
@staticmethod
def prepare() -> None:
Window.make()
Window.add_buttons()
Window.add_view()
Window.add_log()
Window.start()
@staticmethod
def make() -> None:
Window.app = QApplication([])
@ -31,37 +44,51 @@ class Window:
Window.root = QVBoxLayout()
central_widget.setLayout(Window.root)
Window.window.setCentralWidget(central_widget)
Window.window.setWindowIcon(QIcon(str(Config.icon_path)))
Window.app.setStyleSheet("QWidget { background-color: #3c3681; color: #FFF; }")
@staticmethod
def add_buttons() -> None:
btn_hatch = QPushButton("Hatch")
btn_terminate = QPushButton("Terminate")
btn_update = QPushButton("Update")
btn_close = QPushButton("Close")
btn_hatch.clicked.connect(Window.hatch)
btn_terminate.clicked.connect(Window.terminate)
btn_update.clicked.connect(Window.update_view)
btn_close.clicked.connect(Window.close)
layout = QHBoxLayout()
layout.addWidget(btn_hatch)
layout.addWidget(btn_terminate)
layout.addWidget(btn_update)
layout.addWidget(btn_close)
Window.root.addLayout(layout)
@staticmethod
def add_view() -> None:
Window.view = QGraphicsView()
scene = QGraphicsScene()
Window.view.setScene(scene)
Window.root.addWidget(Window.view)
Window.view = QVBoxLayout()
Window.root.addLayout(Window.view)
@staticmethod
def add_log() -> None:
container = QHBoxLayout()
image_label = QLabel()
pixmap = QPixmap(str(Config.image_path))
scaled_pixmap = pixmap.scaled(100, pixmap.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
image_label.setPixmap(scaled_pixmap)
image_label.setFixedWidth(100)
container.addWidget(image_label)
Window.log = QTextEdit()
Window.log.setReadOnly(True)
Window.log.setFixedHeight(100)
Window.root.addWidget(Window.log)
container.addWidget(Window.log)
Window.root.addLayout(container)
@staticmethod
def update_view() -> None:
@ -82,3 +109,7 @@ class Window:
def start() -> None:
Window.window.show()
Window.app.exec()
@staticmethod
def close() -> None:
Window.app.quit()

View File

@ -1 +1,2 @@
PySide6 == 6.7.2
appdirs == 1.4.4