Jogue uma partida de Yahtzee

17

No jogo Yahtzee, os jogadores se revezam lançando 5 dados de 6 lados até três vezes por turno, possivelmente salvando dados entre jogadas e, em seguida, selecionando uma categoria que desejam usar para a rolagem. Isso continua até que não haja mais categorias (o que acontece após 13 turnos). Em seguida, as pontuações dos jogadores são computadas e o jogador com a maior pontuação vence.

As categorias são as seguintes ("soma dos dados" significa somar o número de pips nos dados especificados):

  • Seção superior
    • Ases : soma dos dados mostrando 1 pip
    • Dois : soma dos dados mostrando 2 pips
    • Três : soma dos dados mostrando 3 pips
    • Quatro : soma dos dados mostrando 4 pips
    • Cincos : soma dos dados mostrando 5 pips
    • Seis : soma dos dados mostrando 6 pips
  • Seção inferior
    • Three of a Kind : 3 dados com o mesmo valor, pontuação é a soma de todos os dados
    • Four of a Kind : 4 dados com o mesmo valor, pontuação é a soma de todos os dados
    • Full House : 3 dados com um valor e 2 com outro, a pontuação é 25
    • Straight pequeno : 4 dados seqüenciais, pontuação 30
    • Straight Grande : 5 dados sequenciais, pontuação 40
    • Yahtzee : todos os 5 dados com o mesmo valor, a pontuação é 50
    • Chance : qualquer combinação de dados, pontuação é a soma de todos os dados

Existem algumas regras sobre as opções de categoria:

  • Se um jogador escolher uma categoria que não corresponde à sua jogada, ele recebe uma pontuação de 0 para essa categoria.
  • Se um jogador obtiver uma pontuação de pelo menos 63 na seção superior, ele receberá 35 pontos de bônus.
  • Se um jogador rolou um Yahtzee, mas a categoria Yahtzee já está ocupada (por outro Yahtzee - preencher 0 por uma falta não conta), ele recebe um bônus de 100 pontos. Este bônus é concedido a cada Yahtzee após o primeiro.
    • Além disso, o jogador ainda deve optar por preencher uma categoria. Eles devem escolher a categoria da seção superior correspondente ao seu rolo (por exemplo, um rolo de 5 6s deve ser colocado na categoria Seis). Se a categoria de seção superior correspondente já tiver sido usada, o Yahtzee poderá ser usado para uma categoria de seção inferior (nesse caso, a escolha de Full House, Small Straight ou Large Straight concede a quantidade normal de pontos em vez de 0). Se todas as categorias da seção inferior forem tomadas, o Yahtzee poderá ser aplicado a uma categoria da seção superior não utilizada, com uma pontuação de 0.

O desafio

Neste desafio, os competidores disputarão 1000 jogos de Yahtzee. No final de cada jogo, as finalizações que marcaram a maior pontuação receberão 1 ponto. Depois que todos os jogos terminarem, a finalização com mais pontos será vencida. Se houver empate, jogos adicionais serão disputados apenas com as finalizações empatadas até que o empate seja interrompido.

Controlador

O código completo do controlador pode ser encontrado neste repositório GitHub . Aqui estão as interfaces públicas com as quais os jogadores estarão interagindo:

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

}

Além disso, existem alguns métodos utilitários no Util.java. Eles estão lá principalmente para simplificar o código do controlador, mas podem ser usados ​​pelos jogadores, se assim o desejarem.

Regras

  • Os jogadores não podem interagir de forma alguma, exceto usando o Scorecard.getScoresmétodo para ver a pontuação atual de todos os jogadores. Isso inclui conluio com outros jogadores ou sabotar outros jogadores através da manipulação de partes do sistema que não fazem parte da interface pública.
  • Se um jogador fizer uma jogada ilegal, ele não poderá competir no torneio. Quaisquer problemas que causem movimentos ilegais devem ser resolvidos antes da execução do torneio.
  • Se envios adicionais forem feitos após o término do torneio, um novo torneio será realizado com os novos envios, e o envio vencedor será atualizado de acordo. No entanto, não garanto a rapidez na execução do novo torneio.
  • Os envios não podem explorar nenhum bug no código do controlador que o desvie das regras reais do jogo. Aponte os erros para mim (em um comentário e / ou em um problema do GitHub), e eu os corrigirei.
  • É proibido o uso das ferramentas de reflexão de Java.
  • Qualquer idioma que é executado na JVM ou pode ser compilado no Java ou no bytecode da JVM (como Scala ou Jython) pode ser usado, desde que você forneça qualquer código adicional necessário para fazer a interface com Java.

Comentários finais

Se houver algum método utilitário que você gostaria que eu adicionasse ao controlador, basta perguntar nos comentários e / ou fazer um problema no GitHub, e eu o adicionarei, assumindo que ele não permita a quebra de regras ou exponha informações a quais jogadores não estão a par. Se você quiser escrever e criar uma solicitação de recebimento no GitHub, melhor ainda!

Mego
fonte
ACES? Você quer dizer ONES? Estes são dados, não cartões.
mbomb007
@ mbomb007 Os marcadores Yahtzee chamam de ases .
Mego 17/10
Não me lembro de vê-lo chamado assim quando o toquei, mas tudo bem.
mbomb007
Existe um método para obter a pontuação para uma determinada categoria, dado um conjunto de dados?
mbomb007
@ mbomb007 Não, mas eu certamente pode fazer um :)
Mego

Respostas:

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 player está aqui para servir como um esboço básico de como usar as ferramentas presentes no controlador Yahtzee. Ele escolhe Yahtzee sempre que possível e faz escolhas aleatórias de outra forma, cumprindo as regras estritas do coringa.

Mego
fonte
1

Ases e Oito

Bem, isso levou muito mais tempo do que eu gostaria, graças ao quão ocupado eu estive 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;
    }
}

O bot procura por padrões nos dados que possam corresponder a determinadas categorias e contém as necessárias. Ele escolhe imediatamente uma categoria de correspondência de alta prioridade, se alguém for encontrado; caso contrário, ele escolhe uma categoria que gera a maior pontuação. Marca quase 200 pontos por jogo em média.

TNT
fonte