Mods
|
@ -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]
|
|
@ -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"
|
|
@ -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()
|
|
|
@ -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)
|
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 147 KiB |
After Width: | Height: | Size: 320 KiB |
After Width: | Height: | Size: 271 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 269 KiB |
|
@ -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__":
|
||||||
|
|
|
@ -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
|
|
||||||
);
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
PySide6 == 6.7.2
|
PySide6 == 6.7.2
|
||||||
|
appdirs == 1.4.4
|