arch & placeholders
This commit is contained in:
parent
a3b16ae58a
commit
57543e139a
10 changed files with 233 additions and 2 deletions
60
.gitignore
vendored
Normal file
60
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# =========================
|
||||||
|
# Python bytecode & cache
|
||||||
|
# =========================
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Virtual environments
|
||||||
|
# =========================
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Packaging / build
|
||||||
|
# =========================
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.egg-info/
|
||||||
|
.eggs/
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Test & coverage
|
||||||
|
# =========================
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# IDE / editors
|
||||||
|
# =========================
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# OS generated files
|
||||||
|
# =========================
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Logs
|
||||||
|
# =========================
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Application data
|
||||||
|
# =========================
|
||||||
|
data/*.json
|
||||||
|
data/*.bak
|
||||||
|
|
||||||
|
# But keep example files
|
||||||
|
!data/example.json
|
||||||
17
README.md
17
README.md
|
|
@ -1,3 +1,16 @@
|
||||||
# Wargame_campain_app
|
# Wargame_campaign_app
|
||||||
|
|
||||||
A simplie CLI app to manage players and their scores throughout several organised games of a tabletop wargame.
|
A simple CLI app to manage players and their scores throughout several organised games of a tabletop wargame.
|
||||||
|
|
||||||
|
## Main logic
|
||||||
|
|
||||||
|
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.
|
||||||
|
Battle results determine campaign score which determines the war score. Wars are independent.
|
||||||
|
|
||||||
|
## Design notes
|
||||||
|
|
||||||
|
Players are global identities
|
||||||
|
Influence tokens are scoped to a war
|
||||||
|
Campaign order enables historical tie-breakers
|
||||||
|
Effects are generic → future-proof
|
||||||
9
cli/menu.py
Normal file
9
cli/menu.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
def main_menu():
|
||||||
|
print("\n=== Wargame Campaign App ===")
|
||||||
|
print("1. List wars")
|
||||||
|
print("2. Start new campaign")
|
||||||
|
print("3. Start new round")
|
||||||
|
print("4. Enter battle results")
|
||||||
|
print("5. Save & exit")
|
||||||
|
|
||||||
|
return input("> ")
|
||||||
78
data/example.json
Normal file
78
data/example.json
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
|
||||||
|
"players": {
|
||||||
|
"P1": {
|
||||||
|
"name": "Alice"
|
||||||
|
},
|
||||||
|
"P2": {
|
||||||
|
"name": "Bob"
|
||||||
|
},
|
||||||
|
"P3": {
|
||||||
|
"name": "Charlie"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"wars": [
|
||||||
|
{
|
||||||
|
"id": "WAR2025",
|
||||||
|
"name": "Llael War 2025",
|
||||||
|
"year": 2025,
|
||||||
|
|
||||||
|
"registered_players": {
|
||||||
|
"P1": {
|
||||||
|
"war_points": 0,
|
||||||
|
"influence_tokens": 1
|
||||||
|
},
|
||||||
|
"P2": {
|
||||||
|
"war_points": 0,
|
||||||
|
"influence_tokens": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"campaigns": [
|
||||||
|
{
|
||||||
|
"id": "CAMP01",
|
||||||
|
"name": "Widower's Wood",
|
||||||
|
"order": 1,
|
||||||
|
|
||||||
|
"participants": {
|
||||||
|
"P1": { "campaign_points": 0 },
|
||||||
|
"P2": { "campaign_points": 0 }
|
||||||
|
},
|
||||||
|
|
||||||
|
"rounds": [
|
||||||
|
{
|
||||||
|
"number": 1,
|
||||||
|
"sectors": ["North", "South"],
|
||||||
|
|
||||||
|
"choices": {
|
||||||
|
"P1": { "primary": "North", "secondary": "South" },
|
||||||
|
"P2": { "primary": "North", "secondary": "South" }
|
||||||
|
},
|
||||||
|
|
||||||
|
"battles": [
|
||||||
|
{
|
||||||
|
"sector": "North",
|
||||||
|
"players": ["P1", "P2"],
|
||||||
|
"winner": "P1",
|
||||||
|
|
||||||
|
"effects": {
|
||||||
|
"campaign_points": 1,
|
||||||
|
"grants_influence_token": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"completed": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"completed": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"completed": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
main.py
Normal file
21
main.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
from storage.repository import load_data, save_data
|
||||||
|
from cli.menu import main_menu
|
||||||
|
|
||||||
|
def main():
|
||||||
|
data = load_data()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
choice = main_menu()
|
||||||
|
|
||||||
|
if choice == "1":
|
||||||
|
print("Wars:")
|
||||||
|
for war in data["wars"]:
|
||||||
|
print(f"- {war['name']}")
|
||||||
|
|
||||||
|
elif choice == "5":
|
||||||
|
save_data(data)
|
||||||
|
print("Goodbye.")
|
||||||
|
break
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
4
models/player.py
Normal file
4
models/player.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
def create_player(player_id, name):
|
||||||
|
return {
|
||||||
|
"name": name
|
||||||
|
}
|
||||||
7
services/round_service.py
Normal file
7
services/round_service.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
def resolve_round(round_data, campaign, war):
|
||||||
|
"""
|
||||||
|
Placeholder:
|
||||||
|
- resolve sector assignments
|
||||||
|
- create battle entries
|
||||||
|
"""
|
||||||
|
pass
|
||||||
10
services/scoring_service.py
Normal file
10
services/scoring_service.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
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
|
||||||
7
services/tie_breaker_service.py
Normal file
7
services/tie_breaker_service.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
def break_tie(players, current_campaign, war):
|
||||||
|
"""
|
||||||
|
Placeholder:
|
||||||
|
- future implementation will check
|
||||||
|
previous campaigns and influence tokens
|
||||||
|
"""
|
||||||
|
return players
|
||||||
22
storage/repository.py
Normal file
22
storage/repository.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DATA_FILE = Path("data/warmachron.json")
|
||||||
|
|
||||||
|
def load_data():
|
||||||
|
if not DATA_FILE.exists() or DATA_FILE.stat().st_size == 0:
|
||||||
|
return {"version": 1, "players": {}, "wars": []}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(DATA_FILE, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
raise RuntimeError("Data file is corrupted")
|
||||||
|
|
||||||
|
def save_data(data):
|
||||||
|
if DATA_FILE.exists():
|
||||||
|
shutil.copy(DATA_FILE, DATA_FILE.with_suffix(".json.bak"))
|
||||||
|
|
||||||
|
with open(DATA_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue