Juega un juego de Yahtzee

17

En el juego Yahtzee, los jugadores se turnan para lanzar 5 dados de 6 lados hasta tres veces por turno, posiblemente guardando los dados entre las tiradas y luego seleccionando la categoría que desean usar para su tirada. Esto continúa hasta que no haya más categorías (lo que sucede después de 13 turnos). Luego, se cuentan los puntajes de los jugadores y gana el jugador con el puntaje más alto.

Las categorías son las siguientes ("suma de dados" significa sumar el número de pips en el dado especificado):

  • Sección superior
    • Ases : suma de los dados que muestra 1 pip
    • Dos : suma de los dados que muestran 2 pips
    • Tres : suma de los dados que muestra 3 pips
    • Cuatros : suma de los dados que muestran 4 pips
    • Fives : suma de los dados que muestra 5 pips
    • Sixes : suma de los dados que muestran 6 pips
  • Sección inferior
    • Trío : 3 dados con el mismo valor, la puntuación es la suma de todos los dados
    • Four of a Kind : 4 dados con el mismo valor, la puntuación es la suma de todos los dados
    • Full House : 3 dados con un valor y 2 con otro, la puntuación es 25
    • Pequeña recta : 4 dados secuenciales, la puntuación es 30
    • Recto grande : 5 dados secuenciales, la puntuación es 40
    • Yahtzee : los 5 dados con el mismo valor, la puntuación es 50
    • Oportunidad : cualquier combinación de dados, la puntuación es la suma de todos los dados

Hay algunas reglas sobre las opciones de categoría:

  • Si un jugador elige una categoría que no coincide con su tirada, recibe una puntuación de 0 para esa categoría.
  • Si un jugador obtiene una puntuación de al menos 63 en la sección superior, recibirá 35 puntos de bonificación.
  • Si un jugador ha lanzado un Yahtzee pero la categoría Yahtzee ya está ocupada (por otro Yahtzee; completar 0 para una falta no cuenta), recibe una bonificación de 100 puntos. Este bono se otorga por cada Yahtzee después del primero.
    • Además, el jugador aún debe elegir completar una categoría. Deben elegir la categoría de la sección superior correspondiente a su tirada (por ejemplo, una tirada de 5 6 debe colocarse en la categoría Sixes). Si ya se ha utilizado la categoría de sección superior correspondiente, el Yahtzee se puede utilizar para una categoría de sección inferior (en este caso, al elegir Full House, Small Straight o Large Straight se otorga la cantidad normal de puntos en lugar de 0). Si se toman todas las categorías de la sección inferior, entonces el Yahtzee se puede aplicar a una categoría de la sección superior no utilizada, con una puntuación de 0.

El reto

En este desafío, los competidores jugarán 1000 juegos de Yahtzee. Al final de cada juego, las presentaciones que obtuvieron la puntuación más alta recibirán 1 punto. Una vez finalizados todos los juegos, ganará la presentación con más puntos. Si hay un empate, se jugarán juegos adicionales solo con los envíos empatados hasta que se rompa el empate.

Controlador

El código completo del controlador se puede encontrar en este repositorio de GitHub . Estas son las interfaces públicas con las que los jugadores interactuarán:

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);

}

Además, hay algunos métodos de utilidad en Util.java. Están principalmente allí para simplificar el código del controlador, pero pueden ser utilizados por los jugadores si lo desean.

Reglas

  • Los jugadores no pueden interactuar de ninguna manera, excepto utilizando el Scorecard.getScoresmétodo para ver los puntajes actuales de todos los jugadores. Esto incluye coludir con otros jugadores o sabotear a otros jugadores mediante la manipulación de partes del sistema que no son parte de la interfaz pública.
  • Si un jugador realiza un movimiento ilegal, no se le permitirá competir en el torneo. Cualquier problema que cause movimientos ilegales debe resolverse antes de la ejecución del torneo.
  • Si se realizan envíos adicionales después de la ejecución del torneo, se ejecutará un nuevo torneo con los nuevos envíos y el envío ganador se actualizará en consecuencia. Sin embargo, no garantizo la rapidez en la ejecución del nuevo torneo.
  • Los envíos no pueden explotar ningún error en el código del controlador que haga que se desvíe de las reglas reales del juego. Señalarme los errores (en un comentario y / o en un problema de GitHub), y los solucionaré.
  • El uso de las herramientas de reflexión de Java está prohibido.
  • Se puede usar cualquier lenguaje que se ejecute en JVM, o que se pueda compilar en Java o JVM bytecode (como Scala o Jython), siempre y cuando proporcione cualquier código adicional necesario para interactuar con Java.

Comentarios finales

Si hay algún método de utilidad que le gustaría que agregue al controlador, simplemente pregunte en los comentarios y / o haga un problema en GitHub, y lo agregaré, suponiendo que no permita romper las reglas o exponer información a qué jugadores no están al tanto. Si desea escribirlo usted mismo y crear una solicitud de extracción en GitHub, ¡aún mejor!

Mego
fuente
ACES? Quieres decir ONES? Estos son dados, no cartas.
mbomb007
No recuerdo haberlo visto así cuando lo jugué, pero está bien.
mbomb007
¿Hay algún método para obtener el puntaje de una categoría dada dado un conjunto de dados?
mbomb007
@ mbomb007 No, pero ciertamente puedo hacer uno :)
Mego

Respuestas:

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);
    }

}

Este reproductor está aquí para servir como un esquema básico de cómo usar las herramientas presentes en el controlador Yahtzee. Elige a Yahtzee siempre que sea posible, y toma decisiones aleatorias de lo contrario, mientras cumple con las estrictas reglas de comodín.

Mego
fuente
1

Ases y Ochos

Bueno, esto tardó mucho más de lo que me hubiera gustado gracias a lo ocupado que he estado últimamente.

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;
    }
}

El bot busca patrones en los dados que puedan coincidir con ciertas categorías y tiene los necesarios. Inmediatamente elige una categoría de coincidencia de alta prioridad si se encuentra; de lo contrario, elige una categoría que produce la puntuación más alta. Anota casi 200 puntos por juego en promedio.

TNT
fuente