prepare gui env

This commit is contained in:
Maxime Réaux 2026-01-15 12:43:40 +01:00
parent 02e7221149
commit d2bcf3bdd8
25 changed files with 291 additions and 198 deletions

24
Makefile Normal file
View file

@ -0,0 +1,24 @@
ress:
pyrcc5 .\src\wargame_campaign\view\resources\ui_ressources.qrc -o .\src\wargame_campaign\view\resources\ui_ressources_rc.py
installer :
python -m PyInstaller .\main.spec
ui:
UI_DIR := ./view/ui
UI_FILES := $(wildcard $(UI_DIR)/*.ui)
PY_FILES := $(UI_FILES:.ui=.py)
# Generate all .py UI modules from .ui files
ui: $(PY_FILES)
# Pattern rule: .ui -> .py using pyuic5
$(UI_DIR)/%.py: $(UI_DIR)/%.ui
pyuic5 -x $< -o $@ --import-from wargame_campaign.view.resources
# Function to generate UI file from given name
_ui_generate:
pyuic6 -x .\src\wargame_campaign\view\ui\$(UI_NAME).ui -o .\src\wargame_campaign\view\ui\$(UI_NAME).py --import-from wargame_campaign.view.resources
# Set default UI_NAME if not provided
UI_NAME ?= ui_main_window

View file

@ -1,16 +1,39 @@
# Wargame_campaign_app # Wargame_campaign_app
A simple CLI app to manage players and their scores throughout several organised games of a tabletop wargame. A simple local app to manage players and their scores throughout several organised games of a tabletop wargame.
## Main logic ## Features
### Main logic
Manage a list of players to sign them up to be selectable for war(s) and campaign(s). Manage a list of players to sign them up to be selectable for war(s) and campaign(s).
A year "war" contains several "campaign" events which contain several "battle" games organised in successive rounds. A year "war" contains several "campaign" events which contain several "battle" games organised in successive rounds.
Battle results determine campaign score which determines the war score. Wars are independent. Battle results determine campaign score which determines the war score. Wars are independent.
## Design notes ### Design notes
Players are global identities Players are global identities
Influence tokens are scoped to a war Influence tokens are scoped to a war
Campaign order enables historical tie-breakers Campaign order enables historical tie-breakers
Effects are generic → future-proof Effects are generic → future-proof
## Installation
### Requirements
- Python >= 3.12
- pip
### Setup
```bash
git clone <your-forge-address>/Wargame_campaign_app.git
cd Wargame_campaign_app
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
### Run
`python main.py`

View file

@ -1,38 +0,0 @@
from cli.war_menu import war_menu
from cli.utils import choose_from_list
def app_menu(data):
while True:
print("\n=== Warmachron ===")
print("1. Select war")
print("2. Create war")
print("3. Manage players")
print("0. Exit")
choice = input("> ").strip()
if choice == "1":
war = select_war(data)
if war:
war_menu(data, war)
elif choice == "2":
create_war(data)
elif choice == "3":
manage_players(data)
elif choice == "0":
return
def select_war(data):
wars = data["wars"]
if not wars:
print("No wars available.")
return None
return choose_from_list(
wars,
lambda w: f"{w['name']} ({w['year']})"
)

View file

@ -1,23 +0,0 @@
from cli.round_menu import round_menu
def campaign_menu(data, war, campaign):
while True:
print(f"\n=== Campaign: {campaign['name']} ===")
print("1. Select round")
print("2. Append round")
print("3. Finish campaign")
print("4. Edit/Delete campaign")
print("0. Back")
choice = input("> ").strip()
if choice == "1":
rnd = select_round(campaign)
if rnd:
round_menu(data, war, campaign, rnd)
elif choice == "2":
append_round(campaign)
elif choice == "0":
return

View file

@ -1,34 +0,0 @@
def round_menu(data, war, campaign, rnd):
while True:
print(f"\n=== Round {rnd['number']} ===")
print("1. Enter choices and pairing")
print("2. Enter battle results")
print("3. Finish round")
print("4. Edit/Delete round")
print("0. Back")
choice = input("> ").strip()
if choice == "1":
enter_choices(data, war, campaign, rnd)
elif choice == "2":
enter_battle_results(data, war, campaign, rnd)
elif choice == "0":
return
def enter_choices(data, war, campaign, rnd):
print("Entering choices (placeholder)")
# later:
# - list campaign participants
# - input primary / secondary sector
# - store in rnd["choices"]
def enter_battle_results(data, war, campaign, rnd):
print("Entering battle results (placeholder)")
# later:
# - list battles
# - select winner
# - apply scoring

View file

@ -1,14 +0,0 @@
def choose_from_list(items, label_fn):
for i, item in enumerate(items, start=1):
print(f"{i}. {label_fn(item)}")
print("0. Back")
choice = input("> ").strip()
if choice == "0":
return None
try:
return items[int(choice) - 1]
except (ValueError, IndexError):
print("Invalid choice")
return None

View file

@ -1,33 +0,0 @@
from cli.campaign_menu import campaign_menu
from storage.repository import save_data
def war_menu(data, war):
while True:
print(f"\n=== War: {war['name']} ===")
print("1. Select campaign")
print("2. Append campaign")
print("3. Finish war")
print("4. Edit/Delete war")
print("0. Back")
choice = input("> ").strip()
if choice == "1":
campaign = select_campaign(war)
if campaign:
campaign_menu(data, war, campaign)
elif choice == "2":
append_campaign(war)
elif choice == "0":
return
def append_campaign(war, data):
name = input("Campaign name: ")
war["campaigns"].append({
"name": name,
"rounds": [],
"completed": False
})
save_data(data)

View file

@ -1,17 +1,20 @@
{ {
"version": 1, "version": 1,
"players": { "players": [
"P1": { {
"name": "Alice" "id" : "p1",
"name" :"Alice"
}, },
"P2": { {
"name": "Bob" "id" : "p2",
"name" :"Bob"
}, },
"P3": { {
"name": "Charlie" "id" : "p3",
"name" :"Charlie"
} }
}, ],
"wars": [ "wars": [
{ {
@ -20,11 +23,11 @@
"year": 2025, "year": 2025,
"registered_players": { "registered_players": {
"P1": { "p1": {
"war_points": 0, "war_points": 0,
"influence_tokens": 1 "influence_tokens": 1
}, },
"P2": { "p2": {
"war_points": 0, "war_points": 0,
"influence_tokens": 0 "influence_tokens": 0
} }
@ -35,10 +38,10 @@
"id": "CAMP01", "id": "CAMP01",
"name": "Widower's Wood", "name": "Widower's Wood",
"order": 1, "order": 1,
"month" : "June",
"participants": { "participants": {
"P1": { "campaign_points": 0 }, "p1": { "campaign_points": 0 },
"P2": { "campaign_points": 0 } "p2": { "campaign_points": 0 }
}, },
"rounds": [ "rounds": [
@ -47,15 +50,15 @@
"sectors": ["North", "South"], "sectors": ["North", "South"],
"choices": { "choices": {
"P1": { "primary": "North", "secondary": "South" }, "p1": { "primary": "North", "secondary": "South" },
"P2": { "primary": "North", "secondary": "South" } "p2": { "primary": "North", "secondary": "South" }
}, },
"battles": [ "battles": [
{ {
"sector": "North", "sector": "North",
"players": ["P1", "P2"], "players": ["p1", "p2"],
"winner": "P1", "winner": "p1",
"effects": { "effects": {
"campaign_points": 1, "campaign_points": 1,

27
main.py
View file

@ -1,10 +1,23 @@
from storage.repository import load_data, save_data import sys
from cli.app import app_menu import os
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "src"))
def main(): from PyQt6.QtWidgets import QApplication
data = load_data()
app_menu(data) from wargame_campaign.view.view import View
save_data(data) from wargame_campaign.model.model import Model
from wargame_campaign.controller.controller import Controller
if sys.version_info < (3, 12):
raise RuntimeError("Python 3.12 or higher is required")
if __name__ == "__main__": if __name__ == "__main__":
main() app = QApplication(sys.argv)
view = View()
model = Model()
controller = Controller(model, view)
view.show()
sys.exit(app.exec())

View file

@ -1,4 +0,0 @@
def create_player(player_id, name):
return {
"name": name
}

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
PyQt6>=6.6,<6.8

View file

@ -1,7 +0,0 @@
def resolve_round(round_data, campaign, war):
"""
Placeholder:
- resolve sector assignments
- create battle entries
"""
pass

View file

@ -1,10 +0,0 @@
def apply_battle_effects(battle, campaign, war):
"""
Placeholder scoring logic.
"""
winner = battle["winner"]
effects = battle.get("effects", {})
campaign_points = effects.get("campaign_points", 0)
if winner in campaign["participants"]:
campaign["participants"][winner]["campaign_points"] += campaign_points

View file

@ -1,7 +0,0 @@
def break_tie(players, current_campaign, war):
"""
Placeholder:
- future implementation will check
previous campaigns and influence tokens
"""
return players

View file

@ -0,0 +1,15 @@
class Controller:
def __init__(self, model, view):
self.model = model
self.view = view
self.__connect()
def __connect(self):
# self.view.players_view.btn_add.clicked.connect(self.add_player)
pass
def add_player(self):
print(f"test")

View file

View file

@ -0,0 +1,55 @@
from pathlib import Path
import json
import shutil
from wargame_campaign.model.player import Player
class Model:
def __init__(self):
self.players = {}
data_file_path = Path("data/warmachron.json")
self.load_data(data_file_path)
def load_data(self, data_file_path):
if not data_file_path.exists() or data_file_path.stat().st_size == 0:
pass # Create empty json
try:
with open(data_file_path, "r", encoding="utf-8") as f:
data = json.load(f)
for player in data["players"] :
print(f"player {player}")
saved_player = Player.fromDict(player["id"], player['name'])
self.players[saved_player.id] = saved_player
for war in data["wars"]:
pass
except json.JSONDecodeError:
raise RuntimeError("Data file is corrupted")
def save_data(self, data_file_path):
if data_file_path.exists():
shutil.copy(data_file_path, data_file_path.with_suffix(".json.bak"))
data = {}
data['verion'] = "1.0"
data['players'] = []
for player in self.players:
data['players'].append(player.toDict())
with open(data_file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
def add_player(self, name):
player = Player(name)
self.players[player.id] = player
return player
def get_player(self, id):
return self.players[id]
def update_player(self, id, name):
player = self.get_player(id)
player.set_name(name)
def delete_player(self, id):
del self.players[id]

View file

@ -0,0 +1,24 @@
from uuid import uuid4
class Player:
def __init__(self, name ):
self.id = str(uuid4())
self.name = name
def set_id(self, new_id):
self.id = new_id
def set_name(self, name):
self.name = name
def toDict(self):
return {
"id" : self.id,
"name" : self.name
}
@staticmethod
def fromDict(id, name):
tmp = Player(name=name)
tmp.set_id(id)
return tmp

View file

@ -0,0 +1,23 @@
from models.player import create_player
from model.repository import save_data
def generate_player_id(players):
return f"P{len(players) + 1}"
def add_player(data, name):
players = data["players"]
if any(p["name"].lower() == name.lower() for p in players.values()):
raise ValueError("Player already exists")
player_id = generate_player_id(players)
players[player_id] = create_player(player_id, name)
save_data(data)
def update_player(data, player_id, new_name):
data["players"][player_id]["name"] = new_name
save_data(data)
def delete_player(data, player_id):
del data["players"][player_id]
save_data(data)

View file

View file

@ -0,0 +1,42 @@
# Form implementation generated from reading ui file '.\src\wargame_campaign\view\ui\ui_main_window.ui'
#
# Created by: PyQt6 UI code generator 6.7.1
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget"/>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,9 @@
from PyQt6 import uic, QtWidgets
from wargame_campaign.view.ui.ui_main_window import Ui_MainWindow
class View(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(View, self).__init__(parent)
self.setupUi(self)