1°/ Objectif

  • Le moteur de jeu des TPs précédent est modifié pour intégrer un réseau de villes, chaque ville abritant une population.
  • A chaque tour, une partie des personnes (par famille notamment) de chaque population va migrer d'une ville à une autre.
  • Pour cela, on va lire le maillage constitué par les villes dans un fichier texte et constituer ainsi un graphe orienté pondéré.
  • Du côté des humains, on va ajouter la notion de famille en permettant à chaque humain de référencer ses ascendants et descendants.
 
2°/ Exercices

2.1°/ Le graphe des villes
  • Créez le fichier Vertex.java, à partir du code suivant :
import java.util.*;
 
class Vertex {
 
    Set<Edge> edgesFrom; // set of edges that come from other vertices
    Set<Edge> edgesTo; // set of edges that go to other vertices
    Population pop;
    String name;
 
    public Vertex(Population pop, String name) {
	edgesFrom = new HashSet<Edge>();
	edgesTo = new HashSet<Edge>();
	this.pop = pop;
	this.name = name;
    }
 
    public void setPopulation(Population pop) {
	this.pop = pop;
    }
 
    public String toString() {
	return name+" : "+pop;
    }
}
 
  • Créez le fichier Edge.java à partir du code suivant :
class Edge {
 
    Vertex from;
    Vertex to;
    double weight;
 
    public Edge(double weight) {
	from = null;
	to = null;
	this.weight = weight;
    }
 
    public Edge(Vertex from, Vertex to, double weight) {
	this.from = from;
	this.to = to;
	this.weight = weight;
    }
 
    public String toString() {
	return "edge from "+from.name+" to "+to.name;
    }
}
 

 

  • Créez le fichier GraphOP.java à partir du code suivant :
import java.util.*;
 
class GraphOP {
 
    public List<Vertex> vertices;
    public int nbVertices;
    public int nbEdges;
 
    public GraphOP() {
	vertices = new ArrayList<Vertex>();
	nbVertices = 0;
	nbEdges = 0;
    }
 
    public Vertex createVertex(Population pop, String name) {
	Vertex v = new Vertex(pop, name);
	vertices.add(v);
	nbVertices += 1;
	return v;
    }
 
    public void connectVertices(Vertex src, Vertex dest, double weight) {
	Edge e = new Edge(src,dest,weight);
	src.edgesTo.add(e);
	dest.edgesFrom.add(e);
	nbEdges += 1;
    }
 
    public boolean disconnectVertices(Vertex src, Vertex dest) {
	boolean connOk = false;
	for( Edge e : src.edgesTo) {
	    if (e.to == dest) {
		connOk = true;
		src.edgesTo.remove(e);
		dest.edgesFrom.remove(e);
		break;
	    }
	}
	return connOk;
    }
 
    public void createFromFile(String fileName) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(fileName));
        int nb = Integer.parseInt(br.readLine());
        String line = "";
        for(int i=0;i<nb;i++) {
            line = br.readLine();
            String[] parts = line.split(",");
            createVertex(null, parts[1]);            
        }
        line = "a";
        while ((line !=null) && (!line.isEmpty())) {
            line = br.readLine();
            String[] parts = line.split(",");
            int from = Integer.parseInt(parts[0]) -1;
            int to = Integer.parseInt(parts[1]) -1;
            double dist = Double.parseDouble(parts[2]);
            connectVertices(vertices.get(from), vertices.get(to), dist);
        }
    }

    public void createFromArray(String[] towns, double[] connections) {
        // à compléter
    }
}
 

 

  • La méthode createFromfile()  permet de créer un graph à partir du contenu d'un fichier texte, contenant par exemple :
7
Bordeaux
Lyon
Marseille
Paris
Rennes
Strasbourg
Toulouse
1,2,700
1,3,500
1,4,750
1,7,200
2,1,700
2,3,300
...
 
  • La première ligne contient le nombre de ville N, chacune étant une instance de Vertex dont l'attribut name est le nom de la ville. Lors de l'instanciation, la ville est créée avec une population nulle.
  • Les N lignes suivantes donnent les noms des villes
  • Viennent enfin des lignes au format id1,id2,distance qui donnent le kilométrage (= poids d'une arête) entre 2 villes. Les ids sont l'indice+1 dans la liste vertices.
  • Le nombre de ces lignes est indéterminé puisqu'il n'existe pas forcément une connexion entre toutes les villes.

 

  • La méthode createFromArray() est à compléter et permet de faire la meme chose que createFromFile() mais à partir de tableau passés en paramètre :
    • towns contient le nom des N villes
    • connections contient des triplets de double, où dans chaque triplet la première valeur est l'indice de la ville de départ, la deuxième l'indice de la ville d'arrivée et le troisième la distance entre. Le nombre de connections à créer est donc la taille de connections/3.
2.2°/ Modification des classes de base

  • Pour stocker les ascendants et desendants d'un humain et faciliter la gestion du jeu, on modifie la classe Humain comme suivant :
abstract class Humain {
  ...
  boolean dead;
 
  // ajouts attributs d'ascendance/descendance
  protected Humain pere;
  protected Humain mere;
  protected Set<humain> enfants;
  ...
  // modif méthodes
  public void vieillir() {
    age++;
    if (age > esperanceVie) dead = true;
  }
  public boolean isDead() {
    if ((dead)||(age > esperanceVie)) return true;
    return false;
  }
 
  // ajout méthodes
  public void mourrir() {
    dead = true;
    // à compléter
  }
  public void setParents(Humain pere, Humain mere) {
    // à compléter
  }
 
  public void addChild(Humain enfant) {
    // à compléter
  }
 
  public Set<Humain> getAscendants(int nbLevel) {
    // à compléter
  }
 
  public Set<Humain> getDescendants(int nbLevel) {
    // à compléter
  }
}

 

 
  • La méthode mourrir() doit permettre de supprimer l'objet courant comme père, mère ou enfants d'autres humains : si l'humain courant a un père/une mère h encore en vie, alors il faut l'enlever de la liste des enfants de h. Ou bien s'il a des enfants, pour chacun, il faut mettre son attributs père/mère (selon le sexe) à null.
  • Le paramètre nbLevel des méthodes getAscendants() et getDescendant() permet de regler la profondeur d'exploration des ascendants/descendants. Par exemple, pour un humain h, si nbLevel = 1, le set renvoyé contient respectivement les parents ou les enfants de h. Si nbLevel = 2, le set renvoyé contient les parents+grand-parents ou bien les enfants+petits enfants. Etc.
  • D'autre part, il faut modifier les méthodes rencontre() de Homme et Femme : lorsqu'une naissance a lieu, le bebe doit avoir comme parents les 2 humains impliqués dans la rencontre ET ajouter le bebe comme enfant du père et de la mère.
2.3°/ Le moteur du jeu


Copiez/collez les sources du TP4 et modifiez les pour que :

  • le programme principal ait un nouveau paramètre qui représente le nom du fichier texte contenant la description du graphe des ville (cf. section 2.1)
  • le moteur de jeu contienne une nouvelle variable GraphOP towns. Si un nom de fichier a été donné en paramètre, towns est initialisé grâce à ce fichier. Sinon, on utilise des tableaux écrits directement dans le code (cf. createFromArray() )
  • pour chaque ville de towns, on crée une population initiale comme dans les TPs précédents.
  • à chaque tour de jeu :
    • pour chaque ville, on organise les rencontres comme dans les TPs précédents.
    • pour chaque ville :
      • on tire aléatoirement un nbMove compris entre 0 et 3.
      • pour i de 0 à nbMove :
        • on choisit aléatoirement un humain h dans la population,
        • on tire aléatoirement un entier nbLevel entre 1 et 3,
        • on récupère les sets des ascendants/descendants de niveau nbLevel de h, puis on met les 2 sets ET h dans un même Set<Humain> famille.
        • on tire aléatoirement une ville de destination parmi les villes directement connectées à la ville courante.
        • on déplace famille dans la population de cette ville. Mais comme les routes sont dangereuses, tirez aléatoirement un entier nbMorts entre 0 et min (3,taille famille), et ensuite tirez nbMorts humains dans famille et faîtes les mourrir (sans oublier de les enlever de famille)
      • on fait vieillir la population.

 

  • Pour implémenter ce moteur de jeu, il est conseillé de définir de nouvelles méthodes. A vous de trouver lesquelles et où les définir.