Real rename

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

View file

@ -0,0 +1,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>

1
Projektgruppe_175/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/bin/

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Projektgruppe_175</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View 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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

View file

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: gui.GameWindow

View 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);
}
}

View 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;
}
}

View 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);
}

View 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;
}
}

View 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())));
}
}

View 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;
}
}

View 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();
}
});
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}
}

View 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);
}
}

View 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;
}
}

View 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);
}
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}
}

View 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();
}
}

View 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) { }
}

View 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;
}
}

View file

@ -0,0 +1,6 @@
package gui.components;
public interface ValueListener {
void valueChanged(Object oldValue, Object newValue);
}

View 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");
}
}
}
}

View 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");
}
}

View 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()));
}
}

View 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()));
}
}

View 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);
}
}

View file

@ -0,0 +1,5 @@
package tests.student;
public class GraphConnectionTest {
}