Compare commits

..

3 commits

Author SHA1 Message Date
Maxime Réaux
ea6ec02f3b cleanup example file 2026-02-26 16:24:52 +01:00
Maxime Réaux
9c9a7beb59 default table ordering 2026-02-26 16:16:48 +01:00
Maxime Réaux
0897f8de40 fix random hidden cells in tables 2026-02-26 16:00:42 +01:00
4 changed files with 369 additions and 92 deletions

6
.gitignore vendored
View file

@ -53,8 +53,8 @@ Thumbs.db
# =========================
# Application data
# =========================
data/*.json
data/*.bak
test_data/*.json
test_data/*.bak
# But keep example files
!data/example.json
!test_data/example.json

View file

@ -1,89 +0,0 @@
{
"version": 1,
"players": [
{
"id": "e7844fbb-8366-44e4-bb43-89b9eef6ef64",
"name": "Alice"
},
{
"id": "b7eebce7-cf04-40bc-b80c-400585adb3cd",
"name": "Bob"
},
{
"id": "f87e6d53-30a2-4dd8-a359-d860404ef2ee",
"name": "Charlie"
},
{
"id": "056011da-b0c7-4dc7-8b7c-14213a8df009",
"name": "Dave"
},
{
"id": "50e83cb3-b828-4f1e-ad89-7644a84f3d8c",
"name": "Eve"
}
],
"wars": [
{
"id": "WAR2025",
"name": "Llael War 2025",
"year": 2025,
"participants": {
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": {
"war_points": 0,
"influence_tokens": 1
},
"b7eebce7-cf04-40bc-b80c-400585adb3cd": {
"war_points": 0,
"influence_tokens": 0
}
},
"campaigns": [
{
"id": "CAMP01",
"name": "Widower's Wood",
"order": 1,
"month" : "June",
"participants": {
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": { "campaign_points": 0 },
"b7eebce7-cf04-40bc-b80c-400585adb3cd": { "campaign_points": 0 }
},
"rounds": [
{
"number": 1,
"sectors": ["North", "South"],
"choices": {
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": { "primary": "North", "secondary": "South" },
"b7eebce7-cf04-40bc-b80c-400585adb3cd": { "primary": "North", "secondary": "South" }
},
"battles": [
{
"sector": "North",
"players": ["e7844fbb-8366-44e4-bb43-89b9eef6ef64", "b7eebce7-cf04-40bc-b80c-400585adb3cd"],
"winner": "e7844fbb-8366-44e4-bb43-89b9eef6ef64",
"effects": {
"campaign_points": 1,
"grants_influence_token": false
}
}
],
"completed": true
}
],
"completed": false
}
],
"is_completed": false
}
]
}

View file

@ -197,11 +197,14 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
def display_players(self, players: List[ParticipantOption]) -> None:
table = self.playersTable
table.setSortingEnabled(False)
table.setRowCount(len(players))
for row, player in enumerate(players):
play_item = QtWidgets.QTableWidgetItem(player.name)
play_item.setData(Qt.ItemDataRole.UserRole, player.id)
table.setItem(row, 0, play_item)
table.setSortingEnabled(True)
table.sortItems(0, Qt.SortOrder.AscendingOrder)
table.resizeColumnsToContents()
# Wars view
@ -355,6 +358,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
def display_war_objectives(self, objectives: List[ObjectiveDTO]) -> None:
table = self.objectivesTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(objectives))
for row, obj in enumerate(objectives):
@ -363,6 +367,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
name_item.setData(Qt.ItemDataRole.UserRole, obj.id)
table.setItem(row, 0, name_item)
table.setItem(row, 1, desc_item)
table.setSortingEnabled(True)
table.resizeColumnsToContents()
def display_war_participants(
@ -371,6 +376,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
objectives: List[ObjectiveDTO],
) -> None:
table = self.warParticipantsTable
table.setSortingEnabled(False)
table.clearContents()
base_cols = ["Player", "Faction", "Victory pts"]
headers = (
@ -398,6 +404,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, col, NP_item)
col += 1
table.setItem(row, col, token_item)
table.setSortingEnabled(True)
table.sortItems(2, Qt.SortOrder.DescendingOrder)
table.resizeColumnsToContents()
def _on_major_changed(self, value: int) -> None:
@ -467,6 +475,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
def display_campaign_sectors(self, sectors: List[SectorDTO]) -> None:
table = self.sectorsTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(sectors))
for row, sect in enumerate(sectors):
@ -487,6 +496,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, 4, minor_item)
table.setItem(row, 5, influence_item)
table.setItem(row, 6, description_item)
table.setSortingEnabled(True)
table.sortItems(1, Qt.SortOrder.AscendingOrder)
table.resizeColumnsToContents()
def display_campaign_participants(
@ -495,6 +506,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
objectives: List[ObjectiveDTO],
) -> None:
table = self.campaignParticipantsTable
table.setSortingEnabled(False)
table.clearContents()
base_cols = ["Player", "Leader", "Theme", "Victory pts"]
headers = (
@ -524,6 +536,8 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, col, NP_item)
col += 1
table.setItem(row, col, token_item)
table.setSortingEnabled(True)
table.sortItems(3, Qt.SortOrder.DescendingOrder)
table.resizeColumnsToContents()
# Round page
@ -571,6 +585,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
def display_round_choices(self, participants: List[ChoiceDTO]) -> None:
table = self.choicesTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(participants))
for row, choice in enumerate(participants):
@ -581,10 +596,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, 0, participant_item)
table.setItem(row, 1, priority_item)
table.setItem(row, 2, secondary_item)
table.setSortingEnabled(True)
table.resizeColumnsToContents()
def display_round_battles(self, sectors: List[BattleDTO]) -> None:
table = self.battlesTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(sectors))
table.setIconSize(QSize(32, 16))
@ -608,4 +625,5 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, 3, vp_item)
table.setItem(row, 4, score_item)
table.setItem(row, 5, comment_item)
table.setSortingEnabled(True)
table.resizeColumnsToContents()

348
test_data/example.json Normal file
View file

@ -0,0 +1,348 @@
{
"version": "1.0",
"players": [
{
"id": "c232f334-3c2d-4e62-9c09-23d045e26bf8",
"name": "Alice"
},
{
"id": "82ef39f2-ff50-4f3f-af74-d0cee7ec5a84",
"name": "Bob"
},
{
"id": "d00faaf1-5200-4fb0-bfa7-6b5fe89a4c87",
"name": "Charlie"
},
{
"id": "d5a430cd-11d3-40d4-bf2a-5771cde3c0a9",
"name": "Dave"
},
{
"id": "ea00a996-5ff1-4386-8e7f-b16edf821b19",
"name": "Eve"
}
],
"wars": [
{
"id": "f39a78d5-83a6-43b9-88c7-64a6e3488412",
"name": "Huge War",
"year": 2026,
"major_value": 2,
"minor_value": 1,
"influence_token": true,
"participants": [
{
"id": "accc25f2-43a0-4d41-804f-3c8aec853c97",
"player_id": "c232f334-3c2d-4e62-9c09-23d045e26bf8",
"faction": "Humans"
},
{
"id": "179bdab6-8630-4a92-8fb6-d2637562c66c",
"player_id": "82ef39f2-ff50-4f3f-af74-d0cee7ec5a84",
"faction": "Orcs"
},
{
"id": "1f734e71-bd5b-4c8e-b200-daaafa57f748",
"player_id": "d00faaf1-5200-4fb0-bfa7-6b5fe89a4c87",
"faction": "Elves"
},
{
"id": "fd55d0cf-67f6-4b5a-87d0-6f0eabcc84f1",
"player_id": "d5a430cd-11d3-40d4-bf2a-5771cde3c0a9",
"faction": "Dwarves"
}
],
"objectives": [
{
"id": "effed73b-a408-425b-a53c-2bb847342b2a",
"name": "Military",
"description": "weapons, facilities, organization, etc."
},
{
"id": "05480fff-d0f8-43df-a315-f99222b0c38b",
"name": "Spiritual",
"description": "religion, worship, philosophy, etc."
},
{
"id": "7968f01e-844c-4673-beab-98f8fb837b64",
"name": "Occult",
"description": "esotericism, magic, the supernatural, etc."
},
{
"id": "4d2aa40f-a5f2-49c4-9c68-9bf221ab8931",
"name": "Political",
"description": "alliances/betrayal, institutions, ideology, etc."
}
],
"campaigns": [
{
"id": "0e7f9b46-a3c9-46f6-84b8-d1fe0bc88717",
"name": "Epic Conquest",
"month": 1,
"participants": [
{
"id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"war_participant_id": "accc25f2-43a0-4d41-804f-3c8aec853c97",
"leader": "Emperor",
"theme": "Empire"
},
{
"id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"war_participant_id": "179bdab6-8630-4a92-8fb6-d2637562c66c",
"leader": "Warchief",
"theme": "Wild"
},
{
"id": "237c1291-4331-4242-bd70-bf648185a627",
"war_participant_id": "1f734e71-bd5b-4c8e-b200-daaafa57f748",
"leader": "Lord",
"theme": "Sylvan"
},
{
"id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"war_participant_id": "fd55d0cf-67f6-4b5a-87d0-6f0eabcc84f1",
"leader": "King",
"theme": "Mines"
}
],
"sectors": [
{
"id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"name": "Village",
"round_id": "9771379e-12ae-4d55-ad55-65c08ed95dcd",
"major_objective_id": "effed73b-a408-425b-a53c-2bb847342b2a",
"minor_objective_id": "05480fff-d0f8-43df-a315-f99222b0c38b",
"influence_objective_id": "4d2aa40f-a5f2-49c4-9c68-9bf221ab8931",
"mission": "Capture the flag",
"description": "Wooden houses and windmill"
},
{
"id": "02f3e570-b395-4ea9-b9b6-61eea182c754",
"name": "Suburbs",
"round_id": "9771379e-12ae-4d55-ad55-65c08ed95dcd",
"major_objective_id": "7968f01e-844c-4673-beab-98f8fb837b64",
"minor_objective_id": "05480fff-d0f8-43df-a315-f99222b0c38b",
"influence_objective_id": null,
"mission": "Hold the line",
"description": "Bare shacks and ruined fabrics"
},
{
"id": "4548997e-50e5-493a-b751-483f5bcccc00",
"name": "Riverside",
"round_id": "4402eb72-bd50-4120-947c-c1a24a8c3117",
"major_objective_id": "05480fff-d0f8-43df-a315-f99222b0c38b",
"minor_objective_id": "7968f01e-844c-4673-beab-98f8fb837b64",
"influence_objective_id": "4d2aa40f-a5f2-49c4-9c68-9bf221ab8931",
"mission": "Zone control",
"description": "Large river with massive bridge"
},
{
"id": "4237c185-5d15-4cfc-bd99-669fa8ea2aeb",
"name": "Mountains",
"round_id": "4402eb72-bd50-4120-947c-c1a24a8c3117",
"major_objective_id": "effed73b-a408-425b-a53c-2bb847342b2a",
"minor_objective_id": "7968f01e-844c-4673-beab-98f8fb837b64",
"influence_objective_id": null,
"mission": "Annihilation",
"description": "Cliffs and steep landscapestrghs"
},
{
"id": "d1f7c6cf-40ff-42b8-b1b6-05576017de1f",
"name": "Underground",
"round_id": "a985f5a7-c411-4e31-98fb-8402503880a0",
"major_objective_id": "7968f01e-844c-4673-beab-98f8fb837b64",
"minor_objective_id": "05480fff-d0f8-43df-a315-f99222b0c38b",
"influence_objective_id": null,
"mission": "Double zone",
"description": "Dirty sewers"
},
{
"id": "096da98e-d8b4-4c71-946a-3cee2cc38499",
"name": "Castle",
"round_id": "a985f5a7-c411-4e31-98fb-8402503880a0",
"major_objective_id": "effed73b-a408-425b-a53c-2bb847342b2a",
"minor_objective_id": "05480fff-d0f8-43df-a315-f99222b0c38b",
"influence_objective_id": "4d2aa40f-a5f2-49c4-9c68-9bf221ab8931",
"mission": "Siege",
"description": "Massive dungon with high walls."
}
],
"rounds": [
{
"id": "4402eb72-bd50-4120-947c-c1a24a8c3117",
"choices": [
{
"participant_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"priority_sector_id": "4548997e-50e5-493a-b751-483f5bcccc00",
"secondary_sector_id": "4237c185-5d15-4cfc-bd99-669fa8ea2aeb",
"comment": null
},
{
"participant_id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"priority_sector_id": "4237c185-5d15-4cfc-bd99-669fa8ea2aeb",
"secondary_sector_id": "4548997e-50e5-493a-b751-483f5bcccc00",
"comment": null
},
{
"participant_id": "237c1291-4331-4242-bd70-bf648185a627",
"priority_sector_id": "4237c185-5d15-4cfc-bd99-669fa8ea2aeb",
"secondary_sector_id": "4548997e-50e5-493a-b751-483f5bcccc00",
"comment": null
},
{
"participant_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"priority_sector_id": "4548997e-50e5-493a-b751-483f5bcccc00",
"secondary_sector_id": "4237c185-5d15-4cfc-bd99-669fa8ea2aeb",
"comment": null
}
],
"battles": [
{
"sector_id": "4548997e-50e5-493a-b751-483f5bcccc00",
"player_1_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"player_2_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"winner_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"score": "3/0",
"victory_condition": "Mission",
"comment": "Domination all along."
},
{
"sector_id": "4237c185-5d15-4cfc-bd99-669fa8ea2aeb",
"player_1_id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"player_2_id": "237c1291-4331-4242-bd70-bf648185a627",
"winner_id": "237c1291-4331-4242-bd70-bf648185a627",
"score": "1/2",
"victory_condition": "Assassination",
"comment": "Stable control until sneaky plot twist."
}
],
"is_over": true
},
{
"id": "9771379e-12ae-4d55-ad55-65c08ed95dcd",
"choices": [
{
"participant_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"priority_sector_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"secondary_sector_id": "02f3e570-b395-4ea9-b9b6-61eea182c754",
"comment": null
},
{
"participant_id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"priority_sector_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"secondary_sector_id": "02f3e570-b395-4ea9-b9b6-61eea182c754",
"comment": null
},
{
"participant_id": "237c1291-4331-4242-bd70-bf648185a627",
"priority_sector_id": "02f3e570-b395-4ea9-b9b6-61eea182c754",
"secondary_sector_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"comment": null
},
{
"participant_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"priority_sector_id": "02f3e570-b395-4ea9-b9b6-61eea182c754",
"secondary_sector_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"comment": null
}
],
"battles": [
{
"sector_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"player_1_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"player_2_id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"winner_id": null,
"score": "2/2",
"victory_condition": "Draw",
"comment": "Long holding but no overcome..."
},
{
"sector_id": "02f3e570-b395-4ea9-b9b6-61eea182c754",
"player_1_id": "237c1291-4331-4242-bd70-bf648185a627",
"player_2_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"winner_id": "237c1291-4331-4242-bd70-bf648185a627",
"score": "3/2",
"victory_condition": "Mission",
"comment": "Impressive battle with tactical moves."
}
],
"is_over": true
},
{
"id": "a985f5a7-c411-4e31-98fb-8402503880a0",
"choices": [
{
"participant_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"priority_sector_id": null,
"secondary_sector_id": null,
"comment": null
},
{
"participant_id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"priority_sector_id": null,
"secondary_sector_id": null,
"comment": null
},
{
"participant_id": "237c1291-4331-4242-bd70-bf648185a627",
"priority_sector_id": null,
"secondary_sector_id": null,
"comment": null
},
{
"participant_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"priority_sector_id": null,
"secondary_sector_id": null,
"comment": null
}
],
"battles": [
{
"sector_id": "d1f7c6cf-40ff-42b8-b1b6-05576017de1f",
"player_1_id": "602e2eaf-297e-490b-b0e9-efec818e466a",
"player_2_id": "237c1291-4331-4242-bd70-bf648185a627",
"winner_id": null,
"score": null,
"victory_condition": "tie",
"comment": "Never finished..."
},
{
"sector_id": "096da98e-d8b4-4c71-946a-3cee2cc38499",
"player_1_id": "1f6b4e7c-b1e4-4a2e-9aea-7bb75b20b4de",
"player_2_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"winner_id": "876a1684-7fe2-4d8d-a50a-a4d7e354c5b0",
"score": "4/2",
"victory_condition": "Mission",
"comment": "Decisive fast attack impossible to resist."
}
],
"is_over": false
}
],
"is_over": false
}
],
"events": [
{
"type": "InfluenceGained",
"id": "8f20b587-0ff3-42c9-9965-88112e2a4e74",
"participant_id": "accc25f2-43a0-4d41-804f-3c8aec853c97",
"context_type": "battle",
"context_id": "4548997e-50e5-493a-b751-483f5bcccc00",
"timestamp": "2026-02-26T16:10:54.125407",
"amount": 1
},
{
"type": "TieResolved",
"id": "c83a9d4e-4620-47de-93c8-d29d6e119c59",
"participant_id": null,
"context_type": "battle",
"context_id": "79accf7c-2d93-4ac3-b747-e7092bfe3feb",
"timestamp": "2026-02-26T16:11:44.346337",
"score_value": null
}
],
"is_over": false
}
]
}