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.