StackOverflowError

Fermé
lemec - Modifié par lemec le 3/09/2011 à 16:53
 lemec - 10 sept. 2011 à 13:04
Bonjour,
j'ai commencé à programmer un jeu d'échecs en java.

J'ai écrit une classe Roi dans laquelle deux méthodes s'appellent l'une l'autre (au passage, y a-t-il un terme d'usage pour ce cas ?).
La première renvoie l'ArrayList contenant tous les déplacements possibles du roi en testant les 8 directions possibles du tableau direction, la seconde vérifie si la case à laquelle aboutit un déplacement donné n'est pas en prise en testant tous les déplacements de toutes les pièces ennemies.

J'ai ajouté un attribut booléen statique à la classe pour éviter que ce "double appel" aille creuser d'une méthode à l'autre sans s'arrêter. Mais, c'est là qu'est l'os, ça n'a pas l'air de suffire, voire d'être utile.

Voilà les deux méthodes et le booléen en question testeEnPrise :

public ArrayList<int[]> nouveauDéplacement(){     

  ArrayList<int[]> al = new ArrayList<int[]>();     
  int dl, dc;    
  boolean bool;   

  for(int i = 0;i<8;i++) {     
     dl = directions[2*i];     
     dc = directions[2*i+1];     
     bool = dedans(ln+dl,col+dc) && *la pièce en ln+dl et col+dc n'est pas de la même couleur* && pasEnPrise(ln+dl,col+dc);     
     testeEnPrise = false;  

     if (bool) {     
        al.add(new int[] {dl,dc});     
     }     
  }     
  return al;     
 }     
      
 public boolean pasEnPrise(int l, int c) {     

  if (testeEnPrise) {return true;}     
  testeEnPrise = true;     

  boolean b = true;     
  ArrayList<Pièce> ennemis = *pièces ennemies*;     

  for (int i = 0; i < ennemis.size() && b; i++) {     
     Pièce p = ennemis.get(i);  

     *Appel nouveauDéplacement si p est un roi : *  
     ArrayList<int[]> al = p.nouveauDéplacement();  

     for(int j = 0; j < al.size() && b;j++) {     
        b &= (p.ln+al.get(j)[0] != l || p.col+al.get(j)[1] != c);     
     }     
  }     
  return b;     
 }   


Auriez-vous une idée ?
Merci d'avance pour toute aide.

5 réponses

KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
3 sept. 2011 à 21:15
J'ai pas mal de chose à dire sur ton code ^^

En fait si j'ai bien compris ton int[] c'est toujours un int[2] qui correspond aux coordonnées de la pièce. Dans ce cas il est plutôt d'usage de faire une classe à part, ici par exemple Case, ça permet de gérer les erreurs, et de manipuler des méthodes.

Exemple :

import java.util.List;
import java.util.LinkedList;

enum Couleur {Noir,Blanc};

public class Case
{		
    private final int x;
    private final int y;
    private final Couleur c;
    
    /**
     * Représente une case par ses coordonnés (x,y).
     * La case est noire si x+y est paire, blanche sinon.
     * @param x la ligne entre 0 et 7 (normalement représentée par les lettres A...H)
     * @param y la colonne entre 0 et 7 (normalement représentée par les chiffres 1...8)
     * @throws Exception si x ou y n'est pas dans l'intervalle [0,7]
     */
    public Case(int x,int y) throws Exception
    {
        if (x<0 || x>7 || y<0 || y>7)
        	throw new Exception();
        
        this.x=x;
        this.y=y;
        
        c = ((x+y)%2==0) ? Couleur.Noir : Couleur.Blanc;
    }

    /**
     * @return l'indice de ligne entre 0 et 7
     */
    public int getX()
    {
        return x;
    }
    
    /**
     * @return l'indice de colonne entre 0 et 7
     */
    public int getY()
    {
        return y;
    }
    
    /**
     * @return la couleur de la case (a1=Noire, les autres par alternance)
     */
    public Couleur getCouleur()
    {
    	return c;
    }
        
    @Override
    public String toString()
    {
    	return (char) ('a'+x)+""+(char) ('1'+y);
    }
    
    @Override
    public boolean equals(Object o)
    {
    	if (o instanceof Case)
    	{
    		Case c = (Case) o;
    		return x==c.x && y==c.y;
    	}
    	else	return false;
    }
}

Mais pour en revenir à ton principal problème, voici ce que je propose (dans une classe Roi extends Piece par exemple)
// ajout d'une case à la liste si elle existe et est à proximité du roi
private void ajoutSiAccessible(List<Case> liste, int dx, int dy)
{
	try 
	{			
		liste.add(new Case(position.getX()+dx,position.getY()+dy)); // la case est libre
	}
	catch (Exception e) // la case n'existe pas
	{
	}
}

List<Case> casesAccessiblesSaufRoi() 
{
	List<Case> liste = new LinkedList<Case>();

	ajoutSiAccessible(liste,-1,-1);
	ajoutSiAccessible(liste,-1, 0);
	ajoutSiAccessible(liste,-1, 1);
	ajoutSiAccessible(liste, 0,-1);
	ajoutSiAccessible(liste, 0, 1);
	ajoutSiAccessible(liste, 1,-1);
	ajoutSiAccessible(liste, 1, 0);
	ajoutSiAccessible(liste, 1, 1);
	
	return liste;
}

List<Case> casesAccessibles() 
{		
	return casesAccessiblesSaufRoi().removeAll(roiAdverse.accessiblesSaufRoi());
}

List<Case> casesDisponibles()
{
	List<Case> liste = new LinkedList<Case>();
	
	for (Case c : casesAccessibles()) // parmi les cases accessibles
		if (echiquier.getCase(c).estLibre() // si la case est libre 
		 || echiquier.getCase(c).getPiece().getCamp()!=this.getCamp()) // ou si la pièce est un adversaire
			liste.add(c); // alors on peut s'y mettre
			
	return liste;
}
1
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
3 sept. 2011 à 16:59
deux méthodes s'appellent l'une l'autre : on parle de récursivité croisée
Et comme toute fonction récursive tu dois avoir une condition d'arrêt laquelle est-elle ?
Ou plus globalement, qu'est-ce que tu essayes de faire ?

Le problème c'est que tu dois probablement revenir sur tes pas, c'est à dire je teste la case 1 qui teste la case 2 qui teste la case 1 et on recommence.
Dans ce cas il faudrait plutôt utiliser une pile, y mettre toutes les cases à tester. Une fois remplie tu testes tout d'un coup et enfin tu récupères tout les résultats pour les traiter en même temps.
Ton booléen serait peut-être plus judicieux en paramètre, par exemple : true implique que c'est la première fois que tu fais le test, dans ce cas tu t'autorises à appelle l'autre méthode, si c'est false en revanche tu fais le test mais sans récursivité.

Essaye d'expliquer un peu mieux ce que tu veux faire, en commentant ton code par exemple, à moins bien sûr que mes explications t'ai suffisamment aidé pour continuer ;-)

Remarque : ArrayList<int[]>, c'est un peu bizarre comme type... à quoi te sers-t-il ?
0
Je te remercie d'avoir répondu si vite.
Je suis désolé de ne pas avoir été plus clair.
J'ai défini l'échiquier avec des pièces blanches, noires et vides.
Le problème vient quand la méthode qui calcule les déplacements du roi teste si une des cases autour de lui ne peut être prise par le roi ennemi, puisque pour cela, il faut calculer les déplacements du roi ennemi.
Voilà mon code commenté :


public ArrayList<int[]> nouveauDéplacement(){       
     

  ArrayList<int[]> al = new ArrayList<int[]>();       
  int dl, dc;      
  boolean bool;     

  // Prend chacune des huits directions itérativement  
  for(int i = 0;i<8;i++) {       

   //Prend le déplacement selon les lignes (valeur comprise entre -1 et 1)  
     dl = directions[2*i];       

  // Prend le déplacement selon les colonnes (valeur comprise entre -1 et 1)  
     dc = directions[2*i+1];     
    
 // Vérifie que le déplacement laisse le roi dans l'échiquier, que la pièce à laquelle  
 // aboutit le déplacement est ennemie ou vide, et, dans ce cas-là, si elle n'est pas  
 // menacée par une pièce ennemie  
     bool = dedans(ln+dl,col+dc) && *la pièce en ln+dl et col+dc n'est pas de la même couleur* && pasEnPrise(ln+dl,col+dc);  

// Réinitialise testeEnPrise       
     testeEnPrise = false;    

// Ajoute le déplacement sous forme d'un tableau de deux entiers s'il est valable  
     if (bool) {       
        al.add(new int[] {dl,dc});       
     }       
  }       
  return al;       
 }       
        
 public boolean pasEnPrise(int l, int c) {       

// La méthode retourne vrai sans test si la récursivité croisée a déjà eu une  
// occurrence  
  if (testeEnPrise) {return true;}  

// Attribue vrai à testeEnPrise si c'est la première occurrence de la récursivité croisée       
  testeEnPrise = true;       

  boolean b = true;       
  ArrayList<Pièce> ennemis = *pièces ennemies*;       

// Prend chacune des pièces ennemies  
  for (int i = 0; i < ennemis.size() && b; i++) {       
     Pièce p = ennemis.get(i);    

     // Calcule les déplacements possibles de la pièce    
     ArrayList<int[]> al = p.nouveauDéplacement();    

     // Vérifie pour chaque déplacement s'il n'aboutit à la case (ligne l, colonne c)  
     for(int j = 0; j < al.size() && b;j++) {       
        b &= (p.ln+al.get(j)[0] != l || p.col+al.get(j)[1] != c);       
     }       
  }       
  return b;       
 }   
  


J'espère que c'est assez détaillé. Tu peux voir que j'ai utilisé testeEnPrise comme tu le suggérais. Je ne comprends pour quoi ça ne marche pas, et je ne crois pas qu'en le passant en paramètre ça change quoi que ce soit..

Sinon, le type ArrayList<int[]> n'est pas indispensable, ça permet une meilleure lisibilité des déplacements (puisqu'il y a deux directions de déplacement)
Je ne vois pas en quoi une pile serait plus adaptée qu'une ArrayList puisqu'il n'y a pas besoin de synchronisation normalement.
Mais je n'ai jamais utilisé Vector ou Stack et je ne connais pas bien les threads (si c'est bien de ça que tu parles en disant "tu récupères tout les résultats pour les traiter en même temps") donc je ne m'avance pas plus sur la question.
Merci encore pour ton aide.
0
Oui, mon code n'est pas très objet, je le changerais peut-être plus tard (je suis un programmeur parfaitement novice).

Merci, je n'avais même pas pensé qu'imbriquer plusieurs méthodes résoudrait le problème.

Sinon, sais-tu pourquoi mon test sur le booléen ne suffisait pas ?
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
J'ai finalement suivi ton conseil, et bloque sur une bêtise.

Voici mon code :
public class Échiquier {
	final static Case[][] cases = new Case[8][8];
	final static ArrayList<Pièce>[] camps = new ArrayList[2];
	Roi[] rois = {new Roi(1,5,0,this),new Roi(8,5,1,this)};
	
	Échiquier() throws Exception {
		
                //Je remplis mon "cases" de cases avec les bonnes positions
		for (int i = 0; i < 8; i++) {
			for(int j = 0; j < 8;j++) {
				cases[i][j] = new Case(i+1,j+1);
			}
		}

                 //Là, je fais une boucle où j'ajoute les pièces dans "camps"

                //Pour chaque pièce p comprise dans les camps, je dis à la pièce sur laquelle elle est posée que c'est p qui est sur elle
                for (int i = 0; i < 2; i++) {
			for(int j = 0; j < camps[i].size();j++) {
				Case c = getCase(camps[i].get(j).dép);
				c.setPosée(camps[i].get(j));
			}
		}

//Petite méthode pour raccourcir le code :
        public Case getCase(Case c) {
---->		return cases[c.getLn()-1][c.getCol()-1];
	}



Et le compilateur me renvoie une JavaNullPointerException sur la ligne que j'ai indiquée par une flèche, alors qu'en vérifiant avec System.out.print(), c et ses attributs sont définis et qu'il existe une case à l'emplacement prévu dans cases.

J'imagine que c'est un problème hors du code, je suis sur Eclipse.

Merci à tous.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
10 sept. 2011 à 08:29
NullPointerException signifie que tu fais c.getLn et c.getCol alors que tu as c==null !
Plus haut dans ton code tu as getCase(camps[i].get(j).dép); c'est donc dép qui vaut null...

Il faudrait voir comment tu fais pour initialiser camps car visiblement les Pièce qu'ils contiennent ne sont pas correctement initialisées, vu que le champ dép vaut null pour au moins une des pièces/
0
En fait, j'ai changé un peu le code et ça marche. Mais comme je te le dis, System.out.print(c) mis juste avant la ligne qui pose problème affichait un hashcode "Case..." et non pas "null".
Ca fait donc deux "bugs", avec mon précédent booléen qui ne marchait pas, qui reste assez mystérieux.
Merci quand même.
0
KX Messages postés 16734 Date d'inscription samedi 31 mai 2008 Statut Modérateur Dernière intervention 24 avril 2024 3 015
10 sept. 2011 à 10:59
Sans avoir une vue globale du code c'est difficile de t'en dire plus.
C'est le genre de chose qu'on peut voir au débogueur et qui nécessite de tester le code complet.
Moi je ne fait qu'interpréter le message d'erreur dont je n'ai même pas la trace, juste le type !
Si au lieu de faire throws Exception à échiquier, tu traitais directement les exception avec des try catch, ça te permettrait sûrement de mieux contrôler les bugs.
0
Je prends note du conseil.
0