diff --git a/README.md b/README.md
index 63170de..9ce30b2 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,48 @@
-## 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.
+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's contribution:
+#
+### The "Lakes" map type and some insight to the code in IntelliJ IDEA 👆
+---
+
+#
+### The "Drylands" map type 👆
+---
+
+#
+### The "Continents" map type 👆
+---
+
+#
+### The "Archipelagos" map type 👆
+---
+
+#
+### 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
diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 0000000..e69de29
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/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..63e859b
Binary files /dev/null and b/app/static/favicon.ico differ
diff --git a/app/static/images/polyhead_ancient.jpg b/app/static/images/polyhead_ancient.jpg
new file mode 100644
index 0000000..17f2bc7
Binary files /dev/null and b/app/static/images/polyhead_ancient.jpg differ
diff --git a/app/static/images/polytopia-background.jpg b/app/static/images/polytopia-background.jpg
new file mode 100644
index 0000000..ae759af
Binary files /dev/null and b/app/static/images/polytopia-background.jpg differ
diff --git a/app/static/images/polytopia-main-menu.webp b/app/static/images/polytopia-main-menu.webp
new file mode 100644
index 0000000..7f7f407
Binary files /dev/null and b/app/static/images/polytopia-main-menu.webp differ
diff --git a/app/static/images/soundfx-ico.png b/app/static/images/soundfx-ico.png
new file mode 100644
index 0000000..5adfc16
Binary files /dev/null and b/app/static/images/soundfx-ico.png differ
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"]
+
+
+
+
+
+
Fabian Team: A passionate team of developers dedicated to creating engaging and immersive experiences for Polytopia fans.
+
+
Polysseum
+
The Polysseum is a platform where strategic minds meet, showcasing the best players and their achievements. It's the ultimate hall of fame for Polytopia enthusiasts.
+
+
Meet the Team
+
~Midji Team~ Fabian Team:
+
+ Fabian Bernát: Lead Developer and visionary behind the project.
+ Midjiwan: Creator and leader of Polytopia
+ Zoythrus: Lead Community Manager, Community Member
+
+ Midjitone: Sound, music and ambience. Check out Polytopia music at
+
+
+
+
+
+
Our Mission
+
To create a space where players can share strategies, celebrate victories, and grow the Polytopia community.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/templates/base.html b/app/templates/base.html
new file mode 100644
index 0000000..f84fea4
--- /dev/null
+++ b/app/templates/base.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ {{ title }}
+
+
+
+ Index |
+ Privacy |
+ Dashboard |
+ About |
+ Settings |
+ Hall of Fame |
+ Throne Room
+
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html
new file mode 100644
index 0000000..2bd2f9b
--- /dev/null
+++ b/app/templates/dashboard.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Dashboard
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..c02e56c
--- /dev/null
+++ b/app/templates/home.html
@@ -0,0 +1,10 @@
+
+
+
+
+ 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";
+}
+
+
+
+
+
+
+
+
+
+
\ 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/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/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/java/resources/games.txt b/java/resources/games.txt
new file mode 100644
index 0000000..84297eb
--- /dev/null
+++ b/java/resources/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/resources/guidReference.txt b/java/resources/guidReference.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++ b/java/resources/guidReference.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/java/src/console/Main.java b/java/src/console/Main.java
new file mode 100644
index 0000000..45bd59a
--- /dev/null
+++ b/java/src/console/Main.java
@@ -0,0 +1,78 @@
+package console;
+
+import console.app.ConsoleApp;
+import console.app.ConsoleAppUtils;
+import console.app.main.DesktopApp;
+
+import java.util.Objects;
+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);
+
+ /*
+ * console app:
+ */
+ console(scanner);
+
+ /*
+ * desktop app:
+ */
+ createLoop(scanner, "desktop");
+ exit(scanner);
+ }
+
+ private static void desktop(String choice) {
+ DesktopApp desktopApp = new DesktopApp(choice);
+ }
+
+ private static void console(Scanner scanner) {
+ ConsoleApp consoleApp = new ConsoleApp();
+ createLoop(scanner, "console");
+ }
+
+ public static void exit(Scanner scanner) {
+ log("Press any key to exit...");
+ scanner.nextLine();
+ }
+
+ private static void createLoop(Scanner scanner, String appType) {
+ 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);
+ return;
+ }
+ if ("console".equals(appType)) {
+ ConsoleAppUtils.handleCommand(input);
+ } else if ("desktop".equals(appType)) {
+ 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.");
+ }
+ }
+ }
+}
diff --git a/java/src/console/app/ConsoleApp.java b/java/src/console/app/ConsoleApp.java
new file mode 100644
index 0000000..73afad2
--- /dev/null
+++ b/java/src/console/app/ConsoleApp.java
@@ -0,0 +1,71 @@
+package console.app;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static console.Main.log;
+
+public class ConsoleApp {
+ static int guid = 1;
+ private List games = new ArrayList<>();
+
+ public ConsoleApp() {
+ readGuidReference();
+ readGames();
+ }
+
+ public List getGames() {
+ readGames();
+ return games;
+ }
+
+ private void readGuidReference() {
+ try (InputStream input = ConsoleApp.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();
+ }
+ }
+
+ private void readGames() {
+ try (InputStream input = ConsoleApp.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();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public void reportGames() {
+ games.forEach(System.out::println);
+ }
+
+}
diff --git a/java/src/console/app/ConsoleAppUtils.java b/java/src/console/app/ConsoleAppUtils.java
new file mode 100644
index 0000000..b884f2c
--- /dev/null
+++ b/java/src/console/app/ConsoleAppUtils.java
@@ -0,0 +1,71 @@
+package console.app;
+
+import console.utils.ValidCommands;
+
+import static console.Main.log;
+
+public class ConsoleAppUtils {
+ public static void handleCommand(String input) {
+ if (input.isEmpty())
+ return;
+
+ if (input.equals("help")) {
+ log("Available commands: " + String.join(", ", 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" -> {
+ 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 " + ConsoleApp.guid++));
+ }
+ case "delete" -> report("Deleted game: " + gameName);
+
+ case "games" -> {
+ ConsoleApp consoleApp = new ConsoleApp();
+ report("Games list: " + consoleApp.getGames().toString());
+ }
+ }
+ return;
+ }
+ }
+ log("Executed base command: " + input);
+
+ }
+
+ 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;
+ 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(String message) {
+ log("|--- " + stars + " stars | " + score + " score | " + turn + "th turn" + " ---|" + tabulators + "\n" + message);
+ }
+}
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))
+ );
+ }
+}
diff --git a/java/src/console/app/main/DesktopApp.java b/java/src/console/app/main/DesktopApp.java
new file mode 100644
index 0000000..d7224be
--- /dev/null
+++ b/java/src/console/app/main/DesktopApp.java
@@ -0,0 +1,229 @@
+package console.app.main;
+
+import console.app.PerlinNoise;
+
+import javax.swing.*;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+import static console.Main.exit;
+import static console.Main.log;
+
+public class DesktopApp {
+
+ // Gameplay settings
+ private static final int ROWS = 30;
+ private static final int COLS = 30;
+ private static final int NUMBER_OF_PLAYERS = 2;
+
+
+ // Maptype and generation-related settings
+ private static MapType mapType = MapType.LAKES;
+ private static final double FOREST_RATE = .3;
+
+ // 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 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();
+
+ // 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<>();
+
+ public DesktopApp() {
+ int mapType = 1;
+ DesktopApp.mapType = MapType.values()[mapType];
+ SwingUtilities.invokeLater(DesktopApp::createAndShowUI);
+ }
+
+ public DesktopApp(String choice) {
+ log("DesktopApp mapType set to " + choice +
+ " scale=" + mapType.perlinScale);
+ int mapType = 0;
+ try {
+ mapType = Integer.parseInt(choice);
+ } catch (NumberFormatException e) {
+ exit(new Scanner(System.in));
+ }
+ DesktopApp.mapType = MapType.values()[mapType];
+ SwingUtilities.invokeLater(DesktopApp::createAndShowUI);
+ }
+
+ private static void createAndShowUI() {
+ log("Using PERLIN_SCALE=" + mapType.perlinScale +
+ ", WATER_LAND_RATIO=" + mapType.waterAndLandRatio);
+ JFrame frame = new JFrame("Polytopia");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+
+ 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++) {
+ for (int col = 0; col < COLS; col++) {
+ JPanel tile = new JPanel();
+
+ double value = noise.noise(
+ row * mapType.perlinScale,
+ col * mapType.perlinScale
+ );
+
+ double normalized = (value + 1) / 2.0; // zajgyártás
+
+ tile.setBackground(normalized > mapType.waterAndLandRatio ? LAND : OCEAN); // for archi
+ boolean isForest = random.nextInt(100) / 100.0 < FOREST_RATE;
+ if (isForest && tile.getBackground().equals(LAND)) {
+ tile.setBackground(FOREST);
+ }
+ tile.setBorder(BorderFactory.createLineBorder(Color.BLACK));
+
+ tiles[row][col] = tile;
+ gridPanel.add(tile);
+ }
+ }
+
+ // 2️⃣ Place tribe capitals AFTER generation
+ replaceTilesWithCities(NUMBER_OF_PLAYERS, CAPITAL);
+ FillTheRestOfTheWorldWithVillages();
+ GenerateMountains(); // pl. 10 hegy
+
+ frame.add(gridPanel);
+ frame.setSize(600, 600);
+ frame.setLocationRelativeTo(null);
+ 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
+ *
+ * @param count number of cities to place
+ * @param what color to paint
+ */
+ private static void replaceTilesWithCities(int count, Color what) {
+ int placed = 0;
+
+ while (placed < count) {
+ 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) && isVillageFarEnough(row, col, capitals, 2)) {
+ tile.setBackground(what);
+ capitals.add(new Point(row, col)); // kapitalokat tároljuk ide
+ placed++;
+ }
+ }
+ }
+
+ 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 / 17 - capitals.size(); // alapértelmezett hegy szám
+
+ if (count < 0) count = 0; // negatív érték esetén nulla
+
+ GenerateMountains(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 GenerateMountains(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 hegytől
+ // Csak akkor állítsuk barna színűre, ha még nem capital vagy hegy
+ Color bg = tile.getBackground();
+ if (!bg.equals(OCEAN) && !bg.equals(CAPITAL) && !bg.equals(MOUNTAIN)) {
+ tile.setBackground(MOUNTAIN);
+ mountains.add(new Point(row, col));
+ placed++;
+ }
+
+ }
+ }
+
+ private static void FillTheRestOfTheWorldWithVillages() {
+ int increasingMagicNumber = 20;
+ int totalTiles = ROWS * COLS;
+ int villageCount = totalTiles / increasingMagicNumber - capitals.size();
+
+ if (villageCount <= 0) {
+ return;
+ }
+
+ List candidates = new ArrayList<>();
+
+ // 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(OCEAN)) continue;
+
+ // Check distance from capitals and mountains only (ignore villages here)
+ if (!isVillageFarEnough(row, col, capitals, 2)) continue;
+
+ 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);
+ }
+ }
+}
diff --git a/java/src/console/app/main/MapType.java b/java/src/console/app/main/MapType.java
new file mode 100644
index 0000000..6879e97
--- /dev/null
+++ b/java/src/console/app/main/MapType.java
@@ -0,0 +1,17 @@
+package console.app.main;
+
+enum MapType {
+ DRYLANDS(.4, .0),
+ LAKES(.1, .4),
+ CONTI(.1,.52),
+ ARCHI(.4, .56),
+ WATER_WORLD(.4,.85); // TODO fix Water World
+
+ public final double perlinScale; // lower = larger regions
+ public final double waterAndLandRatio;
+
+ MapType(double ps, double walr) {
+ this.perlinScale = ps;
+ this.waterAndLandRatio = walr;
+ }
+}
diff --git a/java/src/console/utils/ValidCommands.java b/java/src/console/utils/ValidCommands.java
new file mode 100644
index 0000000..f2bf6d5
--- /dev/null
+++ b/java/src/console/utils/ValidCommands.java
@@ -0,0 +1,56 @@
+package console.utils;
+
+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(
+ "help", "start", "delete", "games", "exit", "quit"
+ );
+
+
+ // --- 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 extends String>) 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/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..ae40033
--- /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.ROADS, 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);
+ }
+}
diff --git a/out/production/polytopia-backend/console/Main.class b/out/production/polytopia-backend/console/Main.class
new file mode 100644
index 0000000..e765362
Binary files /dev/null and b/out/production/polytopia-backend/console/Main.class differ
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 0000000..6ce4393
Binary files /dev/null and b/out/production/polytopia-backend/console/utils/ValidCommands.class differ
diff --git a/out/production/src/Main.class b/out/production/src/Main.class
new file mode 100644
index 0000000..c15ac94
Binary files /dev/null and b/out/production/src/Main.class differ
diff --git a/polytopia-backend.iml b/polytopia-backend.iml
new file mode 100644
index 0000000..e13cb1e
--- /dev/null
+++ b/polytopia-backend.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..5bc5fdb
--- /dev/null
+++ b/polytopia_algorithms/alfa_beta.py
@@ -0,0 +1,83 @@
+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 neighbors_of_n:
+ 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 neighbors_of_n:
+ 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..969df80
--- /dev/null
+++ 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
new file mode 100644
index 0000000..9dcc63e
--- /dev/null
+++ b/polytopia_algorithms/min_max.py
@@ -0,0 +1,78 @@
+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 neighbors_of_n:
+ max = max(max, min_value(a))
+ return max
+
+ def min_value(n):
+ if vegallapot(n):
+ return hasznossag(n)
+
+ min = +INF
+ for a in neighbors_of_n:
+ 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..eb3d760
--- /dev/null
+++ 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/__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 99%
rename from game_map.py
rename to polytopia_game/game_map.py
index d42c7ed..49d69ad 100644
--- a/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/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 99%
rename from map_renderer.py
rename to polytopia_game/map_renderer.py
index c834240..9218824 100644
--- a/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
diff --git a/polytopia_game/settings.py b/polytopia_game/settings.py
new file mode 100644
index 0000000..cd19d09
--- /dev/null
+++ b/polytopia_game/settings.py
@@ -0,0 +1,113 @@
+import random
+import pathlib
+from enum import Enum
+
+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/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 0000000..0aa7e9d
Binary files /dev/null and b/polytopia_score_counter/__pycache__/__init__.cpython-312.pyc differ
diff --git a/polytopia_score_counter/__pycache__/actions.cpython-312.pyc b/polytopia_score_counter/__pycache__/actions.cpython-312.pyc
new file mode 100644
index 0000000..18923ac
Binary files /dev/null and b/polytopia_score_counter/__pycache__/actions.cpython-312.pyc differ
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 0000000..363af20
Binary files /dev/null and b/polytopia_score_counter/__pycache__/rewards.cpython-312.pyc differ
diff --git a/polytopia_score_counter/actions.py b/polytopia_score_counter/actions.py
new file mode 100644
index 0000000..062dee0
--- /dev/null
+++ b/polytopia_score_counter/actions.py
@@ -0,0 +1,32 @@
+# polytopia_score_counter/actions.py
+
+# List of valid actions for reference
+ACTIONS = [
+ "train_unit",
+ "lose_unit",
+ "upgrade_city",
+ "place_structure",
+ "capture_city",
+ "lose_city",
+ "gain_territory",
+ "lose_territory",
+ "research_tech",
+ "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
new file mode 100644
index 0000000..a08818c
--- /dev/null
+++ b/polytopia_score_counter/rewards.py
@@ -0,0 +1,58 @@
+# polytopia_score_counter/rewards.py
+
+# Star cost for common units
+UNIT_STAR_COST = {
+ "warrior": 2,
+ "rider": 3,
+ "archer": 3,
+ "defender": 3,
+ "swordsman": 5,
+ "catapult": 8,
+ "cloak": 8,
+ "knight": 8,
+ "giant": 10,
+}
+
+
+def calculate_reward(action, **kwargs):
+ if action == "train_unit":
+ unit_type = kwargs.get("unit_type", None)
+ return 5 * UNIT_STAR_COST.get(unit_type, 0)
+
+ if action == "lose_unit":
+ unit_type = kwargs.get("unit_type", None)
+ return -5 * UNIT_STAR_COST.get(unit_type, 0)
+
+ if action == "upgrade_city":
+ city_population_gain = kwargs.get("city_population_gain", 0)
+ return 50 + (5 * city_population_gain)
+
+ if action == "place_structure":
+ structure = kwargs.get("structure", None)
+ if structure == "monument":
+ return 400
+
+ if action == "capture_city":
+ return 100
+
+ if action == "lose_city":
+ city_population_loss = kwargs.get("city_population_loss", 0)
+ return -(50 + (5 * city_population_loss))
+
+ if action == "gain_territory":
+ return 20
+
+ if action == "lose_territory":
+ return -20
+
+ if action == "research_tech":
+ tech_tier = kwargs.get("tech_tier", 1)
+ return 100 * tech_tier
+
+ if action == "discover_fog":
+ return 5
+
+ if action == "park":
+ return 250
+
+ return 0 # Default reward for neutral actions
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()
diff --git a/requirements.txt b/requirements.txt
index 405ec23..08c39ff 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,7 @@
-numpy==1.19.5
-Pillow==8.2.0
+numpy~=2.2.1
+Pillow~=11.1.0
+
+pip~=24.3.1
+Flask~=3.1.0
+
+jinja2
\ No newline at end of file
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..a012ea7
--- /dev/null
+++ b/run.py
@@ -0,0 +1,135 @@
+# Windows HP templates route: C:\Users\HP\PycharmProjects\polytopia_python\consoleApp\templates
+# Windows truncated templates route: C:\PycharmProjects\polytopia_python\consoleApp\templates
+# relative templates route: polytopia_python\consoleApp\templates
+from flask import Flask, render_template
+
+# Create a Flask consoleApp
+consoleApp = 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
+@consoleApp.route("/")
+def home():
+ 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";
+}
+
+
+
The Battle of Polytopia
+
+
+
+
+
+
+ """
+ )
+
+@consoleApp.route("/game")
+def game():
+ return ""
+# Route for the Privacy page
+# TODO Fix error Template file 'polytopia_python' not found
+@consoleApp.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
+@consoleApp.route("/dashboard")
+def dashboard():
+ return render_template("dashboard.html")
+
+# Route for the About page
+@consoleApp.route("/about")
+def about():
+ return render_template("about.html")
+
+# Route for the Settings page
+@consoleApp.route("/settings")
+def settings():
+ return render_template("settings.html")
+
+# Route for the Hall of Fame page
+@consoleApp.route("/hall-of-fame")
+def hall_of_fame():
+ return render_template("hall_of_fame.html")
+
+# Route for the Throne Room page
+@consoleApp.route("/throne-room")
+def throne_room():
+ return render_template("throne_room.html")
+
+if __name__ == "__main__":
+ consoleApp.run(debug=True, host="127.0.0.1", port=5000)
diff --git a/settings.py b/settings.py
deleted file mode 100644
index 9ff706e..0000000
--- a/settings.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import pathlib
-
-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