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 __future__ import annotations
from typing import ClassVar from typing import ClassVar, Any
from .config import Config
from .utils import Utils from .utils import Utils
from .database import Database from .storage import Storage
class Ant: class Ant:
def __init__(self) -> None: def __init__(self) -> None:
now = Utils.now() now = Utils.now()
self.id: int
self.created = now self.created = now
self.updated = now self.updated = now
self.name = "" self.name = ""
self.status = "" self.status = ""
self.hits = 0 self.hits = 0
self.triumph = 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: def get_name(self) -> str:
return self.name or "Nameless" return self.name or "Nameless"
@ -32,41 +53,36 @@ class Ants:
ants: ClassVar[list[Ant]] = [] ants: ClassVar[list[Ant]] = []
@staticmethod @staticmethod
def get_all() -> None: def prepare() -> None:
Database.cursor.execute( objs = Storage.get_ants()
"SELECT id, created, updated, name, status, hits, triumph FROM ants"
)
rows = Database.cursor.fetchall()
for row in rows: for obj in objs:
ant = Ant() ant = Ant()
ant.id = row[0] ant.from_dict(obj)
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) Ants.ants.append(ant)
@staticmethod @staticmethod
def hatch() -> None: def hatch() -> None:
if len(Ants.ants) >= Config.max_ants:
Utils.print("Too many ants")
return
now = Utils.now() 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 = 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 @staticmethod
def terminate() -> None: def terminate() -> None:
pass 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 from pathlib import Path
import appdirs # type: ignore
class Config: class Config:
title = "Cromulant" title = "Cromulant"
width = 800 width = 800
height = 600 height = 600
max_ants = 100
here: str here: str
database_path: Path ants_json: Path
schema_path: Path icon_path: Path
image_path: Path
names_json: Path
@staticmethod @staticmethod
def prepare() -> None: def prepare() -> None:
Config.here = Path(__file__).parent Config.here = Path(__file__).parent
Config.database_path = Config.here / "cromulant.db" Config.ants_json = Path(appdirs.user_data_dir()) / "cromulant" / "ants.json"
Config.schema_path = Config.here / "schema.sql"
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 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: class Game:
@staticmethod @staticmethod
def update_view() -> None: def update_view() -> None:
scene = Window.view.scene() for ant in Ants.ants:
scene.addRect(0, 0, 10, 10) container = QHBoxLayout()
scene.addRect(10, 10, 10, 10) circle = CircleWidget(ant.color)
scene.addRect(20, 20, 10, 10) text = QLabel(ant.status)
scene.addRect(30, 30, 10, 10) container.addWidget(circle)
scene.addRect(40, 40, 10, 10) 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 __future__ import annotations
from .config import Config from .config import Config
from .database import Database from .utils import Utils
from .window import Window
from .ants import Ants from .ants import Ants
from .window import Window
def main() -> None: def main() -> None:
Config.prepare() Config.prepare()
Database.prepare() Utils.prepare()
Database.create() Ants.prepare()
Window.prepare()
Ants.get_all()
Window.make()
Window.add_buttons()
Window.add_view()
Window.add_log()
Window.start()
if __name__ == "__main__": 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 import time
from .storage import Storage
class Utils: class Utils:
names: list[str] = []
@staticmethod
def prepare() -> None:
Utils.names = Storage.get_names()
@staticmethod @staticmethod
def now() -> float: def now() -> float:
return int(time.time()) return int(time.time())
@ -53,3 +62,17 @@ class Utils:
@staticmethod @staticmethod
def print(text: str) -> None: def print(text: str) -> None:
print(text) # noqa: T201 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 QPushButton
from PySide6.QtWidgets import QHBoxLayout from PySide6.QtWidgets import QHBoxLayout
from PySide6.QtWidgets import QTextEdit 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 from .config import Config
@ -18,8 +22,17 @@ class Window:
window: QMainWindow window: QMainWindow
root: QHBoxLayout root: QHBoxLayout
view: QGraphicsView view: QGraphicsView
view_scene: QGraphicsScene
log: QTextEdit log: QTextEdit
@staticmethod
def prepare() -> None:
Window.make()
Window.add_buttons()
Window.add_view()
Window.add_log()
Window.start()
@staticmethod @staticmethod
def make() -> None: def make() -> None:
Window.app = QApplication([]) Window.app = QApplication([])
@ -31,37 +44,51 @@ class Window:
Window.root = QVBoxLayout() Window.root = QVBoxLayout()
central_widget.setLayout(Window.root) central_widget.setLayout(Window.root)
Window.window.setCentralWidget(central_widget) Window.window.setCentralWidget(central_widget)
Window.window.setWindowIcon(QIcon(str(Config.icon_path)))
Window.app.setStyleSheet("QWidget { background-color: #3c3681; color: #FFF; }")
@staticmethod @staticmethod
def add_buttons() -> None: def add_buttons() -> None:
btn_hatch = QPushButton("Hatch") btn_hatch = QPushButton("Hatch")
btn_terminate = QPushButton("Terminate") btn_terminate = QPushButton("Terminate")
btn_update = QPushButton("Update") btn_update = QPushButton("Update")
btn_close = QPushButton("Close")
btn_hatch.clicked.connect(Window.hatch) btn_hatch.clicked.connect(Window.hatch)
btn_terminate.clicked.connect(Window.terminate) btn_terminate.clicked.connect(Window.terminate)
btn_update.clicked.connect(Window.update_view) btn_update.clicked.connect(Window.update_view)
btn_close.clicked.connect(Window.close)
layout = QHBoxLayout() layout = QHBoxLayout()
layout.addWidget(btn_hatch) layout.addWidget(btn_hatch)
layout.addWidget(btn_terminate) layout.addWidget(btn_terminate)
layout.addWidget(btn_update) layout.addWidget(btn_update)
layout.addWidget(btn_close)
Window.root.addLayout(layout) Window.root.addLayout(layout)
@staticmethod @staticmethod
def add_view() -> None: def add_view() -> None:
Window.view = QGraphicsView() Window.view = QVBoxLayout()
scene = QGraphicsScene() Window.root.addLayout(Window.view)
Window.view.setScene(scene)
Window.root.addWidget(Window.view)
@staticmethod @staticmethod
def add_log() -> None: 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 = QTextEdit()
Window.log.setReadOnly(True) Window.log.setReadOnly(True)
Window.log.setFixedHeight(100) Window.log.setFixedHeight(100)
Window.root.addWidget(Window.log) container.addWidget(Window.log)
Window.root.addLayout(container)
@staticmethod @staticmethod
def update_view() -> None: def update_view() -> None:
@ -82,3 +109,7 @@ class Window:
def start() -> None: def start() -> None:
Window.window.show() Window.window.show()
Window.app.exec() Window.app.exec()
@staticmethod
def close() -> None:
Window.app.quit()

View File

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