Faites une petite partie de Yahtzee


18

Dans le jeu Yahtzee, les joueurs lancent à tour de rôle 5 dés à 6 faces jusqu'à trois fois par tour, économisant éventuellement des dés entre les lancers, puis sélectionnant une catégorie qu'ils souhaitent utiliser pour leur lancer. Cela continue jusqu'à ce qu'il n'y ait plus de catégories (ce qui se produit après 13 tours). Ensuite, les scores des joueurs sont comptabilisés et le joueur avec le score le plus élevé gagne.

Les catégories sont les suivantes («somme des dés» signifie additionner le nombre de pips sur les dés spécifiés):

  • Section supérieure
    • As : somme des dés montrant 1 pip
    • Deux : somme des dés montrant 2 pips
    • Trois : somme des dés montrant 3 pips
    • Fours : somme des dés montrant 4 pips
    • Fives : somme des dés montrant 5 pips
    • Sixes : somme des dés montrant 6 pips
  • Section inférieure
    • Trois d'un genre : 3 dés avec la même valeur, le score est la somme de tous les dés
    • Four of a Kind : 4 dés de même valeur, le score est la somme de tous les dés
    • Full House : 3 dés avec une valeur et 2 avec une autre, le score est de 25
    • Petite ligne droite : 4 dés séquentiels, le score est de 30
    • Grande ligne droite : 5 dés séquentiels, le score est de 40
    • Yahtzee : tous les 5 dés avec la même valeur, le score est de 50
    • Chance : toute combinaison de dés, le score est la somme de tous les dés

Il existe quelques règles concernant les choix de catégorie:

  • Si un joueur choisit une catégorie qui ne correspond pas à son jet, il reçoit un score de 0 pour cette catégorie.
  • Si un joueur gagne un score d'au moins 63 dans la section supérieure, il reçoit 35 points bonus.
  • Si un joueur a lancé un Yahtzee mais que la catégorie Yahtzee est déjà prise (par un autre Yahtzee - remplir 0 pour un échec ne compte pas), il reçoit un bonus de 100 points. Ce bonus est accordé pour chaque Yahtzee après le premier.
    • De plus, le joueur doit toujours choisir de remplir une catégorie. Ils doivent choisir la catégorie de section supérieure correspondant à leur jet (par exemple un jet de 5 6 doit être placé dans la catégorie Sixes). Si la catégorie de section supérieure correspondante a déjà été utilisée, le Yahtzee peut être utilisé pour une catégorie de section inférieure (dans ce cas, choisir Full House, Small Straight ou Large Straight attribue le nombre normal de points plutôt que 0). Si toutes les catégories de la section inférieure sont prises, le Yahtzee peut être appliqué à une catégorie de section supérieure inutilisée, avec un score de 0.

Le défi

Dans ce défi, les concurrents joueront 1000 matchs de Yahtzee. À la fin de chaque partie, la ou les soumissions ayant obtenu le meilleur score recevront 1 point. Une fois tous les jeux terminés, la soumission avec le plus de points gagnera. S'il y a égalité, des parties supplémentaires seront jouées avec seulement les soumissions à égalité jusqu'à ce que l'égalité soit rompue.

Manette

Le code complet du contrôleur se trouve sur ce référentiel GitHub . Voici les interfaces publiques avec lesquelles les joueurs interagiront:

public interface ScorecardInterface {

    // returns an array of unused categories
    Category[] getFreeCategories();

    // returns the current total score
    int getScore();

    // returns the current Yahtzee bonus
    int getYahtzeeBonus();

    // returns the current Upper Section bonus
    int getUpperBonus();

    // returns the current Upper Section total
    int getUpperScore();

}
public interface ControllerInterface {

    // returns the player's scorecard (cloned copy, so don't try any funny business)
    ScorecardInterface getScoreCard(Player p);

    // returns the current scores for all players, in no particular order
    // this allows players to compare themselves with the competition,
    //  without allowing them to know exactly who has what score (besides their own score),
    //  which (hopefully) eliminates any avenues for collusion or sabotage
    int[] getScores();

}
public enum Category {
    ACES,
    TWOS,
    THREES,
    FOURS,
    FIVES,
    SIXES,
    THREE_OF_A_KIND,
    FOUR_OF_A_KIND,
    FULL_HOUSE,
    SMALL_STRAIGHT,
    LARGE_STRAIGHT,
    YAHTZEE,
    CHANCE;

    // determines if the category is part of the upper section
    public boolean isUpper() {
        // implementation
    }

    // determines if the category is part of the lower section
    public boolean isLower() {
        // implementation
    }

    // determines if a given set of dice fits for the category
    public boolean matches(int[] dice) {
        // implementation
    }

    // calculates the score of a set of dice for the category
    public int getScore(int[] dice) {
        // implementation
    }

    // returns all categories that fit the given dice
    public static Category[] getMatchingCategories(int[] dice) {
        // implementation
    }
}
public class TurnChoice {

    // save the dice with the specified indexes (0-4 inclusive)
    public TurnChoice(int[] diceIndexes) {
        // implementation
    }

    // use the current dice for specified category
    public TurnChoice(Category categoryChosen) {
        // implementation
    }

}

public abstract class Player {

    protected ControllerInterface game;

    public Player(ControllerInterface game) {
        this.game = game;
    }

    public String getName() {
        return this.getClass().getSimpleName();
    }

    // to be implemented by players
    // dice is the current roll (an array of 5 integers in 1-6 inclusive)
    // stage is the current roll stage in the turn (0-2 inclusive)
    public abstract TurnChoice turn(int[] dice, int stage);

}

De plus, il existe certaines méthodes utilitaires dans Util.java. Ils sont principalement là pour simplifier le code du contrôleur, mais ils peuvent être utilisés par les joueurs s'ils le souhaitent.

Règles

  • Les joueurs ne sont pas autorisés à interagir de quelque manière que ce soit, sauf en utilisant la Scorecard.getScoresméthode pour voir les scores actuels de tous les joueurs. Cela inclut la collusion avec d'autres joueurs ou le sabotage d'autres joueurs via la manipulation de parties du système qui ne font pas partie de l'interface publique.
  • Si un joueur fait un mouvement illégal, il ne sera pas autorisé à participer au tournoi. Tous les problèmes qui provoquent des mouvements illégaux doivent être résolus avant le déroulement du tournoi.
  • Si des soumissions supplémentaires sont faites après la fin du tournoi, un nouveau tournoi sera organisé avec les nouvelles soumissions et la soumission gagnante sera mise à jour en conséquence. Cependant, je ne garantis pas la rapidité du déroulement du nouveau tournoi.
  • Les soumissions ne peuvent exploiter aucun bogue dans le code du contrôleur qui pourrait le faire dévier des règles de jeu réelles. Signalez-moi des bugs (dans un commentaire et / ou dans un problème GitHub), et je les corrigerai.
  • L'utilisation des outils de réflexion de Java est interdite.
  • Tout langage qui s'exécute sur la JVM, ou qui peut être compilé en Java ou en bytecode JVM (comme Scala ou Jython) peut être utilisé, tant que vous fournissez tout code supplémentaire nécessaire pour l'interfacer avec Java.

Commentaires finaux

S'il y a une méthode utilitaire que vous aimeriez que j'ajoute au contrôleur, demandez simplement dans les commentaires et / ou faites un problème sur GitHub, et je l'ajouterai, en supposant qu'il ne permet pas de briser les règles ou d'exposer des informations à quels joueurs ne sont pas au courant. Si vous voulez l'écrire vous-même et créer une pull request sur GitHub, c'est encore mieux!


ACES? Tu veux dire ONES? Ce sont des dés, pas des cartes.
mbomb007


Je ne me souviens pas l'avoir appelé comme ça quand je l'ai joué, mais d'accord.
mbomb007

Existe-t-il une méthode pour obtenir le score pour une catégorie donnée à partir d'un ensemble de dés?
mbomb007

@ mbomb007 Non, mais je peux certainement en faire un :)
Mego

Réponses:


4

DummyPlayer

package mego.yahtzee;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class DummyPlayer extends Player {

    public DummyPlayer(ControllerInterface game) {
        super(game);
    }

    @Override
    public TurnChoice turn(int[] dice, int stage) {
        Category[] choices = game.getScoreCard(this).getFreeCategories();
        Category choice = choices[new Random().nextInt(choices.length)];
        if(IntStream.of(dice).allMatch(die -> die == dice[0])) {
            if(Stream.of(choices).filter(c -> c == Category.YAHTZEE).count() > 0) {
                choice = Category.YAHTZEE;
            } else if(Stream.of(choices).filter(c -> c == Util.intToUpperCategory(dice[0])).count() > 0) {
                choice = Util.intToUpperCategory(dice[0]);
            } else {
                choices = Stream.of(game.getScoreCard(this).getFreeCategories()).filter(c -> c.isLower()).toArray(Category[]::new);
                if(choices.length > 0) {
                    choice = choices[new Random().nextInt(choices.length)];
                } else {
                    choices = game.getScoreCard(this).getFreeCategories();
                    choice = choices[new Random().nextInt(choices.length)];
                }
            }
        }
        return new TurnChoice(choice);
    }

}

Ce joueur est ici pour servir de plan de base sur la façon d'utiliser les outils présents dans le contrôleur Yahtzee. Il choisit Yahtzee dans la mesure du possible et fait des choix aléatoires dans le cas contraire, tout en respectant les règles strictes du joker.


1

As et huit

Eh bien, cela a pris beaucoup plus de temps que je l'aurais souhaité grâce à la façon dont j'ai été occupé récemment.

package mego.yahtzee;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static mego.yahtzee.Category.*;

public class AcesAndEights extends Player {
    private Category[] freeCategories, matchingCategories, usableCategories;

    public AcesAndEights(ControllerInterface game) {
        super(game);
    }

    @Override
    public TurnChoice turn(int[] dice, int stage) {
        List<Integer> holdIndices = new java.util.ArrayList<>();

        freeCategories = game.getScoreCard(this).getFreeCategories();

        matchingCategories = Category.getMatchingCategories(dice);
        Arrays.sort(matchingCategories);

        usableCategories = Arrays.stream(freeCategories)
                                 .filter(this::isMatchingCategory)
                                 .toArray(Category[]::new);
        Arrays.sort(usableCategories);

        if (isMatchingCategory(YAHTZEE))
            return doYahtzeeProcess(dice);

        if (isUsableCategory(FULL_HOUSE))
            return new TurnChoice(FULL_HOUSE);

        if (stage == 0 || stage == 1) {
            if (isMatchingCategory(THREE_OF_A_KIND)) {
                int num = 0;
                for (int i : dice) {
                    if (Util.count(Util.boxIntArray(dice), i) >= 3) {
                        num = i;
                        break;
                    }
                }
                for (int k = 0; k < 5; k++) {
                    if (dice[k] == num)
                        holdIndices.add(k);
                }
                return new TurnChoice(toIntArray(holdIndices.toArray(new Integer[0])));
            }

            if (isFreeCategory(LARGE_STRAIGHT) || isFreeCategory(SMALL_STRAIGHT)) {
                if (isUsableCategory(LARGE_STRAIGHT))
                    return new TurnChoice(LARGE_STRAIGHT);

                if (isMatchingCategory(SMALL_STRAIGHT)) {
                    if (!isFreeCategory(LARGE_STRAIGHT))
                        return new TurnChoice(SMALL_STRAIGHT);

                    int[] arr = Arrays.stream(Arrays.copyOf(dice, 5))
                                      .distinct()
                                      .sorted()
                                      .toArray();
                    List<Integer> l = Arrays.asList(Util.boxIntArray(dice));
                    if (Arrays.binarySearch(arr, 1) >= 0 && Arrays.binarySearch(arr, 2) >= 0) {
                        holdIndices.add(l.indexOf(1));
                        holdIndices.add(l.indexOf(2));
                        holdIndices.add(l.indexOf(3));
                        holdIndices.add(l.indexOf(4));
                    }
                    else if (Arrays.binarySearch(arr, 2) >= 0 && Arrays.binarySearch(arr, 3) >= 0) {
                        holdIndices.add(l.indexOf(2));
                        holdIndices.add(l.indexOf(3));
                        holdIndices.add(l.indexOf(4));
                        holdIndices.add(l.indexOf(5));
                    }
                    else {
                        holdIndices.add(l.indexOf(3));
                        holdIndices.add(l.indexOf(4));
                        holdIndices.add(l.indexOf(5));
                        holdIndices.add(l.indexOf(6));
                    }
                    return new TurnChoice(toIntArray(holdIndices.toArray(new Integer[0])));
                }
            }

            if (isFreeCategory(FULL_HOUSE)) {
                int o = 0, t = o;
                for (int k = 1; k <= 6; k++) {
                    if (Util.count(Util.boxIntArray(dice), k) == 2) {
                        if (o < 1)
                            o = k;
                        else
                            t = k;
                    }
                }

                if (o > 0 && t > 0) {
                    for (int k = 0; k < 5; k++) {
                        if (dice[k] == o || dice[k] == t)
                            holdIndices.add(k);
                    }
                    return new TurnChoice(toIntArray(holdIndices.toArray(new Integer[0])));
                }
            }
        }
        else {
            Arrays.sort(freeCategories, Comparator.comparingInt((Category c) -> c.getScore(dice))
                                                  .thenComparingInt(this::getPriority)
                                                  .reversed());
            return new TurnChoice(freeCategories[0]);
        }

        return new TurnChoice(new int[0]);
    }

    private TurnChoice doYahtzeeProcess(int[] dice) {
        if (isUsableCategory(YAHTZEE))
            return new TurnChoice(YAHTZEE);

        Category c = Util.intToUpperCategory(dice[0]);
        if (isUsableCategory(c))
            return new TurnChoice(c);

        Category[] arr = Arrays.stream(freeCategories)
                               .filter(x -> x.isLower())
                               .sorted(Comparator.comparing(this::getPriority)
                                                 .reversed())
                               .toArray(Category[]::new);
        if (arr.length > 0)
            return new TurnChoice(arr[0]);

        Arrays.sort(freeCategories, Comparator.comparingInt(this::getPriority));
        return new TurnChoice(freeCategories[0]);
    }

    private boolean isFreeCategory(Category c) {
        return Arrays.binarySearch(freeCategories, c) >= 0;
    }

    private boolean isMatchingCategory(Category c) {
        return Arrays.binarySearch(matchingCategories, c) >= 0;
    }

    private boolean isUsableCategory(Category c) {
        return Arrays.binarySearch(usableCategories, c) >= 0;
    }

    private int getPriority(Category c) {
        switch (c) {
            case YAHTZEE: return -3;        // 50 points
            case LARGE_STRAIGHT: return -1; // 40 points
            case SMALL_STRAIGHT: return -2; // 30 points
            case FULL_HOUSE: return 10;     // 25 points
            case FOUR_OF_A_KIND: return 9;  // sum
            case THREE_OF_A_KIND: return 8; // sum
            case SIXES: return 7;
            case FIVES: return 6;
            case FOURS: return 5;
            case THREES: return 4;
            case TWOS: return 3;
            case ACES: return 2;
            case CHANCE: return 1;          // sum
        }
        throw new RuntimeException();
    }

    private int[] toIntArray(Integer[] arr) {
        int[] a = new int[arr.length];
        for (int k = 0; k < a.length; k++)
            a[k] = arr[k];
        return a;
    }
}

Le bot recherche des motifs dans les dés qui pourraient correspondre à certaines catégories et détient ceux nécessaires. Il choisit immédiatement une catégorie de correspondance prioritaire s'il en existe une; sinon, il choisit une catégorie qui donne le score le plus élevé. Marque près de 200 points par match en moyenne.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.