Hinzufügen der Vorlage
BIN
Projektgruppe_XXX.zip
Normal file
8
Projektgruppe_XXX/.classpath
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="res"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
17
Projektgruppe_XXX/.project
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>Projektgruppe_XXX</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
BIN
Projektgruppe_XXX/res/arrow.png
Normal file
|
After Width: | Height: | Size: 1,006 B |
BIN
Projektgruppe_XXX/res/arrow_deactivated.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
Projektgruppe_XXX/res/castle1.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
Projektgruppe_XXX/res/castle2.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
Projektgruppe_XXX/res/castle3.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
Projektgruppe_XXX/res/castle4.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
Projektgruppe_XXX/res/castle5.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
Projektgruppe_XXX/res/castle6.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
93
Projektgruppe_XXX/res/castles.txt
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
Ehrenberg
|
||||
Hohenzollern
|
||||
Hohenfels
|
||||
Hornberg
|
||||
Hohenstaufen
|
||||
Katzenstein
|
||||
Möckmühl
|
||||
Rotwasserstelz
|
||||
Sponeck
|
||||
Staufenberg
|
||||
Steinsberg
|
||||
Stettenfels
|
||||
Wertheim
|
||||
Wildenstein
|
||||
Hohenasperg
|
||||
Rastatt
|
||||
Beuggen
|
||||
Brackenheim
|
||||
Bronnen
|
||||
Bruchsal
|
||||
Ettlingen
|
||||
Geislingen
|
||||
Gottesaue
|
||||
Haigerloch
|
||||
Hellenstein
|
||||
Hettingen
|
||||
Horneck
|
||||
Kißlegg
|
||||
Kirchberg
|
||||
Lichtenstein
|
||||
Lindach
|
||||
Menzingen
|
||||
Montfort
|
||||
Rechenberg
|
||||
Salem
|
||||
Sigmaringen
|
||||
Spetzgart
|
||||
Winnental
|
||||
Hohentwiel
|
||||
Waldenstein
|
||||
Abenberg
|
||||
Brennberg
|
||||
Dollnstein
|
||||
Donaustauf
|
||||
Falkenberg
|
||||
Falkenstein
|
||||
Feuerstein
|
||||
Guttenberg
|
||||
Hiltpoltstein
|
||||
Hohenberg
|
||||
Hohenfreyberg
|
||||
Hohenstein
|
||||
Kipfenberg
|
||||
Lauenstein
|
||||
Lisberg
|
||||
Lobenstein
|
||||
Marquartstein
|
||||
Pottenstein
|
||||
Randeck
|
||||
Schwaneck
|
||||
Stockenfels
|
||||
Trausnitz
|
||||
Marienberg
|
||||
Rosenberg
|
||||
Birkenfeld
|
||||
Bertholdsheim
|
||||
Dillingen
|
||||
Ebelsbach
|
||||
Egg
|
||||
Elmau
|
||||
Emersacker
|
||||
Frankenberg
|
||||
Gleusdorf
|
||||
Höchstädt
|
||||
Johannisburg
|
||||
Ketschendorf
|
||||
Leutstetten
|
||||
Neidstein
|
||||
Possenhofen
|
||||
Pürkelgut
|
||||
Rathsmannsdorf
|
||||
Regendorf
|
||||
Mespelbrunn
|
||||
Rabenstein
|
||||
Schauenstein
|
||||
Schillingsfürst
|
||||
Schönberg
|
||||
Sommersdorf
|
||||
Thurnau
|
||||
Theuern
|
||||
Tüßling
|
||||
Waal
|
||||
Wörth
|
||||
BIN
Projektgruppe_XXX/res/celtic.ttf
Normal file
BIN
Projektgruppe_XXX/res/check.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
Projektgruppe_XXX/res/dices.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
Projektgruppe_XXX/res/plus.png
Normal file
|
After Width: | Height: | Size: 770 B |
BIN
Projektgruppe_XXX/res/plus_deactivated.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
Projektgruppe_XXX/res/soldier1.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
Projektgruppe_XXX/res/soldier2.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
Projektgruppe_XXX/res/swords.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Projektgruppe_XXX/res/unit.png
Normal file
|
After Width: | Height: | Size: 444 B |
3
Projektgruppe_XXX/src/META-INF/MANIFEST.MF
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: gui.GameWindow
|
||||
|
||||
54
Projektgruppe_XXX/src/base/Edge.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package base;
|
||||
|
||||
/**
|
||||
* Diese Klasse representiert eine generische Kante zwischen zwei Knoten
|
||||
* @param <T> die zugrunde liegende Datenstruktur
|
||||
*/
|
||||
public class Edge<T> {
|
||||
|
||||
private Node<T> nodeA, nodeB;
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Kante zwischen zwei gegebenen Knoten
|
||||
* @param nodeA der erste Knoten
|
||||
* @param nodeB der zweite Knoten
|
||||
*/
|
||||
Edge(Node<T> nodeA, Node<T> nodeB) {
|
||||
this.nodeA = nodeA;
|
||||
this.nodeB = nodeB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt an, ob die Kante mit dem gegebenen Knoten verbunden ist
|
||||
* @param node Der Knoten der überprüft wird
|
||||
* @return true, wenn die Kante mit dem Knoten verbunden ist
|
||||
*/
|
||||
boolean contains(Node<T> node) {
|
||||
return nodeA == node || nodeB == node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den ersten Knoten zurück
|
||||
* @return der erste Knoten
|
||||
*/
|
||||
public Node<T> getNodeA() {
|
||||
return nodeA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den zweiten Knoten zurück
|
||||
* @return der zweite Knoten
|
||||
*/
|
||||
public Node<T> getNodeB() {
|
||||
return nodeB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den jeweils anderen Knoten zurück, abhängig von dem gegebenen
|
||||
* @param source der eine Knoten
|
||||
* @return der andere Knoten
|
||||
*/
|
||||
public Node<T> getOtherNode(Node<T> source) {
|
||||
return (nodeA == source ? nodeB : nodeA);
|
||||
}
|
||||
}
|
||||
127
Projektgruppe_XXX/src/base/Graph.java
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package base;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
|
||||
import game.map.Castle;
|
||||
|
||||
/**
|
||||
* Diese Klasse representiert einen generischen Graphen mit einer Liste aus Knoten und Kanten.
|
||||
*
|
||||
* @param <T> Die zugrundeliegende Datenstruktur, beispielsweise {@link game.map.Castle}
|
||||
*/
|
||||
public class Graph<T> {
|
||||
|
||||
private List<Edge<T>> edges;
|
||||
private List<Node<T>> nodes;
|
||||
|
||||
/**
|
||||
* Konstruktor für einen neuen, leeren Graphen
|
||||
*/
|
||||
public Graph() {
|
||||
this.nodes = new ArrayList<>();
|
||||
this.edges = new LinkedList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Einen neuen Knoten zum Graphen hinzufügen
|
||||
* @param value Der Wert des Knotens
|
||||
* @return Der erstellte Knoten
|
||||
*/
|
||||
public Node<T> addNode(T value) {
|
||||
Node<T> node = new Node<>(value);
|
||||
this.nodes.add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eine neue Kante zwischen zwei Knoten hinzufügen. Sollte die Kante schon existieren, wird die vorhandene Kante zurückgegeben.
|
||||
* @param nodeA Der erste Knoten
|
||||
* @param nodeB Der zweite Knoten
|
||||
* @return Die erstellte oder bereits vorhandene Kante zwischen beiden gegebenen Knoten
|
||||
*/
|
||||
public Edge<T> addEdge(Node<T> nodeA, Node<T> nodeB) {
|
||||
Edge<T> edge = getEdge(nodeA, nodeB);
|
||||
if(edge != null) {
|
||||
return edge;
|
||||
}
|
||||
|
||||
edge = new Edge<>(nodeA, nodeB);
|
||||
this.edges.add(edge);
|
||||
return edge;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Liste aller Knoten zurück
|
||||
* @return die Liste aller Knoten
|
||||
*/
|
||||
public List<Node<T>> getNodes() {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Liste aller Kanten zurück
|
||||
* @return die Liste aller Kanten
|
||||
*/
|
||||
public List<Edge<T>> getEdges() {
|
||||
return this.edges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode gibt alle Werte der Knoten in einer Liste mittels Streams zurück.
|
||||
* @see java.util.stream.Stream#map(Function)
|
||||
* @see java.util.stream.Stream#collect(Collector)
|
||||
* @return Eine Liste aller Knotenwerte
|
||||
*/
|
||||
public List<T> getAllValues() {
|
||||
// TODO: Graph<T>#getAllValues()
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode gibt alle Kanten eines Knotens als Liste mittels Streams zurück.
|
||||
* @param node Der Knoten für die dazugehörigen Kanten
|
||||
* @see java.util.stream.Stream#filter(Predicate)
|
||||
* @see java.util.stream.Stream#collect(Collector)
|
||||
* @return Die Liste aller zum Knoten zugehörigen Kanten
|
||||
*/
|
||||
public List<Edge<T>> getEdges(Node<T> node) {
|
||||
// TODO: Graph<T>#getEdges(Node<T>)
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode sucht eine Kante zwischen beiden angegebenen Knoten und gibt diese zurück
|
||||
* oder null, falls diese Kante nicht existiert
|
||||
* @param nodeA Der erste Knoten
|
||||
* @param nodeB Der zweite Knoten
|
||||
* @return Die Kante zwischen beiden Knoten oder null
|
||||
*/
|
||||
public Edge<T> getEdge(Node<T> nodeA, Node<T> nodeB) {
|
||||
// TODO: Graph<T>#getEdge(Node<T>, Node<T>)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den ersten Knoten mit dem angegebenen Wert zurück oder null, falls dieser nicht gefunden wurde
|
||||
* @param value Der zu suchende Wert
|
||||
* @return Ein Knoten mit dem angegebenen Wert oder null
|
||||
*/
|
||||
public Node<T> getNode(T value) {
|
||||
// TODO: Graph<T>#getNode(T)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft, ob alle Knoten in dem Graphen erreichbar sind.
|
||||
* @return true, wenn alle Knoten erreichbar sind
|
||||
*/
|
||||
public boolean allNodesConnected() {
|
||||
// TODO: Graph<T>#allNodesConnected()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
118
Projektgruppe_XXX/src/base/GraphAlgorithm.java
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package base;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Abstrakte generische Klasse um Wege zwischen Knoten in einem Graph zu finden.
|
||||
* Eine implementierende Klasse ist beispielsweise {@link game.map.PathFinding}
|
||||
* @param <T> Die Datenstruktur des Graphen
|
||||
*/
|
||||
public abstract class GraphAlgorithm<T> {
|
||||
|
||||
/**
|
||||
* Innere Klasse um {@link Node} zu erweitern, aber nicht zu verändern
|
||||
* Sie weist jedem Knoten einen Wert und einen Vorgängerknoten zu.
|
||||
* @param <T>
|
||||
*/
|
||||
private static class AlgorithmNode<T> {
|
||||
|
||||
private Node<T> node;
|
||||
private double value;
|
||||
private AlgorithmNode<T> previous;
|
||||
|
||||
AlgorithmNode(Node<T> parentNode, AlgorithmNode<T> previousNode, double value) {
|
||||
this.node = parentNode;
|
||||
this.previous = previousNode;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Graph<T> graph;
|
||||
|
||||
// Diese Liste enthält alle Knoten, die noch nicht abgearbeitet wurden
|
||||
private List<Node<T>> availableNodes;
|
||||
|
||||
// Diese Map enthält alle Zuordnungen
|
||||
private Map<Node<T>, AlgorithmNode<T>> algorithmNodes;
|
||||
|
||||
/**
|
||||
* Erzeugt ein neues GraphAlgorithm-Objekt mit dem dazugehörigen Graphen und dem Startknoten.
|
||||
* @param graph der zu betrachtende Graph
|
||||
* @param sourceNode der Startknoten
|
||||
*/
|
||||
public GraphAlgorithm(Graph<T> graph, Node<T> sourceNode) {
|
||||
this.graph = graph;
|
||||
this.availableNodes = new LinkedList<>(graph.getNodes());
|
||||
this.algorithmNodes = new HashMap<>();
|
||||
|
||||
for(Node<T> node : graph.getNodes())
|
||||
this.algorithmNodes.put(node, new AlgorithmNode<>(sourceNode, null, -1));
|
||||
|
||||
this.algorithmNodes.get(sourceNode).value = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode gibt einen Knoten mit dem kleinsten Wert, der noch nicht abgearbeitet wurde, zurück und entfernt ihn aus der Liste {@link #availableNodes}.
|
||||
* Sollte kein Knoten gefunden werden, wird null zurückgegeben.
|
||||
* Verbindliche Anforderung: Verwenden Sie beim Durchlaufen der Liste Iteratoren
|
||||
* @return Der nächste abzuarbeitende Knoten oder null
|
||||
*/
|
||||
private AlgorithmNode<T> getSmallestNode() {
|
||||
// TODO: GraphAlgorithm<T>#getSmallestNode()
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode startet den Algorithmus. Dieser funktioniert wie folgt:
|
||||
* 1. Suche den Knoten mit dem geringsten Wert (siehe {@link #getSmallestNode()})
|
||||
* 2. Für jede angrenzende Kante:
|
||||
* 2a. Überprüfe ob die Kante passierbar ist ({@link #isPassable(Edge)})
|
||||
* 2b. Berechne den Wert des Knotens, in dem du den aktuellen Wert des Knotens und den der Kante addierst
|
||||
* 2c. Ist der alte Wert nicht gesetzt (-1) oder ist der neue Wert kleiner, setze den neuen Wert und den Vorgängerknoten
|
||||
* 3. Wiederhole solange, bis alle Knoten abgearbeitet wurden
|
||||
|
||||
* Nützliche Methoden:
|
||||
* @see #getSmallestNode()
|
||||
* @see #isPassable(Edge)
|
||||
* @see Graph#getEdges(Node)
|
||||
* @see Edge#getOtherNode(Node)
|
||||
*/
|
||||
public void run() {
|
||||
// TODO: GraphAlgorithm<T>#run()
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode gibt eine Liste von Kanten zurück, die einen Pfad zu dem angegebenen Zielknoten representiert.
|
||||
* Dabei werden zuerst beginnend mit dem Zielknoten alle Kanten mithilfe des Vorgängerattributs {@link AlgorithmNode#previous} zu der Liste hinzugefügt.
|
||||
* Zum Schluss muss die Liste nur noch umgedreht werden. Sollte kein Pfad existieren, geben Sie null zurück.
|
||||
* @param destination Der Zielknoten des Pfads
|
||||
* @return eine Liste von Kanten oder null
|
||||
*/
|
||||
public List<Edge<T>> getPath(Node<T> destination) {
|
||||
// TODO: GraphAlgorithm<T>#getPath(Node<T>)
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den betrachteten Graphen zurück
|
||||
* @return der zu betrachtende Graph
|
||||
*/
|
||||
protected Graph<T> getGraph() {
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Wert einer Kante zurück.
|
||||
* Diese Methode ist abstrakt und wird in den implementierenden Klassen definiert um eigene Kriterien für Werte zu ermöglichen.
|
||||
* @param edge Eine Kante
|
||||
* @return Ein Wert, der der Kante zugewiesen wird
|
||||
*/
|
||||
protected abstract double getValue(Edge<T> edge);
|
||||
|
||||
/**
|
||||
* Gibt an, ob eine Kante passierbar ist.
|
||||
* @param edge Eine Kante
|
||||
* @return true, wenn die Kante passierbar ist.
|
||||
*/
|
||||
protected abstract boolean isPassable(Edge<T> edge);
|
||||
}
|
||||
26
Projektgruppe_XXX/src/base/Node.java
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package base;
|
||||
|
||||
/**
|
||||
* Diese Klasse representiert einen generischen Knoten
|
||||
* @param <T>
|
||||
*/
|
||||
public class Node<T> {
|
||||
|
||||
private T value;
|
||||
|
||||
/**
|
||||
* Erzeugt einen neuen Knoten mit dem gegebenen Wert
|
||||
* @param value der Wert des Knotens
|
||||
*/
|
||||
Node(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt den Wert des Knotens zurück
|
||||
* @return der Wert des Knotens
|
||||
*/
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
153
Projektgruppe_XXX/src/base/PerlinNoise.java
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package base;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* @author Philipp Imperatori, Nils Nedderhut, Louis Neumann
|
||||
* modified by Roman Hergenreder
|
||||
* <p>
|
||||
* Version 1.0
|
||||
*/
|
||||
public class PerlinNoise {
|
||||
|
||||
private int width ;
|
||||
private int height;
|
||||
private int scale;
|
||||
|
||||
private int gwidth; // scaled width
|
||||
private int gheight; // scaled height
|
||||
|
||||
private Random random;
|
||||
private ArrayList<Vector<Double>> Vectors; //gradients
|
||||
|
||||
public PerlinNoise(int width, int height, int scale) {
|
||||
|
||||
this.width = width * scale;
|
||||
this.height = height * scale;
|
||||
this.scale = scale;
|
||||
this.gwidth = width;
|
||||
this.gheight = height;
|
||||
|
||||
this.random = new Random();
|
||||
this.Vectors = new ArrayList<>();
|
||||
createVectors((1 + gwidth) * (1 + gheight));
|
||||
}
|
||||
|
||||
public Dimension getRealSize() {
|
||||
return new Dimension(this.width, this.height);
|
||||
}
|
||||
|
||||
public Dimension getScaledSize() {
|
||||
return new Dimension(this.gwidth, this.gheight);
|
||||
}
|
||||
|
||||
/**
|
||||
* create's a list with n elements where each element is a vector in the union circle (gradient)
|
||||
* @param n: number of gradients to be created
|
||||
*/
|
||||
private void createVectors(int n) {
|
||||
this.Vectors = new ArrayList<>();
|
||||
for(int i=0;i<n;i++){
|
||||
double randomValue = random.nextDouble();
|
||||
randomValue = randomValue * 2 * Math.PI;
|
||||
double x = Math.cos(randomValue);
|
||||
double y = Math.sin(randomValue);
|
||||
Vector<Double> grad = new Vector<>(x,y);
|
||||
this.Vectors.add(grad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a Sigmoid function to smooth numbers betwenn 0 to 1
|
||||
* @param t: number to smooth
|
||||
* @return smoothed number
|
||||
*/
|
||||
private double fade(double t){
|
||||
return (((6*(t*t*t*t*t))-(15*t*t*t*t))+(10*t*t*t));
|
||||
}
|
||||
|
||||
/** Computes a weighted linear interpolation with point (x,y) and weight w
|
||||
* @param x: value of x-coordinate
|
||||
* @param y: value of y-coordinate
|
||||
* @param w: weight w to interpolate
|
||||
* @return result of linear interpolation
|
||||
*/
|
||||
private double linearInterpolation(double x, double y, double w){
|
||||
return ((1.0-w)*x+w*y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the scalar product between the direction vector and the gradient in the corner
|
||||
* @param vector: direction vector
|
||||
* @param gradient: gradient in the corner
|
||||
* @return scalar product
|
||||
*/
|
||||
private double scalarVekGrad(Vector<Double> vector, Vector<Double> gradient){
|
||||
return (vector.getX()*gradient.getX() + vector.getY()*gradient.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the value from its old interval [oldMin,oldMax] to a new interval [newMin,newMax]
|
||||
* @param value : value to be converted
|
||||
* @return converted value
|
||||
*/
|
||||
private double mapToInterval(double value) {
|
||||
return (value + 1.0)/2.0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates the noise value for the given point (x,y)
|
||||
* @param x: x-coordinate of point
|
||||
* @param y: y-coordinate of point
|
||||
* @return noise of point
|
||||
*/
|
||||
public double getNoise (double x, double y) {
|
||||
if (x >= width || y >= height) {
|
||||
System.out.println("ERROR: x or/and y is not in picture");
|
||||
return 0;
|
||||
}
|
||||
|
||||
double scaledX = x / this.scale;
|
||||
double scaledY = y / this.scale;
|
||||
|
||||
//Left upper edge
|
||||
int xlo = (int) scaledX;
|
||||
int ylo = (int) scaledY;
|
||||
|
||||
//right upper egde
|
||||
int xro = ((int) scaledX)+1;
|
||||
int yro = ((int) scaledY);
|
||||
|
||||
//left lower edge
|
||||
int xlu = (int) scaledX;
|
||||
int ylu = ((int) scaledY)+1;
|
||||
|
||||
//right lower edge
|
||||
int xru = ((int) scaledX)+1;
|
||||
int yru = ((int) scaledY)+1;
|
||||
|
||||
// int cell_nr = xlo+(ylo*this.gwidth);
|
||||
|
||||
Vector<Double> gradLO = this.Vectors.get(xlo+(ylo*(this.gwidth+1)));
|
||||
Vector<Double> gradRO = this.Vectors.get(xro+(yro*(this.gwidth+1)));
|
||||
Vector<Double> gradLU = this.Vectors.get(xlu+((ylu)*(this.gwidth+1)));
|
||||
Vector<Double> gradRU = this.Vectors.get(xru+((yru)*(this.gwidth+1)));
|
||||
|
||||
Vector<Double> rvLO = new Vector<>(scaledX - xlo,scaledY - ylo);
|
||||
Vector<Double> rvRO = new Vector<>(scaledX - xro,scaledY - yro);
|
||||
Vector<Double> rvLU = new Vector<>(scaledX - xlu,scaledY - ylu);
|
||||
Vector<Double> rvRU = new Vector<>(scaledX - xru,scaledY - yru);
|
||||
|
||||
//upper edges
|
||||
double linIntOben = this.linearInterpolation(this.scalarVekGrad(rvLO,gradLO),this.scalarVekGrad(rvRO,gradRO),this.fade(rvLO.getX()));
|
||||
|
||||
//lower edges
|
||||
double linIntUnten = this.linearInterpolation(this.scalarVekGrad(rvLU,gradLU),this.scalarVekGrad(rvRU,gradRU),this.fade(rvLO.getX()));
|
||||
|
||||
//final interpolation
|
||||
return this.mapToInterval(this.linearInterpolation(linIntOben,linIntUnten,this.fade(rvLO.getY())));
|
||||
}
|
||||
}
|
||||
33
Projektgruppe_XXX/src/base/Vector.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package base;
|
||||
|
||||
/**
|
||||
* @author Philipp Imperatori, Nils Nedderhut, Louis Neumann
|
||||
* <p>
|
||||
* Version 1.0
|
||||
*/
|
||||
public class Vector<T> {
|
||||
|
||||
private T x;
|
||||
private T y;
|
||||
|
||||
public Vector(T x, T y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public T getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
public T getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
public void setX(T x) {
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public void setY(T y) {
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
85
Projektgruppe_XXX/src/dice3d/main/MainWindow.java
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package dice3d.main;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import dice3d.models.cuboids.Cuboid;
|
||||
import dice3d.models.cuboids.Dice;
|
||||
|
||||
|
||||
public class MainWindow extends JPanel {
|
||||
World w = new World();
|
||||
|
||||
|
||||
public MainWindow() {
|
||||
addKeyListener(new KeyHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
draw((Graphics2D) g);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void draw(Graphics2D g) {
|
||||
for(Cuboid c : w.cuboids) {
|
||||
c.draw(g);
|
||||
}
|
||||
int yOffset = 0;
|
||||
g.drawString("Press space key to roll again", 10, yOffset =+ 20);
|
||||
|
||||
for(int i = 0; i < w.cuboids.size(); i++) {
|
||||
Cuboid c = w.cuboids.get(i);
|
||||
if(c instanceof Dice) {
|
||||
Dice d = (Dice) c;
|
||||
g.drawString("dice[" + i + "]: moving=" + !d.notMoving() + " number_rolled=" + d.getNumberRolled(), 10, yOffset += 20);
|
||||
} else {
|
||||
g.drawString("cuboid[" + i + "]: moving=" + !c.notMoving(), 10, yOffset += 20);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
private class KeyHandler extends KeyAdapter {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getID() != KeyEvent.KEY_PRESSED) {
|
||||
return;
|
||||
}
|
||||
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
|
||||
for(Cuboid c : w.cuboids) {
|
||||
c.reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MainWindow view = new MainWindow();
|
||||
JFrame frame = new JFrame();
|
||||
frame.setTitle("Dice");
|
||||
frame.getContentPane().add(view);
|
||||
frame.setSize(1280, 720);
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
// frame.setResizable(false);
|
||||
frame.setVisible(true);
|
||||
view.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
43
Projektgruppe_XXX/src/dice3d/main/World.java
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package dice3d.main;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import dice3d.models.cuboids.Cuboid;
|
||||
import dice3d.models.cuboids.Dice;
|
||||
|
||||
public class World {
|
||||
|
||||
public static final double projectionDistance = 1000;
|
||||
|
||||
public ArrayList<Cuboid> cuboids;
|
||||
public Cuboid floor;
|
||||
|
||||
public World() {
|
||||
cuboids = new ArrayList<Cuboid>();
|
||||
|
||||
floor = new Cuboid(10, 400, 600, 700, 1000, 10);
|
||||
cuboids.add(floor);
|
||||
|
||||
cuboids.add(new Dice(150, 100, 800, 80));
|
||||
cuboids.add(new Dice(50, 120, 800, 80));
|
||||
|
||||
long delayInMS = 500; // start updating after 500ms
|
||||
long intervalInMS = 15; // update every 15ms
|
||||
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
for(Cuboid c : cuboids) {
|
||||
if(c == floor) {
|
||||
continue;
|
||||
}
|
||||
c.update();
|
||||
}
|
||||
}
|
||||
}, delayInMS, intervalInMS);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
75
Projektgruppe_XXX/src/dice3d/math/Vector.java
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package dice3d.math;
|
||||
|
||||
public class Vector {
|
||||
|
||||
public double x;
|
||||
public double y;
|
||||
public double z;
|
||||
|
||||
public Vector() {
|
||||
}
|
||||
|
||||
public Vector(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public Vector(Vector v) {
|
||||
this.x = v.x;
|
||||
this.y = v.y;
|
||||
this.z = v.z;
|
||||
}
|
||||
|
||||
public void set(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public void set(Vector v) {
|
||||
this.x = v.x;
|
||||
this.y = v.y;
|
||||
this.z = v.z;
|
||||
}
|
||||
|
||||
public void add(Vector v) {
|
||||
this.x += v.x;
|
||||
this.y += v.y;
|
||||
this.z += v.z;
|
||||
}
|
||||
|
||||
public void sub(Vector v) {
|
||||
this.x -= v.x;
|
||||
this.y -= v.y;
|
||||
this.z -= v.z;
|
||||
}
|
||||
|
||||
public void scale(double s) {
|
||||
scale(s, s, s);
|
||||
}
|
||||
|
||||
public void scale(double sx, double sy, double sz) {
|
||||
this.x *= sx;
|
||||
this.y *= sy;
|
||||
this.z *= sz;
|
||||
}
|
||||
|
||||
public double getSize() {
|
||||
return Math.sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
public void normalize() {
|
||||
scale(1 / getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + x + ", " + y + ", " + z + "]";
|
||||
}
|
||||
|
||||
public boolean equals(Vector v) {
|
||||
return v.x == x && v.y == y && v.z == z;
|
||||
}
|
||||
|
||||
}
|
||||
31
Projektgruppe_XXX/src/dice3d/models/Edge.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package dice3d.models;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import dice3d.main.World;
|
||||
|
||||
public class Edge {
|
||||
|
||||
private Vertex a;
|
||||
private Vertex b;
|
||||
|
||||
|
||||
public Edge(Vertex a, Vertex b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
||||
}
|
||||
|
||||
public void draw(Graphics2D g) {
|
||||
double ax = World.projectionDistance * a.position.x / a.position.z;
|
||||
double ay = World.projectionDistance * a.position.y / a.position.z;
|
||||
double bx = World.projectionDistance * b.position.x / b.position.z;
|
||||
double by = World.projectionDistance * b.position.y / b.position.z;
|
||||
|
||||
g.drawLine((int) ax, (int) ay, (int) bx, (int) by);
|
||||
}
|
||||
|
||||
}
|
||||
40
Projektgruppe_XXX/src/dice3d/models/Vertex.java
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package dice3d.models;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import dice3d.main.World;
|
||||
import dice3d.math.Vector;
|
||||
|
||||
public class Vertex {
|
||||
|
||||
public Vector position = new Vector();
|
||||
public Vector positionReset = new Vector();
|
||||
|
||||
public String id;
|
||||
|
||||
public Vertex(double x, double y, double z) {
|
||||
position.set(x, y, z);
|
||||
positionReset.set(position);
|
||||
}
|
||||
|
||||
|
||||
public void reset() {
|
||||
position.set(positionReset);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
|
||||
}
|
||||
|
||||
public void draw(Graphics2D g) {
|
||||
double px = World.projectionDistance * position.x / position.z;
|
||||
double py = World.projectionDistance * position.y / position.z;
|
||||
if (id == null) {
|
||||
int size = 6;
|
||||
g.fillOval((int) (px - (size / 2)), (int) (py - (size / 2)), size, size);
|
||||
} else {
|
||||
g.drawString(id, (int)px, (int)py);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
9
Projektgruppe_XXX/src/dice3d/models/cuboids/Cube.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package dice3d.models.cuboids;
|
||||
|
||||
public class Cube extends Cuboid {
|
||||
|
||||
public Cube(double x, double y, double z, int size) {
|
||||
super(x, y, z, size, size, size);
|
||||
}
|
||||
|
||||
}
|
||||
147
Projektgruppe_XXX/src/dice3d/models/cuboids/Cuboid.java
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package dice3d.models.cuboids;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dice3d.math.Vector;
|
||||
import dice3d.models.Edge;
|
||||
import dice3d.models.Vertex;
|
||||
|
||||
public class Cuboid {
|
||||
|
||||
public ArrayList<Vertex> vertices = new ArrayList<Vertex>();
|
||||
public ArrayList<Edge> edges = new ArrayList<Edge>();
|
||||
|
||||
|
||||
public boolean collided = false;
|
||||
|
||||
public Cuboid(double x, double y, double z, int length, int width, int height) {
|
||||
|
||||
Vector corner = new Vector(x, y, z);
|
||||
|
||||
Vertex a = new Vertex(corner.x, corner.y, corner.z);
|
||||
Vertex b = new Vertex(corner.x + length, corner.y, corner.z);
|
||||
Vertex c = new Vertex(corner.x + length, corner.y + height, corner.z);
|
||||
Vertex d = new Vertex(corner.x, corner.y + height, corner.z);
|
||||
|
||||
Vertex e = new Vertex(corner.x, corner.y, corner.z + width);
|
||||
Vertex f = new Vertex(corner.x + length, corner.y, corner.z + width);
|
||||
Vertex g = new Vertex(corner.x + length, corner.y + height, corner.z + width);
|
||||
Vertex h = new Vertex(corner.x, corner.y + height, corner.z + width);
|
||||
|
||||
a.id = "A";
|
||||
b.id = "B";
|
||||
c.id = "C";
|
||||
d.id = "D";
|
||||
e.id = "E";
|
||||
f.id = "F";
|
||||
g.id = "G";
|
||||
h.id = "H";
|
||||
|
||||
vertices.add(a);
|
||||
vertices.add(b);
|
||||
vertices.add(c);
|
||||
vertices.add(d);
|
||||
|
||||
vertices.add(e);
|
||||
vertices.add(f);
|
||||
vertices.add(g);
|
||||
vertices.add(h);
|
||||
|
||||
Edge edge01 = new Edge(a, b);
|
||||
Edge edge02 = new Edge(b, c);
|
||||
Edge edge03 = new Edge(c, d);
|
||||
Edge edge04 = new Edge(d, a);
|
||||
|
||||
Edge edge05 = new Edge(e, f);
|
||||
Edge edge06 = new Edge(f, g);
|
||||
Edge edge07 = new Edge(g, h);
|
||||
Edge edge08 = new Edge(h, e);
|
||||
|
||||
Edge edge09 = new Edge(a, e);
|
||||
Edge edge10 = new Edge(b, f);
|
||||
Edge edge11 = new Edge(c, g);
|
||||
Edge edge12 = new Edge(d, h);
|
||||
|
||||
Edge edge13 = new Edge(a, g);
|
||||
Edge edge14 = new Edge(b, h);
|
||||
Edge edge15 = new Edge(c, e);
|
||||
Edge edge16 = new Edge(d, f);
|
||||
|
||||
Edge edge17 = new Edge(a, c);
|
||||
Edge edge18 = new Edge(b, g);
|
||||
Edge edge19 = new Edge(f, h);
|
||||
Edge edge20 = new Edge(d, e);
|
||||
Edge edge21 = new Edge(d, g);
|
||||
Edge edge22 = new Edge(a, f);
|
||||
|
||||
edges.add(edge01);
|
||||
edges.add(edge02);
|
||||
edges.add(edge03);
|
||||
edges.add(edge04);
|
||||
edges.add(edge05);
|
||||
edges.add(edge06);
|
||||
edges.add(edge07);
|
||||
edges.add(edge08);
|
||||
edges.add(edge09);
|
||||
edges.add(edge10);
|
||||
edges.add(edge11);
|
||||
edges.add(edge12);
|
||||
|
||||
edges.add(edge13);
|
||||
edges.add(edge14);
|
||||
edges.add(edge15);
|
||||
edges.add(edge16);
|
||||
|
||||
edges.add(edge17);
|
||||
edges.add(edge18);
|
||||
edges.add(edge19);
|
||||
edges.add(edge20);
|
||||
edges.add(edge21);
|
||||
edges.add(edge22);
|
||||
|
||||
}
|
||||
|
||||
public void update() {
|
||||
for (Vertex v : vertices) {
|
||||
v.update();
|
||||
}
|
||||
for (Edge e : edges) {
|
||||
e.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
for (Vertex v : vertices) {
|
||||
v.reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(Graphics2D g) {
|
||||
// draw all edges, even inner ones
|
||||
g.setColor(Color.GREEN);
|
||||
|
||||
if(collided) {
|
||||
g.setColor(Color.RED);
|
||||
}
|
||||
for(Edge e: edges) {
|
||||
e.draw(g);
|
||||
}
|
||||
|
||||
g.setColor(Color.BLACK);
|
||||
for (Vertex point : vertices) {
|
||||
point.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean notMoving() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void updateCollision(Cuboid d) {
|
||||
collided = false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
111
Projektgruppe_XXX/src/dice3d/models/cuboids/Dice.java
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package dice3d.models.cuboids;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
|
||||
import dice3d.main.World;
|
||||
import dice3d.models.Vertex;
|
||||
|
||||
public class Dice extends Cube {
|
||||
|
||||
|
||||
private Vertex[][] face = new Vertex[6][4];
|
||||
|
||||
|
||||
public Dice(double x, double y, double z, int size) {
|
||||
super(x,y,z, size);
|
||||
|
||||
face[0][0] = vertices.get(0); // a
|
||||
face[0][1] = vertices.get(1); // b
|
||||
face[0][2] = vertices.get(2); // c ...
|
||||
face[0][3] = vertices.get(3);
|
||||
|
||||
face[1][0] = vertices.get(1);
|
||||
face[1][1] = vertices.get(5);
|
||||
face[1][2] = vertices.get(6);
|
||||
face[1][3] = vertices.get(2);
|
||||
|
||||
face[2][0] = vertices.get(5);
|
||||
face[2][1] = vertices.get(4);
|
||||
face[2][2] = vertices.get(7);
|
||||
face[2][3] = vertices.get(6);
|
||||
|
||||
face[3][0] = vertices.get(4);
|
||||
face[3][1] = vertices.get(0);
|
||||
face[3][2] = vertices.get(3);
|
||||
face[3][3] = vertices.get(7);
|
||||
|
||||
face[4][0] = vertices.get(3);
|
||||
face[4][1] = vertices.get(2);
|
||||
face[4][2] = vertices.get(6);
|
||||
face[4][3] = vertices.get(7);
|
||||
|
||||
face[5][0] = vertices.get(1);
|
||||
face[5][1] = vertices.get(0);
|
||||
face[5][2] = vertices.get(4);
|
||||
face[5][3] = vertices.get(5);
|
||||
}
|
||||
|
||||
|
||||
public void draw(Graphics2D g) {
|
||||
drawDice(g);
|
||||
// draw all edges, even inner ones
|
||||
// for(Edge e: edges) {
|
||||
// e.draw(g);
|
||||
// }
|
||||
|
||||
// for (Vertex point : vertices) {
|
||||
// point.draw(g);
|
||||
// }
|
||||
}
|
||||
|
||||
public int getNumberRolled() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void drawDice(Graphics2D g) {
|
||||
for (int f = 0; f < 6; f++) {
|
||||
// each face as polygon
|
||||
Polygon polygon = new Polygon();
|
||||
|
||||
for (int v = 0; v < 4; v++) {
|
||||
Vertex vertex = face[f][v];
|
||||
double vx = World.projectionDistance * vertex.position.x / vertex.position.z;
|
||||
double vy = World.projectionDistance * vertex.position.y / vertex.position.z;
|
||||
|
||||
polygon.addPoint((int) vx, (int) vy);
|
||||
}
|
||||
|
||||
// back-face culling
|
||||
double v1x = polygon.xpoints[0] - polygon.xpoints[1];
|
||||
double v1y = polygon.ypoints[0] - polygon.ypoints[1];
|
||||
double v2x = polygon.xpoints[2] - polygon.xpoints[1];
|
||||
double v2y = polygon.ypoints[2] - polygon.ypoints[1];
|
||||
double visible = v1x * v2y - v1y * v2x;
|
||||
if (visible < 0) {
|
||||
g.setColor(Color.WHITE);
|
||||
if(collided) {
|
||||
g.setColor(Color.RED);
|
||||
}
|
||||
g.fill(polygon);
|
||||
g.setColor(Color.BLACK);
|
||||
g.draw(polygon);
|
||||
|
||||
// draw number of each face (that is visible)
|
||||
// calc center of each face/square
|
||||
double xs = 0d;
|
||||
double ys = 0d;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
xs += polygon.xpoints[i];
|
||||
ys += polygon.ypoints[i];
|
||||
}
|
||||
xs = xs / 4;
|
||||
ys = ys / 4;
|
||||
g.drawString(f + 1 + "", (int) xs, (int) ys);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
72
Projektgruppe_XXX/src/game/AI.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
293
Projektgruppe_XXX/src/game/Game.java
Normal 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;
|
||||
}
|
||||
}
|
||||
41
Projektgruppe_XXX/src/game/GameConstants.java
Normal 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
|
||||
};
|
||||
}
|
||||
18
Projektgruppe_XXX/src/game/GameInterface.java
Normal 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);
|
||||
}
|
||||
33
Projektgruppe_XXX/src/game/Goal.java
Normal 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;
|
||||
}
|
||||
}
|
||||
88
Projektgruppe_XXX/src/game/Player.java
Normal 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;
|
||||
}
|
||||
}
|
||||
95
Projektgruppe_XXX/src/game/ScoreEntry.java
Normal 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;
|
||||
}
|
||||
}
|
||||
45
Projektgruppe_XXX/src/game/goals/ConquerGoal.java
Normal 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;
|
||||
}
|
||||
}
|
||||
128
Projektgruppe_XXX/src/game/map/Castle.java
Normal 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);
|
||||
}
|
||||
}
|
||||
40
Projektgruppe_XXX/src/game/map/Clustering.java
Normal 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<>();
|
||||
}
|
||||
}
|
||||
256
Projektgruppe_XXX/src/game/map/GameMap.java
Normal 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;
|
||||
}
|
||||
}
|
||||
75
Projektgruppe_XXX/src/game/map/Kingdom.java
Normal 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;
|
||||
}
|
||||
}
|
||||
27
Projektgruppe_XXX/src/game/map/MapSize.java
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
58
Projektgruppe_XXX/src/game/map/PathFinding.java
Normal 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));
|
||||
}
|
||||
}
|
||||
103
Projektgruppe_XXX/src/game/players/BasicAI.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Projektgruppe_XXX/src/game/players/Human.java
Normal 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);
|
||||
}
|
||||
}
|
||||
76
Projektgruppe_XXX/src/gui/AttackThread.java
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package gui;
|
||||
|
||||
import game.Game;
|
||||
import game.Player;
|
||||
import game.map.Castle;
|
||||
|
||||
public class AttackThread extends Thread {
|
||||
|
||||
private Castle attackerCastle, defenderCastle;
|
||||
private Player attacker, defender;
|
||||
private int troopAttackCount;
|
||||
private Game game;
|
||||
private boolean fastForward;
|
||||
private Player winner;
|
||||
|
||||
public AttackThread(Game game, Castle attackerCastle, Castle defenderCastle, int troopAttackCount) {
|
||||
this.attackerCastle = attackerCastle;
|
||||
this.defenderCastle = defenderCastle;
|
||||
this.attacker = attackerCastle.getOwner();
|
||||
this.defender = defenderCastle.getOwner();
|
||||
this.winner = defender;
|
||||
this.troopAttackCount = troopAttackCount;
|
||||
this.game = game;
|
||||
this.fastForward = false;
|
||||
}
|
||||
|
||||
public void fastForward() {
|
||||
fastForward = true;
|
||||
}
|
||||
|
||||
private void sleep(int ms) throws InterruptedException {
|
||||
long end = System.currentTimeMillis() + ms;
|
||||
while(System.currentTimeMillis() < end && !fastForward) {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
int attackUntil = Math.max(1, attackerCastle.getTroopCount() - troopAttackCount);
|
||||
|
||||
try {
|
||||
sleep(1500);
|
||||
|
||||
while(attackerCastle.getTroopCount() > attackUntil) {
|
||||
|
||||
// Attacker dices: at maximum 3 and not more than actual troop count
|
||||
int attackerCount = Math.min(troopAttackCount, Math.min(attackerCastle.getTroopCount(), 3));
|
||||
int attackerDice[] = game.roll(attacker, attackerCount, fastForward);
|
||||
|
||||
sleep(1500);
|
||||
|
||||
// Defender dices: at maximum 2
|
||||
int defenderCount = Math.min(2, defenderCastle.getTroopCount());
|
||||
int defenderDice[] = game.roll(defender, defenderCount, fastForward);
|
||||
|
||||
game.doAttack(attackerCastle, defenderCastle, attackerDice, defenderDice);
|
||||
if(defenderCastle.getOwner() == attacker) {
|
||||
winner = attacker;
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(1500);
|
||||
}
|
||||
} catch(InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
game.stopAttack();
|
||||
}
|
||||
|
||||
public Player getWinner() {
|
||||
return winner;
|
||||
}
|
||||
}
|
||||
71
Projektgruppe_XXX/src/gui/GameWindow.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package gui;
|
||||
|
||||
import gui.views.StartScreen;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
* Diese Klasse bietet das Fenster wo die grafische Oberfläche angezeigt wird.
|
||||
*
|
||||
* @author Roman Hergenreder
|
||||
*/
|
||||
public class GameWindow extends JFrame {
|
||||
|
||||
private View activeView;
|
||||
private Resources resources;
|
||||
|
||||
private GameWindow(Resources resources) {
|
||||
this.resources = resources;
|
||||
this.initWindow();
|
||||
this.setView(new StartScreen(this));
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
private void initWindow() {
|
||||
|
||||
LookAndFeel laf = UIManager.getLookAndFeel();
|
||||
if(laf.getSupportsWindowDecorations()) {
|
||||
UIManager.getCrossPlatformLookAndFeelClassName();
|
||||
}
|
||||
|
||||
this.setTitle("Game of Castles - FOP Projekt WiSe 18/19");
|
||||
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
this.setSize(800, 500);
|
||||
this.setMinimumSize(new Dimension(600, 400));
|
||||
this.setLocationRelativeTo(null); // Center Window
|
||||
this.addWindowStateListener(windowStateListener); // Resize Event
|
||||
}
|
||||
|
||||
public void setView(View view) {
|
||||
this.activeView = view;
|
||||
this.activeView.setSize(getContentPane().getSize());
|
||||
this.setContentPane(view);
|
||||
this.requestFocus();
|
||||
}
|
||||
|
||||
private WindowStateListener windowStateListener = new WindowStateListener() {
|
||||
@Override
|
||||
public void windowStateChanged(WindowEvent windowEvent) {
|
||||
if ((windowEvent.getNewState() & JFrame.MAXIMIZED_BOTH) != JFrame.MAXIMIZED_BOTH) {
|
||||
activeView.onResize();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Resources resources = Resources.getInstance();
|
||||
if(!resources.load())
|
||||
return;
|
||||
|
||||
new GameWindow(resources);
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(resources::save));
|
||||
}
|
||||
|
||||
public Resources getResources() {
|
||||
return this.resources;
|
||||
}
|
||||
}
|
||||
315
Projektgruppe_XXX/src/gui/Resources.java
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
package gui;
|
||||
|
||||
import game.ScoreEntry;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Diese Klasse verwaltet die Resourcen des Spiels, darunter beispielsweise Bilder, Icons und Schriftarten.
|
||||
*/
|
||||
public class Resources {
|
||||
|
||||
private static final int NUM_CASTLES = 6;
|
||||
private static Resources instance;
|
||||
private Map<Color, BufferedImage[]> castlesColored;
|
||||
private BufferedImage dices[];
|
||||
private BufferedImage check;
|
||||
private BufferedImage unit;
|
||||
private BufferedImage arrow, arrowDeactivated, plus, plusDeactivated, swords;
|
||||
private BufferedImage soldiers[];
|
||||
private List<String> castleNames;
|
||||
private Font celticFont;
|
||||
|
||||
private List<ScoreEntry> scoreEntries;
|
||||
|
||||
private boolean resourcesLoaded;
|
||||
|
||||
/**
|
||||
* Privater Konstruktor, dieser wird normalerweise nur einmal aufgerufen
|
||||
*/
|
||||
private Resources() {
|
||||
this.resourcesLoaded = false;
|
||||
this.scoreEntries = new LinkedList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Instanz des Resourcen Managers zurück oder erzeugt eine neue
|
||||
* @return Resourcen Manager
|
||||
*/
|
||||
public static Resources getInstance() {
|
||||
if(instance == null) {
|
||||
instance = new Resources();
|
||||
instance.load();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt ein Bild aus den Resourcen
|
||||
* @param name der Name der Datei
|
||||
* @return das Bild als {@link BufferedImage}-Objekt
|
||||
* @throws IOException Eine IOException wird geworfen, falls das Bild nicht gefunden wurde oder andere Probleme beim Laden auftreten
|
||||
*/
|
||||
private BufferedImage loadImage(String name) throws IOException {
|
||||
URL res = Resources.class.getClassLoader().getResource(name);
|
||||
if(res == null)
|
||||
throw new IOException("Resource not found: " + name);
|
||||
|
||||
return ImageIO.read(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle Resourcen
|
||||
* @return true, wenn alle Resourcen erfolgreich geladen wurden
|
||||
*/
|
||||
public boolean load() {
|
||||
if(resourcesLoaded)
|
||||
return true;
|
||||
|
||||
try {
|
||||
// Load Castle
|
||||
castlesColored = new HashMap<>();
|
||||
BufferedImage castles[] = new BufferedImage[NUM_CASTLES];
|
||||
for(int i = 0; i < NUM_CASTLES; i++)
|
||||
castles[i] = loadImage("castle" + (i + 1) + ".png");
|
||||
castlesColored.put(Color.WHITE, castles);
|
||||
|
||||
// Load Dices
|
||||
dices = loadDices();
|
||||
|
||||
// Load Icons
|
||||
soldiers = new BufferedImage[] { loadImage("soldier1.png"), loadImage("soldier2.png") };
|
||||
check = loadImage("check.png");
|
||||
unit = loadImage("unit.png");
|
||||
arrow = loadImage("arrow.png");
|
||||
swords = loadImage("swords.png");
|
||||
plus = loadImage("plus.png");
|
||||
plusDeactivated = loadImage("plus_deactivated.png");
|
||||
arrowDeactivated = loadImage("arrow_deactivated.png");
|
||||
|
||||
// Load Castle names
|
||||
castleNames = loadRegionNames();
|
||||
|
||||
// Load font
|
||||
celticFont = loadFont("celtic.ttf");
|
||||
|
||||
// Load score entries
|
||||
loadScoreEntries();
|
||||
|
||||
resourcesLoaded = true;
|
||||
return true;
|
||||
} catch(Exception ex) {
|
||||
JOptionPane.showMessageDialog(null, "Konnte Resourcen nicht laden: " + ex.getMessage(), "Fehler beim Laden der Resourcen", JOptionPane.ERROR_MESSAGE);
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert bestimmte Resourcen, zurzeit nur den Highscore-Table
|
||||
* @return gibt true zurück, wenn erfolgreich gespeichert wurde
|
||||
*/
|
||||
public boolean save() {
|
||||
try {
|
||||
saveScoreEntries();
|
||||
return true;
|
||||
} catch(IOException ex) {
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Diese Methode speichert alle Objekte des Typs {@link ScoreEntry} in der Textdatei "highscores.txt"
|
||||
* Jede Zeile stellt dabei einen ScoreEntry dar. Sollten Probleme auftreten, muss eine {@link IOException} geworfen werden.
|
||||
* Die Einträge sind in der Liste {@link #scoreEntries} zu finden.
|
||||
* @see ScoreEntry#write(PrintWriter)
|
||||
* @throws IOException Eine IOException wird geworfen, wenn Probleme beim Schreiben auftreten.
|
||||
*/
|
||||
private void saveScoreEntries() throws IOException {
|
||||
// TODO: Resources#saveScoreEntries()
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt den Highscore-Table aus der Datei "highscores.txt".
|
||||
* Dabei wird die Liste {@link #scoreEntries} neu erzeugt und befüllt.
|
||||
* Beachte dabei, dass die Liste nach dem Einlen absteigend nach den Punktzahlen sortiert sein muss.
|
||||
* Sollte eine Exception auftreten, kann diese ausgegeben werden, sie sollte aber nicht weitergegeben werden,
|
||||
* da sonst das Laden der restlichen Resourcen abgebrochen wird ({@link #load()}).
|
||||
* @see ScoreEntry#read(String)
|
||||
* @see #addScoreEntry(ScoreEntry)
|
||||
*/
|
||||
private void loadScoreEntries() {
|
||||
// TODO: Resources#loadScoreEntries()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fügt ein {@link ScoreEntry}-Objekt der Liste von Einträgen hinzu.
|
||||
* Beachte: Nach dem Einfügen muss die Liste nach den Punktzahlen absteigend sortiert bleiben.
|
||||
* @param scoreEntry Der einzufügende Eintrag
|
||||
* @see ScoreEntry#compareTo(ScoreEntry)
|
||||
*/
|
||||
public void addScoreEntry(ScoreEntry scoreEntry) {
|
||||
int i = scoreEntries.size() - 1;
|
||||
for(; i >= 0; i--) {
|
||||
if(scoreEntry.compareTo(scoreEntries.get(i)) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scoreEntries.add(i + 1, scoreEntry);
|
||||
}
|
||||
|
||||
public List<ScoreEntry> getScoreEntries() {
|
||||
return scoreEntries;
|
||||
}
|
||||
|
||||
private Font loadFont(String name) throws IOException, FontFormatException {
|
||||
InputStream is = Resources.class.getClassLoader().getResourceAsStream(name);
|
||||
Font f = Font.createFont(Font.TRUETYPE_FONT, is);
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
ge.registerFont(f);
|
||||
return f.deriveFont(20f);
|
||||
}
|
||||
|
||||
private BufferedImage[] loadDices() throws IOException {
|
||||
BufferedImage[] dices = new BufferedImage[6];
|
||||
BufferedImage diceImage = loadImage("dices.png");
|
||||
int diceSizeW = diceImage.getWidth() / 3;
|
||||
int diceSizeH = diceImage.getHeight() / 2;
|
||||
if(diceSizeW != diceSizeH)
|
||||
System.out.println("Invalid dice dimensions for resource: dice.png. Expected 3x2 dices, got dimensions: " + diceImage.getWidth() + "x" + diceImage.getHeight());
|
||||
|
||||
int diceSize = diceSizeH;
|
||||
int num = 0;
|
||||
|
||||
for (int x = 0; x < 2; x++) {
|
||||
for (int y = 0; y < 3; y++) {
|
||||
//Initialize the image array with image chunks
|
||||
dices[num] = new BufferedImage(diceSize, diceSize, diceImage.getType());
|
||||
|
||||
// draws the image chunk
|
||||
Graphics2D gr = dices[num++].createGraphics();
|
||||
gr.drawImage(diceImage, 0, 0, diceSize, diceSize, diceSize * y, diceSize * x, diceSize * y + diceSize, diceSize * x + diceSize, null);
|
||||
gr.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return dices;
|
||||
}
|
||||
|
||||
private List<String> loadRegionNames() throws IOException {
|
||||
|
||||
List<String> regionNames = new LinkedList<>();
|
||||
InputStream is = Resources.class.getClassLoader().getResourceAsStream("castles.txt");
|
||||
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
|
||||
String line;
|
||||
while((line = br.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if(line.length() > 0 && !line.startsWith("#")) {
|
||||
regionNames.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
br.close();
|
||||
return regionNames;
|
||||
}
|
||||
|
||||
private BufferedImage colorImage(BufferedImage original, Color color) {
|
||||
ColorModel cm = original.getColorModel();
|
||||
boolean isAlphaPremultiplied = cm.isAlphaPremultiplied();
|
||||
WritableRaster raster = original.copyData(null);
|
||||
BufferedImage newImage = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
||||
|
||||
for(int x = 0; x < original.getWidth(); x++) {
|
||||
for(int y = 0; y < original.getHeight(); y++) {
|
||||
Color oldColor = new Color(original.getRGB(x, y), true);
|
||||
if(oldColor.getRed() == 255 && oldColor.getGreen() == 255 && oldColor.getBlue() == 255 && oldColor.getAlpha() == 255) {
|
||||
newImage.setRGB(x, y, color.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
public BufferedImage getCastle(Color color, int index) {
|
||||
if(!resourcesLoaded)
|
||||
return null;
|
||||
|
||||
index = index % NUM_CASTLES;
|
||||
BufferedImage images[];
|
||||
if(castlesColored.containsKey(color))
|
||||
images = castlesColored.get(color);
|
||||
else
|
||||
images = new BufferedImage[NUM_CASTLES];
|
||||
|
||||
if(images[index] != null)
|
||||
return images[index];
|
||||
|
||||
BufferedImage castleGeneric = castlesColored.get(Color.WHITE)[index];
|
||||
images[index] = colorImage(castleGeneric, color);
|
||||
castlesColored.put(color, images);
|
||||
return images[index];
|
||||
}
|
||||
|
||||
public BufferedImage getDice(int value) {
|
||||
if(!resourcesLoaded)
|
||||
return null;
|
||||
|
||||
return dices[value % dices.length];
|
||||
}
|
||||
|
||||
public BufferedImage getCheckIcon() {
|
||||
return this.check;
|
||||
}
|
||||
|
||||
public BufferedImage getUnitIcon() {
|
||||
return this.unit;
|
||||
}
|
||||
|
||||
public BufferedImage getPlusIcon() {
|
||||
return this.plus;
|
||||
}
|
||||
|
||||
public BufferedImage getArrowIcon() {
|
||||
return this.arrow;
|
||||
}
|
||||
|
||||
public BufferedImage getSwordsIcon() {
|
||||
return this.swords;
|
||||
}
|
||||
|
||||
public BufferedImage getArrowIconDeactivated() {
|
||||
return this.arrowDeactivated;
|
||||
}
|
||||
|
||||
public BufferedImage[] getSoldiers() {
|
||||
return this.soldiers;
|
||||
}
|
||||
|
||||
public List<String> getCastleNames() {
|
||||
return castleNames;
|
||||
}
|
||||
|
||||
public BufferedImage getPlusIconDeactivated() {
|
||||
return this.plusDeactivated;
|
||||
}
|
||||
|
||||
public Font getCelticFont() {
|
||||
return this.celticFont;
|
||||
}
|
||||
}
|
||||
135
Projektgruppe_XXX/src/gui/View.java
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* @author Roman Hergenreder
|
||||
*/
|
||||
public abstract class View extends Container implements ActionListener {
|
||||
|
||||
private GameWindow gameWindow;
|
||||
protected static final Dimension BUTTON_SIZE = new Dimension(125, 40);
|
||||
|
||||
public View(GameWindow gameWindow) {
|
||||
this.gameWindow = gameWindow;
|
||||
this.onInit();
|
||||
this.setSize(gameWindow.getSize());
|
||||
this.addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent componentEvent) {
|
||||
onResize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void onResize();
|
||||
protected abstract void onInit();
|
||||
|
||||
public static Font createFont(int Size) {
|
||||
return new Font("Times New Roman", Font.PLAIN, Size);
|
||||
}
|
||||
|
||||
public static Font createCelticFont(float Size) {
|
||||
Resources resources = Resources.getInstance();
|
||||
return resources.getCelticFont().deriveFont(Size);
|
||||
}
|
||||
|
||||
protected GameWindow getWindow() {
|
||||
return this.gameWindow;
|
||||
}
|
||||
|
||||
protected JButton createButton(String text) {
|
||||
JButton button = new JButton(text);
|
||||
button.setSize(BUTTON_SIZE);
|
||||
button.setFont(createFont(16));
|
||||
button.addActionListener(this);
|
||||
button.setBackground(new Color(54, 103, 53));
|
||||
button.setForeground(Color.WHITE);
|
||||
button.setFocusable(false);
|
||||
button.setFont(gameWindow.getResources().getCelticFont());
|
||||
this.add(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
public static Dimension calculateTextSize(String text, Font font) {
|
||||
AffineTransform affinetransform = new AffineTransform();
|
||||
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
|
||||
int width = (int)(font.getStringBounds(text, frc).getWidth());
|
||||
int height = (int)(font.getStringBounds(text, frc).getHeight());
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
private Dimension calculateLabelSize(JLabel label) {
|
||||
return calculateTextSize(label.getText(), label.getFont());
|
||||
}
|
||||
|
||||
protected JLabel createLabel(String text, int fontSize) {
|
||||
return createLabel(text, fontSize, false);
|
||||
}
|
||||
|
||||
protected JLabel createLabel(String text, int fontSize, boolean underline) {
|
||||
JLabel label = new JLabel(text);
|
||||
label.setFont(createFont(fontSize));
|
||||
label.setSize(calculateLabelSize(label));
|
||||
|
||||
if(underline) {
|
||||
Font font = label.getFont();
|
||||
Map<TextAttribute, Object> attributes = new HashMap<>(font.getAttributes());
|
||||
attributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
|
||||
label.setFont(font.deriveFont(attributes));
|
||||
}
|
||||
|
||||
add(label);
|
||||
return label;
|
||||
}
|
||||
|
||||
|
||||
protected JComboBox createCombobox(Vector<String> values, int selectedIndex) {
|
||||
JComboBox<String> comboBox = new JComboBox<>(values);
|
||||
comboBox.setSelectedIndex(selectedIndex);
|
||||
comboBox.setSize(200, 25);
|
||||
add(comboBox);
|
||||
return comboBox;
|
||||
}
|
||||
|
||||
protected JTextPane createTextPane() {
|
||||
JTextPane textPane = new JTextPane();
|
||||
textPane.setEditable(false);
|
||||
textPane.setBorder(BorderFactory.createCompoundBorder(
|
||||
new LineBorder(Color.BLACK),
|
||||
new EmptyBorder(3,3,3,3)
|
||||
));
|
||||
return textPane;
|
||||
}
|
||||
|
||||
protected JTextArea createTextArea(String text, boolean readonly) {
|
||||
JTextArea textArea = new JTextArea(text);
|
||||
textArea.setWrapStyleWord(true);
|
||||
textArea.setLineWrap(true);
|
||||
|
||||
if(readonly) {
|
||||
textArea.setEditable(false);
|
||||
textArea.setBackground(this.getBackground());
|
||||
}
|
||||
|
||||
add(textArea);
|
||||
return textArea;
|
||||
}
|
||||
|
||||
protected void showErrorMessage(String text, String title) {
|
||||
JOptionPane.showMessageDialog(this, text, title, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
42
Projektgruppe_XXX/src/gui/components/ColorChooserButton.java
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package gui.components;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class ColorChooserButton extends JButton {
|
||||
|
||||
private Color current;
|
||||
|
||||
public ColorChooserButton(Color c) {
|
||||
setSelectedColor(c);
|
||||
addActionListener(event -> {
|
||||
Color newColor = JColorChooser.showDialog(null, "Choose a color", current);
|
||||
setSelectedColor(newColor);
|
||||
});
|
||||
}
|
||||
|
||||
public Color getSelectedColor() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public void setSelectedColor(Color newColor) {
|
||||
if (newColor == null)
|
||||
return;
|
||||
|
||||
current = newColor;
|
||||
setIcon(createIcon(current, 16, 16));
|
||||
repaint();
|
||||
}
|
||||
|
||||
public static ImageIcon createIcon(Color main, int width, int height) {
|
||||
BufferedImage image = new BufferedImage(width, height, java.awt.image.BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setColor(main);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
graphics.setXORMode(Color.DARK_GRAY);
|
||||
graphics.drawRect(0, 0, width-1, height-1);
|
||||
image.flush();
|
||||
return new ImageIcon(image);
|
||||
}
|
||||
}
|
||||
79
Projektgruppe_XXX/src/gui/components/DicePanel.java
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package gui.components;
|
||||
|
||||
import gui.Resources;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
public class DicePanel extends JPanel {
|
||||
|
||||
private int diceValues[];
|
||||
private Random random;
|
||||
private Resources resources;
|
||||
private int numDices;
|
||||
|
||||
public DicePanel(Resources resources) {
|
||||
this.diceValues = new int[3];
|
||||
this.resources = resources;
|
||||
this.random = new Random();
|
||||
this.generateRandom(3);
|
||||
}
|
||||
|
||||
public int[] generateRandom(int numDices) {
|
||||
this.numDices = numDices;
|
||||
int[] result = new int[Math.min(numDices, diceValues.length)];
|
||||
|
||||
for (int i = 0; i < Math.min(numDices, diceValues.length); i++) {
|
||||
diceValues[i] = random.nextInt(6) + 1;
|
||||
result[i] = diceValues[i];
|
||||
}
|
||||
|
||||
repaint();
|
||||
return result;
|
||||
}
|
||||
|
||||
public int[] generateRandom(int numDices, boolean animate) throws InterruptedException {
|
||||
if(animate) {
|
||||
long duration = 1500;
|
||||
long start = System.currentTimeMillis();
|
||||
long end = start + duration;
|
||||
long lastTick = 0;
|
||||
|
||||
while(System.currentTimeMillis() < end) {
|
||||
long tick = System.currentTimeMillis() - start;
|
||||
double progress = (double)tick / (double) duration;
|
||||
long waitTime = (long) (200 * Math.pow(progress, 3) - 800 * Math.pow(progress, 2) + 850 * progress + 20);
|
||||
if(lastTick == 0 || (waitTime > 0 && (tick - lastTick) >= waitTime)) {
|
||||
lastTick = tick;
|
||||
generateRandom(numDices);
|
||||
} else {
|
||||
Thread.sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return generateRandom(numDices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int margin = 10;
|
||||
int diceCount = Math.min(diceValues.length, numDices);
|
||||
|
||||
int diceSize = Math.min(height - 2 * margin, (width - (diceCount + 1) * margin) / diceCount);
|
||||
int offsetX = (width - 2 * margin - (diceCount * diceSize)) / 2;
|
||||
|
||||
for(int i = 0; i < diceCount; i++) {
|
||||
int x = offsetX + i * (margin + diceSize);
|
||||
int y = margin;
|
||||
int value = (diceValues[i] - 1) % 6;
|
||||
g.drawImage(resources.getDice(value), x, y, diceSize, diceSize, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
527
Projektgruppe_XXX/src/gui/components/MapPanel.java
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
package gui.components;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.LineBorder;
|
||||
|
||||
import base.Edge;
|
||||
import game.AI;
|
||||
import game.Game;
|
||||
import game.map.PathFinding;
|
||||
import game.Player;
|
||||
import game.map.Castle;
|
||||
import game.map.GameMap;
|
||||
import game.players.Human;
|
||||
import gui.Resources;
|
||||
import gui.View;
|
||||
import gui.views.GameView;
|
||||
|
||||
public class MapPanel extends JScrollPane {
|
||||
|
||||
public enum Action {
|
||||
NONE,
|
||||
MOVING,
|
||||
ATTACKING
|
||||
}
|
||||
|
||||
private static final int CASTLE_SIZE = 50;
|
||||
private static final int ICON_SIZE = 20;
|
||||
private final GameView gameView;
|
||||
|
||||
private ImagePanel imagePanel;
|
||||
private GameMap map;
|
||||
private Point mousePos, oldView;
|
||||
private Castle selectedCastle;
|
||||
private boolean showConnections;
|
||||
private Resources resources;
|
||||
private Game game;
|
||||
private Action currentAction;
|
||||
private PathFinding pathFinding;
|
||||
private List<Edge<Castle>> highlightedEdges;
|
||||
private Castle targetCastle;
|
||||
|
||||
public MapPanel(GameView gameView, Resources resources) {
|
||||
super();
|
||||
this.gameView = gameView;
|
||||
this.setBorder(new LineBorder(Color.BLACK));
|
||||
this.setViewportView(this.imagePanel = new ImagePanel());
|
||||
this.addMouseListener(onMouseInput);
|
||||
this.addMouseMotionListener(onMouseInput);
|
||||
this.showConnections = false;
|
||||
this.setAutoscrolls(true);
|
||||
this.resources = resources;
|
||||
this.currentAction = Action.NONE;
|
||||
|
||||
this.getActionMap().put("Escape", new AbstractAction("Escape") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if(currentAction != Action.NONE) {
|
||||
currentAction = Action.NONE;
|
||||
targetCastle = null;
|
||||
highlightedEdges = null;
|
||||
repaint();
|
||||
} else if(selectedCastle != null) {
|
||||
selectedCastle = null;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Escape");
|
||||
}
|
||||
|
||||
private Castle getRegion(Point p) {
|
||||
if (map == null)
|
||||
return null;
|
||||
|
||||
for (Castle castle : map.getCastles()) {
|
||||
Point location = castle.getLocationOnMap();
|
||||
Rectangle rect = new Rectangle(location.x, location.y, CASTLE_SIZE, CASTLE_SIZE);
|
||||
if (rect.contains(p))
|
||||
return castle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean canPerformAction() {
|
||||
if(game.getCurrentPlayer() instanceof AI)
|
||||
return false;
|
||||
|
||||
if(game.isOver())
|
||||
return false;
|
||||
|
||||
return game.getAttackThread() == null;
|
||||
}
|
||||
|
||||
private MouseAdapter onMouseInput = new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent) {
|
||||
super.mousePressed(mouseEvent);
|
||||
oldView = getViewport().getViewPosition();
|
||||
mousePos = mouseEvent.getPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent mouseEvent) {
|
||||
super.mousePressed(mouseEvent);
|
||||
|
||||
if(getCursor().getType() == Cursor.MOVE_CURSOR)
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
super.mouseDragged(e);
|
||||
|
||||
if (oldView != null && mousePos != null && !mousePos.equals(e.getPoint())) {
|
||||
JViewport vp = getViewport();
|
||||
|
||||
|
||||
if (getWidth() < imagePanel.getWidth() || getHeight() < imagePanel.getHeight()) {
|
||||
int newX = (int) Math.max(0, oldView.getX() - (e.getX() - mousePos.getX()));
|
||||
int newY = (int) Math.max(0, oldView.getY() - (e.getY() - mousePos.getY()));
|
||||
|
||||
// don't update view position, if you cannot move in this direction (e.g. vp.w >= img.w)
|
||||
if(getWidth() >= imagePanel.getWidth())
|
||||
newX = vp.getViewPosition().x;
|
||||
|
||||
if(getHeight() >= imagePanel.getHeight())
|
||||
newY = vp.getViewPosition().y;
|
||||
|
||||
if(currentAction == Action.ATTACKING || currentAction == Action.MOVING)
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
else
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
|
||||
|
||||
Point newPoint = new Point(newX, newY);
|
||||
vp.setViewPosition(newPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
Point mousePos = cursorToMapLocation(e.getPoint());
|
||||
Player currentPlayer = game.getCurrentPlayer();
|
||||
boolean selectNew = true;
|
||||
Action lastAction = currentAction;
|
||||
|
||||
if (selectedCastle != null && canPerformAction()) {
|
||||
Point castlePos = selectedCastle.getLocationOnMap();
|
||||
|
||||
if(canChooseCastle()) {
|
||||
Rectangle iconCheck = getBoundsIconCheck(castlePos);
|
||||
if (iconCheck.contains(mousePos)) {
|
||||
game.chooseCastle(selectedCastle, currentPlayer);
|
||||
gameView.updateStats();
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
}
|
||||
} else if(selectedCastle.getOwner() == currentPlayer && game.getRound() > 1) {
|
||||
Rectangle iconPlus = getBoundsPlusIcon(castlePos);
|
||||
Rectangle iconArrow = getBoundsArrowIcon(castlePos);
|
||||
Rectangle iconSwords = getBoundsSwordsIcon(castlePos);
|
||||
selectNew = false;
|
||||
|
||||
if(iconPlus.contains(mousePos)) {
|
||||
if(currentPlayer.getRemainingTroops() > 0) {
|
||||
game.addTroops(currentPlayer, selectedCastle, 1);
|
||||
gameView.updateStats();
|
||||
}
|
||||
} else if (iconArrow.contains(mousePos)) {
|
||||
if(selectedCastle.getTroopCount() > 1) {
|
||||
currentAction = (currentAction == Action.MOVING ? Action.NONE : Action.MOVING);
|
||||
}
|
||||
} else if (iconSwords.contains(mousePos)) {
|
||||
if(canAttack()) {
|
||||
currentAction = (currentAction == Action.ATTACKING ? Action.NONE : Action.ATTACKING);
|
||||
}
|
||||
} else {
|
||||
selectNew = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(currentAction != Action.NONE) {
|
||||
if(lastAction != currentAction) {
|
||||
pathFinding = new PathFinding(game.getMap().getGraph(), selectedCastle, currentAction, currentPlayer);
|
||||
pathFinding.run();
|
||||
}
|
||||
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
}
|
||||
}
|
||||
|
||||
if(selectNew) {
|
||||
Castle nextCastle = getRegion(mousePos);
|
||||
if(nextCastle == null || nextCastle == selectedCastle || currentAction == Action.NONE) {
|
||||
currentAction = Action.NONE;
|
||||
selectedCastle = nextCastle;
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
} else if(currentAction == Action.MOVING) {
|
||||
NumberDialog nd = new NumberDialog("Wie viele Truppen möchtest du verschieben?", 1, selectedCastle.getTroopCount() - 1, 1);
|
||||
if(nd.showDialog(MapPanel.this)) {
|
||||
selectedCastle.moveTroops(nextCastle, nd.getValue());
|
||||
currentAction = Action.NONE;
|
||||
selectedCastle = null;
|
||||
highlightedEdges = null;
|
||||
targetCastle = null;
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
gameView.updateStats();
|
||||
}
|
||||
} else if(currentAction == Action.ATTACKING) {
|
||||
NumberDialog nd = new NumberDialog("Mit wie vielen Truppen möchtest du angreifen?", 1, selectedCastle.getTroopCount(), selectedCastle.getTroopCount());
|
||||
if(nd.showDialog(MapPanel.this)) {
|
||||
game.startAttack(selectedCastle, targetCastle, nd.getValue());
|
||||
currentAction = Action.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
Point mousePos = cursorToMapLocation(e.getPoint());
|
||||
|
||||
if (selectedCastle != null && getCursor().getType() != Cursor.MOVE_CURSOR) {
|
||||
Point castlePos = selectedCastle.getLocationOnMap();
|
||||
|
||||
if (canChooseCastle()) {
|
||||
Rectangle iconCheck = getBoundsIconCheck(castlePos);
|
||||
if (iconCheck.contains(mousePos)) {
|
||||
setToolTipText("Diese Burg besetzen");
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
return;
|
||||
}
|
||||
} else if(selectedCastle.getOwner() == game.getCurrentPlayer() && game.getRound() > 1) {
|
||||
Rectangle iconPlus = getBoundsPlusIcon(castlePos);
|
||||
Rectangle iconArrow = getBoundsArrowIcon(castlePos);
|
||||
Rectangle iconSwords = getBoundsSwordsIcon(castlePos);
|
||||
Rectangle bounds[] = { iconPlus, iconArrow, iconSwords };
|
||||
String tooltips[] = { "Truppen hinzufügen", "Truppen bewegen", "Burg angreifen" };
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
if (bounds[i].contains(mousePos)) {
|
||||
setToolTipText(tooltips[i]);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentAction == Action.MOVING || currentAction == Action.ATTACKING) {
|
||||
targetCastle = getRegion(mousePos);
|
||||
if(targetCastle != null) {
|
||||
if(currentAction != Action.ATTACKING || targetCastle.getOwner() != selectedCastle.getOwner()) {
|
||||
highlightedEdges = pathFinding.getPath(targetCastle);
|
||||
repaint();
|
||||
} else {
|
||||
targetCastle = null;
|
||||
}
|
||||
} else if(highlightedEdges != null) {
|
||||
highlightedEdges = null;
|
||||
targetCastle = null;
|
||||
repaint();
|
||||
}
|
||||
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
setToolTipText(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
setToolTipText(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private boolean canChooseCastle() {
|
||||
if (selectedCastle == null)
|
||||
return false;
|
||||
|
||||
return game.getCurrentPlayer() instanceof Human &&
|
||||
game.getCurrentPlayer().getRemainingTroops() > 0 &&
|
||||
game.getRound() == 1 &&
|
||||
selectedCastle.getOwner() == null;
|
||||
}
|
||||
|
||||
private boolean canAttack() {
|
||||
if(selectedCastle == null)
|
||||
return false;
|
||||
|
||||
return game.getCurrentPlayer() instanceof Human &&
|
||||
selectedCastle.getOwner() == game.getCurrentPlayer() &&
|
||||
selectedCastle.getTroopCount() > 1;
|
||||
}
|
||||
|
||||
private Rectangle getBoundsIconCheck(Point castlePos) {
|
||||
int x = (CASTLE_SIZE + 10 - ICON_SIZE) / 2 + castlePos.x - 5;
|
||||
int y = (castlePos.y - 5 - ICON_SIZE);
|
||||
|
||||
return new Rectangle(x, y, ICON_SIZE, ICON_SIZE);
|
||||
}
|
||||
|
||||
private Rectangle getBoundsPlusIcon(Point castlePos) {
|
||||
int totalWidth = 3 * (ICON_SIZE + 2);
|
||||
int x = castlePos.x - 5 + (CASTLE_SIZE + 10 - totalWidth) / 2;
|
||||
int y = castlePos.y - 6 - ICON_SIZE;
|
||||
return new Rectangle(x, y, ICON_SIZE, ICON_SIZE);
|
||||
}
|
||||
|
||||
private Rectangle getBoundsArrowIcon(Point castlePos) {
|
||||
Rectangle plusIcon = getBoundsPlusIcon(castlePos);
|
||||
plusIcon.x += ICON_SIZE + 2;
|
||||
return plusIcon;
|
||||
}
|
||||
|
||||
private Rectangle getBoundsSwordsIcon(Point castlePos) {
|
||||
Rectangle arrowIcon = getBoundsArrowIcon(castlePos);
|
||||
arrowIcon.x += ICON_SIZE + 2;
|
||||
return arrowIcon;
|
||||
}
|
||||
|
||||
public void showConnections(boolean showConnections) {
|
||||
this.showConnections = showConnections;
|
||||
repaint();
|
||||
}
|
||||
|
||||
// (0|0) -> (0 + offsetX|0 + offsetY)
|
||||
private Point translate(Point p) {
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
|
||||
if (getSize().getWidth() > map.getBackgroundImage().getWidth())
|
||||
offsetX = (int) ((getSize().getWidth() - map.getBackgroundImage().getWidth()) / 2);
|
||||
|
||||
if (getSize().getHeight() > map.getBackgroundImage().getHeight())
|
||||
offsetY = (int) ((getSize().getHeight() - map.getBackgroundImage().getHeight()) / 2);
|
||||
|
||||
return new Point(p.x + offsetX, p.y + offsetY);
|
||||
}
|
||||
|
||||
private Point cursorToMapLocation(Point p) {
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
|
||||
if (getSize().getWidth() > map.getBackgroundImage().getWidth())
|
||||
offsetX = (int) ((getSize().getWidth() - map.getBackgroundImage().getWidth()) / 2);
|
||||
|
||||
if (getSize().getHeight() > map.getBackgroundImage().getHeight())
|
||||
offsetY = (int) ((getSize().getHeight() - map.getBackgroundImage().getHeight()) / 2);
|
||||
|
||||
JViewport jp = this.getViewport();
|
||||
return new Point(p.x - offsetX + jp.getViewPosition().x, p.y - offsetY + jp.getViewPosition().y);
|
||||
}
|
||||
|
||||
public void setGame(Game game) {
|
||||
this.game = game;
|
||||
this.map = game.getMap();
|
||||
this.imagePanel.setSize(map.getSize());
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
class ImagePanel extends JPanel {
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
return map != null ? map.getSize() : new Dimension();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
Point offset = translate(new Point(0, 0));
|
||||
|
||||
if (map != null) {
|
||||
g.drawImage(map.getBackgroundImage(), offset.x, offset.y, null);
|
||||
|
||||
if (showConnections) {
|
||||
for (Edge<Castle> edge : map.getEdges()) {
|
||||
Point p1 = translate(edge.getNodeA().getValue().getLocationOnMap());
|
||||
Point p2 = translate(edge.getNodeB().getValue().getLocationOnMap());
|
||||
|
||||
if(highlightedEdges != null && highlightedEdges.contains(edge)) {
|
||||
g2.setStroke(new BasicStroke(3));
|
||||
g.setColor(Color.RED);
|
||||
} else {
|
||||
g2.setStroke(new BasicStroke(1));
|
||||
g.setColor(Color.WHITE);
|
||||
}
|
||||
|
||||
g2.draw(new Line2D.Float(p1.x + CASTLE_SIZE / 2.0f, p1.y + CASTLE_SIZE / 2.0f, p2.x + CASTLE_SIZE / 2.0f, p2.y + CASTLE_SIZE / 2.0f));
|
||||
g2.setStroke(new BasicStroke(1));
|
||||
}
|
||||
}
|
||||
|
||||
for (Castle region : map.getCastles()) {
|
||||
Color color = region.getOwner() == null ? Color.WHITE : region.getOwner().getColor();
|
||||
Point location = translate(region.getLocationOnMap());
|
||||
BufferedImage castle = resources.getCastle(color, region.getType());
|
||||
g.drawImage(castle, location.x, location.y, null);
|
||||
|
||||
// Draw troop count
|
||||
if(region.getTroopCount() > 0) {
|
||||
BufferedImage unitIcon = resources.getUnitIcon();
|
||||
String str = String.valueOf(region.getTroopCount());
|
||||
Dimension strDimensions = View.calculateTextSize(str, g.getFont());
|
||||
Font troopCountFont = new Font(g.getFont().getName(), Font.BOLD, 15);
|
||||
FontMetrics fm = g.getFontMetrics(troopCountFont);
|
||||
|
||||
int totalWidth = strDimensions.width + 2 + unitIcon.getWidth();
|
||||
int textX = location.x + (castle.getWidth() - totalWidth) / 2;
|
||||
int textY = location.y + castle.getHeight();
|
||||
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRoundRect(textX - 2, textY - 2, totalWidth + 4, unitIcon.getHeight() + 4, 5, 5);
|
||||
g.setColor(Color.BLACK);
|
||||
g.setFont(troopCountFont);
|
||||
g.drawString(str, textX, textY + fm.getAscent());
|
||||
g.drawImage(unitIcon, textX + 2 + strDimensions.width, textY, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw overlay icon if highlighted
|
||||
if(currentAction != Action.NONE && targetCastle != null && highlightedEdges != null && canPerformAction()) {
|
||||
BufferedImage icon = (currentAction == Action.ATTACKING ? resources.getSwordsIcon() : resources.getArrowIcon());
|
||||
Point targetLocation = translate(targetCastle.getLocationOnMap());
|
||||
int x = targetLocation.x + (CASTLE_SIZE - ICON_SIZE) / 2;
|
||||
int y = targetLocation.y + (CASTLE_SIZE - ICON_SIZE) / 2;
|
||||
g.drawImage(icon, x, y, ICON_SIZE, ICON_SIZE, null);
|
||||
}
|
||||
|
||||
// HUD
|
||||
if (selectedCastle != null) {
|
||||
|
||||
Point location = translate(selectedCastle.getLocationOnMap());
|
||||
g.setColor(selectedCastle.getOwner() == null ? Color.WHITE : selectedCastle.getOwner().getColor());
|
||||
g.drawRect(location.x - 5, location.y - 5, CASTLE_SIZE + 10, CASTLE_SIZE + 10);
|
||||
|
||||
if(canPerformAction()) {
|
||||
if (canChooseCastle()) {
|
||||
BufferedImage icon = resources.getCheckIcon();
|
||||
Rectangle bounds = getBoundsIconCheck(location);
|
||||
g.drawImage(icon, bounds.x, bounds.y, ICON_SIZE, ICON_SIZE, null);
|
||||
} else if (selectedCastle.getOwner() == game.getCurrentPlayer() && game.getRound() > 1) {
|
||||
boolean hasTroops = game.getCurrentPlayer().getRemainingTroops() > 0;
|
||||
boolean canMove = selectedCastle.getTroopCount() > 1;
|
||||
|
||||
BufferedImage plusIcon = hasTroops ? resources.getPlusIcon() : resources.getPlusIconDeactivated();
|
||||
BufferedImage swordsIcon = resources.getSwordsIcon();
|
||||
BufferedImage arrowIcon = canMove ? resources.getArrowIcon() : resources.getArrowIconDeactivated();
|
||||
|
||||
int totalWidth = 3 * (ICON_SIZE + 2);
|
||||
int iconsX = location.x - 5 + (CASTLE_SIZE + 10 - totalWidth) / 2;
|
||||
int iconsY = location.y - 6 - ICON_SIZE;
|
||||
|
||||
BufferedImage icons[] = {plusIcon, arrowIcon, swordsIcon};
|
||||
for (int i = 0; i < icons.length; i++)
|
||||
g.drawImage(icons[i], iconsX + (ICON_SIZE + 2) * i, iconsY, ICON_SIZE, ICON_SIZE, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
super.paint(g);
|
||||
|
||||
if(selectedCastle != null) {
|
||||
|
||||
String titleText;
|
||||
if(currentAction == Action.NONE) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append(selectedCastle.getName());
|
||||
if (selectedCastle.getOwner() != null)
|
||||
text.append(" - Besitzer: ").append(selectedCastle.getOwner().getName());
|
||||
if (selectedCastle.getTroopCount() > 0)
|
||||
text.append(" - Truppen: ").append(selectedCastle.getTroopCount());
|
||||
|
||||
titleText = text.toString();
|
||||
} else if(currentAction == Action.MOVING) {
|
||||
titleText = "Truppen verschieben";
|
||||
} else if(currentAction == Action.ATTACKING) {
|
||||
titleText = "Eine Burg angreifen";
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
Font font = View.createFont(20);
|
||||
g.setFont(font);
|
||||
Dimension titleSize = View.calculateTextSize(titleText, font);
|
||||
titleSize.width += 6;
|
||||
titleSize.height += 3;
|
||||
|
||||
Point textPos = (new Point((MapPanel.this.getWidth() - titleSize.width) / 2, -5));
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRect(textPos.x, textPos.y , titleSize.width, titleSize.height);
|
||||
g.setColor(Color.BLACK);
|
||||
g.drawString(titleText, textPos.x + 3, textPos.y + titleSize.height - 5);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
this.selectedCastle = null;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
currentAction = MapPanel.Action.NONE;
|
||||
selectedCastle = null;
|
||||
highlightedEdges = null;
|
||||
targetCastle = null;
|
||||
setCursor(Cursor.getDefaultCursor());
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
129
Projektgruppe_XXX/src/gui/components/NumberChooser.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package gui.components;
|
||||
|
||||
import gui.View;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class NumberChooser extends JComponent implements MouseListener {
|
||||
|
||||
private int min, max, value;
|
||||
private boolean minClicked, maxClicked;
|
||||
private List<ValueListener> valueListeners;
|
||||
|
||||
public NumberChooser(int min, int max, int val) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.setValue(val);
|
||||
this.valueListeners = new LinkedList<>();
|
||||
this.addMouseListener(this);
|
||||
}
|
||||
|
||||
private void setValue(int val) {
|
||||
int prev = this.value;
|
||||
this.value = clamp(val, min, max);
|
||||
repaint();
|
||||
if(prev != this.value && this.valueListeners != null)
|
||||
this.valueListeners.forEach(l -> l.valueChanged(prev, value));
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void decrement() {
|
||||
this.setValue(this.getValue() - 1);
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
this.setValue(this.getValue() + 1);
|
||||
}
|
||||
|
||||
public void addValueListener(ValueListener valueListener) {
|
||||
this.valueListeners.add(valueListener);
|
||||
}
|
||||
|
||||
private static int clamp(int val, int min, int max) {
|
||||
return Math.max(Math.min(val, max), min);
|
||||
}
|
||||
|
||||
private int getButtonWidth() {
|
||||
return Math.max(25, (int) ((getWidth() - 1) * 0.1));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
|
||||
int w = getWidth() - 1;
|
||||
int h = getHeight() - 1;
|
||||
int buttonWidth = getButtonWidth();
|
||||
|
||||
// Borders
|
||||
g.setColor(Color.DARK_GRAY);
|
||||
g.drawRect(0, 0, w, h);
|
||||
g.drawRect(0, 0, buttonWidth, h);
|
||||
g.drawRect(w - buttonWidth, 0, buttonWidth, h);
|
||||
|
||||
// Background Colors
|
||||
g.setColor(minClicked ? Color.GRAY : Color.LIGHT_GRAY);
|
||||
g.fillRect(1, 1, buttonWidth - 1, h - 1);
|
||||
g.setColor(maxClicked ? Color.GRAY : Color.LIGHT_GRAY);
|
||||
g.fillRect(w - buttonWidth + 1, 1, buttonWidth - 1, h - 1);
|
||||
|
||||
// Text
|
||||
Font font = View.createFont(16);
|
||||
FontMetrics fm = g.getFontMetrics(font);
|
||||
g.setColor(Color.BLACK);
|
||||
g.setFont(font);
|
||||
|
||||
Dimension dim1 = View.calculateTextSize("-", font);
|
||||
Dimension dim2 = View.calculateTextSize("+", font);
|
||||
Dimension dim3 = View.calculateTextSize(String.valueOf(value), font);
|
||||
|
||||
g.drawString("-", (int) ((buttonWidth - dim1.getWidth()) / 2) - 1, (int)((h - dim1.getHeight()) / 2 + fm.getAscent()) - 1);
|
||||
g.drawString("+", w - buttonWidth + (int) ((buttonWidth - dim2.getWidth()) / 2), (int)((h - dim2.getHeight()) / 2 + fm.getAscent()) - 1);
|
||||
g.drawString(String.valueOf(value), buttonWidth + (int) ((w - 2 * buttonWidth - dim3.getWidth()) / 2) - 1, (int)((h - dim3.getHeight()) / 2 + fm.getAscent()) - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent mouseEvent) {
|
||||
if(mouseEvent.getButton() == MouseEvent.BUTTON1) {
|
||||
int x = mouseEvent.getX();
|
||||
|
||||
if(x < getButtonWidth())
|
||||
decrement();
|
||||
else if(x >= getWidth() - getButtonWidth())
|
||||
increment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent mouseEvent) {
|
||||
if(mouseEvent.getButton() == MouseEvent.BUTTON1) {
|
||||
int x = mouseEvent.getX();
|
||||
minClicked = x < getButtonWidth();
|
||||
maxClicked = x >= getWidth() - getButtonWidth();
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent mouseEvent) {
|
||||
if (mouseEvent.getButton() == MouseEvent.BUTTON1) {
|
||||
minClicked = false;
|
||||
maxClicked = false;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent mouseEvent) { }
|
||||
@Override
|
||||
public void mouseExited(MouseEvent mouseEvent) { }
|
||||
}
|
||||
39
Projektgruppe_XXX/src/gui/components/NumberDialog.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package gui.components;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.InputMismatchException;
|
||||
|
||||
public class NumberDialog {
|
||||
|
||||
private String text;
|
||||
private int min, max, value;
|
||||
|
||||
public NumberDialog(String text, int min, int max, int initial) {
|
||||
this.text = text;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.value = initial;
|
||||
}
|
||||
|
||||
boolean showDialog(JComponent parent) {
|
||||
String result = JOptionPane.showInputDialog(parent, text, String.valueOf(value));
|
||||
|
||||
if(result == null)
|
||||
return false;
|
||||
|
||||
result = result.trim();
|
||||
try {
|
||||
value = Integer.parseInt(result);
|
||||
if(value >= min && value <= max)
|
||||
return true;
|
||||
} catch(NumberFormatException | InputMismatchException ignored) {
|
||||
}
|
||||
|
||||
JOptionPane.showMessageDialog(parent, "Bitte gib eine gültige Zahl ein.", "Ungültige Eingabe", JOptionPane.ERROR_MESSAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
6
Projektgruppe_XXX/src/gui/components/ValueListener.java
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package gui.components;
|
||||
|
||||
public interface ValueListener {
|
||||
|
||||
void valueChanged(Object oldValue, Object newValue);
|
||||
}
|
||||
220
Projektgruppe_XXX/src/gui/views/GameMenu.java
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package gui.views;
|
||||
|
||||
import game.*;
|
||||
import game.map.MapSize;
|
||||
import gui.GameWindow;
|
||||
import gui.View;
|
||||
import gui.components.ColorChooserButton;
|
||||
import gui.components.NumberChooser;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class GameMenu extends View {
|
||||
|
||||
private JLabel lblTitle;
|
||||
private JLabel lblPlayerCount;
|
||||
private JLabel lblMapSize;
|
||||
private JLabel lblGoal;
|
||||
private JTextArea lblGoalDescription;
|
||||
|
||||
private NumberChooser playerCount;
|
||||
private JComboBox mapSize;
|
||||
private JComboBox goal;
|
||||
private JComponent[][] playerConfig;
|
||||
private JButton btnStart, btnBack;
|
||||
|
||||
// map size, type?
|
||||
// goal?
|
||||
|
||||
public GameMenu(GameWindow gameWindow) {
|
||||
super(gameWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResize() {
|
||||
|
||||
int offsetY = 25;
|
||||
int offsetX = 25;
|
||||
|
||||
lblTitle.setLocation(offsetX, offsetY);
|
||||
offsetY += 50;
|
||||
|
||||
int columnWidth = Math.max(300, (getWidth() - 75) / 2);
|
||||
|
||||
// Column 1
|
||||
offsetX = (getWidth() - 2*columnWidth - 25) / 2 + (columnWidth - 350) / 2;
|
||||
lblPlayerCount.setLocation(offsetX, offsetY + 2);
|
||||
playerCount.setLocation(offsetX + lblPlayerCount.getWidth() + 10, offsetY);
|
||||
offsetY += 50;
|
||||
|
||||
for(int i = 0; i < GameConstants.MAX_PLAYERS; i++) {
|
||||
int tempOffsetX = offsetX;
|
||||
for(JComponent c : playerConfig[i]) {
|
||||
c.setLocation(tempOffsetX, offsetY);
|
||||
tempOffsetX += c.getWidth() + 10;
|
||||
c.setEnabled(i < playerCount.getValue());
|
||||
}
|
||||
|
||||
offsetY += 40;
|
||||
}
|
||||
|
||||
// Column 2
|
||||
offsetY = 125 - lblMapSize.getHeight();
|
||||
offsetX = (getWidth() - 2*columnWidth - 25) / 2 + columnWidth + 25 + (columnWidth - mapSize.getWidth()) / 2;
|
||||
lblMapSize.setLocation(offsetX, offsetY); offsetY += lblMapSize.getHeight();
|
||||
mapSize.setLocation(offsetX, offsetY); offsetY += mapSize.getHeight() + 10;
|
||||
lblGoal.setLocation(offsetX, offsetY); offsetY += lblGoal.getHeight();
|
||||
goal.setLocation(offsetX, offsetY); offsetY += goal.getHeight();
|
||||
lblGoalDescription.setLocation(offsetX, offsetY);
|
||||
lblGoalDescription.setSize(goal.getWidth() + 25, getHeight() - offsetY - BUTTON_SIZE.height - 50);
|
||||
|
||||
// Button bar
|
||||
offsetY = this.getHeight() - BUTTON_SIZE.height - 25;
|
||||
offsetX = (this.getWidth() - 2*BUTTON_SIZE.width - 25) / 2;
|
||||
btnBack.setLocation(offsetX, offsetY);
|
||||
btnStart.setLocation(offsetX + BUTTON_SIZE.width + 25, offsetY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
|
||||
// Title
|
||||
lblTitle = createLabel("Neues Spiel starten", 25, true);
|
||||
|
||||
// Player Count
|
||||
lblPlayerCount = createLabel("Anzahl Spieler:", 16);
|
||||
playerCount = new NumberChooser(2, GameConstants.MAX_PLAYERS, 2);
|
||||
playerCount.setSize(125, 25);
|
||||
playerCount.addValueListener((oldValue, newValue) -> onResize());
|
||||
add(playerCount);
|
||||
|
||||
// Player rows:
|
||||
// [Number] [Color] [Name] [Human/AI] (Team?)
|
||||
Vector<String> playerTypes = new Vector<>();
|
||||
for(Class<?> c : GameConstants.PLAYER_TYPES)
|
||||
playerTypes.add(c.getSimpleName());
|
||||
|
||||
playerConfig = new JComponent[GameConstants.MAX_PLAYERS][];
|
||||
for(int i = 0; i < GameConstants.MAX_PLAYERS; i++) {
|
||||
playerConfig[i] = new JComponent[] {
|
||||
createLabel(String.format("%d.", i + 1),16),
|
||||
new ColorChooserButton(GameConstants.PLAYER_COLORS[i]),
|
||||
new JTextField(String.format("Spieler %d", i + 1)),
|
||||
new JComboBox<>(playerTypes)
|
||||
};
|
||||
|
||||
playerConfig[i][1].setSize(25, 25);
|
||||
playerConfig[i][2].setSize(200, 25);
|
||||
playerConfig[i][3].setSize(125, 25);
|
||||
|
||||
for(JComponent c : playerConfig[i])
|
||||
add(c);
|
||||
}
|
||||
|
||||
// GameMap config
|
||||
lblMapSize = createLabel("Kartengröße", 16);
|
||||
mapSize = createCombobox(MapSize.getMapSizes(), MapSize.MEDIUM.ordinal());
|
||||
|
||||
// Goals
|
||||
Vector<String> goalNames = new Vector<>();
|
||||
for(Goal goal : GameConstants.GAME_GOALS)
|
||||
goalNames.add(goal.getName());
|
||||
|
||||
lblGoal = createLabel("Mission", 16);
|
||||
lblGoalDescription = createTextArea(GameConstants.GAME_GOALS[0].getDescription(), true);
|
||||
|
||||
goal = createCombobox(goalNames, 0);
|
||||
goal.addItemListener(itemEvent -> {
|
||||
int i = goal.getSelectedIndex();
|
||||
if(i < 0 || i >= GameConstants.GAME_GOALS.length)
|
||||
lblGoalDescription.setText("");
|
||||
else
|
||||
lblGoalDescription.setText(GameConstants.GAME_GOALS[i].getDescription());
|
||||
});
|
||||
|
||||
// Buttons
|
||||
btnBack = createButton("Zurück");
|
||||
btnStart = createButton("Starten");
|
||||
|
||||
getWindow().setSize(750, 450);
|
||||
getWindow().setMinimumSize(new Dimension(750, 450));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if(actionEvent.getSource() == btnBack)
|
||||
getWindow().setView(new StartScreen(getWindow()));
|
||||
else if(actionEvent.getSource() == btnStart) {
|
||||
|
||||
try {
|
||||
|
||||
// Check Inputs
|
||||
int playerCount = this.playerCount.getValue();
|
||||
int mapSize = this.mapSize.getSelectedIndex();
|
||||
int goalIndex = this.goal.getSelectedIndex();
|
||||
|
||||
// Should never happen
|
||||
if (playerCount < 2 || playerCount > GameConstants.MAX_PLAYERS) {
|
||||
showErrorMessage("Bitte geben Sie eine gültige Spielerzahl an.", "Ungültige Eingaben");
|
||||
return;
|
||||
}
|
||||
|
||||
// Should never happen
|
||||
if (mapSize < 0 || mapSize >= MapSize.values().length) {
|
||||
showErrorMessage("Bitte geben Sie eine gültige Kartengröße an.", "Ungültige Eingaben");
|
||||
return;
|
||||
}
|
||||
|
||||
// Should never happen
|
||||
if (goalIndex < 0 || goalIndex >= GameConstants.GAME_GOALS.length) {
|
||||
showErrorMessage("Bitte geben Sie ein gültiges Spielziel an.", "Ungültige Eingaben");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create Players
|
||||
Game game = new Game();
|
||||
for (int i = 0; i < playerCount; i++) {
|
||||
String name = ((JTextField) playerConfig[i][2]).getText().replaceAll(";", "").trim();
|
||||
if (name.isEmpty()) {
|
||||
showErrorMessage(String.format("Bitte geben Sie einen gültigen Namen für Spieler %d an.", i + 1), "Ungültige Eingaben");
|
||||
return;
|
||||
}
|
||||
|
||||
Color color = ((ColorChooserButton) playerConfig[i][1]).getSelectedColor();
|
||||
int playerType = ((JComboBox) playerConfig[i][3]).getSelectedIndex();
|
||||
|
||||
if (playerType < 0 || playerType >= GameConstants.PLAYER_TYPES.length) {
|
||||
showErrorMessage(String.format("Bitte geben Sie einen gültigen Spielertyp für Spieler %d an.", i + 1), "Ungültige Eingaben");
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = Player.createPlayer(GameConstants.PLAYER_TYPES[playerType], name, color);
|
||||
if (player == null) {
|
||||
showErrorMessage(String.format("Fehler beim Erstellen von Spieler %d", i + 1), "Unbekannter Fehler");
|
||||
return;
|
||||
}
|
||||
|
||||
game.addPlayer(player);
|
||||
}
|
||||
|
||||
// Set Goal
|
||||
Goal goal = GameConstants.GAME_GOALS[goalIndex];
|
||||
GameView gameView = new GameView(getWindow(), game);
|
||||
game.setMapSize(MapSize.values()[mapSize]);
|
||||
game.setGoal(goal);
|
||||
game.start(gameView);
|
||||
getWindow().setView(gameView);
|
||||
} catch(IllegalArgumentException ex) {
|
||||
ex.printStackTrace();
|
||||
showErrorMessage("Fehler beim Erstellen des Spiels: " + ex.getMessage(), "Interner Fehler");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
318
Projektgruppe_XXX/src/gui/views/GameView.java
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
package gui.views;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Style;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
|
||||
import game.AI;
|
||||
import game.Game;
|
||||
import game.GameInterface;
|
||||
import game.Player;
|
||||
import game.map.Castle;
|
||||
import gui.GameWindow;
|
||||
import gui.View;
|
||||
import gui.components.DicePanel;
|
||||
import gui.components.MapPanel;
|
||||
|
||||
public class GameView extends View implements GameInterface {
|
||||
|
||||
private MapPanel map;
|
||||
private JScrollPane scrollLog;
|
||||
private JTextPane txtStats;
|
||||
private DicePanel dices;
|
||||
private JTextPane gameLog;
|
||||
private JButton button;
|
||||
private Game game;
|
||||
|
||||
GameView(GameWindow gameWindow, Game game) {
|
||||
super(gameWindow);
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
private int sidebarWidth() {
|
||||
return (int) Math.max(getWidth() * 0.15, 300);
|
||||
}
|
||||
|
||||
private Dimension mapPanelSize() {
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
return new Dimension(w - sidebarWidth() - 40, h - 20);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResize() {
|
||||
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
|
||||
int sidebarWidth = sidebarWidth();
|
||||
Dimension mapPanelSize = mapPanelSize();
|
||||
this.map.setBounds(10, 10, mapPanelSize.width, mapPanelSize.height);
|
||||
this.map.revalidate();
|
||||
this.map.repaint();
|
||||
|
||||
int x = w - sidebarWidth - 20;
|
||||
int y = 10;
|
||||
|
||||
txtStats.setSize(sidebarWidth, 50 + 20 * game.getPlayers().size());
|
||||
dices.setSize(sidebarWidth, 50);
|
||||
scrollLog.setSize(sidebarWidth, h - txtStats.getHeight() - dices.getHeight() - 50 - BUTTON_SIZE.height);
|
||||
scrollLog.revalidate();
|
||||
scrollLog.repaint();
|
||||
|
||||
button.setSize(sidebarWidth, BUTTON_SIZE.height);
|
||||
|
||||
JComponent components[] = { txtStats, dices, scrollLog, button};
|
||||
for(JComponent component : components) {
|
||||
component.setLocation(x, y);
|
||||
y += 10 + component.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
|
||||
this.add(this.map = new MapPanel(this, getWindow().getResources()));
|
||||
this.map.showConnections(true);
|
||||
|
||||
this.txtStats = createTextPane();
|
||||
this.txtStats.addStyle("PlayerColors", null);
|
||||
this.add(txtStats);
|
||||
this.dices = new DicePanel(getWindow().getResources());
|
||||
this.dices.setBorder(new LineBorder(Color.BLACK));
|
||||
this.add(dices);
|
||||
this.gameLog = createTextPane();
|
||||
this.gameLog.addStyle("PlayerColor", null);
|
||||
this.scrollLog = new JScrollPane(gameLog);
|
||||
this.add(scrollLog);
|
||||
this.button = createButton("Nächste Runde");
|
||||
|
||||
getWindow().setSize(1080, 780);
|
||||
getWindow().setMinimumSize(new Dimension(750, 450));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if(actionEvent.getSource() == button) {
|
||||
|
||||
switch (button.getText()) {
|
||||
case "Nächste Runde":
|
||||
|
||||
if (game.getCurrentPlayer() instanceof AI)
|
||||
return;
|
||||
|
||||
if (game.getCurrentPlayer().getRemainingTroops() > 0) {
|
||||
if (game.getRound() == 1) {
|
||||
int troops = game.getCurrentPlayer().getRemainingTroops();
|
||||
JOptionPane.showMessageDialog(this, String.format("Du musst noch %s auswählen.",
|
||||
troops == 1 ? "eine Burg" : troops + " Burgen"),
|
||||
"Burgen auswählen", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
} else {
|
||||
int choice = JOptionPane.showOptionDialog(this, "Du hast noch unverteilte Truppen.\nBist du dir sicher, dass du die Runde beenden möchtest?",
|
||||
"Runde beenden?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.INFORMATION_MESSAGE,
|
||||
null, new String[]{"Runde beenden", "Abbrechen"}, "Runde beenden");
|
||||
|
||||
if (choice == 1)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
game.nextTurn();
|
||||
break;
|
||||
|
||||
case "Beenden":
|
||||
getWindow().setView(new StartScreen(getWindow()));
|
||||
break;
|
||||
|
||||
case "Überspringen":
|
||||
if(game.getAttackThread() != null)
|
||||
game.getAttackThread().fastForward();
|
||||
else if(game.getCurrentPlayer() instanceof AI) {
|
||||
((AI)game.getCurrentPlayer()).fastForward();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateStats() {
|
||||
txtStats.setText("");
|
||||
StyledDocument doc = txtStats.getStyledDocument();
|
||||
Style style = doc.getStyle("PlayerColors");
|
||||
|
||||
try {
|
||||
doc.insertString(doc.getLength(), String.format("Runde: %d", game.getRound()), null);
|
||||
if (game.getCurrentPlayer() != null) {
|
||||
doc.insertString(doc.getLength(), ", Am Zug: ", null);
|
||||
StyleConstants.setForeground(style, game.getCurrentPlayer().getColor());
|
||||
doc.insertString(doc.getLength(), game.getCurrentPlayer().getName(), style);
|
||||
}
|
||||
|
||||
doc.insertString(doc.getLength(), "\n\nName\tPunkte\tBurgen\tTruppen\n", null);
|
||||
for (Player p : game.getPlayers()) {
|
||||
StyleConstants.setForeground(style, p.getColor());
|
||||
doc.insertString(doc.getLength(), p.getName(), style);
|
||||
doc.insertString(doc.getLength(), String.format(":\t%d\t%d\t%d\n", p.getPoints(), p.getNumRegions(game), p.getRemainingTroops()), null);
|
||||
}
|
||||
} catch(BadLocationException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void logText(String text) {
|
||||
Style style = this.gameLog.getStyle("PlayerColor");
|
||||
StyleConstants.setForeground(style, Color.BLACK);
|
||||
StyledDocument doc = this.gameLog.getStyledDocument();
|
||||
|
||||
try { doc.insertString(doc.getLength(), text, style); }
|
||||
catch (BadLocationException ignored) {}
|
||||
}
|
||||
|
||||
private void logLine(String line) {
|
||||
this.logText(line + "\n");
|
||||
gameLog.setCaretPosition(gameLog.getDocument().getLength());
|
||||
}
|
||||
|
||||
private void logLine(String line, Player... playerFormat) {
|
||||
|
||||
StyledDocument doc = this.gameLog.getStyledDocument();
|
||||
Style style = this.gameLog.getStyle("PlayerColor");
|
||||
|
||||
int num = 0;
|
||||
int index;
|
||||
while(!line.isEmpty() && (index = line.indexOf("%PLAYER%")) != -1) {
|
||||
|
||||
if(index > 0)
|
||||
this.logText(line.substring(0, index));
|
||||
|
||||
if(num < playerFormat.length) {
|
||||
Player insert = playerFormat[num++];
|
||||
StyleConstants.setForeground(style, insert.getColor());
|
||||
try { doc.insertString(doc.getLength(), insert.getName(), style); }
|
||||
catch (BadLocationException ignored) {}
|
||||
}
|
||||
|
||||
line = line.substring(index + ("%PLAYER%").length());
|
||||
}
|
||||
|
||||
logLine(line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCastleChosen(Castle castle, Player player) {
|
||||
logLine("%PLAYER% wählt " + castle.getName() + ".", player);
|
||||
updateStats();
|
||||
map.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNextTurn(Player currentPlayer, int troopsGot, boolean human) {
|
||||
this.logLine("%PLAYER% ist am Zug.", currentPlayer);
|
||||
|
||||
if(game.getRound() == 1)
|
||||
this.logLine("%PLAYER% muss " + troopsGot + " Burgen auswählen.", currentPlayer);
|
||||
else
|
||||
this.logLine("%PLAYER% erhält " + troopsGot + " Truppen.", currentPlayer);
|
||||
|
||||
map.clearSelection();
|
||||
updateStats();
|
||||
|
||||
button.setText(human ? "Nächste Runde" : "Überspringen");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewRound(int round) {
|
||||
this.logLine(String.format("Runde %d.", round));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGameOver(Player winner) {
|
||||
if(winner == null) {
|
||||
this.logLine("Spiel vorbei - Unentschieden.");
|
||||
} else {
|
||||
this.logLine("Spiel vorbei - %PLAYER% gewinnt das Spiel.", winner);
|
||||
}
|
||||
|
||||
button.setText("Beenden");
|
||||
updateStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGameStarted(Game game) {
|
||||
this.map.setGame(game);
|
||||
this.gameLog.setText("");
|
||||
this.logLine("Neues Spiel gestartet.");
|
||||
this.updateStats();
|
||||
|
||||
Dimension mapSize = game.getMap().getSize();
|
||||
Dimension panelSize = mapPanelSize();
|
||||
if(mapSize.getWidth() > panelSize.getWidth() || mapSize.getHeight() > panelSize.getHeight()) {
|
||||
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
|
||||
int newWidth = Math.min(screenSize.width, mapSize.width) + getWidth() - panelSize.width + 50;
|
||||
int newHeight = Math.min(screenSize.height, mapSize.height) + getHeight() - panelSize.height + 50;
|
||||
|
||||
getWindow().setSize(newWidth, newHeight);
|
||||
getWindow().setLocationRelativeTo(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConquer(Castle castle, Player player) {
|
||||
logLine("%PLAYER% erobert " + castle.getName(), player);
|
||||
updateStats();
|
||||
map.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpdate() {
|
||||
updateStats();
|
||||
map.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddScore(Player player, int score) {
|
||||
updateStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] onRoll(Player player, int dices, boolean fastForward) {
|
||||
try {
|
||||
int[] roll = this.dices.generateRandom(dices, !fastForward);
|
||||
StringBuilder rolls = new StringBuilder();
|
||||
rolls.append("%PLAYER% würfelt: ");
|
||||
for(int i = 0; i < roll.length; i++) {
|
||||
rolls.append(i == 0 ? " " : ", ");
|
||||
rolls.append(roll[i]);
|
||||
}
|
||||
|
||||
logLine(rolls.toString(), player);
|
||||
return roll;
|
||||
} catch(InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttackStarted(Castle source, Castle target, int troopCount) {
|
||||
button.setText("Überspringen");
|
||||
logLine("%PLAYER% greift " + target.getName() + " mit " + troopCount + " Truppen an.", source.getOwner());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttackStopped() {
|
||||
map.reset();
|
||||
updateStats();
|
||||
button.setText("Nächste Runde");
|
||||
}
|
||||
}
|
||||
67
Projektgruppe_XXX/src/gui/views/HighscoreView.java
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package gui.views;
|
||||
|
||||
import game.ScoreEntry;
|
||||
import gui.GameWindow;
|
||||
import gui.Resources;
|
||||
import gui.View;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
public class HighscoreView extends View {
|
||||
|
||||
private JButton btnBack;
|
||||
// private JTextPane txtScores;
|
||||
private JTable scoreTable;
|
||||
private JLabel lblTitle;
|
||||
private JScrollPane scrollPane;
|
||||
|
||||
public HighscoreView(GameWindow gameWindow) {
|
||||
super(gameWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResize() {
|
||||
int offsetY = 25;
|
||||
lblTitle.setLocation((getWidth() - lblTitle.getWidth()) / 2, offsetY); offsetY += lblTitle.getSize().height + 25;
|
||||
// txtScores.setLocation(25, offsetY);
|
||||
// txtScores.setSize(getWidth() - 50, getHeight() - 50 - BUTTON_SIZE.height - offsetY);
|
||||
scrollPane.setLocation(25, offsetY);
|
||||
scrollPane.setSize(getWidth() - 50, getHeight() - 50 - BUTTON_SIZE.height - offsetY);
|
||||
|
||||
btnBack.setLocation((getWidth() - BUTTON_SIZE.width) / 2, getHeight() - BUTTON_SIZE.height - 25);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
btnBack = createButton("Zurück");
|
||||
lblTitle = createLabel("Highscores", 25, true);
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
|
||||
Resources resources = Resources.getInstance();
|
||||
String[] columns = { "Datum", "Name", "Spielmodus", "Punkte" };
|
||||
Object[][] data = new Object[resources.getScoreEntries().size()][];
|
||||
for(int i = 0; i < resources.getScoreEntries().size(); i++) {
|
||||
ScoreEntry scoreEntry = resources.getScoreEntries().get(i);
|
||||
data[i] = new Object[] {
|
||||
simpleDateFormat.format(scoreEntry.getDate()),
|
||||
scoreEntry.getName(),
|
||||
scoreEntry.getMode(),
|
||||
scoreEntry.getScore()
|
||||
};
|
||||
}
|
||||
|
||||
scoreTable = new JTable(data, columns);
|
||||
scoreTable.setBackground(this.getBackground());
|
||||
scoreTable.setFillsViewportHeight(true);
|
||||
|
||||
scrollPane = new JScrollPane(scoreTable);
|
||||
add(scrollPane);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
getWindow().setView(new StartScreen(getWindow()));
|
||||
}
|
||||
}
|
||||
67
Projektgruppe_XXX/src/gui/views/InfoView.java
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package gui.views;
|
||||
|
||||
import gui.GameWindow;
|
||||
import gui.View;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public class InfoView extends View {
|
||||
|
||||
private static final String ABOUT_TEXT =
|
||||
"~ Game of Castles ~\nFOP-Projekt WiSe 18/19\n" +
|
||||
"Hauptautor: Roman Hergenreder\n" +
|
||||
"Mitwirkende: Philipp Imperatori, Nils Nedderhut, Louis Neumann\n" +
|
||||
"Icons/Bilder: Smashicons, Freepick, Retinaicons, Skyclick (www.flaticon.com)\n" +
|
||||
"Keine Haftung für Bugs, Systemabstürze, Datenverlust und rauchende Grafikkarten\n\n" +
|
||||
"HowTo:\n" +
|
||||
"Bevor ein neues Spiel gestartet werden kann, müssen Sie 2-4 Spieler sowie die Kartengröße und die Spielmission festlegen. " +
|
||||
"Es ist auch möglich, ein Programm als Spieler einzustellen (z.B. BasicAI). " +
|
||||
"Anschließend wird eine Karte generiert. In der ersten Runde müssen abwechselnd 3 Burgen ausgewählt werden. Nachdem alle Burgen " +
|
||||
"verteilt wurden, beginnt das eigentliche Spiel. Sie haben die Möglichkeit neue Truppen auf Ihre Burgen aufzuteilen, Truppen zwischen " +
|
||||
"Ihren Burgen zu bewegen sowie andere Burgen anzugreifen. Bei der Standardmission 'Eroberung' gewinnt der Spieler, der zuerst alle Burgen " +
|
||||
"eingenommen hat.";
|
||||
|
||||
private JButton btnBack;
|
||||
private JTextPane txtInfo;
|
||||
private JLabel lblTitle;
|
||||
|
||||
public InfoView(GameWindow gameWindow) {
|
||||
super(gameWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResize() {
|
||||
|
||||
int offsetY = 25;
|
||||
lblTitle.setLocation((getWidth() - lblTitle.getWidth()) / 2, offsetY); offsetY += lblTitle.getSize().height + 25;
|
||||
txtInfo.setLocation(25, offsetY);
|
||||
txtInfo.setSize(getWidth() - 50, getHeight() - 50 - BUTTON_SIZE.height - offsetY);
|
||||
|
||||
btnBack.setLocation((getWidth() - BUTTON_SIZE.width) / 2, getHeight() - BUTTON_SIZE.height - 25);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
btnBack = createButton("Zurück");
|
||||
lblTitle = createLabel("Über", 25, true);
|
||||
txtInfo = createTextPane();
|
||||
txtInfo.setText(ABOUT_TEXT);
|
||||
txtInfo.setBorder(null);
|
||||
txtInfo.setBackground(this.getBackground());
|
||||
add(txtInfo);
|
||||
|
||||
StyledDocument doc = txtInfo.getStyledDocument();
|
||||
SimpleAttributeSet center = new SimpleAttributeSet();
|
||||
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
|
||||
doc.setParagraphAttributes(0, doc.getLength(), center, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
getWindow().setView(new StartScreen(getWindow()));
|
||||
}
|
||||
}
|
||||
79
Projektgruppe_XXX/src/gui/views/StartScreen.java
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
package gui.views;
|
||||
|
||||
import gui.GameWindow;
|
||||
import gui.View;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class StartScreen extends View {
|
||||
|
||||
private JButton btnStart, btnStats, btnInfo, btnQuit;
|
||||
private JLabel lblTitle;
|
||||
|
||||
public StartScreen(GameWindow gameWindow) {
|
||||
super(gameWindow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResize() {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
int labelHeight = 40;
|
||||
|
||||
int offsetY = (height - 4 * (BUTTON_SIZE.height + 15) - labelHeight) / 3;
|
||||
|
||||
lblTitle.setSize(width, labelHeight);
|
||||
lblTitle.setLocation(0, offsetY);
|
||||
|
||||
offsetY += labelHeight + 50;
|
||||
|
||||
int offsetX = (width - BUTTON_SIZE.width) / 2;
|
||||
JButton[] buttons = { btnStart, btnStats, btnInfo, btnQuit };
|
||||
for (JButton button : buttons) {
|
||||
button.setLocation(offsetX, offsetY);
|
||||
offsetY += BUTTON_SIZE.height + 15;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
this.lblTitle = createLabel("Game of Castles", 25);
|
||||
this.lblTitle.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
this.lblTitle.setFont(View.createCelticFont(25));
|
||||
this.btnStart = createButton("Start");
|
||||
this.btnStats = createButton("Punkte");
|
||||
this.btnInfo = createButton("Info");
|
||||
this.btnQuit = createButton("Beenden");
|
||||
|
||||
getWindow().setSize(750, 450);
|
||||
getWindow().setMinimumSize(new Dimension(600, 400));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
if(actionEvent.getSource() == btnQuit) {
|
||||
getWindow().dispose();
|
||||
} else if(actionEvent.getSource() == btnStart) {
|
||||
getWindow().setView(new GameMenu(getWindow()));
|
||||
} else if(actionEvent.getSource() == btnInfo) {
|
||||
getWindow().setView(new InfoView(getWindow()));
|
||||
} else if(actionEvent.getSource() == btnStats) {
|
||||
getWindow().setView(new HighscoreView(getWindow()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
super.paint(g);
|
||||
|
||||
BufferedImage soldiers[] = getWindow().getResources().getSoldiers();
|
||||
int width = 200;
|
||||
int height = (int) (((double)width / soldiers[0].getWidth()) * soldiers[0].getHeight());
|
||||
|
||||
g.drawImage(soldiers[0], 25, 100, width, height, null);
|
||||
g.drawImage(soldiers[1], getWidth() - 25 - width, 100, width, height, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package tests.student;
|
||||
|
||||
public class GraphConnectionTest {
|
||||
|
||||
}
|
||||