Une implémentation simple de Snakes and Ladders en Java

Sep 22 2020

J'ai créé une implémentation de serpents et d'échelles assez simple en Java juste pour le plaisir. Le programme peut se résumer comme suit:

  1. Object of Board contient un tableau de type Tile pour représenter la carte réelle.

  2. L'objet de type Tile contient un membre de données Enum pour identifier s'il s'agit d'une tuile normale ou d'un serpent / échelle.

    Contient également une tuile de destination pour identifier la destination du serpent / échelle.

  3. L'objet Player contient le nom et la position du joueur.

  4. Lors de l'initialisation, via des aléas, je génère des tuiles aléatoires pour être des serpents et des échelles. Ensuite, l'utilisateur est invité à entrer non. de joueurs, puis continuez à lancer les dés jusqu'à ce qu'un joueur atteigne la 100e tuile.

Le code:

Classe BoardImpl:

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class BoardImpl implements Board{

    Tile[] board = new Tile[101];         //A array to represent the 'board'
    List<Player> players = new ArrayList<>();
    SecureRandom secureRandom = new SecureRandom();
    Set<Integer> occupiedPositions = new HashSet<>();

    public void setPlayers(List<Player> players) {
        this.players = players;
    }

    public Tile[] getBoard() {
        return board;
    }

    public List<Player> getPlayers() {
        return players;
    }

    public void addPlayers(Player player) {
        this.players.add(player);
    }

    @Override
    public void initialize() {
//this function generates the board, placing random snakes and ladders and
//filling the rest with normal tiles
        System.out.println("Initializing board");

        int snakes = secureRandom.nextInt(15) + 1; //generates snakes & 
        int ladders = secureRandom.nextInt(15) + 1; //ladders at random
                                                    //positions
        setSnakes(snakes);
        setLadders(ladders);

        for(int i = 0; i < 101; i++){
            if(board[i] != null){
                continue;
            }
            Tile t  = new TileImpl(TileType.NORMAL);
            board[i] = t;
        }


    }

    @Override
    public void movePlayer(Player player, int places) {
        int currentPosition = player.getPosition();
        String checkPos = checkEndingPosition(currentPosition, places);
        if(checkPos.equals("winner")){
                winner(player);
        }
        if(checkPos.equals("outOfBoard")){
            System.out.println("That move cannot be made from your position! " +
                    "Roll a different number next time");
        }
        if(checkPos.equals("valid")){
                Tile t = getTile(currentPosition + places);
                if(t.getType().equals(TileType.NORMAL)){
                    player.setPosition(currentPosition + places);
                    System.out.println("Player " + player.getName() +
                            " moves to position "+ player.getPosition());
                }else {
                    player.setPosition(t.getDestination());
                    System.out.println(" Player " + player.getName() +
                            " encountered a "+ t.getType()+ "!!, moves to position "+ player.getPosition());
                }
        }
    }

    @Override
    public Tile getTile(int n) {
        return board[n];
    }

    public String checkEndingPosition(int currentPosition, int places){

        if((currentPosition + places) > 100){
            return "outOfBoard";
        }else if((currentPosition+ places) == 100){
            return "winner";
        }
        else{
            return "valid";
        }
    }

    public void winner(Player player){

        System.out.print("Player " + player.getName() + " has won the game !");
        System.exit(0);
    }

    public void setSnakes(int n){
//this function generates snakes at random positions
        for(int i = 0; i < n; i ++){
            boolean flag = true;
            int start = 0;
            int dest = 0;

            while(flag) {
                 start = secureRandom.nextInt(98);
                if(!occupiedPositions.contains(start)){
                    occupiedPositions.add(start);
                    break;
                }
            }

            while(flag) {
 //Setting the destination for a snake tile to lower than its position
                 dest = (int)(start * secureRandom.nextDouble());
                if(!occupiedPositions.contains(dest)){
                    occupiedPositions.add(dest);
                    break;
                }
            }

            Tile tile = new TileImpl(TileType.SNAKE, dest);
            board[start] = tile;

            System.out.println("Created snake " + "[" + start+ "," + dest + "]");
        }

    }

    public void setLadders(int n) {
//this function generates ladders randomly
        for (int i = 0; i < n; i++) {
            boolean flag = true;
            int start = 0;
            int dest = 0;

            while (flag) {
                start = secureRandom.nextInt(80);
                if (!occupiedPositions.contains(start)) {
                    occupiedPositions.add(start);
                    break;
                }
            }

            while (flag) {
//this step places the destination for the ladder tile to greater than its position
                dest = (int) (start + secureRandom.nextDouble() * 10);
                if (!occupiedPositions.contains(dest)) {
                    occupiedPositions.add(dest);
                    break;
                }
            }

            Tile tile = new TileImpl(TileType.LADDER, dest);
            board[start] = tile;
            System.out.println("Created ladder " + "[" + start+ "," + dest + "]");
        }
    }
}

Classe PlayerImpl:

public class PlayerImpl implements Player{


    private int position = 0;
    private String name = null;

    public PlayerImpl(String name){
        this.name = name;
    }

    @Override
    public int getPosition() {
        return position;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setPosition(int currentPosition) {
        this.position = currentPosition;
    }

    @Override
    public void setName(String playerName) {
        this.name = playerName;
    }
}

Classe TileImpl:

public class TileImpl implements Tile {

    private TileType type;
    private int position;
    private int destination;

    public TileImpl(TileType type) {
        this.type = type;
    }

    public TileImpl(TileType type, int dest) {
        this.type = type;
        this.destination = dest;
    }

    public int getPosition() {
        return position;
    }

    public int getDestination() {
        return destination;
    }

    public void setType(TileType type) {
        this.type = type;
    }

    public void setPosition(int position) {
        this.position = position;
    }

    public void setDestination(int destination) {
        this.destination = destination;
    }

    @Override
    public TileType getType() {
        return type;
    }

}

Classe SnakesAndLadders:

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class SnakesAndLadders {

    public static void main(String args[]){

        Board board = new BoardImpl();
        List<Player> players = new ArrayList<>();
        System.out.println("How many players ?");

        Scanner scan = new Scanner(System.in);
        int noOfPlayers = scan.nextInt();

        for(int i = 0; i < noOfPlayers; i++){
            System.out.println("Enter name for player "+ i);
            String name = scan.next();
            Player player = new PlayerImpl(name);
            players.add(player);

        }

        board.initialize();
        int counter = 0;
        System.out.println("Lets Play!");
        String choice = "";
        SecureRandom random = new SecureRandom();
//simulating a game through the do while loop
        do{
            if(counter >= noOfPlayers) counter = 0;
            Player currPlayer = players.get(counter);
            System.out.println(" Player " + currPlayer.getName() + " turn to play!");
            System.out.println(" R = Roll the dice, Q = quit");
            choice = scan.next();
            if(choice.equalsIgnoreCase("R")){
                int places = random.nextInt(6) + 1;
                board.movePlayer(currPlayer, places);
                counter++;

            }
        }while(!choice.equalsIgnoreCase("Q"));
    }
}

Je pense que le jeu est très simple. Je voulais surtout savoir si

  1. Il existe une meilleure façon de générer les nombres aléatoires de serpents et d'échelles.

  2. Une structure de données meilleure et plus appropriée pour représenter le `` conseil ''

  3. Si mon code peut être amélioré pour contenir des paradigmes / pratiques importants.

Réponses

2 Marc Sep 23 2020 at 09:48

Bienvenue dans Code Review. Belle implémentation et bonne structure du code, retrouvez mes suggestions en ligne.

  1. Il existe une meilleure façon de générer les nombres aléatoires de serpents et d'échelles.

Les méthodes setSnakeset setLaddersdiffèrent par juste quelques lignes de code, ce qui est un indicateur de duplication de code. Voici la méthode setLadders:

public void setLadders(int n) {
//this function generates ladders randomly
    for (int i = 0; i < n; i++) {
        boolean flag = true;
        int start = 0;
        int dest = 0;

        while (flag) {
            start = secureRandom.nextInt(80);
            if (!occupiedPositions.contains(start)) {
                occupiedPositions.add(start);
                break;
            }
        }

        while (flag) {
//this step places the destination for the ladder tile to greater than its position
            dest = (int) (start + secureRandom.nextDouble() * 10);
            if (!occupiedPositions.contains(dest)) {
                occupiedPositions.add(dest);
                break;
            }
        }

        Tile tile = new TileImpl(TileType.LADDER, dest);
        board[start] = tile;
        System.out.println("Created ladder " + "[" + start+ "," + dest + "]");
    }
}

Une approche consiste à créer une méthode pour générer les nombres aléatoires, puis à l'utiliser pour générer des échelles et des serpents. Par exemple:

private void setRandomLadder() {
    while (true) {
        int[] segment = getRandomSegment(1,99);
        int start = segment[0];
        int end = segment[1];
        if (isTileFree(start) && isTileFree(end)) {
            board[start] = new TileImpl(TileType.LADDER, end);
            board[end] = new TileImpl(TileType.OCCUPIED);
            break;
        }
    }
}

Cette méthode peut être appelée à plusieurs nreprises en fonction du nombre d'échelles que vous souhaitez ajouter au tableau. La méthode getRandomSegmentrenvoie deux points aléatoires entre 1 et 99. J'ai également changé le nom de setLaddersà setRandomLadder, pour le rendre auto-descriptif. Enfin, gardez à l'esprit que si vous ajoutez trop d'échelles, ces méthodes ne se terminent jamais, alors pensez à vérifier s'il y a des tuiles libres avant.


  1. Une structure de données meilleure et plus appropriée pour représenter le `` conseil ''

En utilisant la méthode précédente, nous pouvons supprimer la liste occupiedPositions, ce qui était possible en ajoutant TileType.OCCUPIED.

Le tableau de Tileest ok comme structure de données, une alternative est d'utiliser ArrayList.


  1. Si mon code peut être amélioré pour contenir des paradigmes / pratiques importants.
  • Sortie de la console :

    How many players ?
    2
    Enter name for player 0
    

    Il est déroutant pour l'utilisateur de voir le joueur 0, à partir du joueur 1.

  • Les nombres magiques : new Tile[101], secureRandom.nextInt(98), etc. Créer des constantes, par exemple:

    private static final int SIZE = 101;
    
  • Modificateurs : les méthodes setSnakeset setLaddersdoivent être privées, idem pour toutes les variables d'instance.

  • Initialiser dans le constructeur : le BoardImpla une méthode initialize()mais et un constructeur vide. Vous pouvez utiliser directement le constructeur.

  • Encapsulation : l'interface Boardaccepte un Playeravec un public setPosition, ce qui signifie que n'importe qui peut le changer. Seul le plateau doit changer l'état du joueur. Une interface plus sûre pourrait être:

    public interface Board {
      void movePlayer(String playerId, int places);
      //..
    }
    
  • Évitez les variables temporaires : au lieu de

    Tile tile = new TileImpl(TileType.LADDER, dest);
    board[start] = tile;
    

    Tu peux écrire:

    board[start] = new TileImpl(TileType.LADDER, dest);
    
  • Odeur de code :

        public String checkEndingPosition(int currentPosition, int places){
    
          if((currentPosition + places) > 100){
              return "outOfBoard";
          }else if((currentPosition+ places) == 100){
              return "winner";
          }
          else{
              return "valid";
          }
      }
    

    Le renvoi d'une chaîne est très sujet aux erreurs ici. Par exemple, vous pouvez renvoyer un booléen et éventuellement une exception.

  • Test et réutilisabilité : la méthode winnerappelle System.exit(0), ce qui rend impossible le test. De plus, il y en a beaucoup System.out.printlndans la BoardImplclasse. Il est très difficile de tester une classe comme celle-là. Idéalement, la classe Boardne devrait jamais s'imprimer à l'utilisateur, uniquement pour le débogage. Le maindevrait être responsable d'interagir avec l'utilisateur.

3 user985366 Sep 22 2020 at 15:53

Lisibilité et style

Veuillez exécuter votre code via un formateur pour le rendre plus lisible et respecter les normes.

Celui-ci peut fonctionner:

https://www.tutorialspoint.com/online_java_formatter.htm

Drapeau?

            boolean flag = true;
            int start = 0;
            int dest = 0;

            while (flag) {
                start = secureRandom.nextInt(80);
                if (!occupiedPositions.contains(start)) {
                    occupiedPositions.add(start);
                    break;
                }
            }

Vous n'utilisez pas, ne vérifiez pas ou ne modifiez pas le drapeau ici. Cette boucle devrait juste être while(true)et la boucle suivante fait la même chose.

Sinon-

if(checkPos.equals("winner")){
                winner(player);
        }

Lorsque vous vérifiez plusieurs cas exclusifs et comparez à une chaîne, je pense qu'une switchinstruction est une manière plus appropriée / standard de le faire. Cela montre également plus clairement que les cas sont exclusifs.

Structure de données

Set<Integer> occupiedPositions = new HashSet<>();

Cela me semble inutile / non naturel. J'ajouterais à la classe Tile a boolpour marquer une tuile comme occupée, puis cette information peut être contenue dans la tuile / planche elle-même au lieu d'avoir une tuile séparée Setpour la comptabilité.