Commit 420833b2 authored by Marc BRUN's avatar Marc BRUN
Browse files

Added comments, fixes Diplomacy issues

parent e9674a07
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
/**
* DNA contient toutes les informations relatives à la stratégie d'une tribu :
* <br/>- early game : ses 500 premières décisions détaillées (créations, améliorations d'individus)
* <br/>- late game : probabilités des décisions suivantes
* <br/>- personnalité diplomatique
* <br/>
* <br/>L'ADN d'une tribu est sa personnalité.
* @author marc
*/
public class DNA {
/**
* Construit un ADN aléatoire si les params sont null
* @param buildOrder
* @param endProbas
* @param diplo
*/
public DNA(ArrayList<Build> buildOrder, double[] endProbas, Diplo diplo){
if(buildOrder != null) this.buildOrder = buildOrder;
else{
......@@ -27,12 +40,13 @@ public class DNA {
}
if(diplo != null) this.diplo = diplo;
else{
this.diplo = new Diplo(rand.nextDouble() * 100, (rand.nextDouble() - 1) * 100, rand.nextInt(5), rand.nextDouble());
this.diplo = new Diplo((rand.nextDouble()-0.5) * 200, (rand.nextDouble() - 0.5) * 200, rand.nextInt(5), rand.nextDouble());
}
}
public static Random rand = new Random();
/**Action à effectuer : création ou amélioration d'individu.*/
public static enum BuildType {
Farmer,
Lumberjack,
......@@ -49,6 +63,7 @@ public class DNA {
Nothing
}
/**Contient l'information de l'action à effectuer : buildType et features.*/
public static class Build {
public BuildType buildType;
public ArrayList<Object> features;
......@@ -58,6 +73,7 @@ public class DNA {
this.features = features;
}
/**Construit des features aléatoires selon le buildType.*/
public Build(BuildType bt){
buildType = bt;
ArrayList<Object> features = new ArrayList<Object>();
......@@ -87,6 +103,13 @@ public class DNA {
}
}
/** Diplomatie.
<br/>Contient les seuils de :
<br/>- reddition
<br/>- danger accepté pour déclarer une guerre
<br/>- vassalisation/pillage
<br/>Et le nombre maximum de guerres simultanées.
*/
public static class Diplo{
public double surrenderThreshold;
public double startWarThreshold;
......@@ -265,7 +288,7 @@ public class DNA {
}
/** les générations de tribus sont divisées en 4 pools de 50 */
public static int nbOfPools = 4;
public static DNA[] generationDNAs = new DNA[World.nbOfTribes * nbOfPools];
public static int kept = 10;
......
import java.awt.Color;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Random;
/** Diplomacy dirige tout ce qui a rapport avec les guerres :
<br/>- coloriage des tribus en fonction de l'empire à laquelle elles appartiennent
<br/>- recherche d'un adversaire
<br/>- déclaration de guerre entre alliances et empires
<br/>- déroulement de la guerre (compte du score)
<br/>- fin de la guerre, vassalisation, pillage
@author marc
*/
public class Diplomacy {
Random rand = new Random();
// ============================= COLORS ===================================================================================================
// ====================================== COLORS =======================================================================================
public static Color[] colors = new Color[]{
new Color(0x800000), new Color(0x9A6324), new Color(0x469990), new Color(0x000075),
new Color(0xe6194B), new Color(0xf58231), new Color(0xffe119), new Color(0xbfef45) , new Color(0x3cb44b),
......@@ -22,12 +29,18 @@ public class Diplomacy {
}
}
// ============================== WAR ===========================================================================================================
// ======================================== WAR =========================================================================================
public static class War{
public enum Goal{
Vassalize,
Independence,
Loot
}
private enum State{
DefenderSurrender,
AttackerSurrender,
NoSurrender
}
public Goal goal;
public ArrayList<Tribe> attackers;
public ArrayList<Tribe> defenders;
......@@ -38,16 +51,31 @@ public class Diplomacy {
this.goal = goal;
this.attackers = attackers;
this.defenders = defenders;
this.attackersScore = 20*(attackers.get(0).warriorAttack + defenders.get(0).warriorAttack); // prevents division by zero and overweight of first blows
this.defendersScore = attackersScore;
this.attackersScore = 0;
this.defendersScore = 0;
}
public double getScore(){
return 100*(attackersScore - defendersScore)/(attackersScore + defendersScore);
/** Mesure la douleur subie par le camp => propension à se rendre */
public double getPain(ArrayList<Tribe> side){
if(side == attackers)
return (defendersScore - attackersScore) / (getGroupPower(attackers)+1); // +1 pour éviter une division par zéro (improbable mais possible)
else
return (attackersScore - defendersScore) / (getGroupPower(defenders)+1);
}
/** Détermine si un camp a suffisamment souffert pour se rendre */
private State getState(){
if(getPain(defenders) > defenders.get(0).dna.diplo.surrenderThreshold){
return State.DefenderSurrender;
}
else if(getPain(attackers) > attackers.get(0).dna.diplo.surrenderThreshold){
return State.AttackerSurrender;
}
else
return State.NoSurrender;
}
/** pas utilisée pour l'instant */
public void changeSide(Tribe t){
if(defenders.contains(t)){
defenders.remove(t);
......@@ -71,7 +99,7 @@ public class Diplomacy {
}
}
/** pas utilisée pour l'instant */
public void join(Tribe joiner, Tribe inviter){
if(attackers.contains(inviter)){
attackers.add(joiner);
......@@ -87,54 +115,61 @@ public class Diplomacy {
}
}
/** true si t1 et t2 sont dans des camps opposés de cette guerre */
public boolean areEnemies(Tribe t1, Tribe t2){
return ((attackers.contains(t1) && defenders.contains(t2)) ||
(defenders.contains(t1) && attackers.contains(t2)));
}
/** Appelée chaque jour de jeu : détermine si l'un des belligérants principaux (_.get(0)) veut se rendre.
<br/>Si oui, le gagnant applique le but (vassalisation ou pillage) de la guerre sur le perdant.
<br/>Seuls les belligérants principaux sont mis en jeu. */
public void checkForEnd(){
double score = getScore();
// On regarde si un des belligérants principaux veut se rendre
State state = getState();
Tribe winner = null;
Tribe loser = null;
if(score > defenders.get(0).dna.diplo.surrenderThreshold){ // defender surrenders
switch(state){
case DefenderSurrender:
winner = attackers.get(0);
loser = defenders.get(0);
}
else if(-score > attackers.get(0).dna.diplo.surrenderThreshold){ // attacker surrenders
break;
case AttackerSurrender:
winner = defenders.get(0);
loser = attackers.get(0);
}
else{
return; // war should continue until someone surrenders
break;
case NoSurrender:
return; // personne ne s'est rendu : la guerre continue, on ne résout pas le conflit
}
// Un belligérant s'est rendu : application du but de guerre.
switch(goal){
case Vassalize:
boolean hadOverlord = (winner.diplomacy.overlord != null);
if(winner.diplomacy.isUnder(loser))
case Independence:
switch(state){
case DefenderSurrender: // le rebelle gagne son indépendance vis-à-vis du perdant, il grimpe à égalité avec lui (indépendant ou vassal du même seigneur).
winner.diplomacy.becomeVassalOf(loser.diplomacy.overlord);
else loser.diplomacy.becomeVassalOf(winner);
ArrayList<War> loserWars = (ArrayList<War>) loser.diplomacy.wars.clone();
for(War war : loserWars){
if(war != this){
war.end(false);
}
break;
case AttackerSurrender: // le rebelle perd : il est vassalisé à nouveau
winner.diplomacy.vassalize(loser, this);
break;
}
if(winner.color == Color.white || hadOverlord) winner.color = colors[colorNumber % colors.length];
colorVassals(winner, winner.color);
colorNumber++;
break;
case Vassalize:
winner.diplomacy.vassalize(loser, this);
break;
case Loot:
winner.resources.add(loser.resources.multiply(0.5));
loser.resources = loser.resources.multiply(0.5);
// le gagnant pille la moitié des ressources du perdant
Resources loot = loser.resources.multiply(0.5);
winner.resources.add(loot);
loser.resources.substract(loot);
break;
}
end(true);
}
/** Fait la paix entre tous les belligérants et enlève la guerre de la liste de guerres de chacun.
<br/>Etablit une trêve de 4 ans entre les belligérants principaux. */
public void end(boolean truce){
for(Tribe attacker : attackers){
for(Tribe defender : defenders){
......@@ -145,11 +180,15 @@ public class Diplomacy {
truces.add(new Truce(attackers.get(0), defenders.get(0), 480));
}
public String toString(){
return attackers.get(0).toString().substring(6)+" - "+defenders.get(0).toString().substring(6)+" : "+goal+" : "+(int)getScore();
return attackers.get(0).toString().substring(6)+" - "+defenders.get(0).toString().substring(6)+" : "+goal+" : "+(int)getPain(defenders)+"/"+(int)getPain(attackers);
}
}
// ===================================== TRUCE ===============================================================================================
/** Les trêves sont établies à la fin des guerres entre les deux belligérants principaux seulement, et durent 4 ans.
<br/>Pendant la trêve, il est impossible à l'un d'entrer en guerre contre l'autre. */
public static class Truce{
Tribe tribe1;
Tribe tribe2;
......@@ -166,6 +205,7 @@ public class Diplomacy {
}
public static ArrayList<Truce> truces = new ArrayList<Truce>();
// ====================================== DIPLOMACY =========================================================================================
public Tribe tribe;
public Tribe overlord;
public ArrayList<Tribe> vassals;
......@@ -182,8 +222,18 @@ public class Diplomacy {
this.wars = new ArrayList<War>();
}
// ===================================== EVALUATING TRIBES POWER ===================================================================================
/** Détermine les forces des camps pendant les guerres */
static double getGroupPower(ArrayList<Tribe> group){
double power = 0;
for(Tribe t : group){
power += t.getMilitaryPower();
}
return power;
}
public double getAlliancePower(){
/** Estime les forces que l'on peut espérer avoir avec nous en guerre */
double getAlliancePower(){
double alliancePower = tribe.getMilitaryPower();
for(Tribe ally : allies){
alliancePower += ally.getMilitaryPower();
......@@ -194,7 +244,7 @@ public class Diplomacy {
return alliancePower;
}
// utilisé pour le fitness score de la tribu
public double getPrestige(){
double prestige = 0;
prestige += tribe.getPopulation();
......@@ -210,63 +260,26 @@ public class Diplomacy {
}
public double getDangerFrom(Tribe t){
ArrayList<Tribe> them = new ArrayList<Tribe>();
them.add(t);
ArrayList<Tribe> us = new ArrayList<Tribe>();
us.add(tribe);
for(Tribe ally : t.diplomacy.allies){
if(!us.contains(ally) && !ally.diplomacy.enemies.contains(tribe))
them.add(ally);
}
for(Tribe ally : allies){
if(!them.contains(ally) && !ally.diplomacy.enemies.contains(t))
us.add(ally);
}
for(Tribe vassal : t.diplomacy.vassals){
if(!us.contains(vassal) && !vassal.diplomacy.enemies.contains(tribe))
them.add(vassal);
}
for(Tribe vassal : vassals){
if(!them.contains(vassal) && !vassal.diplomacy.enemies.contains(t))
us.add(vassal);
}
double ourPower = 0;
for(Tribe attacker : us){
ourPower += attacker.getMilitaryPower();
}
double theirPower = 0;
for(Tribe defender : them){
theirPower += defender.getMilitaryPower();
}
return 100*(theirPower - ourPower)/(theirPower + ourPower);
}
// On cherche en permanence à déclarer des guerres qui nous avantagent
// =================================== CHOOSING ENEMY =======================================================================================
/** Appelé chaque jour : on cherche en permanence à déclarer des guerres qui nous avantagent. Renvoie true si une guerre a pu être déclarée, false sinon */
public boolean startWar(){
// Si on est déjà impliqués dans trop de guerres, on n'en déclare pas de nouvelle : on s'arrête là.
// On est déjà impliqués dans trop de guerres, on en déclare pas de nouvelle : on s'arrête là.
if(wars.size() >= tribe.dna.diplo.maxWars)
return false;
// On cherche les tribus faibles que nous pouvons attaquer (pas de trêve, cible indépendante)
// On cherche les tribus faibles qu'on peut attaquer (pas de trêve, cible indépendante)
ArrayList<Tribe> targets = new ArrayList<Tribe>();
ArrayList<Double> reluctanceToAttack = new ArrayList<Double>();
double minDanger = Double.MAX_VALUE;
for(Tribe t : World.tribes){
if(t != tribe && !enemies.contains(t) && (t.diplomacy.overlord == null || t == overlord)){ // valid targets : independent or overlord
if(!truceExists(tribe, t)){
double danger = getDangerFrom(t);
if(danger < tribe.dna.diplo.startWarThreshold){
targets.add(t);
// On combine danger et distance pour éviter que les tribus attaquent toutes la même cible
reluctanceToAttack.add(danger + World.dist(tribe.base.x, tribe.base.y, t.base.x, t.base.y)/10);
}
if(t != tribe && !enemies.contains(t) && // on attaque pas quelqu'un avec qui on est déjà en guerre, même en tant qu'ennemis secondaires
( (overlord == null && t.diplomacy.overlord == null) || // si on est indépendant, on ne peut pas attaquer le vassal de quelqu'un
t == overlord ) && // si on est vassal, on peut attaquer son seigneur direct
!truceExists(tribe, t)){ // on vérifie qu'il n'y a pas de trêve entre la cible et nous
double danger = getDangerFrom(t);
if(danger < tribe.dna.diplo.startWarThreshold){
targets.add(t);
// On combine danger et distance pour éviter que les tribus attaquent toutes la même cible
reluctanceToAttack.add(danger + World.dist(tribe.base.x, tribe.base.y, t.base.x, t.base.y)/10);
}
}
}
......@@ -284,13 +297,27 @@ public class Diplomacy {
}
}
// on décide de vassaliser ou piller selon notre ADN
if(rand.nextDouble() < tribe.dna.diplo.vassalizeGoalThreshold)
declareWarToAlliance(bestTarget, War.Goal.Vassalize);
else declareWarToAlliance(bestTarget, War.Goal.Loot);
else
declareWarToAlliance(bestTarget, War.Goal.Loot);
return true;
}
/** Checks if t is a direct or indirect overlord of self */
boolean isUnder(Tribe t){
if(overlord == null)
return false;
else if(overlord == t)
return true;
else
return overlord.diplomacy.isUnder(t);
}
/** true si t1 et t2 sont en trêve */
public boolean truceExists(Tribe t1, Tribe t2){
for(Truce truce : truces){
if((truce.tribe1 == t1 && truce.tribe2 == t2) || (truce.tribe1 == t2 && truce.tribe2 == t1)){
......@@ -301,11 +328,58 @@ public class Diplomacy {
}
public void declareWarToAlliance(Tribe t, War.Goal goal){
/** Simule une entrée en guerre pour déterminer les forces en présence et évaluer si on veut attaquer t */
double getDangerFrom(Tribe t){
ArrayList<Tribe> them = new ArrayList<Tribe>();
them.add(t);
ArrayList<Tribe> us = new ArrayList<Tribe>();
us.add(tribe);
for(Tribe ally : t.diplomacy.allies){
if(!us.contains(ally) && !ally.diplomacy.enemies.contains(tribe))
them.add(ally);
}
for(Tribe ally : allies){
if(!them.contains(ally) && !ally.diplomacy.enemies.contains(t))
us.add(ally);
}
for(Tribe vassal : t.diplomacy.vassals){
if(!us.contains(vassal) && !vassal.diplomacy.enemies.contains(tribe))
them.add(vassal);
}
for(Tribe vassal : vassals){
if(!them.contains(vassal) && !vassal.diplomacy.enemies.contains(t))
us.add(vassal);
}
double ourPower = 0;
for(Tribe attacker : us){
ourPower += attacker.getMilitaryPower();
}
double theirPower = 0;
for(Tribe defender : them){
theirPower += defender.getMilitaryPower();
}
return 100*(theirPower - ourPower)/(theirPower + ourPower);
}
// ======================================= DECLARING WAR =======================================================================
/** Chaque camp appelle ses alliés et vassaux (directs et indirects) puis les deux camps se déclarent la guerre */
void declareWarToAlliance(Tribe t, War.Goal goal){
// guerre contre son seigneur direct => guerre d'indépendance
if(t == overlord){
overlord.diplomacy.vassals.remove(tribe);
overlord = null;
t.diplomacy.vassals.remove(tribe);
goal = War.Goal.Vassalize;
// le rebelle change de couleur
tribe.color = colors[colorNumber % colors.length];
// On colore (récursivement) tous les vassaux du rebelle de sa couleur
colorVassals(tribe, tribe.color);
colorNumber++;
goal = War.Goal.Independence;
}
ArrayList<Tribe> defenders = new ArrayList<Tribe>();
defenders.add(t);
......@@ -336,8 +410,8 @@ public class Diplomacy {
}
}
// calls all vassals into war recursively
public void callVassals(Tribe him, ArrayList<Tribe> us, ArrayList<Tribe> them){
/** Calls all vassals into side recursively */
void callVassals(Tribe him, ArrayList<Tribe> us, ArrayList<Tribe> them){
for(Tribe vassal : vassals){
if(!them.contains(vassal) && !vassal.diplomacy.enemies.contains(him) && !truceExists(vassal, him)){
us.add(vassal);
......@@ -346,8 +420,8 @@ public class Diplomacy {
}
}
public void declareWarToTribe(War w, Tribe t){ // reciprocal
/** Réciproque */
void declareWarToTribe(War w, Tribe t){
allies.remove(t);
enemies.add(t);
......@@ -361,7 +435,9 @@ public class Diplomacy {
}
public void makePeaceWithTribe(War w, Tribe t){
// =========================================== MAKING PEACE ============================================================================
/** Réciproque */
void makePeaceWithTribe(War w, Tribe t){
enemies.remove(t);
t.diplomacy.enemies.remove(tribe);
......@@ -370,22 +446,36 @@ public class Diplomacy {
}
public void becomeVassalOf(Tribe t){
/** Vassalise t, met fin (sans trêve) à toutes les guerres dans lesquelles il est impliqué et change sa couleur */
void vassalize(Tribe t, War w){
// le perdant devient vassal donc n'est plus attaquable (on ne peut attaquer que les tribus indépendantes)
// on arrête les guerres du perdant sans trêves pour permettre aux attaquants de redéclarer une guerre sur le gagnant s'ils le veulent
// NB: on peut aussi forcer le gagnant à entrer dans toutes les guerres du vassal mais cela provoque trop de bugs
ArrayList<War> loserWars = (ArrayList<War>) t.diplomacy.wars.clone();
for(War war : loserWars){
if(war != w){
war.end(false);
}
}
// le gagnant n'a jamais été vassalisé et c'est la première fois qu'il vassalise quelqu'un
// on veut garder la même couleur quand il vassalise quelqu'un d'autre
if(tribe.color == Color.white){
tribe.color = colors[colorNumber % colors.length];
colorNumber++;
}
t.diplomacy.becomeVassalOf(tribe);
}
void becomeVassalOf(Tribe t){
if(overlord != null)
overlord.diplomacy.vassals.remove(tribe);
overlord = t;
if(t != null)
if(t != null){
t.diplomacy.vassals.add(tribe);
if(isUnder(tribe)) System.out.println(tribe+" IS UNDER ITSELF");
// coloriage récursif du perdant et de ses vassaux selon la couleur du gagnant
colorVassals(tribe, t.color);
}
}
// Checks if t is a direct or indirect overlord of self
public boolean isUnder(Tribe t){
if(overlord == null)
return false;
else if(overlord == t)
return true;
else
return overlord.diplomacy.isUnder(t);
}
}
......@@ -9,23 +9,16 @@ public class Healer extends Individual{
super.live();
// Heals first sick or injured member found in the tribe
Individual healed = null;
for(Individual sick : tribe.sickMembers){
if(sick.health == HealthState.SICK){
sick.health = HealthState.HEALTHY;
healed = sick;
break;
}
if(!tribe.sickMembers.isEmpty()){
healed = tribe.sickMembers.pop();
healed.health = HealthState.HEALTHY;
}
if(healed != null){
tribe.sickMembers.remove(healed);
for(Individual injured : tribe.injuredMembers){
injured.hp += 0.3;
if(injured.hp >= injured.startHP){
healed = injured;
}
break;
else if(!tribe.injuredMembers.isEmpty()){
healed = tribe.injuredMembers.getFirst();
healed.hp += 0.3;
if(healed.hp >= healed.startHP){
tribe.injuredMembers.removeFirst();
}
}
tribe.injuredMembers.remove(healed);
}
}
public class Military extends Individual{
public boolean focus;
......@@ -21,6 +20,11 @@ public class Military extends Individual{
}
}
/** Attaque une unité ennemie
*<br/>Ajoute la victime aux blessés de sa tribu (pour les soigneurs)
*<br/>Ajoute les dégâts aux compteurs de dommages totaux
*<br/>Met à jour les scores de guerres associés aux tribus
*/
public void attack(Individual i){
if(tribe.diplomacy.enemies.contains(i.tribe)){
double damage = this instanceof Warrior ? tribe.warriorAttack : tribe.towerAttack;
......@@ -32,33 +36,27 @@ public class Military extends Individual{
i.tribe.damageTaken += damage;
World.totalWarDamage += damage;
if(!(i instanceof Warrior)){
for(Diplomacy.War war : tribe.diplomacy.wars){
if(war.attackers.contains(tribe) && war.defenders.contains(i.tribe)){
war.attackersScore += damage;
}
if(war.defenders.contains(tribe) && war.attackers.contains(i.tribe)){
war.defendersScore += damage;
}