Gioca una partita a Yahtzee


18

Nel gioco Yahtzee, i giocatori a turno lanciano 5 dadi a 6 facce fino a tre volte per turno, possibilmente salvando i dadi tra i tiri e quindi selezionando una categoria che desiderano usare per il proprio tiro. Questo continua fino a quando non ci sono più categorie (cosa che accade dopo 13 turni). Quindi, vengono conteggiati i punteggi dei giocatori e vince il giocatore con il punteggio più alto.

Le categorie sono le seguenti ("somma dei dadi" significa sommare il numero di semi sui dadi specificati):

  • Sezione superiore
    • Assi : somma dei dadi con 1 pip
    • Due : somma dei dadi con 2 semi
    • Tre : somma dei dadi con 3 semi
    • Fours : somma dei dadi con 4 semi
    • Five : somma dei dadi con 5 pips
    • Sei : somma dei dadi con 6 semi
  • Sezione inferiore
    • Tris : 3 dadi con lo stesso valore, il punteggio è la somma di tutti i dadi
    • Four of a Kind : 4 dadi con lo stesso valore, il punteggio è la somma di tutti i dadi
    • Full : 3 dadi con un valore e 2 con un altro, il punteggio è 25
    • Piccola scala : 4 dadi sequenziali, il punteggio è 30
    • Scala grande : 5 dadi sequenziali, il punteggio è 40
    • Yahtzee : tutti e 5 i dadi con lo stesso valore, il punteggio è 50
    • Probabilità : qualsiasi combinazione di dadi, il punteggio è la somma di tutti i dadi

Ci sono alcune regole sulle scelte di categoria:

  • Se un giocatore sceglie una categoria che non corrisponde al proprio tiro, riceve un punteggio di 0 per quella categoria.
  • Se un giocatore guadagna un punteggio di almeno 63 nella sezione superiore, riceverà 35 punti bonus.
  • Se un giocatore ha lanciato uno Yahtzee ma la categoria Yahtzee è già presa (da un altro Yahtzee - il conteggio 0 per un mancato guadagno non conta), riceveranno un bonus di 100 punti. Questo bonus viene assegnato per ogni Yahtzee dopo il primo.
    • Inoltre, il giocatore deve comunque scegliere di compilare una categoria. Devono scegliere la categoria della sezione superiore corrispondente al proprio tiro (ad es. Un tiro di 5 6 deve essere inserito nella categoria Sei). Se la categoria della sezione superiore corrispondente è già stata utilizzata, lo Yahtzee può essere utilizzato per una categoria della sezione inferiore (in questo caso, scegliendo Full House, Small Straight o Large Straight viene assegnato il normale numero di punti anziché 0). Se vengono prese tutte le categorie della sezione inferiore, lo Yahtzee può essere applicato a una categoria della sezione superiore non utilizzata, con un punteggio di 0.

La sfida

In questa sfida, i concorrenti giocheranno 1000 partite di Yahtzee. Alla fine di ogni partita, le iscrizioni che hanno ottenuto il punteggio più alto riceveranno 1 punto. Al termine di tutte le partite, vincerà il contributo con il maggior numero di punti. In caso di pareggio, le partite aggiuntive verranno giocate solo con gli invii in pareggio fino a quando il pareggio non verrà interrotto.

controllore

Il codice completo del controller è disponibile su questo repository GitHub . Ecco le interfacce pubbliche con cui i giocatori interagiranno:

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

}

Inoltre, ci sono alcuni metodi di utilità in Util.java . Sono principalmente lì per semplificare il codice del controller, ma possono essere utilizzati dai giocatori se lo desiderano.

Regole

  • I giocatori non sono autorizzati ad interagire in alcun modo tranne che con il Scorecard.getScores metodo per vedere i punteggi attuali di tutti i giocatori. Ciò include colludere con altri giocatori o sabotare altri giocatori manipolando parti del sistema che non fanno parte dell'interfaccia pubblica.
  • Se un giocatore fa una mossa illegale, non gli sarà permesso di competere nel torneo. Eventuali problemi che causano mosse illegali devono essere risolti prima dello svolgimento del torneo.
  • Se dopo la fine del torneo vengono presentate ulteriori proposte, verrà avviato un nuovo torneo con le nuove proposte e la proposta vincente verrà aggiornata di conseguenza. Tuttavia, non garantisco la prontezza nello svolgimento del nuovo torneo.
  • Gli invii non possono sfruttare alcun bug nel codice del controller che ne derivi dalle regole di gioco effettive. Sottolineami i bug (in un commento e / o in un problema di GitHub) e li risolverò.
  • L'uso degli strumenti di riflessione di Java è vietato.
  • È possibile utilizzare qualsiasi linguaggio eseguito su JVM o che può essere compilato in bytecode Java o JVM (come Scala o Jython), purché si fornisca qualsiasi codice aggiuntivo necessario per interfacciarlo con Java.

Commenti finali

Se esiste un metodo di utilità che vorresti che aggiungessi al controller, chiedi semplicemente nei commenti e / o fai un problema su GitHub, e lo aggiungerò, supponendo che non consenta la violazione delle regole o esporre informazioni a quali giocatori non sono a conoscenza. Se vuoi scriverlo tu stesso e creare una richiesta pull su GitHub, ancora meglio!


ACES? Intendi ONES? Questi sono dadi, non carte.
mbomb007,


Non ricordo di averlo chiamato così quando l'ho suonato, ma va bene.
mbomb007,

Esiste un metodo per ottenere il punteggio per una determinata categoria dato un set di dadi?
mbomb007,

@ mbomb007 No, ma di certo ne posso fare uno :)
Mego,

Risposte:


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

}

Questo giocatore serve qui come schema di base per l'utilizzo degli strumenti presenti nel controller Yahtzee. Scegli Yahtzee ogni volta che è possibile, e fa scelte casuali in caso contrario, nel rispetto delle rigide regole del joker.


1

Assi e Otto

Bene, ci è voluto molto più tempo di quanto avrei voluto grazie a quanto sono stato impegnato ultimamente.

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

Il bot cerca schemi nei dadi che potrebbero corrispondere a determinate categorie e contiene quelli necessari. Sceglie immediatamente una categoria di corrispondenza prioritaria se viene trovata; altrimenti sceglie una categoria che produce il punteggio più alto. Segna quasi 200 punti per partita in media.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.