Real rename

This commit is contained in:
joachimschmidt557 2019-02-12 19:22:33 +01:00
parent aa3dd87076
commit cafb36cb26
68 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,72 @@
package game;
import java.awt.Color;
import java.util.Random;
public abstract class AI extends Player {
private AIThread aiThread;
private Random random;
protected boolean fastForward;
public AI(String name, Color color) {
super(name, color);
this.random = new Random();
}
protected Random getRandom() {
return this.random;
}
protected abstract void actions(Game game) throws InterruptedException;
public void doNextTurn(Game game) {
if(aiThread != null)
return;
fastForward = false;
aiThread = new AIThread(game);
aiThread.start();
}
public void fastForward() {
if(aiThread != null)
fastForward = true;
}
protected void sleep(int ms) throws InterruptedException {
long end = System.currentTimeMillis() + ms;
while(System.currentTimeMillis() < end && !fastForward) {
Thread.sleep(10);
}
}
private class AIThread extends Thread {
private Game game;
private AIThread(Game game) {
this.game = game;
}
private void finishTurn() {
aiThread = null;
fastForward = false;
// Trigger next round, if not automatically
if(game.getRound() > 1 && game.getCurrentPlayer() == AI.this)
game.nextTurn();
}
@Override
public void run() {
try {
actions(game);
} catch (InterruptedException e) {
e.printStackTrace();
}
finishTurn();
}
}
}

View file

@ -0,0 +1,293 @@
package game;
import java.util.*;
import game.map.Castle;
import game.map.Kingdom;
import game.map.GameMap;
import game.map.MapSize;
import gui.AttackThread;
import gui.Resources;
public class Game {
private Goal goal;
private List<Player> players;
private boolean isOver;
private boolean hasStarted;
private int round;
private MapSize mapSize;
private GameMap gameMap;
private Queue<Player> playerQueue;
private Player startingPlayer;
private Player currentPlayer;
private GameInterface gameInterface;
private AttackThread attackThread;
public Game() {
this.isOver = false;
this.hasStarted = false;
this.mapSize = MapSize.MEDIUM;
this.players = new LinkedList<>();
}
public void addPlayer(Player p) {
if(players.contains(p))
throw new IllegalArgumentException("Spieler wurde bereits hinzugefügt");
this.players.add(p);
}
public void setGoal(Goal goal) {
this.goal = goal;
this.goal.setGame(this);
}
public int getRound() {
return round;
}
public void setMapSize(MapSize mapSize) {
this.mapSize = mapSize;
}
private void generateMap() {
int mapSizeMultiplier = this.mapSize.ordinal() + 1;
int playerCount = players.size();
int numRegions = playerCount * GameConstants.CASTLES_NUMBER_MULTIPLIER * mapSizeMultiplier;
double tileMultiplier = 1.0 + (mapSizeMultiplier * 0.3);
// We set up space for 2 times the region count
int numTiles = (int) Math.ceil(numRegions * tileMultiplier);
// Our map should be 3:2
int width = (int) Math.ceil(0.6 * numTiles);
int height = (int) Math.ceil(0.4 * numTiles);
int continents = Math.min(3, playerCount + this.mapSize.ordinal());
this.gameMap = GameMap.generateRandomMap(width, height, 40, numRegions, continents);
}
public void start(GameInterface gameInterface) {
if(hasStarted)
throw new IllegalArgumentException("Spiel wurde bereits gestartet");
if(players.size() < 2)
throw new IllegalArgumentException("Nicht genug Spieler");
if(goal == null)
throw new IllegalArgumentException("Kein Spielziel gesetzt");
this.generateMap();
// Create random player order
this.gameInterface = gameInterface;
List<Player> tempList = new ArrayList<>(players);
playerQueue = new ArrayDeque<>();
while(!tempList.isEmpty()) {
Player player = tempList.remove((int) (Math.random() * tempList.size()));
player.reset();
playerQueue.add(player);
}
startingPlayer = playerQueue.peek();
hasStarted = true;
isOver = false;
round = 0;
gameInterface.onGameStarted(this);
nextTurn();
}
public AttackThread startAttack(Castle source, Castle target, int troopCount) {
if(attackThread != null)
return attackThread;
if(source.getOwner() == target.getOwner() || troopCount < 1)
return null;
attackThread = new AttackThread(this, source, target, troopCount);
attackThread.start();
gameInterface.onAttackStarted(source, target, troopCount);
return attackThread;
}
public void doAttack(Castle attackerCastle, Castle defenderCastle, int[] rollAttacker, int[] rollDefender) {
Integer[] rollAttackerSorted = Arrays.stream(rollAttacker).boxed().sorted(Comparator.reverseOrder()).toArray(Integer[]::new);
Integer[] rollDefenderSorted = Arrays.stream(rollDefender).boxed().sorted(Comparator.reverseOrder()).toArray(Integer[]::new);
Player attacker = attackerCastle.getOwner();
Player defender = defenderCastle.getOwner();
for(int i = 0; i < Math.min(rollAttacker.length, rollDefender.length); i++) {
if(rollAttackerSorted[i] > rollDefenderSorted[i]) {
defenderCastle.removeTroops(1);
if(defenderCastle.getTroopCount() == 0) {
attackerCastle.removeTroops(1);
defenderCastle.setOwner(attacker);
defenderCastle.addTroops(1);
gameInterface.onConquer(defenderCastle, attacker);
addScore(attacker, 50);
break;
} else {
addScore(attacker, 20);
}
} else {
attackerCastle.removeTroops(1);
addScore(defender, 30);
}
}
gameInterface.onUpdate();
}
public void moveTroops(Castle source, Castle destination, int troopCount) {
if(troopCount >= source.getTroopCount() || source.getOwner() != destination.getOwner())
return;
source.moveTroops(destination, troopCount);
gameInterface.onUpdate();
}
public void stopAttack() {
this.attackThread = null;
this.gameInterface.onAttackStopped();
}
public int[] roll(Player player, int dices, boolean fastForward) {
return gameInterface.onRoll(player, dices, fastForward);
}
private boolean allCastlesChosen() {
return gameMap.getCastles().stream().noneMatch(c -> c.getOwner() == null);
}
public AttackThread getAttackThread() {
return this.attackThread;
}
public void chooseCastle(Castle castle, Player player) {
if(castle.getOwner() != null || player.getRemainingTroops() == 0)
return;
gameInterface.onCastleChosen(castle, player);
player.removeTroops(1);
castle.setOwner(currentPlayer);
castle.addTroops(1);
addScore(player, 5);
if(player.getRemainingTroops() == 0 || allCastlesChosen()) {
player.removeTroops(player.getRemainingTroops());
nextTurn();
}
}
public void addTroops(Player player, Castle castle, int count) {
if(count < 1 || castle.getOwner() != player)
return;
count = Math.min(count, player.getRemainingTroops());
castle.addTroops(count);
player.removeTroops(count);
}
public void addScore(Player player, int score) {
player.addPoints(score);
gameInterface.onAddScore(player, score);
}
public void endGame() {
isOver = true;
Player winner = goal.getWinner();
if(winner != null)
addScore(goal.getWinner(), 150);
Resources resources = Resources.getInstance();
for(Player player : players) {
resources.addScoreEntry(new ScoreEntry(player, goal));
}
gameInterface.onGameOver(winner);
}
public void nextTurn() {
if(goal.isCompleted()) {
endGame();
return;
}
// Choose next player
Player nextPlayer;
do {
nextPlayer = playerQueue.remove();
// if player has already lost, remove him from queue
if(goal.hasLost(nextPlayer)) {
if(startingPlayer == nextPlayer) {
startingPlayer = playerQueue.peek();
}
nextPlayer = null;
}
} while(nextPlayer == null && !playerQueue.isEmpty());
if(nextPlayer == null) {
isOver = true;
gameInterface.onGameOver(goal.getWinner());
return;
}
currentPlayer = nextPlayer;
if(round == 0 || (round == 1 && allCastlesChosen()) || (round > 1 && currentPlayer == startingPlayer)) {
round++;
gameInterface.onNewRound(round);
}
int numRegions = currentPlayer.getNumRegions(this);
int addTroops;
if(round == 1)
addTroops = GameConstants.CASTLES_AT_BEGINNING;
else {
addTroops = Math.max(3, numRegions / GameConstants.TROOPS_PER_ROUND_DIVISOR);
addScore(currentPlayer, addTroops * 5);
for(Kingdom kingdom : gameMap.getKingdoms()) {
if(kingdom.getOwner() == currentPlayer) {
addScore(currentPlayer, 10);
addTroops++;
}
}
}
currentPlayer.addTroops(addTroops);
boolean isAI = (currentPlayer instanceof AI);
gameInterface.onNextTurn(currentPlayer, addTroops, !isAI);
if(isAI) {
((AI)currentPlayer).doNextTurn(this);
}
playerQueue.add(currentPlayer);
}
public Player getCurrentPlayer() {
return this.currentPlayer;
}
public List<Player> getPlayers() {
return this.players;
}
public GameMap getMap() {
return this.gameMap;
}
public boolean isOver() {
return this.isOver;
}
}

View file

@ -0,0 +1,41 @@
package game;
import game.goals.*;
import game.players.*;
import java.awt.*;
public class GameConstants {
public static final int MAX_PLAYERS = 4;
// Determines how many regions are generated per player,
// e.g. PlayerCount * 7 for Small, PlayerCount * 14 for Medium and PlayerCount * 21 for Large Maps
public static final int CASTLES_NUMBER_MULTIPLIER = 7;
public static final int CASTLES_AT_BEGINNING = 3;
public static final int TROOPS_PER_ROUND_DIVISOR = 3;
public static final Color COLOR_WATER = Color.BLUE;
public static final Color COLOR_SAND = new Color(210, 170, 109);
public static final Color COLOR_GRASS = new Color(50, 89, 40);
public static final Color COLOR_STONE = Color.GRAY;
public static final Color COLOR_SNOW = Color.WHITE;
public static final Color PLAYER_COLORS[] = {
Color.CYAN,
Color.RED,
Color.GREEN,
Color.ORANGE
};
public static final Goal GAME_GOALS[] = {
new ConquerGoal(),
// TODO: Add more Goals
};
public static final Class<?> PLAYER_TYPES[] = {
Human.class,
BasicAI.class,
// TODO: Add more Player types, like different AIs
};
}

View file

@ -0,0 +1,18 @@
package game;
import game.map.Castle;
public interface GameInterface {
void onAttackStopped();
void onAttackStarted(Castle source, Castle target, int troopCount);
void onCastleChosen(Castle castle, Player player);
void onNextTurn(Player currentPlayer, int troopsGot, boolean human);
void onNewRound(int round);
void onGameOver(Player winner);
void onGameStarted(Game game);
void onConquer(Castle castle, Player player);
void onUpdate();
void onAddScore(Player player, int score);
int[] onRoll(Player player, int dices, boolean fastForward);
}

View file

@ -0,0 +1,33 @@
package game;
public abstract class Goal {
private Game game;
private final String description;
private final String name;
public Goal(String name, String description) {
this.name = name;
this.description = description;
}
public void setGame(Game game) {
this.game = game;
}
public abstract boolean isCompleted();
public abstract Player getWinner();
public abstract boolean hasLost(Player player);
public final String getDescription() {
return this.description;
}
public final String getName() {
return this.name;
}
protected Game getGame() {
return this.game;
}
}

View file

@ -0,0 +1,88 @@
package game;
import game.map.Castle;
import java.awt.Color;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.stream.Collectors;
public abstract class Player {
private final String name;
private Color color;
private int points;
private int remainingTroops;
protected Player(String name, Color color) {
this.name = name;
this.points = 0;
this.color = color;
this.remainingTroops = 0;
}
public int getRemainingTroops() {
return this.remainingTroops;
}
public static Player createPlayer(Class<?> playerType, String name, Color color) {
if(!Player.class.isAssignableFrom(playerType))
throw new IllegalArgumentException("Not a player class");
try {
Constructor<?> constructor = playerType.getConstructor(String.class, Color.class);
return (Player) constructor.newInstance(name, color);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
public void setColor(Color c) {
this.color = c;
}
public Color getColor() {
return this.color;
}
public String getName() {
return this.name;
}
public int getPoints() {
return points;
}
public void addPoints(int points) {
this.points += points;
}
public void addTroops(int troops) {
if(troops < 0)
return;
this.remainingTroops += troops;
}
public void removeTroops(int troops) {
if(this.remainingTroops - troops < 0 || troops < 0)
return;
this.remainingTroops -= troops;
}
public int getNumRegions(Game game) {
return this.getCastles(game).size();
}
public List<Castle> getCastles(Game game) {
return game.getMap().getCastles().stream().filter(c -> c.getOwner() == this).collect(Collectors.toList());
}
public void reset() {
this.remainingTroops = 0;
this.points = 0;
}
}

View file

@ -0,0 +1,95 @@
package game;
import java.io.PrintWriter;
import java.util.Date;
/**
* Diese Klasse stellt einen Eintrag in der Bestenliste dar.
* Sie enthält den Namen des Spielers, das Datum, die erreichte Punktzahl sowie den Spieltypen.
*/
public class ScoreEntry implements Comparable<ScoreEntry> {
private String name;
private Date date;
private int score;
private String gameType;
/**
* Erzeugt ein neues ScoreEntry-Objekt
* @param name der Name des Spielers
* @param score die erreichte Punktzahl
* @param date das Datum
* @param gameGoal der Spieltyp
*/
private ScoreEntry(String name, int score, Date date, String gameGoal) {
this.name = name;
this.score = score;
this.date = date;
this.gameType = gameGoal;
}
/**
* Erzeugt ein neues ScoreEntry-Objekt
* @param player der Spieler
* @param gameGoal der Spieltyp
*/
public ScoreEntry(Player player, Goal gameGoal) {
this.name = player.getName();
this.score = player.getPoints();
this.date = new Date();
this.gameType = gameGoal.getName();
}
@Override
public int compareTo(ScoreEntry scoreEntry) {
return Integer.compare(this.score, scoreEntry.score);
}
/**
* Schreibt den Eintrag als neue Zeile mit dem gegebenen {@link PrintWriter}
* Der Eintrag sollte im richtigen Format gespeichert werden.
* @see #read(String)
* @see Date#getTime()
* @param printWriter der PrintWriter, mit dem der Eintrag geschrieben wird
*/
public void write(PrintWriter printWriter) {
// TODO: ScoreEntry#write(PrintWriter)
}
/**
* List eine gegebene Zeile ein und wandelt dies in ein ScoreEntry-Objekt um.
* Ist das Format der Zeile ungültig oder enthält es ungültige Daten, wird null zurückgegeben.
* Eine gültige Zeile enthält in der Reihenfolge durch Semikolon getrennt:
* den Namen, das Datum als Unix-Timestamp (in Millisekunden), die erreichte Punktzahl, den Spieltypen
* Gültig wäre beispielsweise: "Florian;1546947397000;100;Eroberung"
*
*
* @see String#split(String)
* @see Long#parseLong(String)
* @see Integer#parseInt(String)
* @see Date#Date(long)
*
* @param line Die zu lesende Zeile
* @return Ein ScoreEntry-Objekt oder null
*/
public static ScoreEntry read(String line) {
// TODO: ScoreEntry#read(String)
return null;
}
public Date getDate() {
return date;
}
public String getName() {
return this.name;
}
public int getScore() {
return this.score;
}
public String getMode() {
return this.gameType;
}
}

View file

@ -0,0 +1,45 @@
package game.goals;
import game.Game;
import game.Goal;
import game.Player;
import game.map.Castle;
public class ConquerGoal extends Goal {
public ConquerGoal() {
super("Eroberung", "Derjenige Spieler gewinnt, der als erstes alle Gebiete erobert hat.");
}
@Override
public boolean isCompleted() {
return this.getWinner() != null;
}
@Override
public Player getWinner() {
Game game = this.getGame();
if(game.getRound() < 2)
return null;
Player p = null;
for(Castle c : game.getMap().getCastles()) {
if(c.getOwner() == null)
return null;
else if(p == null)
p = c.getOwner();
else if(p != c.getOwner())
return null;
}
return p;
}
@Override
public boolean hasLost(Player player) {
if(getGame().getRound() < 2)
return false;
return player.getNumRegions(getGame()) == 0;
}
}

View file

@ -0,0 +1,128 @@
package game.map;
import game.Player;
import java.awt.*;
/**
* Diese Klasse representiert eine Burg.
* Jede Burg hat Koordinaten auf der Karte und einen Namen.
* Falls die Burg einen Besitzer hat, hat sie auch eine Anzahl von zugewiesenen Truppen.
* Die Burg kann weiterhin Teil eines Königreichs sein.
*/
public class Castle {
private int troopCount;
private Player owner;
private Kingdom kingdom;
private Point location;
private String name;
/**
* Eine neue Burg erstellen
* @param location die Koordinaten der Burg
* @param name der Name der Burg
*/
public Castle(Point location, String name) {
this.location = location;
this.troopCount = 0;
this.owner = null;
this.kingdom = null;
this.name = name;
}
public Player getOwner() {
return this.owner;
}
public Kingdom getKingdom() {
return this.kingdom;
}
public int getTroopCount() {
return this.troopCount;
}
/**
* Truppen von dieser Burg zur angegebenen Burg bewegen.
* Dies funktioniert nur, wenn die Besitzer übereinstimmen und bei der aktuellen Burg mindestens eine Truppe übrig bleibt
* @param target
* @param troops
*/
public void moveTroops(Castle target, int troops) {
// Troops can only be moved to own regions
if(target.owner != this.owner)
return;
// At least one unit must remain in the source region
if(this.troopCount - troops < 1)
return;
this.troopCount -= troops;
target.troopCount += troops;
}
public Point getLocationOnMap() {
return this.location;
}
/**
* Berechnet die eukldische Distanz zu dem angegebenen Punkt
* @param dest die Zielkoordinate
* @return die euklidische Distanz
*/
public double distance(Point dest) {
return Math.sqrt(Math.pow(this.location.x - dest.x, 2) + Math.pow(this.location.y - dest.y, 2));
}
/**
* Berechnet die eukldische Distanz zu der angegebenen Burg
* @param next die Zielburg
* @return die euklidische Distanz
* @see #distance(Point)
*/
public double distance(Castle next) {
Point otherLocation = next.getLocationOnMap();
return this.distance(otherLocation);
}
public void setOwner(Player player) {
this.owner = player;
}
public void addTroops(int i) {
if(i <= 0)
return;
this.troopCount += i;
}
public String getName() {
return this.name;
}
public void removeTroops(int i) {
this.troopCount = Math.max(0, this.troopCount - i);
if(this.troopCount == 0)
this.owner = null;
}
/**
* Gibt den Burg-Typen zurück. Falls die Burg einem Königreich angehört, wird der Typ des Königreichs zurückgegeben, ansonsten 0
* @return der Burg-Typ für die Anzeige
*/
public int getType() {
return this.kingdom == null ? 0 : this.kingdom.getType();
}
/**
* Die Burg einem Königreich zuordnen
* @param kingdom Ein Königreich oder null
*/
public void setKingdom(Kingdom kingdom) {
this.kingdom = kingdom;
if(kingdom != null)
kingdom.addCastle(this);
}
}

View file

@ -0,0 +1,40 @@
package game.map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
/**
* Diese Klasse teilt Burgen in Königreiche auf
*/
public class Clustering {
private Random random;
private final List<Castle> allCastles;
private final int kingdomCount;
/**
* Ein neues Clustering-Objekt erzeugen.
* @param castles Die Liste von Burgen, die aufgeteilt werden sollen
* @param kingdomCount Die Anzahl von Königreichen die generiert werden sollen
*/
public Clustering(List<Castle> castles, int kingdomCount) {
if (kingdomCount < 2)
throw new IllegalArgumentException("Ungültige Anzahl an Königreichen");
this.random = new Random();
this.kingdomCount = kingdomCount;
this.allCastles = Collections.unmodifiableList(castles);
}
/**
* Gibt eine Liste von Königreichen zurück.
* Jedes Königreich sollte dabei einen Index im Bereich 0-5 bekommen, damit die Burg richtig angezeigt werden kann.
* Siehe auch {@link Kingdom#getType()}
*/
public List<Kingdom> getPointsClusters() {
// TODO Clustering#getPointsClusters()
return new ArrayList<>();
}
}

View file

@ -0,0 +1,256 @@
package game.map;
import base.*;
import game.GameConstants;
import gui.Resources;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
/**
* Diese Klasse representiert das Spielfeld. Sie beinhaltet das Hintergrundbild, welches mit Perlin noise erzeugt wurde,
* eine Liste mit Königreichen und alle Burgen und deren Verbindungen als Graphen.
*
* Die Karte wird in mehreren Schritten generiert, siehe dazu {@link #generateRandomMap(int, int, int, int, int)}
*/
public class GameMap {
private BufferedImage backgroundImage;
private Graph<Castle> castleGraph;
private List<Kingdom> kingdoms;
// Map Generation
private double[][] noiseValues;
private int width, height, scale;
/**
* Erzeugt eine neue leere Karte. Der Konstruktor sollte niemals direkt aufgerufen werden.
* Um eine neue Karte zu erstellen, muss {@link #generateRandomMap(int, int, int, int, int)} verwendet werden
* @param width die Breite der Karte
* @param height die Höhe der Karte
* @param scale der Skalierungsfaktor
*/
private GameMap(int width, int height, int scale) {
this.castleGraph = new Graph<>();
this.width = width;
this.height = height;
this.scale = scale;
}
/**
* Wandelt einen Noise-Wert in eine Farbe um. Die Methode kann nach belieben angepasst werden
* @param value der Perlin-Noise-Wert
* @return die resultierende Farbe
*/
private Color doubleToColor(double value) {
if (value <= 0.40)
return GameConstants.COLOR_WATER;
else if (value <= 0.5)
return GameConstants.COLOR_SAND;
else if (value <= 0.7)
return GameConstants.COLOR_GRASS;
else if (value <= 0.8)
return GameConstants.COLOR_STONE;
else
return GameConstants.COLOR_SNOW;
}
/**
* Hier wird das Hintergrund-Bild mittels Perlin-Noise erzeugt.
* Siehe auch: {@link PerlinNoise}
*/
private void generateBackground() {
PerlinNoise perlinNoise = new PerlinNoise(width, height, scale);
Dimension realSize = perlinNoise.getRealSize();
noiseValues = new double[realSize.width][realSize.height];
backgroundImage = new BufferedImage(realSize.width, realSize.height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < realSize.width; x++) {
for (int y = 0; y < realSize.height; y++) {
double noiseValue = perlinNoise.getNoise(x, y);
noiseValues[x][y] = noiseValue;
backgroundImage.setRGB(x, y, doubleToColor(noiseValue).getRGB());
}
}
}
/**
* Hier werden die Burgen erzeugt.
* Dabei wir die Karte in Felder unterteilt, sodass auf jedes Fals maximal eine Burg kommt.
* Sollte auf einem Feld keine Position für eine Burg existieren (z.B. aufgrund von Wasser oder angrenzenden Burgen), wird dieses übersprungen.
* Dadurch kann es vorkommen, dass nicht alle Burgen generiert werden
* @param castleCount die maximale Anzahl der zu generierenden Burgen
*/
private void generateCastles(int castleCount) {
double square = Math.ceil(Math.sqrt(castleCount));
double length = width + height;
int tilesX = (int) Math.max(1, (width / length + 0.5) * square) + 5;
int tilesY = (int) Math.max(1, (height / length + 0.5) * square) + 5;
int tileW = (width * scale / tilesX);
int tileH = (height * scale / tilesY);
if (tilesX * tilesY < castleCount) {
throw new IllegalArgumentException(String.format("CALCULATION Error: tilesX=%d * tilesY=%d < castles=%d", tilesX, tilesY, castleCount));
}
// Add possible tiles
List<Point> possibleFields = new ArrayList<>(tilesX * tilesY);
for (int x = 0; x < tilesX - 1; x++) {
for (int y = 0; y < tilesY - 1; y++) {
possibleFields.add(new Point(x, y));
}
}
// Generate castles
List<String> possibleNames = generateCastleNames();
int castlesGenerated = 0;
while (possibleFields.size() > 0 && castlesGenerated < castleCount) {
Point randomField = possibleFields.remove((int) (Math.random() * possibleFields.size()));
int x0 = (int) ((randomField.x + 0.5) * tileW);
int y0 = (int) ((randomField.y + 0.5) * tileH);
for (int x = (int) (0.5 * tileW); x >= 0; x--) {
boolean positionFound = false;
for (int y = (int) (0.5 * tileH); y >= 0; y--) {
int x_mid = (int) (x0 + x + 0.5 * tileW);
int y_mid = (int) (y0 + y + 0.5 * tileH);
if (noiseValues[x_mid][y_mid] >= 0.6) {
String name = possibleNames.isEmpty() ? "Burg " + (castlesGenerated + 1) :
possibleNames.get((int) (Math.random() * possibleNames.size()));
Castle newCastle = new Castle(new Point(x0 + x, y0 + y), name);
boolean doesIntersect = false;
for (Castle r : castleGraph.getAllValues()) {
if (r.distance(newCastle) < Math.max(tileW, tileH)) {
doesIntersect = true;
break;
}
}
if (!doesIntersect) {
possibleNames.remove(name);
castleGraph.addNode(newCastle);
castlesGenerated++;
positionFound = true;
break;
}
}
}
if (positionFound)
break;
}
}
}
/**
* Hier werden die Kanten erzeugt. Dazu werden zunächst alle Burgen durch eine Linie verbunden und anschließend
* jede Burg mit allen anderen in einem bestimmten Radius nochmals verbunden
*/
private void generateEdges() {
// TODO: GameMap#generateEdges()
}
/**
* Hier werden die Burgen in Königreiche unterteilt. Dazu wird der {@link Clustering} Algorithmus aufgerufen.
* @param kingdomCount die Anzahl der zu generierenden Königreiche
*/
private void generateKingdoms(int kingdomCount) {
if(kingdomCount > 0 && kingdomCount < castleGraph.getAllValues().size()) {
Clustering clustering = new Clustering(castleGraph.getAllValues(), kingdomCount);
kingdoms = clustering.getPointsClusters();
} else {
kingdoms = new ArrayList<>();
}
}
/**
* Eine neue Spielfeldkarte generieren.
* Dazu werden folgende Schritte abgearbeitet:
* 1. Das Hintergrundbild generieren
* 2. Burgen generieren
* 3. Kanten hinzufügen
* 4. Burgen in Köngireiche unterteilen
* @param width die Breite des Spielfelds
* @param height die Höhe des Spielfelds
* @param scale die Skalierung
* @param castleCount die maximale Anzahl an Burgen
* @param kingdomCount die Anzahl der Königreiche
* @return eine neue GameMap-Instanz
*/
public static GameMap generateRandomMap(int width, int height, int scale, int castleCount, int kingdomCount) {
width = Math.max(width, 15);
height = Math.max(height, 10);
if (scale <= 0 || castleCount <= 0)
throw new IllegalArgumentException();
System.out.println(String.format("Generating new map, castles=%d, width=%d, height=%d, kingdoms=%d", castleCount, width, height, kingdomCount));
GameMap gameMap = new GameMap(width, height, scale);
gameMap.generateBackground();
gameMap.generateCastles(castleCount);
gameMap.generateEdges();
gameMap.generateKingdoms(kingdomCount);
if(!gameMap.getGraph().allNodesConnected()) {
System.out.println("Fehler bei der Verifikation: Es sind nicht alle Knoten miteinander verbunden!");
return null;
}
return gameMap;
}
/**
* Generiert eine Liste von Zufallsnamen für Burgen. Dabei wird ein Prefix (Schloss, Burg oder Festung) an einen
* vorhandenen Namen aus den Resourcen angefügt. Siehe auch: {@link Resources#getcastleNames()}
* @return eine Liste mit Zufallsnamen
*/
private List<String> generateCastleNames() {
String[] prefixes = {"Schloss", "Burg", "Festung"};
List<String> names = Resources.getInstance().getCastleNames();
List<String> nameList = new ArrayList<>(names.size());
for (String name : names) {
String prefix = prefixes[(int) (Math.random() * prefixes.length)];
nameList.add(prefix + " " + name);
}
return nameList;
}
public int getWidth() {
return this.backgroundImage.getWidth();
}
public int getHeight() {
return this.backgroundImage.getHeight();
}
public BufferedImage getBackgroundImage() {
return this.backgroundImage;
}
public Dimension getSize() {
return new Dimension(this.getWidth(), this.getHeight());
}
public List<Castle> getCastles() {
return castleGraph.getAllValues();
}
public Graph<Castle> getGraph() {
return this.castleGraph;
}
public List<Edge<Castle>> getEdges() {
return this.castleGraph.getEdges();
}
public List<Kingdom> getKingdoms() {
return this.kingdoms;
}
}

View file

@ -0,0 +1,75 @@
package game.map;
import game.Player;
import java.util.LinkedList;
import java.util.List;
/**
* Diese Klasse representiert ein Königreich. Jedes Königreich hat eine Liste von Burgen sowie einen Index {@link #type} im Bereich von 0-5
*
*/
public class Kingdom {
private List<Castle> castles;
private int type;
/**
* Erstellt ein neues Königreich
* @param type der Typ des Königreichs (im Bereich 0-5)
*/
public Kingdom(int type) {
this.castles = new LinkedList<>();
this.type = type;
}
/**
* Eine Burg zum Königreich hinzufügen
* @param castle die Burg, die hinzugefügt werden soll
*/
public void addCastle(Castle castle) {
this.castles.add(castle);
}
/**
* Gibt den Typen des Königreichs zurück. Dies wird zur korrekten Anzeige benötigt
* @return der Typ des Königreichs.
*/
public int getType() {
return this.type;
}
/**
* Eine Burg aus dem Königreich entfernen
* @param castle die zu entfernende Burg
*/
public void removeCastle(Castle castle) {
this.castles.remove(castle);
}
/**
* Gibt den Spieler zurück, der alle Burgen in dem Köngreich besitzt.
* Sollte es keinen Spieler geben, der alle Burgen besitzt, wird null zurückgegeben.
* @return der Besitzer oder null
*/
public Player getOwner() {
if(castles.isEmpty())
return null;
Player owner = castles.get(0).getOwner();
for(Castle castle : castles) {
if(castle.getOwner() != owner)
return null;
}
return owner;
}
/**
* Gibt alle Burgen zurück, die in diesem Königreich liegen
* @return Liste von Burgen im Königreich
*/
public List<Castle> getCastles() {
return this.castles;
}
}

View file

@ -0,0 +1,27 @@
package game.map;
import java.util.Arrays;
import java.util.Vector;
import java.util.stream.Collectors;
public enum MapSize {
SMALL("Klein"),
MEDIUM("Mittel"),
LARGE("Groß");
private String label;
MapSize(String lbl) {
this.label = lbl;
}
@Override
public String toString() {
return this.label;
}
public static Vector<String> getMapSizes() {
return Arrays.stream(values()).map(MapSize::toString).collect(Collectors.toCollection(Vector::new));
}
}

View file

@ -0,0 +1,58 @@
package game.map;
import base.GraphAlgorithm;
import base.Edge;
import base.Graph;
import game.Player;
import game.map.Castle;
import gui.components.MapPanel;
import java.util.List;
public class PathFinding extends GraphAlgorithm<Castle> {
private MapPanel.Action action;
private Player currentPlayer;
public PathFinding(Graph<Castle> graph, Castle sourceCastle, MapPanel.Action action, Player currentPlayer) {
super(graph, graph.getNode(sourceCastle));
this.action = action;
this.currentPlayer = currentPlayer;
}
@Override
protected double getValue(Edge<Castle> edge) {
Castle castleA = edge.getNodeA().getValue();
Castle castleB = edge.getNodeB().getValue();
return castleA.distance(castleB);
}
@Override
protected boolean isPassable(Edge<Castle> edge) {
Castle castleA = edge.getNodeA().getValue();
Castle castleB = edge.getNodeB().getValue();
// One of the regions should belong to the current player
if(castleA.getOwner() != currentPlayer && castleB.getOwner() != currentPlayer)
return false;
if(action == MapPanel.Action.ATTACKING) {
return castleA.getOwner() != null && castleB.getOwner() != null;
} else if(action == MapPanel.Action.MOVING) {
// One of the regions may be empty
if(castleA.getOwner() == null || castleB.getOwner() == null)
return true;
// Else both regions should belong to the current player
return castleA.getOwner() == castleB.getOwner() && castleA.getOwner() == currentPlayer;
} else {
return false;
}
}
public List<Edge<Castle>> getPath(Castle targetCastle) {
return this.getPath(getGraph().getNode(targetCastle));
}
}

View file

@ -0,0 +1,103 @@
package game.players;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import base.Edge;
import base.Graph;
import base.Node;
import game.AI;
import game.Game;
import game.map.Castle;
import gui.AttackThread;
public class BasicAI extends AI {
public BasicAI(String name, Color color) {
super(name, color);
}
private Castle getCastleWithFewestTroops(List<Castle> castles) {
Castle fewestTroops = castles.get(0);
for(Castle castle : castles) {
if(castle.getTroopCount() < fewestTroops.getTroopCount()) {
fewestTroops = castle;
}
}
return fewestTroops;
}
@Override
protected void actions(Game game) throws InterruptedException {
if(game.getRound() == 1) {
List<Castle> availableCastles = game.getMap().getCastles().stream().filter(c -> c.getOwner() == null).collect(Collectors.toList());
while(availableCastles.size() > 0 && getRemainingTroops() > 0) {
sleep(1000);
Castle randomCastle = availableCastles.remove(this.getRandom().nextInt(availableCastles.size()));
game.chooseCastle(randomCastle, this);
}
} else {
// 1. Distribute remaining troops
Graph<Castle> graph = game.getMap().getGraph();
List<Castle> castleNearEnemy = new ArrayList<>();
for(Castle castle : this.getCastles(game)) {
Node<Castle> node = graph.getNode(castle);
for(Edge<Castle> edge : graph.getEdges(node)) {
Castle otherCastle = edge.getOtherNode(node).getValue();
if(otherCastle.getOwner() != this) {
castleNearEnemy.add(castle);
break;
}
}
}
while(this.getRemainingTroops() > 0) {
Castle fewestTroops = getCastleWithFewestTroops(castleNearEnemy);
sleep(500);
game.addTroops(this, fewestTroops, 1);
}
boolean attackWon;
do {
// 2. Move troops from inside to border
for (Castle castle : this.getCastles(game)) {
if (!castleNearEnemy.contains(castle) && castle.getTroopCount() > 1) {
Castle fewestTroops = getCastleWithFewestTroops(castleNearEnemy);
game.moveTroops(castle, fewestTroops, castle.getTroopCount() - 1);
}
}
// 3. attack!
attackWon = false;
for (Castle castle : castleNearEnemy) {
if(castle.getTroopCount() < 2)
continue;
Node<Castle> node = graph.getNode(castle);
for (Edge<Castle> edge : graph.getEdges(node)) {
Castle otherCastle = edge.getOtherNode(node).getValue();
if (otherCastle.getOwner() != this && castle.getTroopCount() >= otherCastle.getTroopCount()) {
AttackThread attackThread = game.startAttack(castle, otherCastle, castle.getTroopCount());
if(fastForward)
attackThread.fastForward();
attackThread.join();
attackWon = attackThread.getWinner() == this;
break;
}
}
if(attackWon)
break;
}
} while(attackWon);
}
}
}

View file

@ -0,0 +1,11 @@
package game.players;
import java.awt.Color;
import game.Player;
public class Human extends Player {
public Human(String name, Color color) {
super(name, color);
}
}