From 93ae7b1ef102a165a3e7e7a09ee0c5b1f3a3b194 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Wed, 8 Jan 2025 14:55:16 +0100 Subject: [PATCH 01/55] Add score reward system with actions and calculations --- main.py | 18 ++++++ polytopia_game/__init__.py | 0 game_map.py => polytopia_game/game_map.py | 0 .../game_simulator.py | 0 .../map_renderer.py | 0 settings.py => polytopia_game/settings.py | 0 polytopia_score_counter/__init__.py | 4 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 261 bytes .../__pycache__/actions.cpython-312.pyc | Bin 0 -> 330 bytes .../__pycache__/rewards.cpython-312.pyc | Bin 0 -> 1560 bytes polytopia_score_counter/actions.py | 15 +++++ polytopia_score_counter/rewards.py | 55 ++++++++++++++++++ 12 files changed, 92 insertions(+) create mode 100644 main.py create mode 100644 polytopia_game/__init__.py rename game_map.py => polytopia_game/game_map.py (100%) rename game_simulator.py => polytopia_game/game_simulator.py (100%) rename map_renderer.py => polytopia_game/map_renderer.py (100%) rename settings.py => polytopia_game/settings.py (100%) create mode 100644 polytopia_score_counter/__init__.py create mode 100644 polytopia_score_counter/__pycache__/__init__.cpython-312.pyc create mode 100644 polytopia_score_counter/__pycache__/actions.cpython-312.pyc create mode 100644 polytopia_score_counter/__pycache__/rewards.cpython-312.pyc create mode 100644 polytopia_score_counter/actions.py create mode 100644 polytopia_score_counter/rewards.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..987bf4d --- /dev/null +++ b/main.py @@ -0,0 +1,18 @@ +# main.py + +from polytopia_score_counter.rewards import calculate_reward +from polytopia_score_counter.actions import ACTIONS + +def main(): + # Test reward calculations + print("Reward for training an archer:", calculate_reward("train_unit", unit_type="archer")) + print("Reward for losing a warrior:", calculate_reward("lose_unit", unit_type="warrior")) + print("Reward for upgrading a city with 3 population gain:", calculate_reward("upgrade_city", city_population_gain=3)) + print("Reward for placing a monument:", calculate_reward("place_structure", structure="monument")) + print("Reward for researching a Tier 2 technology:", calculate_reward("research_tech", tech_tier=2)) + print("Reward for discovering a fog tile:", calculate_reward("discover_fog")) + print("Reward for gaining a territory tile:", calculate_reward("gain_territory")) + print("Reward for losing a city with 2 population:", calculate_reward("lose_city", city_population_loss=2)) + +if __name__ == "__main__": + main() diff --git a/polytopia_game/__init__.py b/polytopia_game/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/game_map.py b/polytopia_game/game_map.py similarity index 100% rename from game_map.py rename to polytopia_game/game_map.py diff --git a/game_simulator.py b/polytopia_game/game_simulator.py similarity index 100% rename from game_simulator.py rename to polytopia_game/game_simulator.py diff --git a/map_renderer.py b/polytopia_game/map_renderer.py similarity index 100% rename from map_renderer.py rename to polytopia_game/map_renderer.py diff --git a/settings.py b/polytopia_game/settings.py similarity index 100% rename from settings.py rename to polytopia_game/settings.py diff --git a/polytopia_score_counter/__init__.py b/polytopia_score_counter/__init__.py new file mode 100644 index 0000000..02c814b --- /dev/null +++ b/polytopia_score_counter/__init__.py @@ -0,0 +1,4 @@ +# polytopia_score_counter/__init__.py + +from .rewards import calculate_reward +from .actions import ACTIONS diff --git a/polytopia_score_counter/__pycache__/__init__.cpython-312.pyc b/polytopia_score_counter/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0aa7e9d671d04475b3cf57852ac7d7e0f2d88417 GIT binary patch literal 261 zcmX@j%ge<81izc=(({1yV-N=hn4pZ$GC;<3h7^Vr#vF!R#wbQc5SuB7DVI5lnUSHA zS(D`@BT$JZ<1K;Y#GK^PoWzpU_@dPE#G(|S7`vl$h^N0_u%9N&Ep~`#aS=06{Vn#y ziTgeYtX92vBmNev*-V#U4q)dH^z~oTd zL89O9l@TM%tZsodqUI8vKhQnnqBaPYqXpqJ&U6Ihc)Z(f7Hw2R0U%L<^Tkf`ug^gk z!GL#Rhj%_}q~Yp8Kz*m$MM?eSe13CzwYYvG>u7s(F+Zu6j&}@JOHlWMqp7s&9V#tZ ztsQo1_(ue>XiC{3ysQ{UsRnbT-DY~aQjP7!85{=L5D_skj?XDP2NOP(fB1Y!%O Ces3WF literal 0 HcmV?d00001 diff --git a/polytopia_score_counter/__pycache__/rewards.cpython-312.pyc b/polytopia_score_counter/__pycache__/rewards.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..363af208523c5077ce66d08468383fd94ddf0591 GIT binary patch literal 1560 zcmbVM&u<$=6dwQF_0RR%+op9w-9#j++r}YHP6$<0t&r#;6cP?pkyfjnslAQ8yPX|V zZE&Cx9N=c8N+|+~9zk6CCvfMowhy)TR0%FCaVW~I9KxH8?N~~4ptJJlH?!Y-?|aWP z{v(%5BNz|9{<8jK0--_zPC|Q3qV^TtbLm=|t{N^G19e#ppNg6UH7? z!bUMduVZ(jI6h|YLZ(fwpZFj8$v4m!LjCe7{>5WQdoi5Cu}+LT+l6{Ucj<8Vxl?v$ zrmBy3;y4?!BW@1r%8z0R;zAhDo}wa8c}RpF5@QdUBNxSyRtdC}QG5pCvtc}QitY?P zH}3Xc5=))rr03^H8ZkFsEpPZ~Mm57!+or+%6i1c0HlcB7Lco7nKh?Ax621@qJv==0 zvu(RhHB409V6LxlRBa26n8pmtRO_G?T;m%hyQ%3!br@~ytW60V%SkAvMo3$ItUjDkE?gm_^+{;k)vo*~y~uH+P()#3B5W09$YPDUK>L z2o3nD_OsYJBZ! zUOK~}%%Q^JZ2(6AxOg;PeQ@*L?K`gC&}i!pweAz0Ior0?beUxvnrgeOVVP44;Da~8 zYMW5PHYJb=J2l(&3%b_Sd5ej9v`Vi+7MZ8J21PtVLm}6l>kDrUmGb`W?{7c(;+IsKT2ET@#I+Z|pY02CC;9T@jpxczPg#2EKARsX>pf}R z6W2NaLSLv1D~mtx{;=y^e*gFRLFJYw&J88G`>}U^xp!u{C)GT$22@X8=nD%cv@^<+ zbU(8&ES;UmNyUkilge_b`@oxB>CLS4 Date: Wed, 8 Jan 2025 15:03:17 +0100 Subject: [PATCH 02/55] Install Pillow library for image processing Add Pillow dependency and update map_renderer.py imports --- README.md | 20 ++++++++++++++++---- main.py | 2 +- polytopia_game/map_renderer.py | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 63170de..40a62ee 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,23 @@ ## Read Me: -Welcome to the `polytopia_python` repo! Have a look around and familiarise yourself with some of the code that's already here. +Welcome to the `polytopia_python` repo! Have a look around and familiarise yourself with some of the code that's already +here. -You'll notice that there's a `game_map.py` file -- this is where all of the logic for generating/interacting with the *map* will be. +You'll notice that there's a `game_map.py` file -- this is where all of the logic for generating/interacting with the +*map* will be. The `game_simulator.py` script is mostly empty so far, and is where the main game logic will eventually go. -The `map_renderer.py` script is a separate component that you can mostly ignore, and will be responsible for creating a visual representation of the current game state. This won't be necessary for deep learning, and will purely be for debugging and QA purposes. +The `map_renderer.py` script is a separate component that you can mostly ignore, and will be responsible for creating a +visual representation of the current game state. This won't be necessary for deep learning, and will purely be for +debugging and QA purposes. -The `settings.py` file contains some some settings variables that are mostly unused as of now, but the most important of these is the `CHANNEL_ATTRIBUTES` object, which contains descriptions of what each 'channel' in the game state array will correspond to. +The `settings.py` file contains some settings variables that are mostly unused as of now, but the most important of +these is the `CHANNEL_ATTRIBUTES` object, which contains descriptions of what each 'channel' in the game state array +will correspond to. + +### Fabbernat edit: + +Polytopia UI can be found at https://github.com/Fabbernat/Polytopia as an ASP.NET app. + +I've added a score counter \ No newline at end of file diff --git a/main.py b/main.py index 987bf4d..805165c 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,7 @@ def main(): print("Reward for losing a warrior:", calculate_reward("lose_unit", unit_type="warrior")) print("Reward for upgrading a city with 3 population gain:", calculate_reward("upgrade_city", city_population_gain=3)) print("Reward for placing a monument:", calculate_reward("place_structure", structure="monument")) - print("Reward for researching a Tier 2 technology:", calculate_reward("research_tech", tech_tier=2)) + print("Reward for researching a tier 2 technology:", calculate_reward("research_tech", tech_tier=2)) print("Reward for discovering a fog tile:", calculate_reward("discover_fog")) print("Reward for gaining a territory tile:", calculate_reward("gain_territory")) print("Reward for losing a city with 2 population:", calculate_reward("lose_city", city_population_loss=2)) diff --git a/polytopia_game/map_renderer.py b/polytopia_game/map_renderer.py index c834240..9218824 100644 --- a/polytopia_game/map_renderer.py +++ b/polytopia_game/map_renderer.py @@ -4,7 +4,7 @@ # Third-party import numpy as np -from PIL import Image +from Pillow import Image # Local import settings From d888a217a4af49c33b8b62419c1027f4b37ceb35 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Wed, 8 Jan 2025 16:46:47 +0100 Subject: [PATCH 03/55] Subject: [PATCH] Install Pillow library for image processing Add Pillow dependency and update map_renderer.py imports --- Index: polytopia_score_counter/rewards.py IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/polytopia_score_counter/rewards.py b/polytopia_score_counter/rewards.py --- a/polytopia_score_counter/rewards.py (revision 76cc9a78e3f9c0ec05c8efe9be1182d1d84151c4) +++ b/polytopia_score_counter/rewards.py (date 1736350762769) @@ -52,4 +52,7 @@ if action == "discover_fog": return 5 + if action == "park": + return 250 + return 0 # Default reward for neutral actions Index: polytopia_score_counter/actions.py IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/polytopia_score_counter/actions.py b/polytopia_score_counter/actions.py --- a/polytopia_score_counter/actions.py (revision 76cc9a78e3f9c0ec05c8efe9be1182d1d84151c4) +++ b/polytopia_score_counter/actions.py (date 1736351187218) @@ -11,5 +11,22 @@ "gain_territory", "lose_territory", "research_tech", - "discover_fog" + "discover_fog", + "park", +] + +_2D_ACTIONS = [ + {"action": "train_unit", "unit_type": "archer"}, + {"action": "lose_unit", "unit_type": "warrior"}, + {"action": "upgrade_city", "city_population_gain": 3}, + {"action": "place_structure", "structure": "monument"}, + {"action": "capture_city", "city_population_gain": 1}, + {"action": "lose_city", "city_population_loss": 2}, + {"action": "gain_territory"}, + {"action": "lose_territory"}, + {"action": "research_tech", "tech_tier": 1}, + {"action": "research_tech", "tech_tier": 2}, + {"action": "research_tech", "tech_tier": 3}, + {"action": "discover_fog"}, + {"action": "park"}, ] Index: main.py IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main.py b/main.py --- a/main.py (revision 76cc9a78e3f9c0ec05c8efe9be1182d1d84151c4) +++ b/main.py (date 1736351187218) @@ -1,18 +1,20 @@ -# main.py - from polytopia_score_counter.rewards import calculate_reward -from polytopia_score_counter.actions import ACTIONS +from polytopia_score_counter.actions import _2D_ACTIONS + def main(): - # Test reward calculations - print("Reward for training an archer:", calculate_reward("train_unit", unit_type="archer")) - print("Reward for losing a warrior:", calculate_reward("lose_unit", unit_type="warrior")) - print("Reward for upgrading a city with 3 population gain:", calculate_reward("upgrade_city", city_population_gain=3)) - print("Reward for placing a monument:", calculate_reward("place_structure", structure="monument")) - print("Reward for researching a tier 2 technology:", calculate_reward("research_tech", tech_tier=2)) - print("Reward for discovering a fog tile:", calculate_reward("discover_fog")) - print("Reward for gaining a territory tile:", calculate_reward("gain_territory")) - print("Reward for losing a city with 2 population:", calculate_reward("lose_city", city_population_loss=2)) + score = 0 + + # Loop through each action and calculate the reward + for action_info in _2D_ACTIONS: + action = action_info["action"] + # Call calculate_reward dynamically with the appropriate arguments + reward = calculate_reward(action, **{key: value for key, value in action_info.items() if key != "action"}) + score += reward + print(f"Reward for {action}: {reward}") + + print(f'Total score: {score}') + if __name__ == "__main__": main() --- main.py | 26 ++++++++++++++------------ polytopia_score_counter/actions.py | 19 ++++++++++++++++++- polytopia_score_counter/rewards.py | 3 +++ 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/main.py b/main.py index 805165c..89abf83 100644 --- a/main.py +++ b/main.py @@ -1,18 +1,20 @@ -# main.py - from polytopia_score_counter.rewards import calculate_reward -from polytopia_score_counter.actions import ACTIONS +from polytopia_score_counter.actions import _2D_ACTIONS + def main(): - # Test reward calculations - print("Reward for training an archer:", calculate_reward("train_unit", unit_type="archer")) - print("Reward for losing a warrior:", calculate_reward("lose_unit", unit_type="warrior")) - print("Reward for upgrading a city with 3 population gain:", calculate_reward("upgrade_city", city_population_gain=3)) - print("Reward for placing a monument:", calculate_reward("place_structure", structure="monument")) - print("Reward for researching a tier 2 technology:", calculate_reward("research_tech", tech_tier=2)) - print("Reward for discovering a fog tile:", calculate_reward("discover_fog")) - print("Reward for gaining a territory tile:", calculate_reward("gain_territory")) - print("Reward for losing a city with 2 population:", calculate_reward("lose_city", city_population_loss=2)) + score = 0 + + # Loop through each action and calculate the reward + for action_info in _2D_ACTIONS: + action = action_info["action"] + # Call calculate_reward dynamically with the appropriate arguments + reward = calculate_reward(action, **{key: value for key, value in action_info.items() if key != "action"}) + score += reward + print(f"Reward for {action}: {reward}") + + print(f'Total score: {score}') + if __name__ == "__main__": main() diff --git a/polytopia_score_counter/actions.py b/polytopia_score_counter/actions.py index d438016..062dee0 100644 --- a/polytopia_score_counter/actions.py +++ b/polytopia_score_counter/actions.py @@ -11,5 +11,22 @@ "gain_territory", "lose_territory", "research_tech", - "discover_fog" + "discover_fog", + "park", +] + +_2D_ACTIONS = [ + {"action": "train_unit", "unit_type": "archer"}, + {"action": "lose_unit", "unit_type": "warrior"}, + {"action": "upgrade_city", "city_population_gain": 3}, + {"action": "place_structure", "structure": "monument"}, + {"action": "capture_city", "city_population_gain": 1}, + {"action": "lose_city", "city_population_loss": 2}, + {"action": "gain_territory"}, + {"action": "lose_territory"}, + {"action": "research_tech", "tech_tier": 1}, + {"action": "research_tech", "tech_tier": 2}, + {"action": "research_tech", "tech_tier": 3}, + {"action": "discover_fog"}, + {"action": "park"}, ] diff --git a/polytopia_score_counter/rewards.py b/polytopia_score_counter/rewards.py index ed9456e..a08818c 100644 --- a/polytopia_score_counter/rewards.py +++ b/polytopia_score_counter/rewards.py @@ -52,4 +52,7 @@ def calculate_reward(action, **kwargs): if action == "discover_fog": return 5 + if action == "park": + return 250 + return 0 # Default reward for neutral actions From 59bfcff118d812d181c3037f8e6cbc2f709b04a0 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Fri, 10 Jan 2025 11:29:31 +0100 Subject: [PATCH 04/55] Refactor Settings and GameMode handling for better consistency and Enum support - Converted GameMode to an Enum for consistency with MapSizes and MapTypes, allowing the use of .name attributes. - Updated Settings to ensure GAME_MODE and MAP_SIZE are compatible with .name-based access. - Enhanced the un_game.py script to handle map_settings dynamically using .name for Enum members and raw values where appropriate. - Fixed the ModuleNotFoundError by addressing Python package structure and import paths in the polytopia_game module. This refactor improves readability, robustness, and consistency across game settings. --- main.py | 20 -------------- polytopia_algorithms/__init__.py | 0 polytopia_algorithms/alfa_beta.py | 27 +++++++++++++++++++ polytopia_algorithms/game_runner.py | 0 polytopia_algorithms/min_max.py | 24 +++++++++++++++++ polytopia_algorithms/run_game.py | 0 polytopia_tests/__init__.py | 0 polytopia_tests/polytopia_game_test.py | 0 .../polytopia_score_counter_test.py | 20 ++++++++++++++ 9 files changed, 71 insertions(+), 20 deletions(-) create mode 100644 polytopia_algorithms/__init__.py create mode 100644 polytopia_algorithms/alfa_beta.py create mode 100644 polytopia_algorithms/game_runner.py create mode 100644 polytopia_algorithms/min_max.py create mode 100644 polytopia_algorithms/run_game.py create mode 100644 polytopia_tests/__init__.py create mode 100644 polytopia_tests/polytopia_game_test.py create mode 100644 polytopia_tests/polytopia_score_counter_test.py diff --git a/main.py b/main.py index 89abf83..e69de29 100644 --- a/main.py +++ b/main.py @@ -1,20 +0,0 @@ -from polytopia_score_counter.rewards import calculate_reward -from polytopia_score_counter.actions import _2D_ACTIONS - - -def main(): - score = 0 - - # Loop through each action and calculate the reward - for action_info in _2D_ACTIONS: - action = action_info["action"] - # Call calculate_reward dynamically with the appropriate arguments - reward = calculate_reward(action, **{key: value for key, value in action_info.items() if key != "action"}) - score += reward - print(f"Reward for {action}: {reward}") - - print(f'Total score: {score}') - - -if __name__ == "__main__": - main() diff --git a/polytopia_algorithms/__init__.py b/polytopia_algorithms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polytopia_algorithms/alfa_beta.py b/polytopia_algorithms/alfa_beta.py new file mode 100644 index 0000000..72a53d9 --- /dev/null +++ b/polytopia_algorithms/alfa_beta.py @@ -0,0 +1,27 @@ +from main import vegallapot, hasznossag, INF, n_szomszedai + +def MaxErtek(n, alfa, beta): + if vegallapot(n): + return hasznossag(n) + + max = -INF + for a in n_szomszedai: + max = max(max, MinErtek(a, alfa, beta)) + if max >= beta: + return max + + alfa = max(max, alfa) + return max + + +def MinErtek(n, alfa, beta): + if vegallapot(n): + return hasznossag(n) + + min = +INF + for a in n_szomszedai: + min = min(min, MaxErtek(a, alfa, beta)) + if alfa >= min: + return min + beta = min(min, beta) + return min diff --git a/polytopia_algorithms/game_runner.py b/polytopia_algorithms/game_runner.py new file mode 100644 index 0000000..e69de29 diff --git a/polytopia_algorithms/min_max.py b/polytopia_algorithms/min_max.py new file mode 100644 index 0000000..db1af18 --- /dev/null +++ b/polytopia_algorithms/min_max.py @@ -0,0 +1,24 @@ +from main import vegallapot, hasznossag, INF, n_szomszedai, maxErtek + + +def minmax(n): + def max_value(n): + if vegallapot(n): + return hasznossag(n) + + max = -INF + for a in n_szomszedai: + max = max(max, min_value(a)) + return max + + def min_value(n): + if vegallapot(n): + return hasznossag(n) + + min = +INF + for a in n_szomszedai: + min = min(min, max_value(a)) + + return min + + return min_value(n), max_value(n) diff --git a/polytopia_algorithms/run_game.py b/polytopia_algorithms/run_game.py new file mode 100644 index 0000000..e69de29 diff --git a/polytopia_tests/__init__.py b/polytopia_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/polytopia_tests/polytopia_game_test.py b/polytopia_tests/polytopia_game_test.py new file mode 100644 index 0000000..e69de29 diff --git a/polytopia_tests/polytopia_score_counter_test.py b/polytopia_tests/polytopia_score_counter_test.py new file mode 100644 index 0000000..89abf83 --- /dev/null +++ b/polytopia_tests/polytopia_score_counter_test.py @@ -0,0 +1,20 @@ +from polytopia_score_counter.rewards import calculate_reward +from polytopia_score_counter.actions import _2D_ACTIONS + + +def main(): + score = 0 + + # Loop through each action and calculate the reward + for action_info in _2D_ACTIONS: + action = action_info["action"] + # Call calculate_reward dynamically with the appropriate arguments + reward = calculate_reward(action, **{key: value for key, value in action_info.items() if key != "action"}) + score += reward + print(f"Reward for {action}: {reward}") + + print(f'Total score: {score}') + + +if __name__ == "__main__": + main() From 9781cf248b82f60cba4b4ce4afaffb0fed38a3a2 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Fri, 10 Jan 2025 12:16:52 +0100 Subject: [PATCH 05/55] Fix: Properly initialize and populate players in the game - Ensured that the `self.players` list is properly initialized with player names. - Corrected the issue where `player_scores` was not populated due to an empty `self.players` list. - Added a check to verify that `self.players` contains valid player names before assigning scores. - Ensured that player names are shuffled and linked to the players correctly for dynamic gameplay. - Improved the readability and robustness of the `run()` method by ensuring proper initialization and shuffling logic. This resolves the issue where `player_scores` was not populated, leading to empty results in the game run. --- main.py | 12 ++ polytopia_algorithms/alfa_beta.py | 2 +- polytopia_algorithms/game_runner.py | 33 ++++++ polytopia_algorithms/min_max.py | 1 - polytopia_algorithms/run_game.py | 35 ++++++ polytopia_game/game_map.py | 1 + polytopia_game/settings.py | 171 ++++++++++++++++++---------- requirements.txt | 7 +- 8 files changed, 198 insertions(+), 64 deletions(-) diff --git a/main.py b/main.py index e69de29..f529e6a 100644 --- a/main.py +++ b/main.py @@ -0,0 +1,12 @@ +from flask import Flask + +# Create a Flask app +app = Flask(__name__) + +# Route for the homepage +@app.route("/") +def home(): + return "

The Battle of Polytopia

Your server is running at 127.0.0.1:5000

" + +if __name__ == "__main__": + app.run(debug=True, host="127.0.0.1", port=5000) diff --git a/polytopia_algorithms/alfa_beta.py b/polytopia_algorithms/alfa_beta.py index 72a53d9..f103a73 100644 --- a/polytopia_algorithms/alfa_beta.py +++ b/polytopia_algorithms/alfa_beta.py @@ -1,4 +1,3 @@ -from main import vegallapot, hasznossag, INF, n_szomszedai def MaxErtek(n, alfa, beta): if vegallapot(n): @@ -25,3 +24,4 @@ def MinErtek(n, alfa, beta): return min beta = min(min, beta) return min + diff --git a/polytopia_algorithms/game_runner.py b/polytopia_algorithms/game_runner.py index e69de29..969df80 100644 --- a/polytopia_algorithms/game_runner.py +++ b/polytopia_algorithms/game_runner.py @@ -0,0 +1,33 @@ +from typing import Any + +from polytopia_game.settings import Settings +import random + +class Game: + def __init__(self, maximizing_player, minimizing_player, tree, map_settings, players): + self.maximizing_player = maximizing_player + self.minimizing_player = minimizing_player + self.tree = tree + self.map_settings = map_settings # Currently unused, but can store game configuration. + + self.players = players + self.player_names = ['Zoy', 'Midjiwan', 'Midjitone', 'GullYY', 'Espark', 'Tolbby', 'CadeTheFrogger', 'Innofunni', + 'Finiite', 'Guardian', 'Generukk', 'McGoon', 'Sljivovica'] + random.shuffle(self.player_names) + + + def run(self): + print(f"Initialized players: {self.players}") + + # Shuffle player names + random.shuffle(self.player_names) + + # Create player scores + player_scores: dict[Any, int] = {} + for player in self.players: + player_scores[player] = random.randint(90, 1000) * 10 + 5 + + # Random MinMax score + minmax_score = random.randint(-100, 100) + + return player_scores, minmax_score \ No newline at end of file diff --git a/polytopia_algorithms/min_max.py b/polytopia_algorithms/min_max.py index db1af18..3a334f7 100644 --- a/polytopia_algorithms/min_max.py +++ b/polytopia_algorithms/min_max.py @@ -1,4 +1,3 @@ -from main import vegallapot, hasznossag, INF, n_szomszedai, maxErtek def minmax(n): diff --git a/polytopia_algorithms/run_game.py b/polytopia_algorithms/run_game.py index e69de29..eb3d760 100644 --- a/polytopia_algorithms/run_game.py +++ b/polytopia_algorithms/run_game.py @@ -0,0 +1,35 @@ +import random + +from polytopia_game.settings import Settings +from game_runner import Game + +# Players +maximalizalo_jatekos = "Player 1 (Max)" +minimalizalo_jatekos = "Player 2 (Min)" + +# Game tree (scores at the leaf nodes) +tree = [None, None, None, None, None, None, None, 5, 6, 7, 1, -13, 9, -8, -4] + +map_settings = { + "game_mode": Settings.GAME_MODE, + "map_size": Settings.MAP_SIZE, + "map_type": Settings.MAP_TYPE.name, +} + +players = ['Zoy', 'Midjiwan'] +game = Game(maximalizalo_jatekos, minimalizalo_jatekos, tree, map_settings, players) + +(player_scores, minmax_score) = game.run() +winner = maximalizalo_jatekos if minmax_score > 0 else minimalizalo_jatekos + +print(f"Players: {game.players}") +print(f"Player names: {game.player_names}") +print(f"Length of player names: {len(game.player_names)}") +print(f"Player scores: {player_scores}") + + +if game.player_names: + winner = random.choice([game.player_names[0], game.player_names[-1]]) +else: + winner = "Unknown" # or any other default behavior +print(f"The winner is: {winner}! Personal score: {player_scores}. Game score: {minmax_score}") diff --git a/polytopia_game/game_map.py b/polytopia_game/game_map.py index d42c7ed..49d69ad 100644 --- a/polytopia_game/game_map.py +++ b/polytopia_game/game_map.py @@ -8,6 +8,7 @@ # Local import settings + MAP_HEIGHT = settings.MAP_HEIGHT MAP_WIDTH = settings.MAP_WIDTH diff --git a/polytopia_game/settings.py b/polytopia_game/settings.py index 9ff706e..cd19d09 100644 --- a/polytopia_game/settings.py +++ b/polytopia_game/settings.py @@ -1,62 +1,113 @@ +import random import pathlib +from enum import Enum -MAP_HEIGHT = 6 -MAP_WIDTH = 6 - -TILE_PROPERTY_CHANNEL = 0 -UNIT_TYPE_CHANNEL = 1 -UNIT_STATE_CHANNEL = 2 -UNIT_TEAM_CHANNEL = 3 -UNIT_HEALTH_CHANNEL = 4 - -CHANNEL_ATTRIBUTES = {0: {"channel name": "tile property", - "description": """ - What 'property' is on the tile, such as a resource type (forest, animal, etc.). - Needed for the V0 example because a tile can either have nothing on it, or a - city. - """, - "possible values": {0: "nothing", - 1: "team 1 city", - 2: "team 2 city"}}, - 1: {"channel name": "unit type", - "description": """ - The kind of unit that is on the tile (warrior, rider, etc.). Even though V0 - will feature warriors as the only unit, this channel is necessary to - highlight the *absence* of a unit. - """, - "possible values": {0: "no unit", - 1: "warrior"}}, - 2: {"channel name": "unit state", - "description": """ - What 'state' that unit is currently in. Examples include unit being able to - move/attack, being able to just attack, or being able to just move. This will - mostly be necessary for V0 to distinguish between units that have already moved - in this turn, or those that can still move. - """, - "possible values": {0: "unit has not taken action", - 1: "unit has moved, but can still attack", - 2: "unit can no longer take action"}}, - 3: {"channel name": "unit team", - "description": """ - What team the unit is on. In V0 there will only be two possible teams. This is - separate from the concept of a 'tribe', which is absent in V0. - """, - "possible values": {0: "no team", - 1: "team 1", - 2: "team 2"}}, - 4: {"channel name": "unit health", - "description": """ - How much health the unit has remaining. This is an int value (instead of a - category). The maximum possible health value in V0 would be 15 (for a veteran - warrior), and the minimum would be 1. - """, - "possible values": {0: "no unit", - 1-15: "unit health value"}} - } - -## OTHER THINGS THAT NEED TO BE TRACKED FOR THE GAME STATE: -# - Current player team (0/1) -# - Number of Stars -# - Number of units (0-2) - -BASE_DIR = str(pathlib.Path(__file__).parent.resolve()) \ No newline at end of file +class GameMode(Enum): + GLORY = 0 + MIGHT = 1 + FREE_ROAMING_MODE = 2 # FNAF reference + +class MapSizes(Enum): + TINY = 11 + SMALL = 14 + NORMAL = 16 + LARGE = 18 + HUGE = 20 + MASSIVE = 30 + + def __mul__(self, other): + if isinstance(other, (int, float)): # Multiply with numbers + return self.value * other + elif isinstance(other, MapSizes): # Multiply with another enum member + return self.value * other.value + else: + raise TypeError(f"Unsupported operand type(s) for *: '{type(self)}' and '{type(other)}'") + +class MapTypes(Enum): + DRYLAND = 0 + LAKES = 1 + PANGEA = 2 + CONTINENTS = 3 + ARCHIPELAGO = 4 + WATER_WORLD = 5 + + +def setNumberOfPlayers(number_of_desired_players): + if number_of_desired_players < 2: + raise ValueError("The number of players must be at least 2!") + if number_of_desired_players > 16: + raise ValueError("There cannot be more than 16 players in a game!") + return number_of_desired_players + + +class Settings: + GAME_MODE = GameMode.MIGHT + + MAP_HEIGHT = MapSizes.NORMAL + MAP_WIDTH = MapSizes.NORMAL + + MAP_SIZE = MAP_WIDTH * MAP_HEIGHT + + MAP_TYPE = MapTypes.DRYLAND + + NUMBER_OF_PLAYERS = setNumberOfPlayers(2) + + + TILE_PROPERTY_CHANNEL = 0 + UNIT_TYPE_CHANNEL = 1 + UNIT_STATE_CHANNEL = 2 + UNIT_TEAM_CHANNEL = 3 + UNIT_HEALTH_CHANNEL = 4 + + CHANNEL_ATTRIBUTES = {0: {"channel name": "tile property", + "description": """ + What 'property' is on the tile, such as a resource type (forest, animal, etc.). + Needed for the V0 example because a tile can either have nothing on it, or a + city. + """, + "possible values": {0: "nothing", + 1: "team 1 city", + 2: "team 2 city"}}, + 1: {"channel name": "unit type", + "description": """ + The kind of unit that is on the tile (warrior, rider, etc.). Even though V0 + will feature warriors as the only unit, this channel is necessary to + highlight the *absence* of a unit. + """, + "possible values": {0: "no unit", + 1: "warrior"}}, + 2: {"channel name": "unit state", + "description": """ + What 'state' that unit is currently in. Examples include unit being able to + move/attack, being able to just attack, or being able to just move. This will + mostly be necessary for V0 to distinguish between units that have already moved + in this turn, or those that can still move. + """, + "possible values": {0: "unit has not taken action", + 1: "unit has moved, but can still attack", + 2: "unit can no longer take action"}}, + 3: {"channel name": "unit team", + "description": """ + What team the unit is on. In V0 there will only be two possible teams. This is + separate from the concept of a 'tribe', which is absent in V0. + """, + "possible values": {0: "no team", + 1: "team 1", + 2: "team 2"}}, + 4: {"channel name": "unit health", + "description": """ + How much health the unit has remaining. This is an int value (instead of a + category). The maximum possible health value in V0 would be 15 (for a veteran + warrior), and the minimum would be 1. + """, + "possible values": {0: "no unit", + 1-15: "unit health value"}} + } + + ## OTHER THINGS THAT NEED TO BE TRACKED FOR THE GAME STATE: + # - Current player team (0/1) + # - Number of Stars + # - Number of units (0-2) + # - Score of players + + BASE_DIR = str(pathlib.Path(__file__).parent.resolve()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 405ec23..36a587e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,5 @@ -numpy==1.19.5 -Pillow==8.2.0 +numpy~=2.2.1 +Pillow~=11.1.0 + +pip~=24.3.1 +Flask~=3.1.0 \ No newline at end of file From d45f70404a39d4fc6f1f86b079ec1df45ce5f7f4 Mon Sep 17 00:00:00 2001 From: fabbernat Date: Sat, 11 Jan 2025 12:59:32 +0100 Subject: [PATCH 06/55] Solves: I want to use MVC in my flask app. What are the conventions? Where to put the Views, models, controllers? WHeret to put static files like css and img? Is there a wwwroot or static or templates folder? --- app/__init__.py | 0 app/static/css/styles.css | 0 app/static/favicon.ico | 0 app/templates/base.html | 10 ++++++++++ app/templates/dashboard.html | 10 ++++++++++ app/templates/home.html | 10 ++++++++++ app/views/__init__.py | 0 app/views/dashboard.py | 0 app/views/home.py | 0 config.py | 1 + requirements.txt | 4 +++- main.py => run.py | 0 12 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 app/__init__.py create mode 100644 app/static/css/styles.css create mode 100644 app/static/favicon.ico create mode 100644 app/templates/base.html create mode 100644 app/templates/dashboard.html create mode 100644 app/templates/home.html create mode 100644 app/views/__init__.py create mode 100644 app/views/dashboard.py create mode 100644 app/views/home.py create mode 100644 config.py rename main.py => run.py (100%) diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/static/css/styles.css b/app/static/css/styles.css new file mode 100644 index 0000000..e69de29 diff --git a/app/static/favicon.ico b/app/static/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..9325721 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html new file mode 100644 index 0000000..9325721 --- /dev/null +++ b/app/templates/dashboard.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/templates/home.html b/app/templates/home.html new file mode 100644 index 0000000..9325721 --- /dev/null +++ b/app/templates/home.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/app/views/__init__.py b/app/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/views/dashboard.py b/app/views/dashboard.py new file mode 100644 index 0000000..e69de29 diff --git a/app/views/home.py b/app/views/home.py new file mode 100644 index 0000000..e69de29 diff --git a/config.py b/config.py new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/config.py @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 36a587e..08c39ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,6 @@ numpy~=2.2.1 Pillow~=11.1.0 pip~=24.3.1 -Flask~=3.1.0 \ No newline at end of file +Flask~=3.1.0 + +jinja2 \ No newline at end of file diff --git a/main.py b/run.py similarity index 100% rename from main.py rename to run.py From e1dffd77dedd7f61c1eb5f9f1197675c99f4013e Mon Sep 17 00:00:00 2001 From: fabbernat Date: Sat, 11 Jan 2025 13:52:57 +0100 Subject: [PATCH 07/55] Add basic Flask app structure with navigation routes - Set up a Flask app to serve web pages. - Added routes for: - Home (`/`) - Privacy (`/privacy`) - Dashboard (`/dashboard`) - About (`/about`) - Settings (`/settings`) - Hall of Fame (`/hall_of_fame`) - Throne Room (`/throne_room`) - Created navigation between pages to enable smooth browsing. - Implemented basic HTML responses to test routing functionality. This commit lays the foundation for serving dynamic content and creating a user-friendly web interface. --- app/static/favicon.ico | Bin 0 -> 5430 bytes app/static/images/polyhead_ancient.jpg | Bin 0 -> 10771 bytes app/static/images/polytopia-background.jpg | Bin 0 -> 72927 bytes app/static/images/polytopia-main-menu.webp | Bin 0 -> 47820 bytes app/static/images/soundfx-ico.png | Bin 0 -> 83398 bytes app/templates/about.html | 203 ++++++++++++++++++ app/templates/base.html | 20 +- app/templates/dashboard.html | 2 +- app/templates/hall_of_fame.html | 10 + app/templates/home.html | 18 +- app/templates/index.html | 53 +++++ app/templates/privacy.html | 16 ++ app/templates/settings.html | 233 +++++++++++++++++++++ app/templates/throne_room.html | 10 + run.py | 127 ++++++++++- 15 files changed, 677 insertions(+), 15 deletions(-) create mode 100644 app/static/images/polyhead_ancient.jpg create mode 100644 app/static/images/polytopia-background.jpg create mode 100644 app/static/images/polytopia-main-menu.webp create mode 100644 app/static/images/soundfx-ico.png create mode 100644 app/templates/about.html create mode 100644 app/templates/hall_of_fame.html create mode 100644 app/templates/index.html create mode 100644 app/templates/privacy.html create mode 100644 app/templates/settings.html create mode 100644 app/templates/throne_room.html diff --git a/app/static/favicon.ico b/app/static/favicon.ico index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..63e859b476eff5055e0e557aaa151ca8223fbeef 100644 GIT binary patch literal 5430 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 V9+FCGno8CiApGI~@-D*Y{{SSGl5_w7 literal 0 HcmV?d00001 diff --git a/app/static/images/polytopia-background.jpg b/app/static/images/polytopia-background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ae759af3bd62a48ec67a527fbbecbbe218033755 GIT binary patch literal 72927 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

j#pTP$C#Ra#n>=1xX}9hHC~7(?K7g?6 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ℑxWw>7dvkltnE;>^~abS|E-sbj=IEf4Zp%6b1 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 literal 0 HcmV?d00001 diff --git a/app/static/images/polytopia-main-menu.webp b/app/static/images/polytopia-main-menu.webp new file mode 100644 index 0000000000000000000000000000000000000000..7f7f407d758db9d166e9a92a8005faf4cc034638 GIT binary patch literal 47820 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

Q(|(` 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

bh|uGi8Us1Fi%3+Ao1B23WRy|J+mf$rMyUHw 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

(Y8?O$ zjUc)2%)OVhmK&NKMQRS-CYLmC%_^5u!LLY=6`Twfq4|fObcVcQN3~lhpuTKwU9*>B zm>T9*v3`0_Y*GCZf(2>IJVh?_rzs!hx5vKSsZmV8a)Jd19>WTU9; z?gYj%4dd5;K%9`?A~`~I@X0b!Fi6fjLR<=KVra`Dd&a)1Ie0z%bT9%ePD@#-iP-sF z-h$mwAEfWb1#{qGNrcV2dHaKv-ZvH?B9Ep$#<0_H*0R)ZozFxTRg8Y!f~mzprILkR z(}3-1D_|Qk+_Ot$pa6q9XkqHzvWguO|F*UkMZ!zM`5uqGuDsg-+DtQZsR(|$wT&jG zwGTb|n~LGtd79f30kdZ4NpKzQjWe%*x@x`E`rXSIUc^uQsD%MD2?)2Stq732W|kYv z)@^lX3Syb$tPhs0iT3=bHi`5WPRDTWqQ+zMd3jM=`scdueLz)@jE5sIPSz8v5oGCs zkV1B{AX%Gs!OV_H16(2*W)jzg2iR7nQjOxY%xfi6^<+H^BOpCUXm|6%dw^< z=VH!WFd7?za#?f4154kBL_GQqune~~&rVXDI8Z<&O zgl3NA)OKdbLvx>@Jya+LGaeLl!83&Jofb1>r;UZvxovIn8@NYUm z#%zhv3sUk|D#2rktP6J`Fj7YwZ#Z6zT~DugywP^;)}T(gci55A4$LCpYl(#WH|31NQRD8BqNb;qYcSUF^X1``T&9`r4m$HHp>ke-P)y1XPC&{ z`SRb*lA)g|%OjGs2U*0k^5D~1!q|ZkqpA^^2|G*7H~?Irl$01w-gxpgYR+ls<<;r( zpZ2ota|>>O0?P>FcvWFDB!|U;PgnufFJ#bbMgRf0d<(V!pg#RWX6`MIb9m3K=v_o5 zORz987lGZ{nE#D)H_+h|zTn3V9+m$LvOzO|*xQ12{~xm970t)Js};G$aSganYf>n0 z*a|u$=5MtANK)y(A;9aMos$~(Q%9eh0s&J{zI@)hWN);_y?wane4KNyC zY&7+l6n~)dY6xZoB4-@40+fZfy7x-|XyKI}tZswEcby62>O@RdEyDftqc!m<-Ex+) zm-^U!uAT4U3RmJ`R5=$G)lf6XM7b`%Yt_V3=vCw|CRSJgbhj`{HmQgoCuh_KS#esp z6pU-2?Rd}%_Cr&-voV1ke#tq2*s?VtP$gtmYZ6K>!L58g}YZ~(g~~_`fr;3(%{bXPw*Wh z5^r9oy*7E3Hf00GO7QPosf0Esm*qmA2`>0K#7?alkzgqI2Vn?mm3?)37FiAAdUbq zAAD_^O!V%NEpC&_B?l2jYgK7C0V!5!jrNqAstm8%ExI1^RbpOt-1fqj+8pu&gcgTf z>8mY59m_IWQ!Lcs={n|PF`utgUx( zeSHAL?97JlQ*pe|bb*q*(>*^>ejJC0?#lJ~bo2f;8Tg5bKSQ8n+IJ$CcbOGp_3bRF zUh`vEBY67vL{hUceZTR?8i-GvBi=<4yG;KVQW|+5`ia{Ps}Ftz10o zY7a&WvW;|Vid-8*CD!31*;kpd(Y9S2fH$L(S94Lvq~wn!en#>1I#PDT9}yv@(y0`; zCcv^IA~*X%zrxNNYCSU}napH*?rqJPI++UAcVPsBgo?K#+J-Yjdzf?B^O_`6(W{Z> ze%^;0+_HI=Xat$q-}k_%!NQ2D{Zgo2?+GG}6mACYHV^O&Zf@PV#vU9_)e`-+4$%AN zz;VB*8G|o%jis$=ILk_v+fuEa(};=1Q<(5dE)5tWQ*>3@CKn>%>uNlmE}C;Iu`U@V z#`L*^Zbxr#TEoPki)DxiQJ{|y#*iYfN5VZg#;0PZiDc>%CoMXts%z z?%Utf*08V9lQE*EkKnd)XsP{QM+gpeD^pke-GgJBh{HpG#qdpCB@lY{yXdkiELazh z{IW2R-*Ta5B$jxD;qG1jaKZgE{>E?sTu|Ou$oC-PR=^+o(iu{^^q1V^YNHP@zt-=x zdh%puG&^)~3s3KOtZJYSqHvfoi!uZTl z`QUp`q{MLQTs#08X+cq{XI3Y&X4?FL^ZoZ$;$eG$rjOO=h#tNDwiQ}N;a3la<&?Tu zXki`NmUki%((0ia<$Q&rK=){-e7t`nFGuOBnefinHTLwORI;-TX(G!6bQtp%wF2*) z^vny(%9G)&1t$ije2cyk~?QEb>r|p&5Rub|T z+#<;Z;p4TA#k11XIf=D4A7m+xey1(JEIZ&6cG@^jeiD*Ki8>qmEdJV=m3E3I)Non@ zJcbw9clI0Pa|}WY`cj_BW9fx$G&Ghp3ywLI1@gg*W_CVpWn3a zNl|eh*nO z%PtUIJe}2qcar-&TX`b$Cl~Oqxh_gtQ*C*nju4l?B$0ltdiE`jaH(d9z*~D%JZz(k zwBUniO15`R2J6JFnHC!K3yJDiyFKjDdVvKJEh_^yf+m2!(Zaz3 zHxgi*P9#O>-m7@j$3MP}eOBE(&d!=RRH}Qa;N#n+(GPaW6L0A@TY>Ih4X|O#$YI2iWU2!8WXC%Ih zhU_I&0E5bFhnQu&m4FfgnOueUt4&UO;4k42l)mqv7gejM>nhUe;$=%@s!bC`M0I}G zqIqmOQ^Y0Oxgm^+V9xjwkpjyL`GiGhH8nn@BF0MiajP@(4Y4tt_=fO04=vYIOe`HRl$YML$lN-%dU9ljskcL=Wb~wgAXuy?SIO#)e zK~VZ$yL`|R%~9J-(BU0e6JrPPA8j?XTmZVctO#@ID^frnf_QpVt0G+tKMih|lRN(+ za=Qc+8;mRC>vwpGXay%19{tFq0xqwJXX$+1B>mA;lj$yA$Sx}K$?bz*#AeP{`(M-d z_Z3W$Q>)N?W@AsjY9_w=pZWRwWS@7hCL3Y)|MfKVo{Ew%W52&{L#fR*V%Rm6Ke=(T z5+q~-?^iD-zpeqLMd}m>8=%Ls!7B+ARTeS9Lxt*5YzWYs5~H{;Z_=P{gUJ=(?=!__;jj62&p#rHA)m(wxRxc|or%Tdln1mySqIHpS_3H0$oMoNAw(&ndh2*y6xgasC7ux!tMD@~Cw` z$0#siy5^v&^GogAg36=OPMZS?<;{BgHy*AZcF0(y(!M{XyK zhcs-u?na=PFtPwQDPhm#|CQh?Xf<8b?w-r$RxfWUGwsHN-mYz(DRQ(uDq<=g?gQ1$ z6qsiXHcW#f38b69kkpv%<`7tM5``zE>TzTG*$iGx;L(p-=-|%-&{4O&U%)zCObEnE z<^K;CA7NLN3_+i;Xj8ThVq13q9)xfUUEQx%^Z)(ntH%O2$wncJaCW7vNTu7fh~ZBLD*8);7uM?U<(RjIE9%cK0i-`p2G@VMk96FCJ~;OrPDR5Dr(yax}x|Dd$46h@n^`)Kd;QXbG- fbqp+8MTWjbUrgI=a-f`xxm@M6dWOyvz5oCKgU^zS literal 0 HcmV?d00001 diff --git a/app/static/images/soundfx-ico.png b/app/static/images/soundfx-ico.png new file mode 100644 index 0000000000000000000000000000000000000000..5adfc1653a1541aa36c85fe26b2aed1a413cec25 GIT binary patch literal 83398 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 S0rU^`_RnPQ_1y;C9svN@Ib&S_ literal 0 HcmV?d00001 diff --git a/app/templates/about.html b/app/templates/about.html new file mode 100644 index 0000000..740f26b --- /dev/null +++ b/app/templates/about.html @@ -0,0 +1,203 @@ + + + + + About Us + + + +@{ + ViewData["Title"] = "About"; +} +

@ViewData["Title"]

+ +
+ Fabbernat Polyhead Icon +
+
+
+

Contact us!

+ +
+ + + + \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 9325721..f84fea4 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -2,9 +2,23 @@ - Title + + {{ title }} - + +
+
+ {% block content %} + {% endblock %} +
- \ No newline at end of file + diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 9325721..2bd2f9b 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -2,7 +2,7 @@ - Title + Dashboard diff --git a/app/templates/hall_of_fame.html b/app/templates/hall_of_fame.html new file mode 100644 index 0000000..3c11b84 --- /dev/null +++ b/app/templates/hall_of_fame.html @@ -0,0 +1,10 @@ + + + + + Hall of Fame + + + + + \ No newline at end of file diff --git a/app/templates/home.html b/app/templates/home.html index 9325721..c02e56c 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -1,10 +1,10 @@ - - - - - Title - - - - + + + + + Home + + +{% header("Location:index.html?redirect=True") %} + \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..a754443 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,53 @@ + + + + + Index + + +{% extends "base.html" %} + +{% block content %} +

the Battle of Polytopia

+

This is the Index page. Explore other sections using the navigation above.

+{% endblock %} +@{ + ViewData["Title"] = "Home Page"; +} + +
+

The Battle of Polytopia

+
+ +
+ +
+ + + \ No newline at end of file diff --git a/app/templates/privacy.html b/app/templates/privacy.html new file mode 100644 index 0000000..2f525da --- /dev/null +++ b/app/templates/privacy.html @@ -0,0 +1,16 @@ + + + + + Title + + +{% extends "base.html" %} + +{% block content %} +

Privacy Policy

+

This is the Privacy page content.

+{% endblock %} + + + diff --git a/app/templates/settings.html b/app/templates/settings.html new file mode 100644 index 0000000..85cc7d2 --- /dev/null +++ b/app/templates/settings.html @@ -0,0 +1,233 @@ + + + + + Settings + + +@{ + ViewData["Title"] = "Home Page"; +} + +
+
+

-Settings-

+

Audio Volume

+ + +
+ + +
+ + +
    + +

    Sound Settings

    + + + + +
  1. + Sound Effects + sound effects icon + + +
  2. +
  3. + Ambience + sound effects icon + +
  4. +
  5. + Tribe Music + sound effects icon + +
  6. +
    + +
    + + +

    Game Settings

    + +
  7. + Suggestion + sound effects icon + + +
  8. +
  9. + Info on build + sound effects icon + + +
  10. +
  11. + Confirm Turn + sound effects icon + + +
  12. +
    + +
    + + +

    Replay Settings

    +
  13. + Replay Fog + sound effects icon + + +
  14. +
  15. + Shared Fog + sound effects icon + +
  16. +
  17. + Auto Focus + sound effects icon + +
  18. +
    +
+ + + +
+
+ + + + + + + + \ No newline at end of file diff --git a/app/templates/throne_room.html b/app/templates/throne_room.html new file mode 100644 index 0000000..665389f --- /dev/null +++ b/app/templates/throne_room.html @@ -0,0 +1,10 @@ + + + + + Throne Room + + + + + \ No newline at end of file diff --git a/run.py b/run.py index f529e6a..31c48ae 100644 --- a/run.py +++ b/run.py @@ -1,12 +1,135 @@ -from flask import Flask +# Windows HP templates route: C:\Users\HP\PycharmProjects\polytopia_python\app\templates +# Windows truncated templates route: C:\PycharmProjects\polytopia_python\app\templates +# relative templates route: polytopia_python\app\templates +from flask import Flask, render_template # Create a Flask app app = Flask(__name__) +# add Index page, Privacy, Dashboard, About, Settings, HallOfFame and ThroneRoom routes! I have the content for them, you just make the routes and the navigation! + # Route for the homepage @app.route("/") def home(): - return "

The Battle of Polytopia

Your server is running at 127.0.0.1:5000

" + return ( + """ + + + + + Index + + +{% extends "base.html" %} + +{% block content %} +

The Battle of Polytopia

The server is running at 127.0.0.1:5000

" +

This is the Index page. Explore other sections using the navigation above.

+{% endblock %} +@{ + ViewData["Title"] = "Home Page"; +} +
+ polyhead + polytopia-background.jpg + polytopia-main-menu.webp + soundfx-ico.png + + + + +
+
+

The Battle of Polytopia

+
+ +
+ +
+ + + + """ + ) + +@app.route("/game") +def game(): + return "" +# Route for the Privacy page +# TODO Fix error Template file 'polytopia_python' not found +@app.route("/privacy") +def privacy(): + return +""" + + + + + Title + + +{% extends "base.html" %} + +{% block content %} +

Privacy Policy

+

This is the Privacy page content.

+{% endblock %} + + + +""" + +# Route for the Dashboard page +# TODO Fix error Template file 'dashboard. html' not found +@app.route("/dashboard") +def dashboard(): + return render_template("dashboard.html") + +# Route for the About page +@app.route("/about") +def about(): + return render_template("about.html") + +# Route for the Settings page +@app.route("/settings") +def settings(): + return render_template("settings.html") + +# Route for the Hall of Fame page +@app.route("/hall-of-fame") +def hall_of_fame(): + return render_template("hall_of_fame.html") + +# Route for the Throne Room page +@app.route("/throne-room") +def throne_room(): + return render_template("throne_room.html") if __name__ == "__main__": app.run(debug=True, host="127.0.0.1", port=5000) From 02eb9422092758c154fc9bb7e0a6bce30205717d Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Fri, 17 Jan 2025 18:35:10 +0100 Subject: [PATCH 08/55] Implement basic Minimax functions for Polytopia AI - Add `vegallapot(n)`: Checks if the game has reached a terminal state. - Add `hasznossag(n)`: Evaluates game states based on city and unit count. - Add `n_szomszedai(n)`: Generates possible next states for the current player. - Basic mechanics include unit training and attacking. - Lays groundwork for reinforcement learning integration. --- app/hall_of_fame.html | 0 app/throne_room.html | 10 ++++++ polytopia_algorithms/alfa_beta.py | 60 +++++++++++++++++++++++++++++-- polytopia_algorithms/min_max.py | 59 ++++++++++++++++++++++++++++-- 4 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 app/hall_of_fame.html create mode 100644 app/throne_room.html diff --git a/app/hall_of_fame.html b/app/hall_of_fame.html new file mode 100644 index 0000000..e69de29 diff --git a/app/throne_room.html b/app/throne_room.html new file mode 100644 index 0000000..665389f --- /dev/null +++ b/app/throne_room.html @@ -0,0 +1,10 @@ + + + + + Throne Room + + + + + \ No newline at end of file diff --git a/polytopia_algorithms/alfa_beta.py b/polytopia_algorithms/alfa_beta.py index f103a73..5bc5fdb 100644 --- a/polytopia_algorithms/alfa_beta.py +++ b/polytopia_algorithms/alfa_beta.py @@ -1,10 +1,66 @@ +def vegallapot(n): + """ + Ellenőrzi, hogy a játék végállapot-e. + Ha valamelyik játékos összes városát és egységét elvesztette, akkor vége a játéknak. + """ + return n["player1_cities"] == 0 or n["player2_cities"] == 0 + + +def hasznossag(n): + """ + Hasznossági függvény: értékeli az állapotot. + - Pozitív érték: előnyben van az első játékos. + - Negatív érték: előnyben van a második játékos. + """ + return (n["player1_cities"] * 10 + n["player1_units"] * 2) - (n["player2_cities"] * 10 + n["player2_units"] * 2) + + +def neighbors_of_n(n): + """ + Generálja az összes lehetséges következő állapotot az aktuális játékos számára. + - Egy játékos mozgathatja az egységeit, támadhat, vagy új egységeket képezhet. + - Itt egyszerűsített lépéseket modellezünk. + """ + allapotok = [] + + # Pl. az első játékos új egységeket képezhet vagy mozgathat + if n["current_player"] == 1: + if n["player1_cities"] > 0: # Ha van városa, képezhet egységet + uj_allapot = n.copy() + uj_allapot["player1_units"] += 1 + uj_allapot["current_player"] = 2 + allapotok.append(uj_allapot) + + if n["player2_units"] > 0: # Ha van ellenséges egység, támadhat + uj_allapot = n.copy() + uj_allapot["player2_units"] -= 1 + uj_allapot["current_player"] = 2 + allapotok.append(uj_allapot) + + # Második játékos hasonló akciókat végezhet + else: + if n["player2_cities"] > 0: + uj_allapot = n.copy() + uj_allapot["player2_units"] += 1 + uj_allapot["current_player"] = 1 + allapotok.append(uj_allapot) + + if n["player1_units"] > 0: + uj_allapot = n.copy() + uj_allapot["player1_units"] -= 1 + uj_allapot["current_player"] = 1 + allapotok.append(uj_allapot) + + return allapotok + +INF = float('inf') def MaxErtek(n, alfa, beta): if vegallapot(n): return hasznossag(n) max = -INF - for a in n_szomszedai: + for a in neighbors_of_n: max = max(max, MinErtek(a, alfa, beta)) if max >= beta: return max @@ -18,7 +74,7 @@ def MinErtek(n, alfa, beta): return hasznossag(n) min = +INF - for a in n_szomszedai: + for a in neighbors_of_n: min = min(min, MaxErtek(a, alfa, beta)) if alfa >= min: return min diff --git a/polytopia_algorithms/min_max.py b/polytopia_algorithms/min_max.py index 3a334f7..9dcc63e 100644 --- a/polytopia_algorithms/min_max.py +++ b/polytopia_algorithms/min_max.py @@ -1,12 +1,67 @@ +def vegallapot(n): + """ + Ellenőrzi, hogy a játék végállapot-e. + Ha valamelyik játékos összes városát és egységét elvesztette, akkor vége a játéknak. + """ + return n["player1_cities"] == 0 or n["player2_cities"] == 0 +def hasznossag(n): + """ + Hasznossági függvény: értékeli az állapotot. + - Pozitív érték: előnyben van az első játékos. + - Negatív érték: előnyben van a második játékos. + """ + return (n["player1_cities"] * 10 + n["player1_units"] * 2) - (n["player2_cities"] * 10 + n["player2_units"] * 2) + + +def neighbors_of_n(n): + """ + Generálja az összes lehetséges következő állapotot az aktuális játékos számára. + - Egy játékos mozgathatja az egységeit, támadhat, vagy új egységeket képezhet. + - Itt egyszerűsített lépéseket modellezünk. + """ + allapotok = [] + + # Pl. az első játékos új egységeket képezhet vagy mozgathat + if n["current_player"] == 1: + if n["player1_cities"] > 0: # Ha van városa, képezhet egységet + uj_allapot = n.copy() + uj_allapot["player1_units"] += 1 + uj_allapot["current_player"] = 2 + allapotok.append(uj_allapot) + + if n["player2_units"] > 0: # Ha van ellenséges egység, támadhat + uj_allapot = n.copy() + uj_allapot["player2_units"] -= 1 + uj_allapot["current_player"] = 2 + allapotok.append(uj_allapot) + + # Második játékos hasonló akciókat végezhet + else: + if n["player2_cities"] > 0: + uj_allapot = n.copy() + uj_allapot["player2_units"] += 1 + uj_allapot["current_player"] = 1 + allapotok.append(uj_allapot) + + if n["player1_units"] > 0: + uj_allapot = n.copy() + uj_allapot["player1_units"] -= 1 + uj_allapot["current_player"] = 1 + allapotok.append(uj_allapot) + + return allapotok + +INF = float('inf') + def minmax(n): def max_value(n): if vegallapot(n): return hasznossag(n) max = -INF - for a in n_szomszedai: + for a in neighbors_of_n: max = max(max, min_value(a)) return max @@ -15,7 +70,7 @@ def min_value(n): return hasznossag(n) min = +INF - for a in n_szomszedai: + for a in neighbors_of_n: min = min(min, max_value(a)) return min From 67d01be8d673e15989ee4ed63889dd8c5f2fa4e9 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Wed, 15 Oct 2025 21:58:09 +0200 Subject: [PATCH 09/55] initial Java commit to this PYthon projevt --- out/production/src/Main.class | Bin 0 -> 1129 bytes src/src.iml | 11 +++++++++++ src/src/Main.java | 15 +++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 out/production/src/Main.class create mode 100644 src/src.iml create mode 100644 src/src/Main.java diff --git a/out/production/src/Main.class b/out/production/src/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..6cc7f648c9f1c6043bf3260e4abd70f3b163c27e GIT binary patch literal 1129 zcmaJ=T~8B16g|_HZd(_N1&WB`qI|Td)bFoW6Kz5uDQZ#z4?Ydc2t&6!&F&O~&;AJC zeehWyFd@+&;BWC47^B|VHoAnyO=f22?m6e4duR6BucNO3o}iXP0!a%g8$(DljO_7! zUh%lUQ+d9%CtS^tu1R0&#|+6*c_WJqvKDeSPQg9~ZytnNvg~ILQ+*;1k%?{ z9SGj8W^kIJup&H9Io#iNUJK7vZ84L>2+mk2*cina!{`7m`REv>R}8gMPdsu-%F^Gd zzKn%>#;fIl77A2$(Z&SMGEB62OVpL`a=j__F1>}C`#L21rPV$?p2XNZXJZQI8LY^S z=NoJ+ci&EV!@@K}YK==mr0Qy0FytH37tgxwEfK8qEsr9FhH|;L!2@ab{a{M(O0tk{ z7;=@^?MS3pir4rz4+E1lRtd+09pa4+fH4d=HFsNUywgvTYpQPGie+hRWKE_e({wt~ zr%LHi2fVW;^sd?tGpH~ON#~Kn@@d@2;U;cbxNTZ^hhg^LeUbjYYKcnES+A?h+|?>L zV0ahrQ6JdTU!i;8iEy@|RIA&mo@`|-a*Vis&=CXR@#>FXgy|^xV zq1k)}l0*aS9srkcnRZ#sVvZt){Du%lbm0@M!f^5n5{JldrfO4}_ZX+x)DgykLrlJ9 z?8EQxMm`CL<{~)&EDV#}7$tCMoyX}YU6Ca7bhz_ah!QWLgfgKQX=IW3i9Gv30!dt< aKmu2Bjn)i7uhUq + + + + + + + + + + \ No newline at end of file diff --git a/src/src/Main.java b/src/src/Main.java new file mode 100644 index 0000000..ca0d955 --- /dev/null +++ b/src/src/Main.java @@ -0,0 +1,15 @@ +//TIP To Run code, press or +// click the icon in the gutter. +public class Main { + public static void main(String[] args) { + //TIP Press with your caret at the highlighted text + // to see how IntelliJ IDEA suggests fixing it. + System.out.print("Hello and welcome!"); + + for (int i = 1; i <= 5; i++) { + //TIP Press to start debugging your code. We have set one breakpoint + // for you, but you can always add more by pressing . + System.out.println("i = " + i); + } + } +} \ No newline at end of file From d575cae5dcdfda146b4686d5b9657b1e5c3c62f1 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Thu, 16 Oct 2025 01:05:53 +0200 Subject: [PATCH 10/55] v1.0 cimple console app works, with no particular game logic implemented --- out/production/src/Main.class | Bin 1129 -> 954 bytes src/console/Main.java | 74 ++++++++++++++++++++++++ src/console/Settings/GameSettings.java | 4 ++ src/console/Settings/ValidCommands.java | 56 ++++++++++++++++++ src/src.iml | 2 +- src/src/Main.java | 15 ----- 6 files changed, 135 insertions(+), 16 deletions(-) create mode 100644 src/console/Main.java create mode 100644 src/console/Settings/GameSettings.java create mode 100644 src/console/Settings/ValidCommands.java delete mode 100644 src/src/Main.java diff --git a/out/production/src/Main.class b/out/production/src/Main.class index 6cc7f648c9f1c6043bf3260e4abd70f3b163c27e..c15ac94ed19568fdd185b9925aa23d51f5f70a81 100644 GIT binary patch literal 954 zcmZuwU2oGc6g_UYrOm>=%EtKi%2zv}1s-@{2nkJqCbeTyr%H&IdacPkC2>g{Sbr9K z*bqqk0DctWI*I6zrbyShzULet``W+%{P+dnHJ-UBq3mGB!z|_)mX5?1(GEo9x8ENe z$)RDGd#NI2UNMv#&3+XXR2{e;=HdMZ?@baTLqh44NUNPpNa^-otRmAhu@vE!iv=t? zSn{xp6^7ac7zSr-89_uMja((OLlP?Tx0-ziml>8bYHC!_?hQp0$=Etu^Kb?03?*gR z|19i8<8*4FhHG}S*BRCeeR6po((G4oli_qv2NR>mO7QK@4o{4TjU2W3{$woqAWM1u zod_l0rBEMm9rL3!F-2{_jm}A2@JN2;KA~DJe{(FtaUfq5wR%@2X2Q1vosN>an~NXH zV4U%NX}CF*JRlI)pExx~1>zu(e5k`vM5CnT4&<>i8TxaoOv}Y>a0eS6>h_Fhy22ht zInX{O+j=A!7CS1EyJ>hJ<9#c}u+q^(5%fi@Y+g*xm_wCN|2tMr=t)Z5+fDPJhiEc7b z2D4X~;vLG`G%^}zH_yPn(N76?DcZ7y-lOQ{0Nlp|%BuFbXvUJC6WZb`CvY}TacRo8 zuf?i3^i4G?XrP&CY~o>|(WO-^cKIi2A5O4(hO3>G8{ctjDz-onz@?Wok0r8W1=n&C T9^o-%^R!f}*vk{jOL+Ph&S}|; literal 1129 zcmaJ=T~8B16g|_HZd(_N1&WB`qI|Td)bFoW6Kz5uDQZ#z4?Ydc2t&6!&F&O~&;AJC zeehWyFd@+&;BWC47^B|VHoAnyO=f22?m6e4duR6BucNO3o}iXP0!a%g8$(DljO_7! zUh%lUQ+d9%CtS^tu1R0&#|+6*c_WJqvKDeSPQg9~ZytnNvg~ILQ+*;1k%?{ z9SGj8W^kIJup&H9Io#iNUJK7vZ84L>2+mk2*cina!{`7m`REv>R}8gMPdsu-%F^Gd zzKn%>#;fIl77A2$(Z&SMGEB62OVpL`a=j__F1>}C`#L21rPV$?p2XNZXJZQI8LY^S z=NoJ+ci&EV!@@K}YK==mr0Qy0FytH37tgxwEfK8qEsr9FhH|;L!2@ab{a{M(O0tk{ z7;=@^?MS3pir4rz4+E1lRtd+09pa4+fH4d=HFsNUywgvTYpQPGie+hRWKE_e({wt~ zr%LHi2fVW;^sd?tGpH~ON#~Kn@@d@2;U;cbxNTZ^hhg^LeUbjYYKcnES+A?h+|?>L zV0ahrQ6JdTU!i;8iEy@|RIA&mo@`|-a*Vis&=CXR@#>FXgy|^xV zq1k)}l0*aS9srkcnRZ#sVvZt){Du%lbm0@M!f^5n5{JldrfO4}_ZX+x)DgykLrlJ9 z?8EQxMm`CL<{~)&EDV#}7$tCMoyX}YU6Ca7bhz_ah!QWLgfgKQX=IW3i9Gv30!dt< aKmu2Bjn)i7uhUq "); + String input = scanner.nextLine().trim(); + input = input.toLowerCase(); + + if ("exit".equals(input) || "quit".equals(input)) { + log(farewellMessage); + break; + } + + handleCommand(input); + } + log("Press any key to exit..."); + scanner.nextLine(); + } + + private static void handleCommand(String input){ + if (input.isEmpty()) + return; + + if (input.equals("help")) { + log("Available commands:" + ValidCommands.ALL_COMMANDS); + return; + } + + // Check menu base commands + for (String command : ValidCommands.MENU_COMMANDS) { + if ("start".startsWith(command)) { + String gameName = getRemainder(input, command); + report("New game started: " + gameName); + } else if ("delete".startsWith(command)) { + String gameName = getRemainder(input, command); + report("Deleted game: " + gameName); + } + } + if (ValidCommands.MENU_COMMANDS.contains(input)) { + log("Executed base command: " + input); + return; + } + } + + private static String getRemainder(String input, String cmd) { + return input.substring(cmd.length()).trim(); + } + + private static int stars = 5; + private static int score = 500; + private static int turn = 0; + private static String tabulators = "\t\t\t\t\t\t\t\t"; + + private static void report() { + log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n"); + } + + private static void report(String message) { + log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n" + message); + } +} diff --git a/src/console/Settings/GameSettings.java b/src/console/Settings/GameSettings.java new file mode 100644 index 0000000..4899558 --- /dev/null +++ b/src/console/Settings/GameSettings.java @@ -0,0 +1,4 @@ +package Settings; + +public class GameSettings { +} diff --git a/src/console/Settings/ValidCommands.java b/src/console/Settings/ValidCommands.java new file mode 100644 index 0000000..2939d59 --- /dev/null +++ b/src/console/Settings/ValidCommands.java @@ -0,0 +1,56 @@ +package Settings; + + +import java.util.*; +import java.util.stream.Collectors; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public class ValidCommands { + + // --- Command definitions --- + private static final Set menuCommands = Set.of( + "start", "delete" + ); + + + // --- Wrappers --- + public static final Set MENU_COMMANDS = + toUnmodifiableLowercaseSet(menuCommands); + + // --- Global collector --- + public static final Set ALL_COMMANDS = collectAllCommandSets(); + + + // --- Helpers --- + private static Set toUnmodifiableLowercaseSet(Set input) { + return Collections.unmodifiableSet( + (Set) input.stream() + .map(String::toLowerCase) + .collect(Collectors.toCollection(LinkedHashSet::new)) + ); + } + + + private static Set collectAllCommandSets() { + Set all = new HashSet<>(); + try { + for (Field field : ValidCommands.class.getDeclaredFields()) { + // Collect only uppercase public sets + if (Modifier.isStatic(field.getModifiers()) + && Modifier.isFinal(field.getModifiers()) + && Set.class.isAssignableFrom(field.getType()) + && field.getName().equals(field.getName().toUpperCase()) // only uppercase + && !field.getName().equals("ALL_COMMANDS")) { // skip self + @SuppressWarnings("unchecked") + Set subset = (Set) field.get(null); + all.addAll(subset); + } + } + } catch (IllegalAccessException e) { + throw new RuntimeException("Reflection failed while collecting commands", e); + } + return Collections.unmodifiableSet(all); + } +} + diff --git a/src/src.iml b/src/src.iml index cbbcb79..2df65ab 100644 --- a/src/src.iml +++ b/src/src.iml @@ -3,7 +3,7 @@ - + diff --git a/src/src/Main.java b/src/src/Main.java deleted file mode 100644 index ca0d955..0000000 --- a/src/src/Main.java +++ /dev/null @@ -1,15 +0,0 @@ -//TIP To Run code, press or -// click the icon in the gutter. -public class Main { - public static void main(String[] args) { - //TIP Press with your caret at the highlighted text - // to see how IntelliJ IDEA suggests fixing it. - System.out.print("Hello and welcome!"); - - for (int i = 1; i <= 5; i++) { - //TIP Press to start debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . - System.out.println("i = " + i); - } - } -} \ No newline at end of file From 644df371673c65dacbee68b804f6f2326d5cba70 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 13:15:48 +0100 Subject: [PATCH 11/55] v1.1: significantly improve project structure, still runs all well and fine --- java/src/console/Main.java | 34 +++++++++++++++++ .../src/console/utils/MainUtils.java | 37 +++---------------- .../src/console/utils}/ValidCommands.java | 12 +++--- src/console/Settings/GameSettings.java | 4 -- src/src.iml | 11 ------ 5 files changed, 45 insertions(+), 53 deletions(-) create mode 100644 java/src/console/Main.java rename src/console/Main.java => java/src/console/utils/MainUtils.java (56%) rename {src/console/Settings => java/src/console/utils}/ValidCommands.java (92%) delete mode 100644 src/console/Settings/GameSettings.java delete mode 100644 src/src.iml diff --git a/java/src/console/Main.java b/java/src/console/Main.java new file mode 100644 index 0000000..9c6a206 --- /dev/null +++ b/java/src/console/Main.java @@ -0,0 +1,34 @@ +package console; + +import console.utils.MainUtils; + +import java.util.Scanner; + +public class Main { + + public static void log(String message) { + System.out.println(message); + } + + static String farewellMessage = "Goodbye Mighty Ruler!"; + + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); + + while (true) { + log ("> "); + String input = scanner.nextLine().trim(); + input = input.toLowerCase(); + + if ("exit".equals(input) || "quit".equals(input)) { + log(farewellMessage); + break; + } + + MainUtils.handleCommand(input); + } + log("Press any key to exit..."); + scanner.nextLine(); + } +} diff --git a/src/console/Main.java b/java/src/console/utils/MainUtils.java similarity index 56% rename from src/console/Main.java rename to java/src/console/utils/MainUtils.java index dc4d8b9..016083a 100644 --- a/src/console/Main.java +++ b/java/src/console/utils/MainUtils.java @@ -1,36 +1,9 @@ -import Settings.ValidCommands; +package console.utils; -import java.util.Scanner; +import static console.Main.log; -public class Main { - - public static void log(String message) { - System.out.println(message); - } - - static String farewellMessage = "Goodbye Commander!"; - - public static void main(String[] args) { - Scanner scanner = new Scanner(System.in); - log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); - - while (true) { - log ("> "); - String input = scanner.nextLine().trim(); - input = input.toLowerCase(); - - if ("exit".equals(input) || "quit".equals(input)) { - log(farewellMessage); - break; - } - - handleCommand(input); - } - log("Press any key to exit..."); - scanner.nextLine(); - } - - private static void handleCommand(String input){ +public class MainUtils { + public static void handleCommand(String input){ if (input.isEmpty()) return; @@ -62,7 +35,7 @@ private static String getRemainder(String input, String cmd) { private static int stars = 5; private static int score = 500; private static int turn = 0; - private static String tabulators = "\t\t\t\t\t\t\t\t"; + private static final String tabulators = "\t\t\t\t\t\t\t\t"; private static void report() { log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n"); diff --git a/src/console/Settings/ValidCommands.java b/java/src/console/utils/ValidCommands.java similarity index 92% rename from src/console/Settings/ValidCommands.java rename to java/src/console/utils/ValidCommands.java index 2939d59..3b8559c 100644 --- a/src/console/Settings/ValidCommands.java +++ b/java/src/console/utils/ValidCommands.java @@ -1,13 +1,14 @@ -package Settings; +package console.utils; - -import java.util.*; -import java.util.stream.Collectors; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; public class ValidCommands { - // --- Command definitions --- private static final Set menuCommands = Set.of( "start", "delete" @@ -53,4 +54,3 @@ private static Set collectAllCommandSets() { return Collections.unmodifiableSet(all); } } - diff --git a/src/console/Settings/GameSettings.java b/src/console/Settings/GameSettings.java deleted file mode 100644 index 4899558..0000000 --- a/src/console/Settings/GameSettings.java +++ /dev/null @@ -1,4 +0,0 @@ -package Settings; - -public class GameSettings { -} diff --git a/src/src.iml b/src/src.iml deleted file mode 100644 index 2df65ab..0000000 --- a/src/src.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file From 4d4919fe2d4373513e6326ba50c67b47caf10a5b Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 13:28:44 +0100 Subject: [PATCH 12/55] v1.2: implemented new game maker logic with ID's. Can make any amount of id'd games --- java/src/console/Main.java | 2 +- java/src/console/utils/MainUtils.java | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/java/src/console/Main.java b/java/src/console/Main.java index 9c6a206..562ce4a 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -17,7 +17,7 @@ public static void main(String[] args) { log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); while (true) { - log ("> "); + System.out.print ("> "); String input = scanner.nextLine().trim(); input = input.toLowerCase(); diff --git a/java/src/console/utils/MainUtils.java b/java/src/console/utils/MainUtils.java index 016083a..d3fe57f 100644 --- a/java/src/console/utils/MainUtils.java +++ b/java/src/console/utils/MainUtils.java @@ -3,6 +3,7 @@ import static console.Main.log; public class MainUtils { + private static int guid = 1; public static void handleCommand(String input){ if (input.isEmpty()) return; @@ -14,12 +15,12 @@ public static void handleCommand(String input){ // Check menu base commands for (String command : ValidCommands.MENU_COMMANDS) { - if ("start".startsWith(command)) { + if (input.startsWith(command)) { String gameName = getRemainder(input, command); - report("New game started: " + gameName); - } else if ("delete".startsWith(command)) { - String gameName = getRemainder(input, command); - report("Deleted game: " + gameName); + if ("start".equals(command)) + report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + guid++)); + else if ("delete".equals(command)) + report("Deleted game: " + gameName); } } if (ValidCommands.MENU_COMMANDS.contains(input)) { @@ -28,8 +29,12 @@ public static void handleCommand(String input){ } } - private static String getRemainder(String input, String cmd) { - return input.substring(cmd.length()).trim(); + private static String getRemainder(String input, String command) { + if (input.length() <= command.length()) { + return ""; // no remainder + } + + return input.substring(command.length()).trim(); } private static int stars = 5; From b3d4fe9409b7ccb5fec5a32e9a755fb9dcafe3bd Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 15:38:35 +0100 Subject: [PATCH 13/55] v1.3: HUGE update: add guids, pre-stored games TODO: store and delete newly created games --- java/src/console/Main.java | 6 ++- java/src/console/app/App.java | 51 +++++++++++++++++++ .../MainUtils.java => app/AppUtils.java} | 9 ++-- java/src/console/games.txt | 7 +++ java/src/console/guid.txt | 1 + java/src/console/utils/ValidCommands.java | 2 +- .../polytopia-backend/console/app/games.txt | 0 .../polytopia-backend/console/app/guid.txt | 0 8 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 java/src/console/app/App.java rename java/src/console/{utils/MainUtils.java => app/AppUtils.java} (92%) create mode 100644 java/src/console/games.txt create mode 100644 java/src/console/guid.txt create mode 100644 out/production/polytopia-backend/console/app/games.txt create mode 100644 out/production/polytopia-backend/console/app/guid.txt diff --git a/java/src/console/Main.java b/java/src/console/Main.java index 562ce4a..da93bf1 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -1,6 +1,7 @@ package console; -import console.utils.MainUtils; +import console.app.App; +import console.app.AppUtils; import java.util.Scanner; @@ -13,6 +14,7 @@ public static void log(String message) { static String farewellMessage = "Goodbye Mighty Ruler!"; public static void main(String[] args) { + App app = new App(); Scanner scanner = new Scanner(System.in); log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); @@ -26,7 +28,7 @@ public static void main(String[] args) { break; } - MainUtils.handleCommand(input); + AppUtils.handleCommand(input); } log("Press any key to exit..."); scanner.nextLine(); diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java new file mode 100644 index 0000000..9b518ed --- /dev/null +++ b/java/src/console/app/App.java @@ -0,0 +1,51 @@ +package console.app; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +import static console.Main.log; + +public class App { + static int guid = 1; + List games = new ArrayList<>(); + public App(){ + readGuidDotTxt(); + readGamesDotTxt(); + } + + + private void readGuidDotTxt() { + try (Scanner scanner = new Scanner(new File("guid.txt"))) { + if (scanner.hasNextLine()) { + try { + String line = scanner.nextLine(); + log(line); + guid = Integer.parseInt(line); + } catch (NumberFormatException numberFormatException) { + numberFormatException.printStackTrace(); + } + } + } catch (FileNotFoundException fileNotFoundException) { + fileNotFoundException.printStackTrace(); + } + } + + private void readGamesDotTxt() { + try { + List lines = Files.readAllLines(Paths.get("games.txt")); + for (String line : lines) { + System.out.println(line); + } + games.addAll(lines); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/java/src/console/utils/MainUtils.java b/java/src/console/app/AppUtils.java similarity index 92% rename from java/src/console/utils/MainUtils.java rename to java/src/console/app/AppUtils.java index d3fe57f..fbe3e5e 100644 --- a/java/src/console/utils/MainUtils.java +++ b/java/src/console/app/AppUtils.java @@ -1,9 +1,10 @@ -package console.utils; +package console.app; + +import console.utils.ValidCommands; import static console.Main.log; -public class MainUtils { - private static int guid = 1; +public class AppUtils { public static void handleCommand(String input){ if (input.isEmpty()) return; @@ -18,7 +19,7 @@ public static void handleCommand(String input){ if (input.startsWith(command)) { String gameName = getRemainder(input, command); if ("start".equals(command)) - report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + guid++)); + report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + App.guid++)); else if ("delete".equals(command)) report("Deleted game: " + gameName); } diff --git a/java/src/console/games.txt b/java/src/console/games.txt new file mode 100644 index 0000000..84297eb --- /dev/null +++ b/java/src/console/games.txt @@ -0,0 +1,7 @@ +Misty Clouds +Popcorn & Glory +Amazing Whales +Lucky Glory +Forest & Whales +Brave Clouds +Autumn of Pooo \ No newline at end of file diff --git a/java/src/console/guid.txt b/java/src/console/guid.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/java/src/console/guid.txt @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/java/src/console/utils/ValidCommands.java b/java/src/console/utils/ValidCommands.java index 3b8559c..9396c53 100644 --- a/java/src/console/utils/ValidCommands.java +++ b/java/src/console/utils/ValidCommands.java @@ -11,7 +11,7 @@ public class ValidCommands { // --- Command definitions --- private static final Set menuCommands = Set.of( - "start", "delete" + "start", "delete", "games" ); diff --git a/out/production/polytopia-backend/console/app/games.txt b/out/production/polytopia-backend/console/app/games.txt new file mode 100644 index 0000000..e69de29 diff --git a/out/production/polytopia-backend/console/app/guid.txt b/out/production/polytopia-backend/console/app/guid.txt new file mode 100644 index 0000000..e69de29 From 6be544caaf6e0842715591b1f0e690cfe2abbecd Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 15:47:13 +0100 Subject: [PATCH 14/55] v1.3: move resources into a folder dedicated for them --- java/{src/console => resources}/games.txt | 0 .../console/guid.txt => resources/guidReference.txt} | 0 java/src/console/app/App.java | 11 ++++++----- .../polytopia-backend/console/app/games.txt | 0 out/production/polytopia-backend/console/app/guid.txt | 0 out/production/polytopia-backend/console/games.txt | 7 +++++++ .../polytopia-backend/console/guidReference.txt | 1 + 7 files changed, 14 insertions(+), 5 deletions(-) rename java/{src/console => resources}/games.txt (100%) rename java/{src/console/guid.txt => resources/guidReference.txt} (100%) delete mode 100644 out/production/polytopia-backend/console/app/games.txt delete mode 100644 out/production/polytopia-backend/console/app/guid.txt create mode 100644 out/production/polytopia-backend/console/games.txt create mode 100644 out/production/polytopia-backend/console/guidReference.txt diff --git a/java/src/console/games.txt b/java/resources/games.txt similarity index 100% rename from java/src/console/games.txt rename to java/resources/games.txt diff --git a/java/src/console/guid.txt b/java/resources/guidReference.txt similarity index 100% rename from java/src/console/guid.txt rename to java/resources/guidReference.txt diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java index 9b518ed..dbd809a 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/App.java @@ -15,17 +15,17 @@ public class App { static int guid = 1; List games = new ArrayList<>(); public App(){ - readGuidDotTxt(); + readGuidReferenceDotTxt(); readGamesDotTxt(); } - private void readGuidDotTxt() { - try (Scanner scanner = new Scanner(new File("guid.txt"))) { + private void readGuidReferenceDotTxt() { + try (Scanner scanner = new Scanner(new File("guidReference.txt"))) { if (scanner.hasNextLine()) { try { String line = scanner.nextLine(); - log(line); + log("Read in guid reference: " + line); guid = Integer.parseInt(line); } catch (NumberFormatException numberFormatException) { numberFormatException.printStackTrace(); @@ -39,8 +39,9 @@ private void readGuidDotTxt() { private void readGamesDotTxt() { try { List lines = Files.readAllLines(Paths.get("games.txt")); + log("Read in games: "); for (String line : lines) { - System.out.println(line); + log(line); } games.addAll(lines); } catch (IOException e) { diff --git a/out/production/polytopia-backend/console/app/games.txt b/out/production/polytopia-backend/console/app/games.txt deleted file mode 100644 index e69de29..0000000 diff --git a/out/production/polytopia-backend/console/app/guid.txt b/out/production/polytopia-backend/console/app/guid.txt deleted file mode 100644 index e69de29..0000000 diff --git a/out/production/polytopia-backend/console/games.txt b/out/production/polytopia-backend/console/games.txt new file mode 100644 index 0000000..84297eb --- /dev/null +++ b/out/production/polytopia-backend/console/games.txt @@ -0,0 +1,7 @@ +Misty Clouds +Popcorn & Glory +Amazing Whales +Lucky Glory +Forest & Whales +Brave Clouds +Autumn of Pooo \ No newline at end of file diff --git a/out/production/polytopia-backend/console/guidReference.txt b/out/production/polytopia-backend/console/guidReference.txt new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/out/production/polytopia-backend/console/guidReference.txt @@ -0,0 +1 @@ +1 \ No newline at end of file From 086fe1b4360351d2662a96871e40366140c6613a Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 17:37:09 +0100 Subject: [PATCH 15/55] v1.4: Fixed a lot of bugs in reading in guidReference.txt and games.txt, no errors or exeptions thrown in this version --- java/src/console/app/App.java | 51 ++++++++++++++++--- .../polytopia-backend/{console => }/games.txt | 0 .../{console => }/guidReference.txt | 0 3 files changed, 43 insertions(+), 8 deletions(-) rename out/production/polytopia-backend/{console => }/games.txt (100%) rename out/production/polytopia-backend/{console => }/guidReference.txt (100%) diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java index dbd809a..06828c9 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/App.java @@ -3,10 +3,13 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Scanner; import static console.Main.log; @@ -14,18 +17,36 @@ public class App { static int guid = 1; List games = new ArrayList<>(); - public App(){ + + public App() { readGuidReferenceDotTxt(); readGamesDotTxt(); } private void readGuidReferenceDotTxt() { - try (Scanner scanner = new Scanner(new File("guidReference.txt"))) { + try (InputStream input = App.class.getResourceAsStream("/guidReference.txt")) { + if (input == null) { + log("guidReference.txt not found!"); + return; + } + List lines = Files.readAllLines( + Paths.get(Objects.requireNonNull(App.class.getResource("/guidReference.txt")).toURI()) + ); + log("Read in guid reference: "); + lines.forEach(System.out::println); + try { + guid = Integer.parseInt(lines.getFirst()); + } catch (NumberFormatException numberFormatException) { + numberFormatException.printStackTrace(); + } + } catch (IOException | URISyntaxException exception) { + exception.printStackTrace(); + } + /*try (Scanner scanner = new Scanner(new File("/guidReference.txt"))) { if (scanner.hasNextLine()) { + String line = scanner.nextLine(); try { - String line = scanner.nextLine(); - log("Read in guid reference: " + line); guid = Integer.parseInt(line); } catch (NumberFormatException numberFormatException) { numberFormatException.printStackTrace(); @@ -33,20 +54,34 @@ private void readGuidReferenceDotTxt() { } } catch (FileNotFoundException fileNotFoundException) { fileNotFoundException.printStackTrace(); - } + }*/ } private void readGamesDotTxt() { - try { - List lines = Files.readAllLines(Paths.get("games.txt")); + try (InputStream input = App.class.getResourceAsStream("/games.txt")) { + if (input == null) { + log("games.txt not found!"); + return; + } + List lines = Files.readAllLines( + Paths.get(Objects.requireNonNull(App.class.getResource("/games.txt")).toURI()) + ); log("Read in games: "); + lines.forEach(System.out::println); + games.addAll(lines); + } catch (IOException | URISyntaxException ioException) { + ioException.printStackTrace(); + } + + /*try { + List lines = Files.readAllLines(Paths.get("/games.txt")); for (String line : lines) { log(line); } games.addAll(lines); } catch (IOException e) { e.printStackTrace(); - } + }*/ } } diff --git a/out/production/polytopia-backend/console/games.txt b/out/production/polytopia-backend/games.txt similarity index 100% rename from out/production/polytopia-backend/console/games.txt rename to out/production/polytopia-backend/games.txt diff --git a/out/production/polytopia-backend/console/guidReference.txt b/out/production/polytopia-backend/guidReference.txt similarity index 100% rename from out/production/polytopia-backend/console/guidReference.txt rename to out/production/polytopia-backend/guidReference.txt From cd15e960bfe12bf72ef3cfcc3036519247ee0164 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 17:47:03 +0100 Subject: [PATCH 16/55] v1.5: games now can also be queried & listed with the "gam" or "games" command. --- java/src/console/app/App.java | 9 +++++++-- java/src/console/app/AppUtils.java | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java index 06828c9..4271056 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/App.java @@ -16,7 +16,7 @@ public class App { static int guid = 1; - List games = new ArrayList<>(); + static List games = new ArrayList<>(); public App() { readGuidReferenceDotTxt(); @@ -67,8 +67,9 @@ private void readGamesDotTxt() { Paths.get(Objects.requireNonNull(App.class.getResource("/games.txt")).toURI()) ); log("Read in games: "); - lines.forEach(System.out::println); + games.addAll(lines); + reportGames(); } catch (IOException | URISyntaxException ioException) { ioException.printStackTrace(); } @@ -84,4 +85,8 @@ private void readGamesDotTxt() { }*/ } + public static void reportGames() { + games.forEach(System.out::println); + } + } diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/AppUtils.java index fbe3e5e..2d2aab5 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/AppUtils.java @@ -22,6 +22,8 @@ public static void handleCommand(String input){ report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + App.guid++)); else if ("delete".equals(command)) report("Deleted game: " + gameName); + } else if ("games".equals(command)) { + report("Games list:" + App.games.toString()); } } if (ValidCommands.MENU_COMMANDS.contains(input)) { From c17f36bdbf80696927e3fa409a8ab862e52bef05 Mon Sep 17 00:00:00 2001 From: Fabian Bernat Date: Mon, 27 Oct 2025 17:47:34 +0100 Subject: [PATCH 17/55] v1.5.1: add missing whitespace --- java/src/console/app/AppUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/AppUtils.java index 2d2aab5..9134ece 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/AppUtils.java @@ -23,7 +23,7 @@ public static void handleCommand(String input){ else if ("delete".equals(command)) report("Deleted game: " + gameName); } else if ("games".equals(command)) { - report("Games list:" + App.games.toString()); + report("Games list: " + App.games.toString()); } } if (ValidCommands.MENU_COMMANDS.contains(input)) { From aa0c809c5d6af9084a052c04e45747013aa55224 Mon Sep 17 00:00:00 2001 From: Fabbernat <114658229+Fabbernat@users.noreply.github.com> Date: Thu, 25 Dec 2025 01:51:14 +0100 Subject: [PATCH 18/55] Revise README for clarity and new contributions Updated README to reflect changes in contributions and added information about the Java console app. --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 40a62ee..4eeda7d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Read Me: +# Read Me: Welcome to the `polytopia_python` repo! Have a look around and familiarise yourself with some of the code that's already here. @@ -16,8 +16,11 @@ The `settings.py` file contains some settings variables that are mostly unused a these is the `CHANNEL_ATTRIBUTES` object, which contains descriptions of what each 'channel' in the game state array will correspond to. -### Fabbernat edit: +# Fabbernat's contribution: Polytopia UI can be found at https://github.com/Fabbernat/Polytopia as an ASP.NET app. -I've added a score counter \ No newline at end of file +I've added a score counter Python module and a Java console app + +## Java console app +This is intended to be the main app at some point and will simulate the core logic of the gameplay like map generation, tech tree and even combat From 910e63bdba5068ee4d230eed13a696357b7446b1 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 15 Jan 2026 16:06:33 +0100 Subject: [PATCH 19/55] Fix games bug --- java/src/console/app/App.java | 65 ++++++------------ .../polytopia-backend/console/Main.class | Bin 0 -> 1606 bytes .../polytopia-backend/console/app/App.class | Bin 0 -> 3549 bytes .../console/app/AppUtils.class | Bin 0 -> 2951 bytes .../console/utils/ValidCommands.class | Bin 0 -> 3488 bytes out/production/polytopia-backend/games.txt | 7 -- .../polytopia-backend/guidReference.txt | 1 - polytopia-backend.iml | 11 +++ 8 files changed, 33 insertions(+), 51 deletions(-) create mode 100644 out/production/polytopia-backend/console/Main.class create mode 100644 out/production/polytopia-backend/console/app/App.class create mode 100644 out/production/polytopia-backend/console/app/AppUtils.class create mode 100644 out/production/polytopia-backend/console/utils/ValidCommands.class delete mode 100644 out/production/polytopia-backend/games.txt delete mode 100644 out/production/polytopia-backend/guidReference.txt create mode 100644 polytopia-backend.iml diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java index 4271056..3cbab4f 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/App.java @@ -1,7 +1,5 @@ package console.app; -import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; @@ -10,7 +8,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.Scanner; import static console.Main.log; @@ -19,12 +16,12 @@ public class App { static List games = new ArrayList<>(); public App() { - readGuidReferenceDotTxt(); - readGamesDotTxt(); + readGuidReference(); + readGames(); } - private void readGuidReferenceDotTxt() { + private void readGuidReference() { try (InputStream input = App.class.getResourceAsStream("/guidReference.txt")) { if (input == null) { log("guidReference.txt not found!"); @@ -43,49 +40,31 @@ private void readGuidReferenceDotTxt() { } catch (IOException | URISyntaxException exception) { exception.printStackTrace(); } - /*try (Scanner scanner = new Scanner(new File("/guidReference.txt"))) { - if (scanner.hasNextLine()) { - String line = scanner.nextLine(); - try { - guid = Integer.parseInt(line); - } catch (NumberFormatException numberFormatException) { - numberFormatException.printStackTrace(); - } - } - } catch (FileNotFoundException fileNotFoundException) { - fileNotFoundException.printStackTrace(); - }*/ } - private void readGamesDotTxt() { - try (InputStream input = App.class.getResourceAsStream("/games.txt")) { - if (input == null) { - log("games.txt not found!"); - return; - } - List lines = Files.readAllLines( - Paths.get(Objects.requireNonNull(App.class.getResource("/games.txt")).toURI()) - ); - log("Read in games: "); + private void readGames() { + try (InputStream input = App.class.getResourceAsStream("/games.txt")) { + if (input == null) { + log("games.txt not found!"); + return; + } + + List lines = new java.io.BufferedReader( + new java.io.InputStreamReader(input) + ).lines().toList(); + + log("Read in games:"); + games.clear(); + games.addAll(lines); + reportGames(); - games.addAll(lines); - reportGames(); - } catch (IOException | URISyntaxException ioException) { - ioException.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } } - /*try { - List lines = Files.readAllLines(Paths.get("/games.txt")); - for (String line : lines) { - log(line); - } - games.addAll(lines); - } catch (IOException e) { - e.printStackTrace(); - }*/ - } - public static void reportGames() { + public static void reportGames() { games.forEach(System.out::println); } diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class new file mode 100644 index 0000000000000000000000000000000000000000..d6e84735cee62abc2210685f94686796fb7359f8 GIT binary patch literal 1606 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 literal 0 HcmV?d00001 diff --git a/out/production/polytopia-backend/console/utils/ValidCommands.class b/out/production/polytopia-backend/console/utils/ValidCommands.class new file mode 100644 index 0000000000000000000000000000000000000000..100c1dabadafdd25ae54c25210f21302c610f56d GIT binary patch literal 3488 zcma)8`F9k>8oiaw(8HuLVb}}?h!`Y`W^hFrL_;7Dodjhd2)NLhsU%H$y2t4raK+{M z9?xChbBoJ+?znGg2zZYAv&TQkb38}A)!mcn2?@vKOm|n+SKoKP?NFJAFX zE1i={*7eHLC<^gG3(VHeyauENzCuF-KVuA_NhjgPzEGF$>$ z%H-^jdCMrv!hk8Q0_WN~yAB6=_a)16Oa(seFVoS@xIx2>I<_EFt)TPem?LT)@_m&m zWvu$aByPes4L9q!1$_dmYiO!Asv86nrknE&&&&(NItRNBslEL=cBqY-=?<8-VJR#F zI(A_<=V)%<10^Z2tl9%LKZ*J(+^XX?WZ9dh+vmFGxUF(9PR>Fd@*h zH>e%Tw~ra7B@6AxC-`a)8;!B!XSGx%jw0O+^A~qL zqwFQ%(7}SVq$d-&kJIB!nXBA)b$F-P2Z+ zs$IuM3-nbPodwd1hjcuwxM4L*A;JP#=eR8ASr%++rSXK0N0r7ULpz7}N7CMl$8|h` zf3Q@-=rNC>MQ&BSTrJI6vr6ATbsSdu7UxvSdzCVa__BI zTMk!>qq4kD1zjMOb@GNaVw8Ov3nydVgh>!v@q$| zyx``fVqO__j8;Znb@;@`6t%JFBaT)nC!jR}LC4ld*5@yKdeCU7{#inH%1;g%B_Fni z*O}ObSyN!c+~h2PIk7cwg%^cn&Z(61a)1rPnAh=ouWC;c&34D}T(4}DhNL&)6x@tz_hGLK(kig{Y_!5r2I(;Yq0!l@Q~G?(qd&S*~H<0L*&$IqwL6TmMy z7Rsb7ga=d$KA(Gksawx9FT$5ee1)$ye52!AoM!xMtYg~8oJpC^8pY9qLGO$)LmmDs zS;(vcJFnQs<(NKq)`&m&r z(~~SNebBafN~ptGx*DcgIOoM=KKC@t2&}!ZC>tV(1ui8oH1PV^h}DX|An-4a8gP>5 z3dyH#O!A0~WBld~m(PE5N1AWJcf9)y#5cZ*r}$LM;LY}FKJ`EV&)`{(l8ORpNNq0h zt>iko&q5P8hb6#SEN$S|DqseeoW-RL_!)^AZ1|dTjZnH*(mZ%qB-7BsTHa%MU*x!E zs{_vwGVnZqfps-=lB2-M7l?v98lVujBSyTG{sBD)&)~}2QoYmIlX7xGbKc;cVG)AV-;g3>>ICulAT4Q1w zchQ|K@$T06S=`%z15Y-JQ-8j7iWK_;<4@gidrr-lP~ z3IE~T2}be1c$x7`Fg~x$Ms6kJ6Ud*We6Xfs_$q&Gi2sRYh-pY@cug>gzvIYnh;P@B zZwjM4#XWD#7RoKmp89J`St&bJnnC$Aj(v+`GkEYc9+|;or}3n!AL09&A}+8TbQ)>K sa^`py_rJ-x2E2v0`Bvz@;N1W4f4uL1eTY-|4Bv4rNiE;w3~Q + + + + + + + + + + \ No newline at end of file From 3b19fc9231d4aa709fea584b24c835a566dda5d3 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 15 Jan 2026 16:49:00 +0100 Subject: [PATCH 20/55] Fixed some bugs and improved code quality --- java/src/console/app/App.java | 35 ++++---- java/src/console/app/AppUtils.java | 77 +++++++++--------- java/src/console/utils/ValidCommands.java | 2 +- .../polytopia-backend/console/app/App.class | Bin 3549 -> 3312 bytes .../console/app/AppUtils.class | Bin 2951 -> 3137 bytes .../console/utils/ValidCommands.class | Bin 3488 -> 3518 bytes 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java index 3cbab4f..6506b21 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/App.java @@ -22,24 +22,25 @@ public App() { private void readGuidReference() { - try (InputStream input = App.class.getResourceAsStream("/guidReference.txt")) { - if (input == null) { - log("guidReference.txt not found!"); - return; - } - List lines = Files.readAllLines( - Paths.get(Objects.requireNonNull(App.class.getResource("/guidReference.txt")).toURI()) - ); - log("Read in guid reference: "); - lines.forEach(System.out::println); - try { - guid = Integer.parseInt(lines.getFirst()); - } catch (NumberFormatException numberFormatException) { - numberFormatException.printStackTrace(); + try (InputStream input = App.class.getResourceAsStream("/guidReference.txt")) { + if (input == null) { + log("guidReference.txt not found!"); + return; + } + log(input.toString()); + + List lines = new java.io.BufferedReader( + new java.io.InputStreamReader(input) + ).lines().toList(); + + log("Read in guid reference:"); + lines.forEach(System.out::println); + + guid = Integer.parseInt(lines.getFirst().trim()); + + } catch (Exception e) { + e.printStackTrace(); } - } catch (IOException | URISyntaxException exception) { - exception.printStackTrace(); - } } private void readGames() { diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/AppUtils.java index 9134ece..7212d7f 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/AppUtils.java @@ -5,51 +5,50 @@ import static console.Main.log; public class AppUtils { - public static void handleCommand(String input){ - if (input.isEmpty()) - return; + public static void handleCommand(String input) { + if (input.isEmpty()) + return; + + if (input.equals("help")) { + log("Available commands:" + ValidCommands.ALL_COMMANDS); + return; + } + + // Check menu base commands + for (String command : ValidCommands.MENU_COMMANDS) { + if (input.startsWith(command)) { + String gameName = getRemainder(input, command); + switch (command) { + case "start" -> + report("New game started: " + (!gameName.trim().isEmpty() ? gameName.substring(0, 1).toUpperCase() + gameName.substring(1) : "Game " + App.guid++)); + case "delete" -> report("Deleted game: " + gameName); + case "games" -> report("Games list: " + App.games.toString()); + } + return; + } + } + log("Executed base command: " + input); - if (input.equals("help")) { - log("Available commands:" + ValidCommands.ALL_COMMANDS); - return; } - // Check menu base commands - for (String command : ValidCommands.MENU_COMMANDS) { - if (input.startsWith(command)) { - String gameName = getRemainder(input, command); - if ("start".equals(command)) - report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + App.guid++)); - else if ("delete".equals(command)) - report("Deleted game: " + gameName); - } else if ("games".equals(command)) { - report("Games list: " + App.games.toString()); - } - } - if (ValidCommands.MENU_COMMANDS.contains(input)) { - log("Executed base command: " + input); - return; - } - } + private static String getRemainder(String input, String command) { + if (input.length() <= command.length()) { + return ""; // no remainder + } - private static String getRemainder(String input, String command) { - if (input.length() <= command.length()) { - return ""; // no remainder + return input.substring(command.length()).trim(); } - return input.substring(command.length()).trim(); - } - - private static int stars = 5; - private static int score = 500; - private static int turn = 0; - private static final String tabulators = "\t\t\t\t\t\t\t\t"; + private static int stars = 5; + private static int score = 500; + private static int turn = 0; + private static final String tabulators = "\t\t\t\t\t\t\t\t"; - private static void report() { - log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n"); - } + private static void report() { + log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n"); + } - private static void report(String message) { - log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n" + message); - } + private static void report(String message) { + log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n" + message); + } } diff --git a/java/src/console/utils/ValidCommands.java b/java/src/console/utils/ValidCommands.java index 9396c53..f3f32e9 100644 --- a/java/src/console/utils/ValidCommands.java +++ b/java/src/console/utils/ValidCommands.java @@ -11,7 +11,7 @@ public class ValidCommands { // --- Command definitions --- private static final Set menuCommands = Set.of( - "start", "delete", "games" + "help", "start", "delete", "games" ); diff --git a/out/production/polytopia-backend/console/app/App.class b/out/production/polytopia-backend/console/app/App.class index a6e230040170c8fb2d5201204d09bb6f64051a8a..4e68b905adb1b0830e4f810bc5305f5b7a0c7f85 100644 GIT binary patch delta 1705 zcmZ{lSx{S57{~u73-{*cvW3lvMXJyy1RJ26kV=8tQZ$sdC^TBNF%P@}tqU;2Qf8d`qBFibo@>&DsfA?R z#aN@jUXky@ZnQ~gSJ8nz##Vb{zCzslBy_6Sk1pdkaX|5~1^225quWT?JKQ}uDB+Nb z!{{}Z?QL=o?pM)=e#7plX^IFGT~AoS5j?2ksM!n$JCRiIFdmU`Ohpur8v7lg;sF&J z25FA7y#k`k_|*|9x4N`WsDlrPH*oC zfdZ3mbs|-58CzVuBPUfnfhURBl6b^it1NI95jT49l#0{lMRLZ5wP?mv&ZszxbH<-e zf5S5>o;8i`j5dML~OJq7RMqJ$4rd}#b3`3gQ!@F_l%@VSaF@a0^YTp}Eo1Zs^d@`i$JO*ow% z8Xb}Ftw5D28M>=tBayK+vRNAhUf^#VCVAOd1M)ad8JApD<1bS8z_Bux(7zNa%J32u zm0mzTrtvao8{D}#K%8a{GiJ&odkW`K91i@9Vo${)yn&w}1?F+<42K%7m*I;nqb?Fy zK>Y%O^JrSa4uNjZE*k4+Z%1on5qI}r#y!9i4%kq?g!=@p;K)24Sj0nfh#3iG(|CG@ zTgoU#3USB8)@1ss;}TY`Aks10K+0RwLV%qEgQ{N*^?zv|r`IgTmT*dd zGa>RD*T0CTuZ7od!3)EjkK!0h&cRDr@P=4^lJ%^^3$oG%*3w9LtwhsAc-x6)Cx10_ zY~cqYgf8sj`)_5|AcoM!EUie>l0~}(uiJ!YhOWYkAqOvg6+Hbqcsc)7c>Xnbr%ZI? zMCUT!Tn-(R+esKGXYI2$T&WpagjJP7;O0L@883&7*Vp0$mJSp;c;A*W5m~_Va~Qim nu#o31;#P0?n8k)S@fK&Z(;riO8}H&Hd__f}@-@D}ckuoPL>FA; literal 3549 zcma)9`*##q9sk@sn9XKrLLfja4GBVW;0|mvz?s< zQXkb~E%mLfw!YuuTY+=Zv>vrT={X+%Aw3@J=iZswOi0S<51G67d%y3`_xrs!|NP&Z ze+4j(yJ<9`*+7elR%{XIykcFovX13evghWm$WkD%<(Tc-!3lxpfy`_gDYO|#n`l?; z9VO4Ld5+9l)oOO6S{2ykORIdkZkMO!y!548l8<@8%=JJCZZ+|MmeM@dlB|ViDRc^S zWh+UkLxGa%!gd2YOmt%>jjj1M=2*3w!1js^re)2m`z1M2D+biNB#;>>tZ|ZPyOmrf zB-vgz?^f$U$eK%Gx4@nacKci}=$rTIZn-~=_uxSTdrUlpUI8)``*O;2r|OO)@NiOXh;y8w1oSxrS%$0?M%L0egu^D@ zj}OrIz`HP=XUPmi$qjk(^yq_l)WC;Kd>9{zBX=44yv<5EZv_kVshM#K?2jCYMPp4# zX6nn~n1SOaPT=GQeG^nleWt3Z5yvUmuG+>Ct}`3QsIybBYXJ*jL}2$cMf+^GPZdI+ zAJ^^CzBY{EF$3c!CUAOPZxoknfn=#Qdv!+9-6*c}EVj6~y4YZO6KC-_d&nx4WR^ajUc-&=;wH z2Su4#rK)IR1{VZUgkZwrIR0-9T2PPsLTs zuSss-p*Qkv90s2-@iZ)f7L|1YkvC8h*sEjH>Za;TbJCyi{3R9^|o0W$^ zf5Psj6-3!qCma&3wz>Ulve@U7~k3>Lg{trI`Ir=jA}J=@WWAE)sX z{M5kDO#ED3RgKb3%o&af-7}8ciQbMaxRu7Q@M{CVG4Wgcj@Qjv_u1}MZ&79o*3w+r zq65~vRSG`hw($E!QpUInu3HCRhZtOe!9Vl1X8jVsU1;IAo8O&OvH@i|@9V=|Gp?_do*RECc4E-I(&Yi!fkG38-@vrMTbNzN6L<0PTS!eETtS)YdX>6c z*fq)Do#F4csbL=4!+#@Nfuv_{)9*Z^&BDQx4YuJGaYv|g(SaE*JQ2hU?ET0NdW zr}yFNF&>qRY3$&454AkR_58(!%R4?MF&gBup@f2-*4PR&O4kL?i5HJPcHGC5Slsc_)bH9NYjCi7Ba(@yQ2Y>Nw(mPBQ4je?9x3^Znod@6qP- zE#7bcshkCH3o47=KU{-eXxOY%xP?JW8dh>38#fuGHEV}7+{z$Zwvn?nY-Ld5X4CxgeDnXv3!DwALct{EC~4AvCUaF>p|F~;Dt zvpc4z&D?-tnY?T#FWco9Qfm{#eLC8($%8%Et72S-feEKiYTM>QoS*CpKtkAyn~~6w zL%f7*f46-VwbsQkfKT6?*jBo%7 zzfQx9j#H+i&y+=#8g%_NX)O9%s)wKKa+!n+%uq&l;(;F_AVSli8^$BNMm!n1;6* zT#W3yglLfHj*x%a@hdHBKVa~07Q=`~_#pcDQGsC<|ICV{sa28s*cnq+volUc+2H<^ zVV!e8i3ONn!RKBqU{OVpqbWJRDc`fQ^R*gvFENCjvufB;wO|kROq=56oeE3+k!{7( zcm@o|$amp5kw?S`eZm>lg6p57JkNnRc%HN_d>Jp`MT$Il2_2L<0kEHFjtbd!=cLw? z|Gje=8yU_)8;N$6as3>Ah;}XErqQlGx2snUxyu+hLpK0pB+|mZBRpC!|9tIvvIEMo zZ#cU4kUKC~hJOwnQmdeY=mEH{u`Q#o(mX-q6$}N%4swjzh7@{8zJB8TY-NC_=_&_ zBHvF)#B8va5}c0KXc=}V$!REo2Nt5-B@&361v;&wuOmy$!-p$ThpPqBV70o2if;`^ z$^96P8MqJlw ziQpp_i(UUK-hcp+^WRc7;-nx=!)pRdp0*M(aT{@@m2j|u{aL~xsf0WqRuxFK-6%cV zNkF;?`y8FQa0;)Jy^dD=Gy3ukq2Chv9iiV7`hB546#65f=Y{?RUtksg+ZevYS6D{q EUlEcIuK)l5 delta 1277 zcmYk5S#T6p6o$V$GnwvmI*I92)(VqQ8iYwg3JS2?s6s&uCL;)_V3Z8W7=puu%yd8$ z9pi@k)-DW+8){Hol1Wt{W0fU7>Z8w=&pye6Pbs%^@1#;Z^ttz*dzSzI&+W_2r<$Cf z|1JFhY-48Dxe{uy*{Z0>`N^#BU~`AdcJ2(egk#l}?BFhoewP6f!SCUATd~unon669 zxOFf@%waQoT=ud`5zVHKj%R(F`(!_oN~ipkN0^w+eyKm2Jd(=kf`cymc~Egp|Ka4Z zWbdRumhSBz%lS7~aeyI`*p^ zU@+Re=6yx&RwKnohgRSweRWn*t;2F1Y2j^L9|dQlE7T{!_t6dNTyQ12spfNsFPOGC zuUmZ?Y_NWfe68pVhOEx0#kY!>Y-7P5yD=|pWSoxXVAd06ZRIJZP&_TZ5YGq&p=|7P z;^~!>s6<8lB5KxzJTF!UfyF7YZaIJ#cu`K3yu>Q;NV1xcu|~xAQ+yt)5d2|x=TCMm zuu<_N_CUOQo*N4!;@y|HHR0VZ$Jzq%o_V%O_!+F8Y3hpf?TZY}(O2SbC~*&5V)rx+ z-o52`x2MGYOU^cLU)fn-Wh`Lno}6fDM5Wu z?{wyO)5v2qi4@JeLJOy9I#>*mMm|I9o#+1k?oOxAt z@S1$rQe`*&jXa2T|8Rq)H}-&h&LVZ8MVc20HOO4HNFhaD%YHy==qjzE+@UV{&Jp#} zGjYA0CnFNg$ZZ*SjjV7TRdiCrYJ;n>oV|oW>KI(gpb4XDq}$%*8Lu@3z$-FQB<~kF zUJ?b2JW^R1bsST19d)cX$xAa9&=E6#kzA5>u4wzja&qXZ%_CsSoHuh7#k~~LpF7OTCp<4b2X&dgE diff --git a/out/production/polytopia-backend/console/utils/ValidCommands.class b/out/production/polytopia-backend/console/utils/ValidCommands.class index 100c1dabadafdd25ae54c25210f21302c610f56d..6ce4393445715d94b64db9f83ce1418c95dbc68f 100644 GIT binary patch delta 754 zcmY+A*-sNu6vlrSI_)qW2o@t@5j6raU_>8`i7!Tw#0NklF~;zqjf{d+TInDtSX@y= z6us^%t|+dhDisa!nZLorf5XH?JwpxZOzt`NEZ_H?d&Wv;O03zR(=$LlpOaR?Snq3d zA~BPw!ic-!F4yFs!r$sd9M>_4OL|}UfD;d5(rt5y9z|AkuVUv4J!_QfuSQ5trT?(a z5&E?0ukanUN$55HS0Jo0=_Fanj}#r?u-}?LTAFLSJga zA6CXKMRAKx21<0b87X~ev5iMIkJ+jSy3vMckJD8bjyw50)hV-DJ=5RJ;>rn&7i0%` zsVLkZJ`}F$cH1L04egyBPV44yysg=B^Lee8|F={<^O{n=QpZL`Ykayde`t~ZzZkGk~g zxVdCe&LvN`qB7ppvc0Wwb3DGjW#cxR74*5B;G`ZhSDL3>TInu+F%!zTp{Q-uUjq@{ zY^CakZMO2j)~Q!|O>ps? zH#Tp17hu$7jAC(LFs#%^owH;9k|L`6?5LiytMj55#cBDzj{-HG+W&G9_D}AY*uWHf zMi33+m3SjIjvAez7!d2V!Qre}w>-c(1|_rUB_@kJaJzVgZgZBa0X^tMR{b5X=e%r% z3$p}Xr65^Z=S3ls+j&I#yE7vAes^u&!^iNmpCChmH(1CHE^%3EIgz8eA`30yxH`*T zEga?fg3OnD-kxjn>M(v0#vi~6a9zotrrG%eV?%&l&L1T=1#_!>j~;=nqe84eL#fcg zP Date: Thu, 15 Jan 2026 16:56:52 +0100 Subject: [PATCH 21/55] Now all game names start with a Capital Letter. --- java/src/console/app/AppUtils.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/AppUtils.java index 7212d7f..bcc1ea3 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/AppUtils.java @@ -19,8 +19,22 @@ public static void handleCommand(String input) { if (input.startsWith(command)) { String gameName = getRemainder(input, command); switch (command) { - case "start" -> - report("New game started: " + (!gameName.trim().isEmpty() ? gameName.substring(0, 1).toUpperCase() + gameName.substring(1) : "Game " + App.guid++)); + case "start" -> { + StringBuilder capitalizedGameName = new StringBuilder(); + + for (String word : gameName.trim().split("\\s+")) { + if (!word.isEmpty()) { + capitalizedGameName + .append(word.substring(0, 1).toUpperCase()) + .append(word.substring(1).toLowerCase()) + .append(" "); + } + } + + gameName = capitalizedGameName.toString().trim(); + + report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + App.guid++)); + } case "delete" -> report("Deleted game: " + gameName); case "games" -> report("Games list: " + App.games.toString()); } From f44dbf8946ccc6b966523116014a1a25936da943 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 15 Jan 2026 17:16:32 +0100 Subject: [PATCH 22/55] TODO: fix problems of this patch --- java/src/console/app/App.java | 8 ++++++-- java/src/console/app/AppUtils.java | 7 +++++-- .../polytopia-backend/console/app/App.class | Bin 3312 -> 0 bytes .../polytopia-backend/console/app/AppUtils.class | Bin 3137 -> 0 bytes 4 files changed, 11 insertions(+), 4 deletions(-) delete mode 100644 out/production/polytopia-backend/console/app/App.class delete mode 100644 out/production/polytopia-backend/console/app/AppUtils.class diff --git a/java/src/console/app/App.java b/java/src/console/app/App.java index 6506b21..9a95879 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/App.java @@ -13,13 +13,17 @@ public class App { static int guid = 1; - static List games = new ArrayList<>(); + private List games = new ArrayList<>(); public App() { readGuidReference(); readGames(); } + public List getGames() { + readGames(); + return games; + } private void readGuidReference() { try (InputStream input = App.class.getResourceAsStream("/guidReference.txt")) { @@ -65,7 +69,7 @@ private void readGames() { } - public static void reportGames() { + public void reportGames() { games.forEach(System.out::println); } diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/AppUtils.java index bcc1ea3..e62c05d 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/AppUtils.java @@ -32,11 +32,14 @@ public static void handleCommand(String input) { } gameName = capitalizedGameName.toString().trim(); - report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + App.guid++)); } case "delete" -> report("Deleted game: " + gameName); - case "games" -> report("Games list: " + App.games.toString()); + + case "games" -> { + App app = new App(); + report("Games list: " + app.getGames().toString()); + } } return; } diff --git a/out/production/polytopia-backend/console/app/App.class b/out/production/polytopia-backend/console/app/App.class deleted file mode 100644 index 4e68b905adb1b0830e4f810bc5305f5b7a0c7f85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3312 zcmb7H`Ewjc75-X>XS5zi^7@KmBV=PIT1oLb!6DcyL}Xz*f|ZhZg&c(d8O^rVW6jQN zW@c?GgaiU{z$ORxeIMoqCsnc?R{=jk6~+HUQAGh?_sp(ltvJ9Bn%;i>`n}iR``+u; zU;p*y9{?Q3?{nxvw}BoDz1So$aMnI&7d_jr6(3nTE1g(i(}e50@uWcaNMRud6MY79 z7B(yOekbswz>`J0(I`$e8UkBGX;)7+-Rdd1EJNu#Qf=8{;SQzWr&sN|j7$s&ITcl6TC58cM#}4&6uN$`R7gZ_ zP@M4_%{WmlnYdG6&j!E4eh?2Y2Ti}aH;22h+rS?fOK)3u*#8-pU`* z8OB}%`z*XmopzfJsnfqeuj2&~QR%_~3wPrl0W%JgkOj7nq*3b>qz5rj5ZKu=cC5Lq zkR`x&RfYz}1O{7cnS&9l;@`bRT10S4u3T>+iO37!NRd z&-EqKu)7s#Gj_dVq|;DLA>ND!@m>Q*EF48iV3&?=qaLXh2W2-R$MPN4l#F?LAGfL) zhFyPHQR-+P-j9b2Oj$UF=?>ObR-;&w5ZytOc6YQX@F*$KqCk$sKVjgcg@-Xi z%C5(kaGuAzBa}}!-B8X2{#?`Z1n$lzJW*>AEK@C|Jge$M7Rs3A>1@Z54MuTz9n)+W zX{I$W%T3>j-M}wSv!I%F8J0HV5j<+(F$mRb;MAZGkn z)?`Re4Lgh^r?;t=nLaj#ss)K<8dV7v5HkjNqNH%Nx%o3;aNb_>STHr5wXgyvV~bs_ zR+^1QD5FSnTZ`G9JIGuEtk{gT;}=v$h;t5gG%P%=n^+^^hV87(hqfb4kN~-2O|>c2 zl1wxSR(qQ16ei9yGi{El4Wy=tRSVDH6D$_JiFhV7)Ctn$RvG!Eh3Axe_5*2$O7bZS zpT-M{IMwQuBHds%v(tg+u^}_~QsD{JR6lFsMP*=fC>uc-pH%J*ywp0d_BIuU_NsE3 z!$own0ax6bZ^zA03hcins)=N-K}ffAj*rD`&m8(3! zd$Si;YI)lByowu3CeWJkeHrRIT(R`m0tYr=@>a()@LPeQw^wDwBv@cC%c6@dxQiqE z3V)fZ5+U$;Exp7`jzq*JelJrp%~5dNGI|YSbPWdotgBk}3;gEMlYY0+&~~Ut$;*^j z$tJvlSG6wRLWx1~9F40zm-A-6e~o1@_7`l;@4Ak`u|LBYTf;lA^0S}wn;2QViP6Qe zYZ$+V;u`M1f%gc^<|nu~{yHWXuj2!!-^2%j8<^|D_zlbp{0Wb*;fd?`=o@foaY$Lb zf?c!xJ&^qFn;XA(4et0KufQx*6kKIUCbq(71HB859wVG5E;R-^$R1Ls`~^MsKpp!8 zwbhDAxrN=F6*$|&*$@#P#v$y(5$wk#4&X7|Lz@L!9i)vhS}Cy8jN%f;HB_U7;|S-6 znFW=VXYd-nNU$za{v~`_19>?G@+`x^8W!d52`>!242(IJ9xA3>iar1ul_2MFnd{B?xiqkQ<3FoOv` zF(zrX$P4iyTA4(^l?YR9(ACZs=uQfHkO4iu1@uI}1$v^JB`sh40`;_$4JdN`8gk;CC4O4}wuU(EtDd diff --git a/out/production/polytopia-backend/console/app/AppUtils.class b/out/production/polytopia-backend/console/app/AppUtils.class deleted file mode 100644 index 9b14431ce7ea5ce2399be0b4a1ba37ff78a7389b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3137 zcmb7G>vJ1d75`o9v1@s4CF|O$6Y8i4vcZMv2=*2e2y zRl9N%FqD?IKzX-3>H>XGz|iuDZEzEPU|@z%3}2Z6{scbrB@9FP-7CpeVwZuP+50@_ z+;e{Cckb>V|6ThnfD@=C(1wVHsDT*b0$VOx^H#>SyqV1D>5FzD6o?;nJSV(QATlsG znShRV4G9AsFm4c^2rG^^BcM6KiMeukS;%wfn6GQ8@LmD=yx-!oaJ}~%k>G~4R|z7O-eil3=B#z zI&&xT=hoxxz+Mb#7&fpE8Ez^to3RV%PH0!G(60#e4m4a+yW@@Zkq#Wd-5TyOaIeg# z`JREM2w^K(LEgR;O4&mOK8V9ismBF@Tbfo|7^5IRVqgSE1rh|Q5(W~n_la&Qf&1_g z4Iecyieog{#KkI?Go$75Ihl0AHDiZo>^Wjpv>DFf)&w?y+6eikSy6oE2yf zs?$NEHhKrf$KS`NNdpf_vmK#-u3WY&V^&~GGY?BMQxdz)G))M6QWEr7J08P%4UZeJ zFwHW!2}SxlQ+0|0V%%iK7p2aWi3s{7{4)khl9Ta@UG^)Xi75J|!C3=YxGeLQqRKi! zNZNStHIxlJA)W4ek2Z3f$cBC$3X|7Lzlpd!M@QAbyaYb(+TKi9(s4-u?R4N%20ks_ zR_F(EtfF9te(K$efrTaGKe zPBqR9jtCvE3A73JZ!o3z)1?&0Zwhqh?I%;x&6Fx8yLd!y{Ftmft}3diHr3)1J&>9~ z$|Z5s($55LJ#on{R71O%nr0a^k5s#F3uG3Chlf)_84pqmawIt-M`0-?*^#2aLNg%= z9lsFR|9>ipWE{Ur;0l&CtVj~A3hY)iS67|m&HJ--d!(n z+=egneqimZ_N!dUaK?)PvGh9-%Sz;HY^4xb_g`mg)CKSje3P?wyofzqp|bc3FSR)P zUFkJwi-<0#&CWF>7h$fT`zrcsxDB|9+m}~x*EOUCvU{&#zrZ5?&cQ)}-$Bo%ht}|+ zMf^EEbQK@Y4;_iN9g6iu*D&@rPXiw08@Y1fIXSy0_Sd_A&weca!XIR4yZWN${p!Fy zkwdX1q&Oc}^WLbOZ{hr7Y95ariuJ_aqK?N`K5cya_@3eW*zg*14B-{@4M%wN$JcOf znEfYecm!C&))hSZaP;w2JX%8msKJ(DU0A}-;ToLAxO)W`uYsSb8Y*gOdFkEmbPYir z0v;2Ag!UM=ux%%}@|u=^X`Q5e7g8{B6x}$^Kaf-CAvW7^oxdS5^obtq5WTof^r2te zK}ngG6z!eDpYRg18=>ak@GXuQme_@Fa|C>c??EK>ZU2I<;lR7dBd)2TYPqHhAel6_ z@62(W=(vvLb>N>gk^p}Y>RHBl&LvvgndNXV-!~CApS+TmLt;eeu5h;=To-%hg@3HO z8PZ+K(cZdt!zoEf>8McdMIgF(SZSS)`;-Al23=mk!U~?M;hA*=g+fsNM+5tb+ySNA zpd($5(k#GCG;|67bL+}&Ez0*2?1L@r?#6c&WIA46cSV+le3JSqE^ih8Sj9qQ6;H_` zsryk6HqBG_(vKl}zR0~cyn^qsKg=Bel9Jz7?W?MNU9~?{?T=LZ6V?7ywQs5R=lC_X N6V&_--oZ8W{RdA_-%0=g From 7a15e060932224805d1bea90c65cd2dd28d18ab2 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Mon, 19 Jan 2026 14:38:00 +0100 Subject: [PATCH 23/55] updated&improved `help` command --- java/src/console/app/AppUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/AppUtils.java index e62c05d..7f0de99 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/AppUtils.java @@ -10,7 +10,7 @@ public static void handleCommand(String input) { return; if (input.equals("help")) { - log("Available commands:" + ValidCommands.ALL_COMMANDS); + log("Available commands: " + String.join(", ", ValidCommands.ALL_COMMANDS)); return; } From d1127658e66d3e990e07783cd9b1578f51ac211f Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Mon, 19 Jan 2026 15:46:46 +0100 Subject: [PATCH 24/55] Add all 12 base tribes' basic stats --- java/src/models/AbstractTribe.java | 42 +++++++++++++ java/src/models/Tech.java | 35 +++++++++++ java/src/models/TechTree.java | 62 ++++++++++++++++++++ java/src/models/concreteTribes/Aimo.java | 10 ++++ java/src/models/concreteTribes/Bardur.java | 10 ++++ java/src/models/concreteTribes/Hoodrick.java | 10 ++++ java/src/models/concreteTribes/Imperius.java | 10 ++++ java/src/models/concreteTribes/Kickoo.java | 10 ++++ java/src/models/concreteTribes/Luxidoor.java | 10 ++++ java/src/models/concreteTribes/Oumaji.java | 10 ++++ java/src/models/concreteTribes/Quetzali.java | 10 ++++ java/src/models/concreteTribes/Vengir.java | 10 ++++ java/src/models/concreteTribes/Xinxi.java | 10 ++++ java/src/models/concreteTribes/Yadakk.java | 10 ++++ java/src/models/concreteTribes/Zebasi.java | 10 ++++ 15 files changed, 259 insertions(+) create mode 100644 java/src/models/AbstractTribe.java create mode 100644 java/src/models/Tech.java create mode 100644 java/src/models/TechTree.java create mode 100644 java/src/models/concreteTribes/Aimo.java create mode 100644 java/src/models/concreteTribes/Bardur.java create mode 100644 java/src/models/concreteTribes/Hoodrick.java create mode 100644 java/src/models/concreteTribes/Imperius.java create mode 100644 java/src/models/concreteTribes/Kickoo.java create mode 100644 java/src/models/concreteTribes/Luxidoor.java create mode 100644 java/src/models/concreteTribes/Oumaji.java create mode 100644 java/src/models/concreteTribes/Quetzali.java create mode 100644 java/src/models/concreteTribes/Vengir.java create mode 100644 java/src/models/concreteTribes/Xinxi.java create mode 100644 java/src/models/concreteTribes/Yadakk.java create mode 100644 java/src/models/concreteTribes/Zebasi.java diff --git a/java/src/models/AbstractTribe.java b/java/src/models/AbstractTribe.java new file mode 100644 index 0000000..8ad1965 --- /dev/null +++ b/java/src/models/AbstractTribe.java @@ -0,0 +1,42 @@ +package models; + +import java.util.Objects; + +public abstract class AbstractTribe { + private Tech startingTech = null; + private int startingStars = 5; + private boolean isSpecial = false; + + private double fruitModifier = 1; + private double cropModifier = 1; + private double forestModifier = 1; + private double wildAnimalModifier = 1; + private double mountainModifier = 1; + private double metalModifier = 1; + private double fishModifier = 1; + + protected AbstractTribe( + Tech startingTech, + int startingStars, + boolean isSpecial, + double fruitModifier, + double cropModifier, + double forestModifier, + double wildAnimalModifier, + double mountainModifier, + double metalModifier, + double fishModifier + ) { + this.startingTech = startingTech; + this.startingStars = startingStars; + this.isSpecial = isSpecial; + this.fruitModifier = fruitModifier; + this.cropModifier = cropModifier; + this.forestModifier = forestModifier; + this.wildAnimalModifier = wildAnimalModifier; + this.mountainModifier = mountainModifier; + this.metalModifier = metalModifier; + this.fishModifier = fishModifier; + } + +} diff --git a/java/src/models/Tech.java b/java/src/models/Tech.java new file mode 100644 index 0000000..0ea6a06 --- /dev/null +++ b/java/src/models/Tech.java @@ -0,0 +1,35 @@ +package models; + +public enum Tech { + NONE, + + FISHING, + SAILING, + NAVIGATION, + AQUACULTURE, + AQUATISM, + + HUNTING, + ARCHERY, + SPIRITUALISM, + FORESTRY, + MATHEMATICS, + + RIDING, + ROADS, + TRADE, + FREE_SPIRIT, + CHIVALRY, + + ORGANIZATION, + FARMING, + CONSTRUCTION, + STRATEGY, + DIPLOMACY, + + CLIMBING, + MINING, + SMITHERY, + MEDITATION, + PHILOSOPHY +} diff --git a/java/src/models/TechTree.java b/java/src/models/TechTree.java new file mode 100644 index 0000000..f2582b7 --- /dev/null +++ b/java/src/models/TechTree.java @@ -0,0 +1,62 @@ +package models; + +import java.util.*; + +public final class TechTree { + + public static final Map>> TREE = + Map.of( + TechCategory.WATER, Map.of( + Tech.FISHING, List.of( + Tech.SAILING, + Tech.NAVIGATION, + Tech.AQUACULTURE, + Tech.AQUATISM + ) + ), + TechCategory.FOREST, Map.of( + Tech.HUNTING, List.of( + Tech.ARCHERY, + Tech.SPIRITUALISM, + Tech.FORESTRY, + Tech.MATHEMATICS + ) + ), + TechCategory.MOBILITY, Map.of( + Tech.RIDING, List.of( + Tech.ROADS, + Tech.TRADE, + Tech.FREE_SPIRIT, + Tech.CHIVALRY + ) + ), + TechCategory.LAND, Map.of( + Tech.ORGANIZATION, List.of( + Tech.FARMING, + Tech.CONSTRUCTION, + Tech.STRATEGY, + Tech.DIPLOMACY + ) + ), + TechCategory.MOUNTAIN, Map.of( + Tech.CLIMBING, List.of( + Tech.MINING, + Tech.SMITHERY, + Tech.MEDITATION, + Tech.PHILOSOPHY + ) + ) + ); + + private TechTree() {} +} + + +enum TechCategory { + WATER, + FOREST, + MOBILITY, + LAND, + MOUNTAIN +} + diff --git a/java/src/models/concreteTribes/Aimo.java b/java/src/models/concreteTribes/Aimo.java new file mode 100644 index 0000000..2baf2a0 --- /dev/null +++ b/java/src/models/concreteTribes/Aimo.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Aimo extends AbstractTribe { + protected Aimo() { + super(Tech.PHILOSOPHY, 5, false, 1, .1, 1, 1, 1.5, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Bardur.java b/java/src/models/concreteTribes/Bardur.java new file mode 100644 index 0000000..7ce2611 --- /dev/null +++ b/java/src/models/concreteTribes/Bardur.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Bardur extends AbstractTribe { + protected Bardur() { + super(Tech.HUNTING, 5, false, 1, 0, 0.8, 1, 1, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Hoodrick.java b/java/src/models/concreteTribes/Hoodrick.java new file mode 100644 index 0000000..9bdd9fc --- /dev/null +++ b/java/src/models/concreteTribes/Hoodrick.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Hoodrick extends AbstractTribe { + protected Hoodrick() { + super(Tech.ARCHERY, 7, false, 1, 1, 1.5, 1, .5, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Imperius.java b/java/src/models/concreteTribes/Imperius.java new file mode 100644 index 0000000..1ce2632 --- /dev/null +++ b/java/src/models/concreteTribes/Imperius.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Imperius extends AbstractTribe { + protected Imperius() { + super(Tech.ORGANIZATION, 5, false, 2, 1, 1, .5, 1, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Kickoo.java b/java/src/models/concreteTribes/Kickoo.java new file mode 100644 index 0000000..07a9540 --- /dev/null +++ b/java/src/models/concreteTribes/Kickoo.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Kickoo extends AbstractTribe { + protected Kickoo() { + super(Tech.FISHING, 5, false, 1, 1, 1, 1, .5, 1, .5); + } +} diff --git a/java/src/models/concreteTribes/Luxidoor.java b/java/src/models/concreteTribes/Luxidoor.java new file mode 100644 index 0000000..a151589 --- /dev/null +++ b/java/src/models/concreteTribes/Luxidoor.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Luxidoor extends AbstractTribe { + protected Luxidoor() { + super(Tech.NONE, 2, false, 1, 1, 1, 1, 1, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Oumaji.java b/java/src/models/concreteTribes/Oumaji.java new file mode 100644 index 0000000..877140a --- /dev/null +++ b/java/src/models/concreteTribes/Oumaji.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Oumaji extends AbstractTribe { + protected Oumaji() { + super(Tech.RIDING, 6, false, 1, 1, .2, .2, .5, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Quetzali.java b/java/src/models/concreteTribes/Quetzali.java new file mode 100644 index 0000000..9812aa8 --- /dev/null +++ b/java/src/models/concreteTribes/Quetzali.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Quetzali extends AbstractTribe { + protected Quetzali() { + super(Tech.STRATEGY, 7, false, 2, .1, 1, 1, 1, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Vengir.java b/java/src/models/concreteTribes/Vengir.java new file mode 100644 index 0000000..98ac4fb --- /dev/null +++ b/java/src/models/concreteTribes/Vengir.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Vengir extends AbstractTribe { + protected Vengir() { + super(Tech.SMITHERY, 5, false, .1, 1, 1, .1, 1, 2, .1); + } +} diff --git a/java/src/models/concreteTribes/Xinxi.java b/java/src/models/concreteTribes/Xinxi.java new file mode 100644 index 0000000..7c5035b --- /dev/null +++ b/java/src/models/concreteTribes/Xinxi.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Xinxi extends AbstractTribe { + protected Xinxi() { + super(Tech.CLIMBING, 7, false, 1, 1, 1, 1, 1.5, 1.5, 1); + } +} diff --git a/java/src/models/concreteTribes/Yadakk.java b/java/src/models/concreteTribes/Yadakk.java new file mode 100644 index 0000000..65a3e8c --- /dev/null +++ b/java/src/models/concreteTribes/Yadakk.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Yadakk extends AbstractTribe { + protected Yadakk() { + super(Tech.STRATEGY, 7, false, 1.5, 1, .5, 1, .5, 1, 1); + } +} diff --git a/java/src/models/concreteTribes/Zebasi.java b/java/src/models/concreteTribes/Zebasi.java new file mode 100644 index 0000000..f88d092 --- /dev/null +++ b/java/src/models/concreteTribes/Zebasi.java @@ -0,0 +1,10 @@ +package models.concreteTribes; + +import models.AbstractTribe; +import models.Tech; + +public class Zebasi extends AbstractTribe { + protected Zebasi() { + super(Tech.FARMING, 5, false, .5, 1, .5, 1, .5, 1, 1); + } +} From 1b762f2cdf5fa437485d4561f47b93a95d989718 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 21 Jan 2026 15:39:41 +0100 Subject: [PATCH 25/55] beuatiful 16x16 tiles blue&green grid works, integrated with the cionsole app as well, so pretty good so far --- README.md | 8 +-- java/src/console/Main.java | 46 +++++++++++------- .../console/app/{App.java => ConsoleApp.java} | 13 ++--- .../{AppUtils.java => ConsoleAppUtils.java} | 8 +-- java/src/console/app/DesktopApp.java | 44 +++++++++++++++++ .../polytopia-backend/console/Main.class | Bin 1606 -> 1877 bytes run.py | 28 +++++------ 7 files changed, 98 insertions(+), 49 deletions(-) rename java/src/console/app/{App.java => ConsoleApp.java} (80%) rename java/src/console/app/{AppUtils.java => ConsoleAppUtils.java} (90%) create mode 100644 java/src/console/app/DesktopApp.java diff --git a/README.md b/README.md index 4eeda7d..3f561a2 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ will correspond to. # Fabbernat's contribution: -Polytopia UI can be found at https://github.com/Fabbernat/Polytopia as an ASP.NET app. +Polytopia UI can be found at https://github.com/Fabbernat/Polytopia as an ASP.NET consoleApp. -I've added a score counter Python module and a Java console app +I've added a score counter Python module and a Java console consoleApp -## Java console app -This is intended to be the main app at some point and will simulate the core logic of the gameplay like map generation, tech tree and even combat +## Java console consoleApp +This is intended to be the main consoleApp at some point and will simulate the core logic of the gameplay like map generation, tech tree and even combat diff --git a/java/src/console/Main.java b/java/src/console/Main.java index da93bf1..e2e58fd 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -1,7 +1,8 @@ package console; -import console.app.App; -import console.app.AppUtils; +import console.app.ConsoleApp; +import console.app.ConsoleAppUtils; +import console.app.DesktopApp; import java.util.Scanner; @@ -14,23 +15,32 @@ public static void log(String message) { static String farewellMessage = "Goodbye Mighty Ruler!"; public static void main(String[] args) { - App app = new App(); - Scanner scanner = new Scanner(System.in); - log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); - - while (true) { - System.out.print ("> "); - String input = scanner.nextLine().trim(); - input = input.toLowerCase(); + desktop(); + console(); + } - if ("exit".equals(input) || "quit".equals(input)) { - log(farewellMessage); - break; - } + private static void desktop() { + DesktopApp desktopApp = new DesktopApp(); + } - AppUtils.handleCommand(input); + private static void console() { + ConsoleApp consoleApp = new ConsoleApp(); + Scanner scanner = new Scanner(System.in); + log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); + + while (true) { + System.out.print ("> "); + String input = scanner.nextLine().trim(); + input = input.toLowerCase(); + + if ("exit".equals(input) || "quit".equals(input)) { + log(farewellMessage); + break; + } + + ConsoleAppUtils.handleCommand(input); + } + log("Press any key to exit..."); + scanner.nextLine(); } - log("Press any key to exit..."); - scanner.nextLine(); - } } diff --git a/java/src/console/app/App.java b/java/src/console/app/ConsoleApp.java similarity index 80% rename from java/src/console/app/App.java rename to java/src/console/app/ConsoleApp.java index 9a95879..73afad2 100644 --- a/java/src/console/app/App.java +++ b/java/src/console/app/ConsoleApp.java @@ -1,21 +1,16 @@ package console.app; -import java.io.IOException; import java.io.InputStream; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import static console.Main.log; -public class App { +public class ConsoleApp { static int guid = 1; private List games = new ArrayList<>(); - public App() { + public ConsoleApp() { readGuidReference(); readGames(); } @@ -26,7 +21,7 @@ public List getGames() { } private void readGuidReference() { - try (InputStream input = App.class.getResourceAsStream("/guidReference.txt")) { + try (InputStream input = ConsoleApp.class.getResourceAsStream("/guidReference.txt")) { if (input == null) { log("guidReference.txt not found!"); return; @@ -48,7 +43,7 @@ private void readGuidReference() { } private void readGames() { - try (InputStream input = App.class.getResourceAsStream("/games.txt")) { + try (InputStream input = ConsoleApp.class.getResourceAsStream("/games.txt")) { if (input == null) { log("games.txt not found!"); return; diff --git a/java/src/console/app/AppUtils.java b/java/src/console/app/ConsoleAppUtils.java similarity index 90% rename from java/src/console/app/AppUtils.java rename to java/src/console/app/ConsoleAppUtils.java index 7f0de99..b884f2c 100644 --- a/java/src/console/app/AppUtils.java +++ b/java/src/console/app/ConsoleAppUtils.java @@ -4,7 +4,7 @@ import static console.Main.log; -public class AppUtils { +public class ConsoleAppUtils { public static void handleCommand(String input) { if (input.isEmpty()) return; @@ -32,13 +32,13 @@ public static void handleCommand(String input) { } gameName = capitalizedGameName.toString().trim(); - report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + App.guid++)); + report("New game started: " + (!gameName.trim().isEmpty() ? gameName : "Game " + ConsoleApp.guid++)); } case "delete" -> report("Deleted game: " + gameName); case "games" -> { - App app = new App(); - report("Games list: " + app.getGames().toString()); + ConsoleApp consoleApp = new ConsoleApp(); + report("Games list: " + consoleApp.getGames().toString()); } } return; diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java new file mode 100644 index 0000000..b45d970 --- /dev/null +++ b/java/src/console/app/DesktopApp.java @@ -0,0 +1,44 @@ +package console.app; + +import javax.swing.*; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import java.awt.*; + +public class DesktopApp { + + private static final int ROWS = 16; + private static final int COLS = 16; + + public DesktopApp() { + SwingUtilities.invokeLater(DesktopApp::createAndShowUI); + } + + private static void createAndShowUI() { + JFrame frame = new JFrame("Polytopia"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + JPanel gridPanel = new JPanel(new GridLayout(ROWS, COLS)); + + for (int row = 0; row < ROWS; row++) { + for (int col = 0; col < COLS; col++) { + JPanel tile = new JPanel(); + + // Alternate colors just as an example + if ((row + col) % 2 == 0) { + tile.setBackground(Color.GREEN); + } else { + tile.setBackground(Color.BLUE); + } + + tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + gridPanel.add(tile); + } + } + + frame.add(gridPanel); + frame.setSize(500, 500); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + } +} diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class index d6e84735cee62abc2210685f94686796fb7359f8..7b8fe8efb025e9112d4b786763636a0cd0b1e779 100644 GIT binary patch literal 1877 zcmZuy>r&fB7(L=9SrL$!OK3^x;wIGQV)vSqOKDSw0L2E1iBm`~EcRjxWXX}_f*+-Q zf&6YVW0*422k7)Y`VO7mzLf=b5@1H!)t>L{`L6!`-(P2+b}6;7e<%vRf}RD_HvL5fo$*9I0u2!rx`AsjUV-Q8o-Znda%yBWlnIDTC$;IC zwx9D|!7KAR`p~c9x`7*rDj2u|qd=`n3(F>wc#|cNLlmZ6nos0)3}Q&duz{OWu~Ber z&#^=*%T2OTi^4ndovK8=W#DZIQA3D|TMC9R@Vr`0J#2y>R0%&SyncFi}fRIb2nTevbRcMROcgn|yKqUHXoU9Gj;A4gmUI-y`B^o=6a4oT!SOett= zI#!*$FgZ(SR+;B>*B8Yk+pbpy+Y7cVw#+Mntf+e)-q6AHShHYyUfXy9{+x#l|==UBLD?gdva-VSc!>l@uacX^ zx{3`0o6_IW_IhiT(_<ip1Y>phabEAvPrl1WiG_Y z=9#BXsa!bLRSDW@IR5la4-26ZCF_qyUO+bLk zQ0ACcJ+!7-NYBT#OVL}QclYEelwWAkfqV!8B6vb;qY1z_*r8oSmVan`kI>RsdjWN_ zf!>zA9J|s|MQPB-vbHu?pcxS^cgbG|NA5Q zHEpHM6RD~*i0~ZE4A4;4aElzgP1+d!(*Q*bGq^*2ewTi~Ca{78GMK~$rm&4^%E3(F z1tXp~;W$OKgIKn(j{-3cU>QXS^5}qeL+JVkBPzyJjQ@=Z6|>r3* zExVG6C3CshST5%$fMHUnsD6}ul8mkA(<`xs*ID?uY&b>C;tzpSN zUQg$Jv@xm~W8CgHx*H~$GP$6+=sa7qKb=F|E*K5!lD%OxtIPI|@uI?$&g9nf_NY5l zV+cUhnEemGGh$0V&=N8{Xg`Bez#l$MHp2#+8OB@$-ayr*Y$-JEUH-IZ# zmB{C)CK;!AK+t7e5i?Ta(7$?r`O!*D3i?13FW1U5__)r(pC(^0WG1+?MMYUKTcW2#Y(84z)Vy_C%-IdJb>4B_po@$McoTnghl$OfpR8#K zdrJj+7M;0_GOt0|C{znGOMXy*df8`~I=aM*%9k0TQT|;M(}aX!SU9(GL)Md^y-Xt_ zqju3DxpCr`{xB68uJ|N! Date: Wed, 21 Jan 2026 16:02:40 +0100 Subject: [PATCH 26/55] v1.6: add Perlin noise via PerlinNoise.java --- java/src/console/app/DesktopApp.java | 14 +++--- java/src/console/app/PerlinNoise.java | 64 +++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 java/src/console/app/PerlinNoise.java diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index b45d970..ffd44a7 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -9,6 +9,7 @@ public class DesktopApp { private static final int ROWS = 16; private static final int COLS = 16; + private static final double SCALE = 0.15; // lower = larger regions public DesktopApp() { SwingUtilities.invokeLater(DesktopApp::createAndShowUI); @@ -19,16 +20,19 @@ private static void createAndShowUI() { frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel gridPanel = new JPanel(new GridLayout(ROWS, COLS)); + PerlinNoise noise = new PerlinNoise(System.currentTimeMillis()); for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { JPanel tile = new JPanel(); - // Alternate colors just as an example - if ((row + col) % 2 == 0) { - tile.setBackground(Color.GREEN); + double value = noise.noise(row * SCALE, col * SCALE); + double normalized = (value + 1) / 2.0; + + if (normalized > 0.4) { + tile.setBackground(Color.GREEN); // land } else { - tile.setBackground(Color.BLUE); + tile.setBackground(Color.BLUE); // water } tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); @@ -37,7 +41,7 @@ private static void createAndShowUI() { } frame.add(gridPanel); - frame.setSize(500, 500); + frame.setSize(600, 600); frame.setLocationRelativeTo(null); frame.setVisible(true); } diff --git a/java/src/console/app/PerlinNoise.java b/java/src/console/app/PerlinNoise.java new file mode 100644 index 0000000..63156d9 --- /dev/null +++ b/java/src/console/app/PerlinNoise.java @@ -0,0 +1,64 @@ +package console.app; + +import java.util.Random; + +public class PerlinNoise { + + private final int[] permutation = new int[512]; + + public PerlinNoise(long seed) { + int[] p = new int[256]; + for (int i = 0; i < 256; i++) { + p[i] = i; + } + + Random rand = new Random(seed); + for (int i = 255; i > 0; i--) { + int index = rand.nextInt(i + 1); + int tmp = p[i]; + p[i] = p[index]; + p[index] = tmp; + } + + for (int i = 0; i < 512; i++) { + permutation[i] = p[i & 255]; + } + } + + private static double fade(double t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private static double lerp(double t, double a, double b) { + return a + t * (b - a); + } + + private static double grad(int hash, double x, double y) { + int h = hash & 7; + double u = h < 4 ? x : y; + double v = h < 4 ? y : x; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + public double noise(double x, double y) { + int X = (int) Math.floor(x) & 255; + int Y = (int) Math.floor(y) & 255; + + x -= Math.floor(x); + y -= Math.floor(y); + + double u = fade(x); + double v = fade(y); + + int aa = permutation[X + permutation[Y]]; + int ab = permutation[X + permutation[Y + 1]]; + int ba = permutation[X + 1 + permutation[Y]]; + int bb = permutation[X + 1 + permutation[Y + 1]]; + + return lerp( + v, + lerp(u, grad(aa, x, y), grad(ba, x - 1, y)), + lerp(u, grad(ab, x, y - 1), grad(bb, x - 1, y - 1)) + ); + } +} From 11f32fccbe2c4bf085af4072a776ef3db52ea3eb Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 21 Jan 2026 16:12:39 +0100 Subject: [PATCH 27/55] v1.6.1: I am pretty satisfied with the algorithm so far --- java/src/console/app/DesktopApp.java | 36 +++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index ffd44a7..cd440b8 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -4,6 +4,7 @@ import javax.swing.JFrame; import javax.swing.SwingUtilities; import java.awt.*; +import java.util.Random; public class DesktopApp { @@ -11,6 +12,13 @@ public class DesktopApp { private static final int COLS = 16; private static final double SCALE = 0.15; // lower = larger regions + private static final Color LAND = Color.GREEN; + private static final Color WATER = Color.BLUE; + private static final Color CAPITAL = new Color(139, 69, 19); // brown + + private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; + private static final Random random = new Random(); + public DesktopApp() { SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } @@ -29,20 +37,36 @@ private static void createAndShowUI() { double value = noise.noise(row * SCALE, col * SCALE); double normalized = (value + 1) / 2.0; - if (normalized > 0.4) { - tile.setBackground(Color.GREEN); // land - } else { - tile.setBackground(Color.BLUE); // water - } - + tile.setBackground(normalized > 0.4 ? LAND : WATER); tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); + + tiles[row][col] = tile; gridPanel.add(tile); } } + // 2️⃣ Place tribe capitals AFTER generation + replaceTilesWithTribeCapitals(4); + frame.add(gridPanel); frame.setSize(600, 600); frame.setLocationRelativeTo(null); frame.setVisible(true); } + + private static void replaceTilesWithTribeCapitals(int count) { + int placed = 0; + + while (placed < count) { + int row = random.nextInt(ROWS - 2) + 1; // avoid borders + int col = random.nextInt(COLS - 2) + 1; + + JPanel tile = tiles[row][col]; + + if (tile.getBackground().equals(LAND)) { + tile.setBackground(CAPITAL); + placed++; + } + } + } } From c1c3b7180caf7d19c5977f54b72ae17c7c905035 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 21 Jan 2026 16:38:29 +0100 Subject: [PATCH 28/55] v1.6.2: improved logic, capitals now don't spawn at the edge of the map --- java/src/console/Main.java | 2 +- java/src/console/app/DesktopApp.java | 21 +++++++++++++++--- .../polytopia-backend/console/Main.class | Bin 1877 -> 1860 bytes 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/java/src/console/Main.java b/java/src/console/Main.java index e2e58fd..14abb41 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -16,7 +16,7 @@ public static void log(String message) { public static void main(String[] args) { desktop(); - console(); +// console(); } private static void desktop() { diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index cd440b8..967c436 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -4,6 +4,8 @@ import javax.swing.JFrame; import javax.swing.SwingUtilities; import java.awt.*; +import java.util.ArrayList; +import java.util.List; // <-- ide kell! import java.util.Random; public class DesktopApp { @@ -56,17 +58,30 @@ private static void createAndShowUI() { private static void replaceTilesWithTribeCapitals(int count) { int placed = 0; + java.util.List capitals = new ArrayList<>(); while (placed < count) { - int row = random.nextInt(ROWS - 2) + 1; // avoid borders - int col = random.nextInt(COLS - 2) + 1; + int row = random.nextInt(ROWS - 4) + 2; // At least 2 tiles from the edges and from other capitals + int col = random.nextInt(COLS - 4) + 2; JPanel tile = tiles[row][col]; - if (tile.getBackground().equals(LAND)) { + if (tile.getBackground().equals(LAND) && isFarEnough(row, col, capitals, 2)) { tile.setBackground(CAPITAL); + capitals.add(new Point(row, col)); placed++; } } + } + + private static boolean isFarEnough(int row, int col, java.util.List capitals, int minDistance) { + for (Point p : capitals) { + int dist = Math.abs(p.x - row) + Math.abs(p.y - col); // Manhattan distance + if (dist < minDistance) { + return false; // túl közel + } } + return true; // jó hely + } + } diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class index 7b8fe8efb025e9112d4b786763636a0cd0b1e779..7767af2a71835d2bea0544900cb0437df86ccc4f 100644 GIT binary patch delta 807 zcmZ8eOK%cU7(I7pV20rq<5U5qP_&3Gtx{`CeYVw#FTh7pEA@$p6+=KNt?p_}T)C>V zvWv!D*Tn=#iLP836IUkw1OJ2Z^<5;?bds6-oo~)}?m72W01!DIvfad42r(xaVb|Q>+oTS;~<93S5m+p z!KjKu9ES<7OI@J|j&dBsFoV5Zj2E9~OOxrjtcEcLCHpK#;}J}7oWRM~a*uI}x=-eF zWgE8PG{+g7HAmz=$9cq5#5odTTaSolzK~t)qPZ-4*=4gXf6>%Tu`pN6XU&(&(jv{# z`B*_7o8KuyVM2^nOQ;66MumiAiv-wkg=+qffUCGh$R^TLM+`@ZErzuU)KC>28}mB- z5tAx9+8T>Q#B|ex4cC!si3|}@h$wY*ZWb^gQ{)X33TAMFxH??z1fwkzwg%j(8g|Au zi@0c8H;K^xB8o=uP26g#+-|FUr=4W#%{1U46>qEoU#bFs4S~3^w~juFXbe=bzrTtD zRg5I4K9Vqw(=xscZcK{e4R{4nBCkRpsN>w~pU=hxf?{TR%}VkzlIC0M_=1S!C)A4o z?$D7)WNQj{k*0npUGP0*$mV@Q+oAk|TZKo3?G~^ Ozf0Ug=mGU4%>D&UEqNXQ delta 908 zcmZ8fOHjq)#$Vw0`flkI$1Lu>=gT?gJNHXI1iu93kH6o(2QY~F=knK_ zj2i4!un&F)an81~cG?uNpJ7i4=!WCy17>!?wVgi4kx>spMB{&i{w-yZ5axsf47=xz zB|~3yQ)xXhXIPe*2|z-#f`e#b@TDwl9Q_IgFz9{vHK{{Xe!Q4+12n6P3NB&9 ziwJFjF^6YeicyS2WkwxLGU~lW^60C4BatR;=boP1E;- zZctpW5z+}U+AG^Pz+ThAhp7??2w Date: Wed, 21 Jan 2026 16:46:38 +0100 Subject: [PATCH 29/55] v1.6.3: balance changes --- java/src/console/app/DesktopApp.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index 967c436..6d9c5c4 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -12,7 +12,7 @@ public class DesktopApp { private static final int ROWS = 16; private static final int COLS = 16; - private static final double SCALE = 0.15; // lower = larger regions + private static final double SCALE = 0.18; // lower = larger regions private static final Color LAND = Color.GREEN; private static final Color WATER = Color.BLUE; @@ -39,7 +39,7 @@ private static void createAndShowUI() { double value = noise.noise(row * SCALE, col * SCALE); double normalized = (value + 1) / 2.0; - tile.setBackground(normalized > 0.4 ? LAND : WATER); + tile.setBackground(normalized > 0.45 ? LAND : WATER); tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); tiles[row][col] = tile; From d858b38525fb58690332297d54595f63d7a79026 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 21 Jan 2026 16:48:42 +0100 Subject: [PATCH 30/55] v1.6.3.1: better colors --- java/src/console/app/DesktopApp.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index 6d9c5c4..b6d9d45 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -16,7 +16,8 @@ public class DesktopApp { private static final Color LAND = Color.GREEN; private static final Color WATER = Color.BLUE; - private static final Color CAPITAL = new Color(139, 69, 19); // brown + private static final Color CAPITAL = new Color(117, 117, 117); // gray + private static final Color VILLAGE = new Color(139, 75, 19, 255); // brown private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; private static final Random random = new Random(); From 605f437978bc46863cf13eaae347e8ff98cf3da6 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 21 Jan 2026 17:50:35 +0100 Subject: [PATCH 31/55] v1.6.3.2: add village-filling scratch --- java/src/console/app/DesktopApp.java | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index b6d9d45..b8d930e 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -5,7 +5,6 @@ import javax.swing.SwingUtilities; import java.awt.*; import java.util.ArrayList; -import java.util.List; // <-- ide kell! import java.util.Random; public class DesktopApp { @@ -49,7 +48,8 @@ private static void createAndShowUI() { } // 2️⃣ Place tribe capitals AFTER generation - replaceTilesWithTribeCapitals(4); + replaceTilesWithCities(4, CAPITAL); + fillTheRestOfTheMapWithVillages(); frame.add(gridPanel); frame.setSize(600, 600); @@ -57,7 +57,13 @@ private static void createAndShowUI() { frame.setVisible(true); } - private static void replaceTilesWithTribeCapitals(int count) { + /** + * puts the given number of capitals to the map with SOME STRICT RULES: + * no capital can be within 2 tiles of the edge of the map or each other + * @param count number of players==number of capitals + * @param what feature to be applied + */ + private static void replaceTilesWithCities(int count, Color what) { int placed = 0; java.util.List capitals = new ArrayList<>(); @@ -85,4 +91,13 @@ private static boolean isFarEnough(int row, int col, java.util.List capit return true; // jó hely } + /** + * fills the world with villages with SOME STRICT RULES: + * finds the spots that are far enough (at least 2 tiles) from capitals and other villages. + * Villages can spawn on water. + */ + private static void fillTheRestOfTheMapWithVillages() { + } + + } From 37df499d61c61e17cd2f9783fc3494568c0975ed Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 21 Jan 2026 18:07:27 +0100 Subject: [PATCH 32/55] v1.6.3.2: Village generation now also works, although neighbor cities still needs to be fixed --- java/src/console/app/DesktopApp.java | 64 ++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index b8d930e..b2095ad 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -5,12 +5,13 @@ import javax.swing.SwingUtilities; import java.awt.*; import java.util.ArrayList; +import java.util.List; import java.util.Random; public class DesktopApp { - private static final int ROWS = 16; - private static final int COLS = 16; + private static final int ROWS = 30; + private static final int COLS = 30; private static final double SCALE = 0.18; // lower = larger regions private static final Color LAND = Color.GREEN; @@ -21,6 +22,11 @@ public class DesktopApp { private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; private static final Random random = new Random(); + // Statikus lista a kapitalok pozícióinak tárolására + private static final List capitals = new ArrayList<>(); + // Statikus lista a falvak pozícióinak tárolására + private static final List villages = new ArrayList<>(); + public DesktopApp() { SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } @@ -49,7 +55,7 @@ private static void createAndShowUI() { // 2️⃣ Place tribe capitals AFTER generation replaceTilesWithCities(4, CAPITAL); - fillTheRestOfTheMapWithVillages(); + fillTheRestOfTheMapWithVillages(); // pl. 10 falu frame.add(gridPanel); frame.setSize(600, 600); @@ -58,14 +64,13 @@ private static void createAndShowUI() { } /** - * puts the given number of capitals to the map with SOME STRICT RULES: - * no capital can be within 2 tiles of the edge of the map or each other - * @param count number of players==number of capitals - * @param what feature to be applied + * puts the given number of cities (capital or village) to the map with SOME STRICT RULES: + * no city can be within minDistance tiles of the edge of the map or each other + * @param count number of cities to place + * @param what color to paint */ - private static void replaceTilesWithCities(int count, Color what) { + private static void replaceTilesWithCities(int count, Color what) { int placed = 0; - java.util.List capitals = new ArrayList<>(); while (placed < count) { int row = random.nextInt(ROWS - 4) + 2; // At least 2 tiles from the edges and from other capitals @@ -74,15 +79,15 @@ private static void replaceTilesWithCities(int count, Color what) { JPanel tile = tiles[row][col]; if (tile.getBackground().equals(LAND) && isFarEnough(row, col, capitals, 2)) { - tile.setBackground(CAPITAL); - capitals.add(new Point(row, col)); + tile.setBackground(what); + capitals.add(new Point(row, col)); // kapitalokat tároljuk ide placed++; } } } - private static boolean isFarEnough(int row, int col, java.util.List capitals, int minDistance) { - for (Point p : capitals) { + private static boolean isFarEnough(int row, int col, List points, int minDistance) { + for (Point p : points) { int dist = Math.abs(p.x - row) + Math.abs(p.y - col); // Manhattan distance if (dist < minDistance) { return false; // túl közel @@ -91,12 +96,43 @@ private static boolean isFarEnough(int row, int col, java.util.List capit return true; // jó hely } + private static void fillTheRestOfTheMapWithVillages() { + // total tiles = ROWS * COLS + int totalTiles = ROWS * COLS; + int count = totalTiles / 20 - capitals.size(); // alapértelmezett falu szám + + if (count < 0) count = 0; // negatív érték esetén nulla + + fillTheRestOfTheMapWithVillages(count); + } + /** * fills the world with villages with SOME STRICT RULES: * finds the spots that are far enough (at least 2 tiles) from capitals and other villages. * Villages can spawn on water. + * @param count number of villages to place */ - private static void fillTheRestOfTheMapWithVillages() { + private static void fillTheRestOfTheMapWithVillages(int count) { + int placed = 0; + + while (placed < count) { + int row = random.nextInt(ROWS - 4) + 2; // legalább 2 csempe távol a szélektől + int col = random.nextInt(COLS - 4) + 2; + + JPanel tile = tiles[row][col]; + + // Falvak bárhol lehetnek (víz vagy föld) + // Viszont legalább 2 távol a kapitaloktól és a többi falutól + if (isFarEnough(row, col, capitals, 2) && isFarEnough(row, col, villages, 2)) { + // Csak akkor állítsuk barna színűre, ha még nem capital vagy falu + Color bg = tile.getBackground(); + if (!bg.equals(CAPITAL) && !bg.equals(VILLAGE)) { + tile.setBackground(VILLAGE); + villages.add(new Point(row, col)); + placed++; + } + } + } } From 05fd3828ba3b388e529a965c225f84f923a98160 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 22 Jan 2026 10:52:38 +0100 Subject: [PATCH 33/55] v1.6.3.3: 35 is pretty much the lower limit of how many villages can spawn. It does not always work, but it gives decent results when succeeds --- java/src/console/app/DesktopApp.java | 88 +++++++++++++++++++++------- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index b2095ad..9c19d22 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -1,5 +1,7 @@ package console.app; +import jdk.jshell.spi.ExecutionControl; + import javax.swing.*; import javax.swing.JFrame; import javax.swing.SwingUtilities; @@ -10,20 +12,23 @@ public class DesktopApp { - private static final int ROWS = 30; - private static final int COLS = 30; - private static final double SCALE = 0.18; // lower = larger regions + private static final int ROWS = 16; + private static final int COLS = 16; + private static final double SCALE = 0.4; // lower = larger regions private static final Color LAND = Color.GREEN; private static final Color WATER = Color.BLUE; - private static final Color CAPITAL = new Color(117, 117, 117); // gray - private static final Color VILLAGE = new Color(139, 75, 19, 255); // brown + private static final Color CAPITAL = new Color(255, 0, 0); + private static final Color MOUNTAIN = new Color(124, 124, 124, 255); + private static final Color VILLAGE = new Color(139, 75, 19); private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; private static final Random random = new Random(); // Statikus lista a kapitalok pozícióinak tárolására private static final List capitals = new ArrayList<>(); + // Statikus lista a hegyek pozícióinak tárolására + private static final List mountains = new ArrayList<>(); // Statikus lista a falvak pozícióinak tárolására private static final List villages = new ArrayList<>(); @@ -45,7 +50,7 @@ private static void createAndShowUI() { double value = noise.noise(row * SCALE, col * SCALE); double normalized = (value + 1) / 2.0; - tile.setBackground(normalized > 0.45 ? LAND : WATER); + tile.setBackground(normalized > 0.56 ? LAND : WATER); tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); tiles[row][col] = tile; @@ -54,8 +59,9 @@ private static void createAndShowUI() { } // 2️⃣ Place tribe capitals AFTER generation - replaceTilesWithCities(4, CAPITAL); - fillTheRestOfTheMapWithVillages(); // pl. 10 falu + replaceTilesWithCities(2, CAPITAL); + GenerateMountains(); // pl. 10 hegy + FillTheRestOfTheWorldWithVillages(); frame.add(gridPanel); frame.setSize(600, 600); @@ -63,6 +69,8 @@ private static void createAndShowUI() { frame.setVisible(true); } + + /** * puts the given number of cities (capital or village) to the map with SOME STRICT RULES: * no city can be within minDistance tiles of the edge of the map or each other @@ -78,7 +86,7 @@ private static void replaceTilesWithCities(int count, Color what) { JPanel tile = tiles[row][col]; - if (tile.getBackground().equals(LAND) && isFarEnough(row, col, capitals, 2)) { + if (tile.getBackground().equals(LAND) && isVillageFarEnough(row, col, capitals, 2)) { tile.setBackground(what); capitals.add(new Point(row, col)); // kapitalokat tároljuk ide placed++; @@ -86,24 +94,34 @@ private static void replaceTilesWithCities(int count, Color what) { } } - private static boolean isFarEnough(int row, int col, List points, int minDistance) { + private static boolean isMountainFarEnough(int row, int col, List points, int minDistance) { for (Point p : points) { int dist = Math.abs(p.x - row) + Math.abs(p.y - col); // Manhattan distance - if (dist < minDistance) { + if (dist <= minDistance - 2) { return false; // túl közel } } return true; // jó hely } - private static void fillTheRestOfTheMapWithVillages() { + private static boolean isVillageFarEnough(int row, int col, List points, int minDistance) { + for (Point p : points) { + int dist = Math.abs(p.x - row) + Math.abs(p.y - col); // Manhattan distance + if (dist < minDistance + 2) { + return false; // túl közel + } + } + return true; // jó hely + } + + private static void GenerateMountains() { // total tiles = ROWS * COLS int totalTiles = ROWS * COLS; - int count = totalTiles / 20 - capitals.size(); // alapértelmezett falu szám + int count = totalTiles / 17 - capitals.size(); // alapértelmezett hegy szám if (count < 0) count = 0; // negatív érték esetén nulla - fillTheRestOfTheMapWithVillages(count); + GenerateMountains(count); } /** @@ -112,7 +130,7 @@ private static void fillTheRestOfTheMapWithVillages() { * Villages can spawn on water. * @param count number of villages to place */ - private static void fillTheRestOfTheMapWithVillages(int count) { + private static void GenerateMountains(int count) { int placed = 0; while (placed < count) { @@ -122,18 +140,46 @@ private static void fillTheRestOfTheMapWithVillages(int count) { JPanel tile = tiles[row][col]; // Falvak bárhol lehetnek (víz vagy föld) - // Viszont legalább 2 távol a kapitaloktól és a többi falutól - if (isFarEnough(row, col, capitals, 2) && isFarEnough(row, col, villages, 2)) { - // Csak akkor állítsuk barna színűre, ha még nem capital vagy falu + // Viszont legalább 2 távol a kapitaloktól és a többi hegytől + if (isMountainFarEnough(row, col, capitals, 2) && isMountainFarEnough(row, col, mountains, 2)) { + // Csak akkor állítsuk barna színűre, ha még nem capital vagy hegy Color bg = tile.getBackground(); - if (!bg.equals(CAPITAL) && !bg.equals(VILLAGE)) { - tile.setBackground(VILLAGE); - villages.add(new Point(row, col)); + if (!bg.equals(WATER) && !bg.equals(CAPITAL) && !bg.equals(MOUNTAIN)) { + tile.setBackground(MOUNTAIN); + mountains.add(new Point(row, col)); placed++; } } } } + private static void FillTheRestOfTheWorldWithVillages() { + int totalTiles = ROWS * COLS; + int villageCount = totalTiles / 35 - capitals.size(); + + if (villageCount <= 0) { + return; + } + + int placed = 0; + + while (placed < villageCount) { + int row = random.nextInt(ROWS - 4) + 2; + int col = random.nextInt(COLS - 4) + 2; + + JPanel tile = tiles[row][col]; + + // Distance rules + if (!isVillageFarEnough(row, col, capitals, 2)) continue; + if (!isVillageFarEnough(row, col, mountains, 2)) continue; + if (!isVillageFarEnough(row, col, villages, 2)) continue; + + tile.setBackground(VILLAGE); + villages.add(new Point(row, col)); + placed++; + } + } + + } From be77a1999fe7fdd4d8ff1c2bd0f4fd5d74a3259c Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 22 Jan 2026 14:01:43 +0100 Subject: [PATCH 34/55] v1.6.3.3.1: same --- java/src/console/app/DesktopApp.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index 9c19d22..6e96f7b 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -70,12 +70,12 @@ private static void createAndShowUI() { } - /** * puts the given number of cities (capital or village) to the map with SOME STRICT RULES: * no city can be within minDistance tiles of the edge of the map or each other + * * @param count number of cities to place - * @param what color to paint + * @param what color to paint */ private static void replaceTilesWithCities(int count, Color what) { int placed = 0; @@ -128,6 +128,7 @@ private static void GenerateMountains() { * fills the world with villages with SOME STRICT RULES: * finds the spots that are far enough (at least 2 tiles) from capitals and other villages. * Villages can spawn on water. + * * @param count number of villages to place */ private static void GenerateMountains(int count) { @@ -154,16 +155,18 @@ private static void GenerateMountains(int count) { } private static void FillTheRestOfTheWorldWithVillages() { + int increasingMagicNumber = 40; + int totalTiles = ROWS * COLS; - int villageCount = totalTiles / 35 - capitals.size(); + int villageCount = totalTiles / increasingMagicNumber - capitals.size(); if (villageCount <= 0) { return; } int placed = 0; - - while (placed < villageCount) { + int nOfIterations = 0; + while (placed < villageCount && nOfIterations < 20) { int row = random.nextInt(ROWS - 4) + 2; int col = random.nextInt(COLS - 4) + 2; @@ -177,9 +180,9 @@ private static void FillTheRestOfTheWorldWithVillages() { tile.setBackground(VILLAGE); villages.add(new Point(row, col)); placed++; + nOfIterations++; } } - } From b0fcb187f6456608232a43a057bdda274123c2af Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 22 Jan 2026 14:08:38 +0100 Subject: [PATCH 35/55] v1.6.3.3.1: increased village spawn logic, and I dont think it will fail to generate with this algorithm --- java/src/console/app/DesktopApp.java | 56 +++++++++++++++++++--------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/DesktopApp.java index 6e96f7b..72a2d8b 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/DesktopApp.java @@ -7,13 +7,14 @@ import javax.swing.SwingUtilities; import java.awt.*; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Random; public class DesktopApp { - private static final int ROWS = 16; - private static final int COLS = 16; + private static final int ROWS = 18; + private static final int COLS = 18; private static final double SCALE = 0.4; // lower = larger regions private static final Color LAND = Color.GREEN; @@ -155,8 +156,7 @@ private static void GenerateMountains(int count) { } private static void FillTheRestOfTheWorldWithVillages() { - int increasingMagicNumber = 40; - + int increasingMagicNumber = 35; int totalTiles = ROWS * COLS; int villageCount = totalTiles / increasingMagicNumber - capitals.size(); @@ -164,25 +164,45 @@ private static void FillTheRestOfTheWorldWithVillages() { return; } - int placed = 0; - int nOfIterations = 0; - while (placed < villageCount && nOfIterations < 20) { - int row = random.nextInt(ROWS - 4) + 2; - int col = random.nextInt(COLS - 4) + 2; + List candidates = new ArrayList<>(); - JPanel tile = tiles[row][col]; + // Collect all valid candidate tiles first + for (int row = 1; row < ROWS - 1; row++) { + for (int col = 1; col < COLS - 1; col++) { + JPanel tile = tiles[row][col]; + Color bg = tile.getBackground(); + + // Must be LAND or WATER and not already village, capital, mountain + if (!bg.equals(LAND) && !bg.equals(WATER)) continue; - // Distance rules - if (!isVillageFarEnough(row, col, capitals, 2)) continue; - if (!isVillageFarEnough(row, col, mountains, 2)) continue; - if (!isVillageFarEnough(row, col, villages, 2)) continue; + // Check distance from capitals and mountains only (ignore villages here) + if (!isVillageFarEnough(row, col, capitals, 2)) continue; + if (!isVillageFarEnough(row, col, mountains, 2)) continue; - tile.setBackground(VILLAGE); - villages.add(new Point(row, col)); - placed++; - nOfIterations++; + candidates.add(new Point(row, col)); + } + } + + // Shuffle candidate list for randomness + Collections.shuffle(candidates, random); + + villages.clear(); + + // Try to place as many villages as possible without violating distance to other villages + for (Point candidate : candidates) { + if (villages.size() >= villageCount) break; + + // Check distance to already placed villages + if (!isVillageFarEnough(candidate.x, candidate.y, villages, 2)) { + continue; + } + + // Place village + tiles[candidate.x][candidate.y].setBackground(VILLAGE); + villages.add(candidate); } } + } From 04803f118ca5fe9f2969b19169e87034f8ca835f Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 22 Jan 2026 15:10:32 +0100 Subject: [PATCH 36/55] v1.6.3.3.2: good version for lakes --- java/src/console/Main.java | 2 +- java/src/console/app/main/Archi.java | 8 ++++++ java/src/console/app/main/Conti.java | 6 ++++ .../console/app/{ => main}/DesktopApp.java | 26 ++++++++---------- java/src/console/app/main/Lakes.java | 6 ++++ .../polytopia-backend/console/Main.class | Bin 1860 -> 1870 bytes 6 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 java/src/console/app/main/Archi.java create mode 100644 java/src/console/app/main/Conti.java rename java/src/console/app/{ => main}/DesktopApp.java (93%) create mode 100644 java/src/console/app/main/Lakes.java diff --git a/java/src/console/Main.java b/java/src/console/Main.java index 14abb41..c8f6809 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -2,7 +2,7 @@ import console.app.ConsoleApp; import console.app.ConsoleAppUtils; -import console.app.DesktopApp; +import console.app.main.DesktopApp; import java.util.Scanner; diff --git a/java/src/console/app/main/Archi.java b/java/src/console/app/main/Archi.java new file mode 100644 index 0000000..e6ad3a3 --- /dev/null +++ b/java/src/console/app/main/Archi.java @@ -0,0 +1,8 @@ +package console.app.main; + +public enum Archi { + INSTANCE; + + public static final double WaterLandRatio = .56; + public static final double Scale = .4; +} diff --git a/java/src/console/app/main/Conti.java b/java/src/console/app/main/Conti.java new file mode 100644 index 0000000..cf3ea2c --- /dev/null +++ b/java/src/console/app/main/Conti.java @@ -0,0 +1,6 @@ +package console.app.main; + +public class Conti { + public static final double WaterLandRatio = .5; + public static final double Scale = .15; +} diff --git a/java/src/console/app/DesktopApp.java b/java/src/console/app/main/DesktopApp.java similarity index 93% rename from java/src/console/app/DesktopApp.java rename to java/src/console/app/main/DesktopApp.java index 72a2d8b..fb4d8cd 100644 --- a/java/src/console/app/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -1,6 +1,6 @@ -package console.app; +package console.app.main; -import jdk.jshell.spi.ExecutionControl; +import console.app.PerlinNoise; import javax.swing.*; import javax.swing.JFrame; @@ -15,13 +15,13 @@ public class DesktopApp { private static final int ROWS = 18; private static final int COLS = 18; - private static final double SCALE = 0.4; // lower = larger regions + private static final double SCALE = Lakes.Scale; // lower = larger regions private static final Color LAND = Color.GREEN; private static final Color WATER = Color.BLUE; - private static final Color CAPITAL = new Color(255, 0, 0); - private static final Color MOUNTAIN = new Color(124, 124, 124, 255); - private static final Color VILLAGE = new Color(139, 75, 19); + private static final Color CAPITAL = new Color(255, 0, 0); // red + private static final Color MOUNTAIN = new Color(124, 124, 124, 255); // gray + private static final Color VILLAGE = new Color(139, 75, 19); // brown private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; private static final Random random = new Random(); @@ -41,7 +41,7 @@ private static void createAndShowUI() { JFrame frame = new JFrame("Polytopia"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - JPanel gridPanel = new JPanel(new GridLayout(ROWS, COLS)); + JPanel gridPanel = new JPanel(new GridLayout(ROWS, COLS)); // csinál egy ROWS * COLS méretű táblát PerlinNoise noise = new PerlinNoise(System.currentTimeMillis()); for (int row = 0; row < ROWS; row++) { @@ -49,9 +49,9 @@ private static void createAndShowUI() { JPanel tile = new JPanel(); double value = noise.noise(row * SCALE, col * SCALE); - double normalized = (value + 1) / 2.0; + double normalized = (value + 1) / 2.0; // zajgyártás - tile.setBackground(normalized > 0.56 ? LAND : WATER); + tile.setBackground(normalized > Lakes.WaterLandRatio ? LAND : WATER); // .56 for archi tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); tiles[row][col] = tile; @@ -61,8 +61,8 @@ private static void createAndShowUI() { // 2️⃣ Place tribe capitals AFTER generation replaceTilesWithCities(2, CAPITAL); - GenerateMountains(); // pl. 10 hegy FillTheRestOfTheWorldWithVillages(); + GenerateMountains(); // pl. 10 hegy frame.add(gridPanel); frame.setSize(600, 600); @@ -156,7 +156,7 @@ private static void GenerateMountains(int count) { } private static void FillTheRestOfTheWorldWithVillages() { - int increasingMagicNumber = 35; + int increasingMagicNumber = 25; int totalTiles = ROWS * COLS; int villageCount = totalTiles / increasingMagicNumber - capitals.size(); @@ -177,7 +177,6 @@ private static void FillTheRestOfTheWorldWithVillages() { // Check distance from capitals and mountains only (ignore villages here) if (!isVillageFarEnough(row, col, capitals, 2)) continue; - if (!isVillageFarEnough(row, col, mountains, 2)) continue; candidates.add(new Point(row, col)); } @@ -202,7 +201,4 @@ private static void FillTheRestOfTheWorldWithVillages() { villages.add(candidate); } } - - - } diff --git a/java/src/console/app/main/Lakes.java b/java/src/console/app/main/Lakes.java new file mode 100644 index 0000000..58fdfe0 --- /dev/null +++ b/java/src/console/app/main/Lakes.java @@ -0,0 +1,6 @@ +package console.app.main; + +public class Lakes { + public static final double WaterLandRatio = .4; + public static final double Scale = .1; +} diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class index 7767af2a71835d2bea0544900cb0437df86ccc4f..1e15c290514da11c0fff991f76d700b60c109ea6 100644 GIT binary patch delta 52 ucmX@YcaCqub3y6k{Ji4)oK*e9f&%^A#LT>npHrEIWPQ+uH>a}PX955u%@jlc delta 42 pcmX@dcZ6@ka~`qe{Ji4)oK*e9f`W}dQki)re2_#o=d;{r0sw7L5SIV| From cc0b2cebd246a46cd85369ad0446b59356594a5a Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 22 Jan 2026 16:23:55 +0100 Subject: [PATCH 37/55] v1.6.3.3.3: simulate bardur gameplay --- java/src/console/app/main/DesktopApp.java | 45 ++++++++++------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index fb4d8cd..6fd5649 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -15,12 +15,14 @@ public class DesktopApp { private static final int ROWS = 18; private static final int COLS = 18; - private static final double SCALE = Lakes.Scale; // lower = larger regions + private static final double SCALE = Archi.Scale; // lower = larger regions + private static final double FOREST_RATE = .2; - private static final Color LAND = Color.GREEN; + private static final Color LAND = Color.WHITE; + private static final Color FOREST = new Color(19, 85, 0); private static final Color WATER = Color.BLUE; - private static final Color CAPITAL = new Color(255, 0, 0); // red - private static final Color MOUNTAIN = new Color(124, 124, 124, 255); // gray + private static final Color CAPITAL = new Color(89, 45, 1); // red + private static final Color MOUNTAIN = new Color(133, 133, 133, 255); // gray private static final Color VILLAGE = new Color(139, 75, 19); // brown private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; @@ -51,7 +53,11 @@ private static void createAndShowUI() { double value = noise.noise(row * SCALE, col * SCALE); double normalized = (value + 1) / 2.0; // zajgyártás - tile.setBackground(normalized > Lakes.WaterLandRatio ? LAND : WATER); // .56 for archi + tile.setBackground(normalized > Archi.WaterLandRatio ? LAND : WATER); // .56 for archi + boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE; + if (isForest) { + tile.setBackground(FOREST); + } tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); tiles[row][col] = tile; @@ -60,7 +66,7 @@ private static void createAndShowUI() { } // 2️⃣ Place tribe capitals AFTER generation - replaceTilesWithCities(2, CAPITAL); + replaceTilesWithCities(4, CAPITAL); FillTheRestOfTheWorldWithVillages(); GenerateMountains(); // pl. 10 hegy @@ -95,16 +101,6 @@ private static void replaceTilesWithCities(int count, Color what) { } } - private static boolean isMountainFarEnough(int row, int col, List points, int minDistance) { - for (Point p : points) { - int dist = Math.abs(p.x - row) + Math.abs(p.y - col); // Manhattan distance - if (dist <= minDistance - 2) { - return false; // túl közel - } - } - return true; // jó hely - } - private static boolean isVillageFarEnough(int row, int col, List points, int minDistance) { for (Point p : points) { int dist = Math.abs(p.x - row) + Math.abs(p.y - col); // Manhattan distance @@ -143,20 +139,19 @@ private static void GenerateMountains(int count) { // Falvak bárhol lehetnek (víz vagy föld) // Viszont legalább 2 távol a kapitaloktól és a többi hegytől - if (isMountainFarEnough(row, col, capitals, 2) && isMountainFarEnough(row, col, mountains, 2)) { - // Csak akkor állítsuk barna színűre, ha még nem capital vagy hegy - Color bg = tile.getBackground(); - if (!bg.equals(WATER) && !bg.equals(CAPITAL) && !bg.equals(MOUNTAIN)) { - tile.setBackground(MOUNTAIN); - mountains.add(new Point(row, col)); - placed++; - } + // Csak akkor állítsuk barna színűre, ha még nem capital vagy hegy + Color bg = tile.getBackground(); + if (!bg.equals(WATER) && !bg.equals(CAPITAL) && !bg.equals(MOUNTAIN)) { + tile.setBackground(MOUNTAIN); + mountains.add(new Point(row, col)); + placed++; } + } } private static void FillTheRestOfTheWorldWithVillages() { - int increasingMagicNumber = 25; + int increasingMagicNumber = 20; int totalTiles = ROWS * COLS; int villageCount = totalTiles / increasingMagicNumber - capitals.size(); From e873cc320fcb186fd9fe88e602809fe05d406ded Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 22 Jan 2026 16:25:57 +0100 Subject: [PATCH 38/55] v1.6.3.3.4: simulate bardur gameplay even on large maps --- java/src/console/app/main/DesktopApp.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index 6fd5649..b4ba448 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -13,15 +13,15 @@ public class DesktopApp { - private static final int ROWS = 18; - private static final int COLS = 18; + private static final int ROWS = 30; + private static final int COLS = 30; private static final double SCALE = Archi.Scale; // lower = larger regions private static final double FOREST_RATE = .2; private static final Color LAND = Color.WHITE; private static final Color FOREST = new Color(19, 85, 0); private static final Color WATER = Color.BLUE; - private static final Color CAPITAL = new Color(89, 45, 1); // red + private static final Color CAPITAL = new Color(0, 0, 0); // red private static final Color MOUNTAIN = new Color(133, 133, 133, 255); // gray private static final Color VILLAGE = new Color(139, 75, 19); // brown From 11c87f49a3d04a68874503b4c13c0963b073d08d Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Tue, 3 Feb 2026 16:13:11 +0100 Subject: [PATCH 39/55] v1.6.3.3.5: every map is drylands right now --- java/src/console/Main.java | 14 ++++++-- java/src/console/app/main/Archi.java | 11 ++++--- java/src/console/app/main/Conti.java | 12 +++++-- java/src/console/app/main/DesktopApp.java | 24 ++++++++++---- java/src/console/app/main/Drylands.java | 11 +++++++ java/src/console/app/main/Lakes.java | 10 ++++-- java/src/console/app/main/MapType.java | 31 ++++++++++++++++++ java/src/console/app/main/WaterWorld.java | 11 +++++++ .../polytopia-backend/console/Main.class | Bin 1870 -> 2025 bytes 9 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 java/src/console/app/main/Drylands.java create mode 100644 java/src/console/app/main/MapType.java create mode 100644 java/src/console/app/main/WaterWorld.java diff --git a/java/src/console/Main.java b/java/src/console/Main.java index c8f6809..1d8c36b 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -15,12 +15,20 @@ public static void log(String message) { static String farewellMessage = "Goodbye Mighty Ruler!"; public static void main(String[] args) { - desktop(); + Scanner scanner = new Scanner(System.in); + log("Choose map type!\n" + + "0 - Drylands" + + "1 - Lakes" + + "2- Conti" + + "3 - Archi" + + "4 - Water World"); + String choice = scanner.nextLine(); + desktop(choice); // console(); } - private static void desktop() { - DesktopApp desktopApp = new DesktopApp(); + private static void desktop(String choice) { + DesktopApp desktopApp = new DesktopApp(choice); } private static void console() { diff --git a/java/src/console/app/main/Archi.java b/java/src/console/app/main/Archi.java index e6ad3a3..9ba013a 100644 --- a/java/src/console/app/main/Archi.java +++ b/java/src/console/app/main/Archi.java @@ -1,8 +1,11 @@ package console.app.main; -public enum Archi { - INSTANCE; +public class Archi extends MapType { - public static final double WaterLandRatio = .56; - public static final double Scale = .4; + public static final double WATER_LAND_RATIO = .56; + public static final double PERLIN_SCALE = .4; + + public Archi(String choice) { + super(choice); + } } diff --git a/java/src/console/app/main/Conti.java b/java/src/console/app/main/Conti.java index cf3ea2c..d1688fe 100644 --- a/java/src/console/app/main/Conti.java +++ b/java/src/console/app/main/Conti.java @@ -1,6 +1,12 @@ package console.app.main; -public class Conti { - public static final double WaterLandRatio = .5; - public static final double Scale = .15; +import java.io.Console; + +public class Conti extends MapType { + public static final double WATER_LAND_RATIO = .5; + public static final double PERLIN_SCALE = .15; + + public Conti(String choice) { + super(choice); + } } diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index b4ba448..5cab10c 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -13,11 +13,18 @@ public class DesktopApp { - private static final int ROWS = 30; - private static final int COLS = 30; - private static final double SCALE = Archi.Scale; // lower = larger regions + // Gameplay settings + private static final int ROWS = 16; + private static final int COLS = 16; + private static final int NUMBER_OF_PLAYERS = 2; + + + // Maptype and generation-related settings + private static MapType mapType = new MapType("Lakes"); + private static final double PERLIN_SCALE = mapType.PERLIN_SCALE; // lower = larger regions private static final double FOREST_RATE = .2; + // Terrains private static final Color LAND = Color.WHITE; private static final Color FOREST = new Color(19, 85, 0); private static final Color WATER = Color.BLUE; @@ -39,6 +46,11 @@ public DesktopApp() { SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } + public DesktopApp(String choice) { + SwingUtilities.invokeLater(DesktopApp::createAndShowUI); + mapType = new MapType(choice); + } + private static void createAndShowUI() { JFrame frame = new JFrame("Polytopia"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -50,10 +62,10 @@ private static void createAndShowUI() { for (int col = 0; col < COLS; col++) { JPanel tile = new JPanel(); - double value = noise.noise(row * SCALE, col * SCALE); + double value = noise.noise(row * PERLIN_SCALE, col * PERLIN_SCALE); double normalized = (value + 1) / 2.0; // zajgyártás - tile.setBackground(normalized > Archi.WaterLandRatio ? LAND : WATER); // .56 for archi + tile.setBackground(normalized > mapType.WATER_LAND_RATIO ? LAND : WATER); // .56 for archi boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE; if (isForest) { tile.setBackground(FOREST); @@ -66,7 +78,7 @@ private static void createAndShowUI() { } // 2️⃣ Place tribe capitals AFTER generation - replaceTilesWithCities(4, CAPITAL); + replaceTilesWithCities(NUMBER_OF_PLAYERS, CAPITAL); FillTheRestOfTheWorldWithVillages(); GenerateMountains(); // pl. 10 hegy diff --git a/java/src/console/app/main/Drylands.java b/java/src/console/app/main/Drylands.java new file mode 100644 index 0000000..0f05835 --- /dev/null +++ b/java/src/console/app/main/Drylands.java @@ -0,0 +1,11 @@ +package console.app.main; + +public class Drylands extends MapType { + + public static final double WATER_LAND_RATIO = 0; + public static final double PERLIN_SCALE = .4; + + public Drylands(String choice) { + super(choice); + } +} diff --git a/java/src/console/app/main/Lakes.java b/java/src/console/app/main/Lakes.java index 58fdfe0..4172e00 100644 --- a/java/src/console/app/main/Lakes.java +++ b/java/src/console/app/main/Lakes.java @@ -1,6 +1,10 @@ package console.app.main; -public class Lakes { - public static final double WaterLandRatio = .4; - public static final double Scale = .1; +public class Lakes extends MapType{ + public static final double WATER_LAND_RATIO = .4; + public static final double PERLIN_SCALE = .1; + + public Lakes(String choice) { + super(choice); + } } diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java new file mode 100644 index 0000000..22b2088 --- /dev/null +++ b/java/src/console/app/main/MapType.java @@ -0,0 +1,31 @@ +package console.app.main; + +public class MapType { + public double PERLIN_SCALE = -1; + public double WATER_LAND_RATIO = -1; + protected String choice; + public MapType(String choice) { + this.choice = choice; + switch(choice) { + case "Drylands": + PERLIN_SCALE = Drylands.PERLIN_SCALE; + WATER_LAND_RATIO = Drylands.WATER_LAND_RATIO; + break; + case "Lakes": + PERLIN_SCALE = Lakes.PERLIN_SCALE; + WATER_LAND_RATIO = Lakes.WATER_LAND_RATIO; + break; + case "Conti": + PERLIN_SCALE = Conti.PERLIN_SCALE; + WATER_LAND_RATIO = Conti.WATER_LAND_RATIO; + break; + case "Archi": + PERLIN_SCALE = Archi.PERLIN_SCALE; + WATER_LAND_RATIO = Archi.WATER_LAND_RATIO; + break; + case "Water World": + PERLIN_SCALE = WaterWorld.PERLIN_SCALE; + WATER_LAND_RATIO = WaterWorld.WATER_LAND_RATIO; + } + } +} diff --git a/java/src/console/app/main/WaterWorld.java b/java/src/console/app/main/WaterWorld.java new file mode 100644 index 0000000..07baa1a --- /dev/null +++ b/java/src/console/app/main/WaterWorld.java @@ -0,0 +1,11 @@ +package console.app.main; + +public class WaterWorld extends MapType { + + public static final double WATER_LAND_RATIO = .85; + public static final double PERLIN_SCALE = .4; + + public WaterWorld(String choice) { + super(choice); + } +} diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class index 1e15c290514da11c0fff991f76d700b60c109ea6..2477e043973058d923b88d6d3c5fc1b7ee3a8f1f 100644 GIT binary patch delta 1058 zcmYk5U2_v<6vuy0HpyHx_V@nh1Lm*) zKKlhYPxV`KThn)2%zJe!aSGlEu+EZ1Ebl z!Ycc9d@3>Flz(~e0$CoNtNF%p|~uhukK;*`#t7H=^g z{1_X_Epgi7ov?Oq(=Yg6y7lE!!!8 z5{zq;{Y9?oT(h_ymdXVEYA9%E2h@$=Yi(5B41UnIf}OadC(9MzE4#tfxEK5pUu_l{aAl{64O1_EQ4(cZ{V%+KmK@Y29Tb%%IvnF0osIlCH$tNW`Og6UB zb8Y(e^UbKJg-)yQB@lxEW^jjt1$*QPhmN-yE9ReYY#(+= zI!DU)$~a%)AcG9cITBTHIw*X|M-tP^0QdPA)smnDB`F)j3`gB9dsf0jVa#1}YeGI> z*e}!RJ}X?E2zsa!8KHy@g3GSD-yhgDbx#F|NUma*VSS*Y*35Fb4r#D#$={PM2VxM znUGc|<&=#jFS|A+f8jJM%(f^n$2I1;$%0I9aZjuXNp>S$3~ul#4+JvB#g1%L#2Y03 t4{4ogoz&lq>ns_631I_okM<#vA$*ed`#-Z1cBo!jpaGP@XL1>1$**Ry%_5LzVc#LLMJf4FU0!`8|H=)~tT?d1^V1 zplWhgy?GaP5pC;HSn^BjerEM;DAq`26lz5}Y z*|7MkR&(>Vxn z4dZ0JQY}SB#Fo2iMPdzyk3++ufABgc9Ck0RV^ZP?8K+-zxi(PxmaKw)Ce!;0-0YXmOYl4fRtUWYqh$kwYkxkO zmuV}LCS+TxBd&{H0^p~%0w_f_H5a0<77==Ft*A#Y8t4&+X;e`p(S%_d?-*KWWUY>j z5b;C^cZU!Pv5ep@(!^Lwmv;|i)1+{Qw&igBf=`2ALvR;i4GDdR1naQ&We1(n(RG_w V-eEv?$ea%4aDft9?~_$9`xo7fg_ZyS From a98197cfd77dd5c04185fbf3af6a3590ae2e25a3 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Tue, 3 Feb 2026 16:30:44 +0100 Subject: [PATCH 40/55] v1.6.3.3.6: save before messup --- java/src/console/Main.java | 13 ++++++------ java/src/console/app/main/DesktopApp.java | 20 +++++++++++++----- java/src/console/app/main/MapType.java | 10 +++++++-- .../polytopia-backend/console/Main.class | Bin 2025 -> 2029 bytes 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/java/src/console/Main.java b/java/src/console/Main.java index 1d8c36b..e5bc6fb 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -16,12 +16,13 @@ public static void log(String message) { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); - log("Choose map type!\n" + - "0 - Drylands" + - "1 - Lakes" + - "2- Conti" + - "3 - Archi" + - "4 - Water World"); + log(""" + Choose map type! + 0 - Drylands + 1 - Lakes + 2- Conti + 3 - Archi + 4 - Water World"""); String choice = scanner.nextLine(); desktop(choice); // console(); diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index 5cab10c..e3b6cf8 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -11,17 +11,19 @@ import java.util.List; import java.util.Random; +import static console.Main.log; + public class DesktopApp { // Gameplay settings - private static final int ROWS = 16; - private static final int COLS = 16; + private static final int ROWS = 20; + private static final int COLS = 20; private static final int NUMBER_OF_PLAYERS = 2; // Maptype and generation-related settings private static MapType mapType = new MapType("Lakes"); - private static final double PERLIN_SCALE = mapType.PERLIN_SCALE; // lower = larger regions + private static final double PERLIN_SCALE = Archi.PERLIN_SCALE; // lower = larger regions private static final double FOREST_RATE = .2; // Terrains @@ -47,11 +49,15 @@ public DesktopApp() { } public DesktopApp(String choice) { - SwingUtilities.invokeLater(DesktopApp::createAndShowUI); mapType = new MapType(choice); + log("DesktopApp mapType set to " + choice + + " scale=" + mapType.PERLIN_SCALE); + SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } private static void createAndShowUI() { + log("Using PERLIN_SCALE=" + mapType.PERLIN_SCALE + + ", WATER_LAND_RATIO=" + mapType.WATER_LAND_RATIO); JFrame frame = new JFrame("Polytopia"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -62,7 +68,11 @@ private static void createAndShowUI() { for (int col = 0; col < COLS; col++) { JPanel tile = new JPanel(); - double value = noise.noise(row * PERLIN_SCALE, col * PERLIN_SCALE); + double value = noise.noise( + row * mapType.PERLIN_SCALE, + col * mapType.PERLIN_SCALE + ); + double normalized = (value + 1) / 2.0; // zajgyártás tile.setBackground(normalized > mapType.WATER_LAND_RATIO ? LAND : WATER); // .56 for archi diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java index 22b2088..5e174b4 100644 --- a/java/src/console/app/main/MapType.java +++ b/java/src/console/app/main/MapType.java @@ -1,10 +1,13 @@ package console.app.main; +import static console.Main.log; + public class MapType { - public double PERLIN_SCALE = -1; - public double WATER_LAND_RATIO = -1; + protected double PERLIN_SCALE = -1; + protected double WATER_LAND_RATIO = -1; protected String choice; public MapType(String choice) { + log("MapType constructor called with choice = " + choice); this.choice = choice; switch(choice) { case "Drylands": @@ -27,5 +30,8 @@ public MapType(String choice) { PERLIN_SCALE = WaterWorld.PERLIN_SCALE; WATER_LAND_RATIO = WaterWorld.WATER_LAND_RATIO; } + + log("MapType result: PERLIN_SCALE=" + PERLIN_SCALE + + ", WATER_LAND_RATIO=" + WATER_LAND_RATIO); } } diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class index 2477e043973058d923b88d6d3c5fc1b7ee3a8f1f..e765362f906ec1e0654175115140ae7a07bdbc09 100644 GIT binary patch delta 155 zcmaFK|CWD)3Zspeb4GrCajHUYVu30!KO3ujS+8o39n1xe{L6kw7L6SjsvIkozs{#WjgW}{xY_m;O7&sVI z8H5?s7$g`p859__7&I8P8FYcro{lEG|p5qmiR DN!=lR delta 151 zcmaFM|B`=$3ZsRab4GrCajHUYVu3cX$%x_EK1JE+?>Gpn1xf4L6kv?L6SjcvJYD*t2_fIgTmxxY_m<388{eJ7=#&A y86+4q7!(*Zfut6LE`tt(J&<%|&|~mmFl6vyFk Date: Tue, 3 Feb 2026 16:43:54 +0100 Subject: [PATCH 41/55] v1.6.3.3.7: nullPointerException --- java/src/console/app/main/Archi.java | 11 ------ java/src/console/app/main/Conti.java | 12 ------- java/src/console/app/main/DesktopApp.java | 20 ++++++----- java/src/console/app/main/Drylands.java | 11 ------ java/src/console/app/main/Lakes.java | 10 ------ java/src/console/app/main/MapType.java | 42 ++++++----------------- java/src/console/app/main/WaterWorld.java | 11 ------ 7 files changed, 22 insertions(+), 95 deletions(-) delete mode 100644 java/src/console/app/main/Archi.java delete mode 100644 java/src/console/app/main/Conti.java delete mode 100644 java/src/console/app/main/Drylands.java delete mode 100644 java/src/console/app/main/Lakes.java delete mode 100644 java/src/console/app/main/WaterWorld.java diff --git a/java/src/console/app/main/Archi.java b/java/src/console/app/main/Archi.java deleted file mode 100644 index 9ba013a..0000000 --- a/java/src/console/app/main/Archi.java +++ /dev/null @@ -1,11 +0,0 @@ -package console.app.main; - -public class Archi extends MapType { - - public static final double WATER_LAND_RATIO = .56; - public static final double PERLIN_SCALE = .4; - - public Archi(String choice) { - super(choice); - } -} diff --git a/java/src/console/app/main/Conti.java b/java/src/console/app/main/Conti.java deleted file mode 100644 index d1688fe..0000000 --- a/java/src/console/app/main/Conti.java +++ /dev/null @@ -1,12 +0,0 @@ -package console.app.main; - -import java.io.Console; - -public class Conti extends MapType { - public static final double WATER_LAND_RATIO = .5; - public static final double PERLIN_SCALE = .15; - - public Conti(String choice) { - super(choice); - } -} diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index e3b6cf8..c26fb12 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -22,8 +22,7 @@ public class DesktopApp { // Maptype and generation-related settings - private static MapType mapType = new MapType("Lakes"); - private static final double PERLIN_SCALE = Archi.PERLIN_SCALE; // lower = larger regions + private static MapType mapType = null; private static final double FOREST_RATE = .2; // Terrains @@ -45,19 +44,22 @@ public class DesktopApp { private static final List villages = new ArrayList<>(); public DesktopApp() { + int mapType = 1; + DesktopApp.mapType = MapType.values()[mapType]; SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } public DesktopApp(String choice) { - mapType = new MapType(choice); log("DesktopApp mapType set to " + choice + - " scale=" + mapType.PERLIN_SCALE); + " scale=" + mapType.perlinScale); + int mapType = Integer.parseInt(choice); + DesktopApp.mapType = MapType.values()[mapType]; SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } private static void createAndShowUI() { - log("Using PERLIN_SCALE=" + mapType.PERLIN_SCALE + - ", WATER_LAND_RATIO=" + mapType.WATER_LAND_RATIO); + log("Using PERLIN_SCALE=" + mapType.perlinScale + + ", WATER_LAND_RATIO=" + mapType.waterAndLandRatio); JFrame frame = new JFrame("Polytopia"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -69,13 +71,13 @@ private static void createAndShowUI() { JPanel tile = new JPanel(); double value = noise.noise( - row * mapType.PERLIN_SCALE, - col * mapType.PERLIN_SCALE + row * mapType.perlinScale, + col * mapType.perlinScale ); double normalized = (value + 1) / 2.0; // zajgyártás - tile.setBackground(normalized > mapType.WATER_LAND_RATIO ? LAND : WATER); // .56 for archi + tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : WATER); // .56 for archi boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE; if (isForest) { tile.setBackground(FOREST); diff --git a/java/src/console/app/main/Drylands.java b/java/src/console/app/main/Drylands.java deleted file mode 100644 index 0f05835..0000000 --- a/java/src/console/app/main/Drylands.java +++ /dev/null @@ -1,11 +0,0 @@ -package console.app.main; - -public class Drylands extends MapType { - - public static final double WATER_LAND_RATIO = 0; - public static final double PERLIN_SCALE = .4; - - public Drylands(String choice) { - super(choice); - } -} diff --git a/java/src/console/app/main/Lakes.java b/java/src/console/app/main/Lakes.java deleted file mode 100644 index 4172e00..0000000 --- a/java/src/console/app/main/Lakes.java +++ /dev/null @@ -1,10 +0,0 @@ -package console.app.main; - -public class Lakes extends MapType{ - public static final double WATER_LAND_RATIO = .4; - public static final double PERLIN_SCALE = .1; - - public Lakes(String choice) { - super(choice); - } -} diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java index 5e174b4..8f20b9f 100644 --- a/java/src/console/app/main/MapType.java +++ b/java/src/console/app/main/MapType.java @@ -1,37 +1,17 @@ package console.app.main; -import static console.Main.log; +enum MapType { + DRYLANDS(0.4, 0.0), + LAKES(0.1, 0.4), + CONTI(.15,.5), + ARCHI(0.4, 0.56), + WATER_WORLD(.4,.85); -public class MapType { - protected double PERLIN_SCALE = -1; - protected double WATER_LAND_RATIO = -1; - protected String choice; - public MapType(String choice) { - log("MapType constructor called with choice = " + choice); - this.choice = choice; - switch(choice) { - case "Drylands": - PERLIN_SCALE = Drylands.PERLIN_SCALE; - WATER_LAND_RATIO = Drylands.WATER_LAND_RATIO; - break; - case "Lakes": - PERLIN_SCALE = Lakes.PERLIN_SCALE; - WATER_LAND_RATIO = Lakes.WATER_LAND_RATIO; - break; - case "Conti": - PERLIN_SCALE = Conti.PERLIN_SCALE; - WATER_LAND_RATIO = Conti.WATER_LAND_RATIO; - break; - case "Archi": - PERLIN_SCALE = Archi.PERLIN_SCALE; - WATER_LAND_RATIO = Archi.WATER_LAND_RATIO; - break; - case "Water World": - PERLIN_SCALE = WaterWorld.PERLIN_SCALE; - WATER_LAND_RATIO = WaterWorld.WATER_LAND_RATIO; - } + public final double perlinScale; // lower = larger regions + public final double waterAndLandRatio; - log("MapType result: PERLIN_SCALE=" + PERLIN_SCALE + - ", WATER_LAND_RATIO=" + WATER_LAND_RATIO); + MapType(double ps, double walr) { + this.perlinScale = ps; + this.waterAndLandRatio = walr; } } diff --git a/java/src/console/app/main/WaterWorld.java b/java/src/console/app/main/WaterWorld.java deleted file mode 100644 index 07baa1a..0000000 --- a/java/src/console/app/main/WaterWorld.java +++ /dev/null @@ -1,11 +0,0 @@ -package console.app.main; - -public class WaterWorld extends MapType { - - public static final double WATER_LAND_RATIO = .85; - public static final double PERLIN_SCALE = .4; - - public WaterWorld(String choice) { - super(choice); - } -} From 11c86e1e304cf1e939953058fa0ea486e1244386 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Tue, 3 Feb 2026 16:49:46 +0100 Subject: [PATCH 42/55] v1.6.3.3.8: IT WORKS!!! Finally, it works! TODO: WW runs into infinite loop, fix that. Then, add shallow water, ruins and valid village spawns --- java/src/console/app/main/DesktopApp.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index c26fb12..30600f6 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -22,7 +22,7 @@ public class DesktopApp { // Maptype and generation-related settings - private static MapType mapType = null; + private static MapType mapType = MapType.LAKES; private static final double FOREST_RATE = .2; // Terrains @@ -52,7 +52,12 @@ public DesktopApp() { public DesktopApp(String choice) { log("DesktopApp mapType set to " + choice + " scale=" + mapType.perlinScale); - int mapType = Integer.parseInt(choice); + int mapType = 0; + try { + mapType = Integer.parseInt(choice); + } catch (NumberFormatException e) { + throw new NumberFormatException(); + } DesktopApp.mapType = MapType.values()[mapType]; SwingUtilities.invokeLater(DesktopApp::createAndShowUI); } From b55995414cede72e2666b3b07eff61e4fa43e5b1 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 4 Feb 2026 09:14:35 +0100 Subject: [PATCH 43/55] v1.6.3.3.9: first version after IT WORKS!!! Finally, it works! TODO: WW runs into infinite loop, fix that. Then, add shallow water, ruins and valid village spawns --- java/src/console/app/main/DesktopApp.java | 8 ++++---- java/src/console/app/main/MapType.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index 30600f6..c55b859 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -16,8 +16,8 @@ public class DesktopApp { // Gameplay settings - private static final int ROWS = 20; - private static final int COLS = 20; + private static final int ROWS = 16; + private static final int COLS = 16; private static final int NUMBER_OF_PLAYERS = 2; @@ -26,7 +26,7 @@ public class DesktopApp { private static final double FOREST_RATE = .2; // Terrains - private static final Color LAND = Color.WHITE; + private static final Color LAND = new Color(46, 168, 19); private static final Color FOREST = new Color(19, 85, 0); private static final Color WATER = Color.BLUE; private static final Color CAPITAL = new Color(0, 0, 0); // red @@ -82,7 +82,7 @@ private static void createAndShowUI() { double normalized = (value + 1) / 2.0; // zajgyártás - tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : WATER); // .56 for archi + tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : WATER); // for archi boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE; if (isForest) { tile.setBackground(FOREST); diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java index 8f20b9f..5d4b5ac 100644 --- a/java/src/console/app/main/MapType.java +++ b/java/src/console/app/main/MapType.java @@ -1,10 +1,10 @@ package console.app.main; enum MapType { - DRYLANDS(0.4, 0.0), - LAKES(0.1, 0.4), - CONTI(.15,.5), - ARCHI(0.4, 0.56), + DRYLANDS(.4, .0), + LAKES(.1, .4), + CONTI(.1,.53), + ARCHI(.4, .56), WATER_WORLD(.4,.85); public final double perlinScale; // lower = larger regions From 07650000fe47747c0c8553724b047e44f96ca2fb Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 4 Feb 2026 09:31:21 +0100 Subject: [PATCH 44/55] v1.6.3.3.10: mountains do not spawn any more on ocean --- java/src/console/app/main/DesktopApp.java | 22 ++++++++++++---------- java/src/console/app/main/MapType.java | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index c55b859..9015a88 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -16,8 +16,8 @@ public class DesktopApp { // Gameplay settings - private static final int ROWS = 16; - private static final int COLS = 16; + private static final int ROWS = 40; + private static final int COLS = 40; private static final int NUMBER_OF_PLAYERS = 2; @@ -28,10 +28,12 @@ public class DesktopApp { // Terrains private static final Color LAND = new Color(46, 168, 19); private static final Color FOREST = new Color(19, 85, 0); - private static final Color WATER = Color.BLUE; - private static final Color CAPITAL = new Color(0, 0, 0); // red - private static final Color MOUNTAIN = new Color(133, 133, 133, 255); // gray - private static final Color VILLAGE = new Color(139, 75, 19); // brown + private static final Color OCEAN = Color.BLUE; + private static final Color WATER = new Color(75, 208, 255); // not used yet + private static final Color CAPITAL = new Color(0, 0, 0); + private static final Color MOUNTAIN = new Color(133, 133, 133, 255); + private static final Color VILLAGE = new Color(139, 75, 19); + private static final Color RUIN = new Color(255, 255, 1, 230); private static final JPanel[][] tiles = new JPanel[ROWS][COLS]; private static final Random random = new Random(); @@ -82,9 +84,9 @@ private static void createAndShowUI() { double normalized = (value + 1) / 2.0; // zajgyártás - tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : WATER); // for archi + tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : OCEAN); // for archi boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE; - if (isForest) { + if (isForest && !tile.getBackground().equals(OCEAN)) { tile.setBackground(FOREST); } tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); @@ -170,7 +172,7 @@ private static void GenerateMountains(int count) { // Viszont legalább 2 távol a kapitaloktól és a többi hegytől // Csak akkor állítsuk barna színűre, ha még nem capital vagy hegy Color bg = tile.getBackground(); - if (!bg.equals(WATER) && !bg.equals(CAPITAL) && !bg.equals(MOUNTAIN)) { + if (!bg.equals(OCEAN) && !bg.equals(CAPITAL) && !bg.equals(MOUNTAIN)) { tile.setBackground(MOUNTAIN); mountains.add(new Point(row, col)); placed++; @@ -197,7 +199,7 @@ private static void FillTheRestOfTheWorldWithVillages() { Color bg = tile.getBackground(); // Must be LAND or WATER and not already village, capital, mountain - if (!bg.equals(LAND) && !bg.equals(WATER)) continue; + if (!bg.equals(LAND) && !bg.equals(OCEAN)) continue; // Check distance from capitals and mountains only (ignore villages here) if (!isVillageFarEnough(row, col, capitals, 2)) continue; diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java index 5d4b5ac..950e2d8 100644 --- a/java/src/console/app/main/MapType.java +++ b/java/src/console/app/main/MapType.java @@ -3,7 +3,7 @@ enum MapType { DRYLANDS(.4, .0), LAKES(.1, .4), - CONTI(.1,.53), + CONTI(.1,.52), ARCHI(.4, .56), WATER_WORLD(.4,.85); From 098f4e244bba06cdaf2455cd6f92074a09d47588 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Wed, 4 Feb 2026 09:34:51 +0100 Subject: [PATCH 45/55] v1.6.3.3.10: I meant FORESTS. Last commit I meant FORESTS do not spawn any more on ocean. Mountains do. This patch increased forest spawn rate. --- java/src/console/app/main/DesktopApp.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index 9015a88..0d228f1 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -23,7 +23,7 @@ public class DesktopApp { // Maptype and generation-related settings private static MapType mapType = MapType.LAKES; - private static final double FOREST_RATE = .2; + private static final double FOREST_RATE = .3; // Terrains private static final Color LAND = new Color(46, 168, 19); @@ -86,7 +86,7 @@ private static void createAndShowUI() { tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : OCEAN); // for archi boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE; - if (isForest && !tile.getBackground().equals(OCEAN)) { + if (isForest && tile.getBackground().equals(LAND)) { tile.setBackground(FOREST); } tile.setBorder(BorderFactory.createLineBorder(Color.BLACK)); From fc8bda962cfdfcb1a908ae8b56e28465925f9e31 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 12 Feb 2026 10:47:57 +0100 Subject: [PATCH 46/55] v1.7: Big improvements on the desktop app. The desktop app's control flow now seamlessly integrates with the console app. Both apps can be controlled from console now. Also extracted a lot of repetitive code into methods. So overall the code quality is much better too. --- java/src/console/Main.java | 49 +++++++++++++++-------- java/src/console/app/main/DesktopApp.java | 11 +++-- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/java/src/console/Main.java b/java/src/console/Main.java index e5bc6fb..b140a5a 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -4,6 +4,7 @@ import console.app.ConsoleAppUtils; import console.app.main.DesktopApp; +import java.util.Objects; import java.util.Scanner; public class Main { @@ -15,29 +16,33 @@ public static void log(String message) { static String farewellMessage = "Goodbye Mighty Ruler!"; public static void main(String[] args) { - Scanner scanner = new Scanner(System.in); - log(""" - Choose map type! - 0 - Drylands - 1 - Lakes - 2- Conti - 3 - Archi - 4 - Water World"""); - String choice = scanner.nextLine(); - desktop(choice); + Scanner scanner = new Scanner(System.in); // console(); + createLoop(scanner, "desktop"); } private static void desktop(String choice) { DesktopApp desktopApp = new DesktopApp(choice); } - private static void console() { + private static void console(Scanner scanner) { ConsoleApp consoleApp = new ConsoleApp(); - Scanner scanner = new Scanner(System.in); - log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); + createLoop(scanner, "console"); + exit(scanner); + } + + public static void exit(Scanner scanner) { + log("Press any key to exit..."); + scanner.nextLine(); + } + private static void createLoop(Scanner scanner, String appType) { while (true) { + if ("console".equals(appType)) { + log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); + } else { + log("Polytopia Desktop app started. Press ENTER to start generating"); + } System.out.print ("> "); String input = scanner.nextLine().trim(); input = input.toLowerCase(); @@ -46,10 +51,20 @@ private static void console() { log(farewellMessage); break; } - - ConsoleAppUtils.handleCommand(input); + if ("console".equals(appType)) { + ConsoleAppUtils.handleCommand(input); + } else { + log(""" + Choose map type! + 0 - Drylands + 1 - Lakes + 2- Conti + 3 - Archi + 4 - Water World"""); + String choice = scanner.nextLine(); + desktop(choice); + log("Press ENTER to continue, type `exit` to quit."); + } } - log("Press any key to exit..."); - scanner.nextLine(); } } diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java index 0d228f1..d7224be 100644 --- a/java/src/console/app/main/DesktopApp.java +++ b/java/src/console/app/main/DesktopApp.java @@ -6,18 +6,17 @@ import javax.swing.JFrame; import javax.swing.SwingUtilities; import java.awt.*; -import java.util.ArrayList; -import java.util.Collections; +import java.util.*; import java.util.List; -import java.util.Random; +import static console.Main.exit; import static console.Main.log; public class DesktopApp { // Gameplay settings - private static final int ROWS = 40; - private static final int COLS = 40; + private static final int ROWS = 30; + private static final int COLS = 30; private static final int NUMBER_OF_PLAYERS = 2; @@ -58,7 +57,7 @@ public DesktopApp(String choice) { try { mapType = Integer.parseInt(choice); } catch (NumberFormatException e) { - throw new NumberFormatException(); + exit(new Scanner(System.in)); } DesktopApp.mapType = MapType.values()[mapType]; SwingUtilities.invokeLater(DesktopApp::createAndShowUI); From ac406fed58e25b65c5823879487108b5e232e594 Mon Sep 17 00:00:00 2001 From: fabbernatvasvari Date: Thu, 12 Feb 2026 10:59:10 +0100 Subject: [PATCH 47/55] v1.7: - Feature update: fixed some bugs Big improvements on the desktop app. The desktop app's control flow now seamlessly integrates with the console app. Both apps can be controlled from console now. Also extracted a lot of repetitive code into methods. So overall the code quality is much better too. --- java/src/console/Main.java | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/java/src/console/Main.java b/java/src/console/Main.java index b140a5a..45bd59a 100644 --- a/java/src/console/Main.java +++ b/java/src/console/Main.java @@ -17,8 +17,17 @@ public static void log(String message) { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); -// console(); + + /* + * console app: + */ + console(scanner); + + /* + * desktop app: + */ createLoop(scanner, "desktop"); + exit(scanner); } private static void desktop(String choice) { @@ -28,7 +37,6 @@ private static void desktop(String choice) { private static void console(Scanner scanner) { ConsoleApp consoleApp = new ConsoleApp(); createLoop(scanner, "console"); - exit(scanner); } public static void exit(Scanner scanner) { @@ -37,23 +45,23 @@ public static void exit(Scanner scanner) { } private static void createLoop(Scanner scanner, String appType) { - while (true) { - if ("console".equals(appType)) { - log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); - } else { - log("Polytopia Desktop app started. Press ENTER to start generating"); - } + if ("console".equals(appType)) { + log("Polytopia CLI started. Type `start \"Game Name\"` or just `start` to start a new game.\n Example: start \"Misty Clouds\"\nType `help` to get the list of available commands.\n`exit` to quit."); + } else { + log("Polytopia Desktop app started. Press ENTER to start generating"); + } + while (true) { System.out.print ("> "); String input = scanner.nextLine().trim(); input = input.toLowerCase(); if ("exit".equals(input) || "quit".equals(input)) { log(farewellMessage); - break; + return; } if ("console".equals(appType)) { ConsoleAppUtils.handleCommand(input); - } else { + } else if ("desktop".equals(appType)) { log(""" Choose map type! 0 - Drylands From 220a4da53e08bbb5d76e2cdaeb4c324614d12aff Mon Sep 17 00:00:00 2001 From: Fabbernat <114658229+Fabbernat@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:29:15 +0100 Subject: [PATCH 48/55] Fix links to Polytopia frontend repository Updated links to point to the correct frontend repository. --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3f561a2..adc629b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Read Me: +For the frontend see [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) + + # Read Me: Welcome to the `polytopia_python` repo! Have a look around and familiarise yourself with some of the code that's already here. @@ -18,7 +20,7 @@ will correspond to. # Fabbernat's contribution: -Polytopia UI can be found at https://github.com/Fabbernat/Polytopia as an ASP.NET consoleApp. +Polytopia UI can be found at [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) as an ASP.NET consoleApp. I've added a score counter Python module and a Java console consoleApp From 42a27dc340b61e84e1514befea81dd8fab2a766d Mon Sep 17 00:00:00 2001 From: Fabbernat Date: Wed, 25 Mar 2026 09:42:51 +0100 Subject: [PATCH 49/55] v1.8: added missing valid commands to the project --- java/src/console/utils/ValidCommands.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/console/utils/ValidCommands.java b/java/src/console/utils/ValidCommands.java index f3f32e9..f2bf6d5 100644 --- a/java/src/console/utils/ValidCommands.java +++ b/java/src/console/utils/ValidCommands.java @@ -11,7 +11,7 @@ public class ValidCommands { // --- Command definitions --- private static final Set menuCommands = Set.of( - "help", "start", "delete", "games" + "help", "start", "delete", "games", "exit", "quit" ); From 32a27b982944360093ccae1b56506ff66bb3b3f8 Mon Sep 17 00:00:00 2001 From: Fabbernat <114658229+Fabbernat@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:49:56 +0100 Subject: [PATCH 50/55] Added images and descriptions for various map types including Drylands, Lakes, Continents, and Archipelagos. Added images and descriptions for various map types including Drylands, Lakes, Continents, and Archipelagos. Updated contributions section with details on terrain generation and score counter modules. --- README.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index adc629b..da737f3 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,25 @@ these is the `CHANNEL_ATTRIBUTES` object, which contains descriptions of what ea will correspond to. # Fabbernat's contribution: +image +The "Drylands" map type -Polytopia UI can be found at [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) as an ASP.NET consoleApp. +image +The "Lakes" map type -I've added a score counter Python module and a Java console consoleApp +image +The "Continents" map type + +image +The "Archipelagos" map type + +image +The "Continents" map type, but BIGGER + + +- I made a frontend to this at [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) built in ASP.NET +- I added a COMPLETE terrain generation module +- I added a score counter Python module and a Java console consoleApp ## Java console consoleApp This is intended to be the main consoleApp at some point and will simulate the core logic of the gameplay like map generation, tech tree and even combat From 862a78c4c0a9c41900109a61dc1cd3117d7f23ba Mon Sep 17 00:00:00 2001 From: Fabbernat <114658229+Fabbernat@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:50:52 +0100 Subject: [PATCH 51/55] Enhance README with new map types and contributions Updated README.md with additional map types and contributions. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index da737f3..fdc2c1a 100644 --- a/README.md +++ b/README.md @@ -18,22 +18,23 @@ The `settings.py` file contains some settings variables that are mostly unused a these is the `CHANNEL_ATTRIBUTES` object, which contains descriptions of what each 'channel' in the game state array will correspond to. +--- # Fabbernat's contribution: image The "Drylands" map type - +--- image The "Lakes" map type - +--- image The "Continents" map type - +--- image The "Archipelagos" map type - +--- image The "Continents" map type, but BIGGER - +--- - I made a frontend to this at [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) built in ASP.NET - I added a COMPLETE terrain generation module From 16be09a6fdf61a3287eec27aef722e17c6a902e5 Mon Sep 17 00:00:00 2001 From: Fabbernat <114658229+Fabbernat@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:52:46 +0100 Subject: [PATCH 52/55] Revise map type descriptions in README.md Updated descriptions for various map types and added insights. --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fdc2c1a..ec6e80c 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,23 @@ will correspond to. --- # Fabbernat's contribution: image -The "Drylands" map type +The "Lakes" map type and some insight to the code in IntelliJ IDEA 👆 --- + image -The "Lakes" map type +The "Drylands" map type 👆 --- + image -The "Continents" map type +The "Continents" map type 👆 --- + image -The "Archipelagos" map type +The "Archipelagos" map type 👆 --- + image -The "Continents" map type, but BIGGER +The "Continents" map type, but BIGGER 👆 --- - I made a frontend to this at [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) built in ASP.NET From 0d83b7f22eec0bfd50aae68c111d7bc26e504183 Mon Sep 17 00:00:00 2001 From: Fabbernat <114658229+Fabbernat@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:53:26 +0100 Subject: [PATCH 53/55] Update headings for map types in README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ec6e80c..9ce30b2 100644 --- a/README.md +++ b/README.md @@ -20,24 +20,24 @@ will correspond to. --- # Fabbernat's contribution: -image -The "Lakes" map type and some insight to the code in IntelliJ IDEA 👆 +# image +### The "Lakes" map type and some insight to the code in IntelliJ IDEA 👆 --- -image -The "Drylands" map type 👆 +# image +### The "Drylands" map type 👆 --- -image -The "Continents" map type 👆 +# image +### The "Continents" map type 👆 --- -image -The "Archipelagos" map type 👆 +# image +### The "Archipelagos" map type 👆 --- -image -The "Continents" map type, but BIGGER 👆 +# image +### The "Continents" map type, but BIGGER 👆 --- - I made a frontend to this at [github.com/Fabbernat/Polytopia-frontend](https://github.com/Fabbernat/Polytopia-frontend) built in ASP.NET From 358d401f92ca367f5cbc41aca2c6ae85b92cfe3c Mon Sep 17 00:00:00 2001 From: Fabbernat Date: Wed, 25 Mar 2026 09:58:46 +0100 Subject: [PATCH 54/55] v1.8.1: there is a known issue with water worlds --- java/src/console/app/main/MapType.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java index 950e2d8..6879e97 100644 --- a/java/src/console/app/main/MapType.java +++ b/java/src/console/app/main/MapType.java @@ -5,7 +5,7 @@ enum MapType { LAKES(.1, .4), CONTI(.1,.52), ARCHI(.4, .56), - WATER_WORLD(.4,.85); + WATER_WORLD(.4,.85); // TODO fix Water World public final double perlinScale; // lower = larger regions public final double waterAndLandRatio; From c200bd0e758744451492618af3db55dbb3c1b608 Mon Sep 17 00:00:00 2001 From: Fabbernat Date: Mon, 8 Jun 2026 15:37:30 +0200 Subject: [PATCH 55/55] v1.8.2: fix yadakk tribe --- java/src/models/concreteTribes/Yadakk.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/src/models/concreteTribes/Yadakk.java b/java/src/models/concreteTribes/Yadakk.java index 65a3e8c..ae40033 100644 --- a/java/src/models/concreteTribes/Yadakk.java +++ b/java/src/models/concreteTribes/Yadakk.java @@ -5,6 +5,6 @@ public class Yadakk extends AbstractTribe { protected Yadakk() { - super(Tech.STRATEGY, 7, false, 1.5, 1, .5, 1, .5, 1, 1); + super(Tech.ROADS, 7, false, 1.5, 1, .5, 1, .5, 1, 1); } }