Zagraj w grę Yahtzee

18

W grze Yahtzee gracze na zmianę rzucają 5 6-stronnymi kostkami do trzech razy na turę, prawdopodobnie oszczędzając kości między rzutami, a następnie wybierając kategorię, której chcą użyć do rzutu. Trwa to do momentu, gdy nie będzie więcej kategorii (co nastąpi po 13 turach). Następnie wyniki graczy są sumowane, a gracz z najwyższym wynikiem wygrywa.

Kategorie są następujące („suma kości” oznacza sumowanie liczby pipsów na określone kości):

  • Górna sekcja
    • Asy : suma kości pokazująca 1 pip
    • Dwójki : suma kości pokazująca 2 pipsy
    • Trójki : suma kości pokazująca 3 pipsy
    • Czwórki : suma kości pokazująca 4 pipsy
    • Piątki : suma kości pokazująca 5 pipsów
    • Szóstki : suma kości pokazująca 6 pipsów
  • Dolna sekcja
    • Trójka : 3 kości o tej samej wartości, wynik jest sumą wszystkich kości
    • Four of a Kind : 4 kości o tej samej wartości, wynik to suma wszystkich kości
    • Full House : 3 kości o jednej wartości i 2 o drugiej, wynik to 25
    • Mała prosta : 4 kolejne kości, wynik to 30
    • Duża prosta : 5 kolejnych kości, wynik to 40
    • Yahtzee : wszystkie 5 kości o tej samej wartości, wynik to 50
    • Szansa : dowolna kombinacja kości, wynik jest sumą wszystkich kości

Istnieje kilka zasad dotyczących wyboru kategorii:

  • Jeśli gracz wybierze kategorię, która nie pasuje do jego rzutu, otrzyma wynik 0 dla tej kategorii.
  • Jeśli gracz zdobędzie co najmniej 63 punkty w górnej części, otrzymuje 35 punktów bonusowych.
  • Jeśli gracz rzucił Yahtzee, ale kategoria Yahtzee jest już zajęta (przez innego Yahtzee - wypełnienie 0 za chybienie się nie liczy), otrzymuje bonus 100 punktów. Ta premia jest przyznawana za każdego Yahtzee po pierwszym.
    • Dodatkowo gracz musi nadal wybrać kategorię. Muszą wybrać kategorię górnej sekcji odpowiadającą ich rzutowi (np. Rzut 5 6 musi być umieszczony w kategorii Szóstek). Jeśli odpowiednia kategoria górnej sekcji została już użyta, Yahtzee może być wykorzystana do kategorii dolnej sekcji (w tym przypadku wybranie Full House, Small Straight lub Large Straight przyznaje normalną liczbę punktów zamiast 0). Jeśli wszystkie dolne kategorie sekcji zostaną zajęte, wówczas Yahtzee można zastosować do nieużywanej kategorii górnej sekcji z wynikiem 0.

Wyzwanie

W tym wyzwaniu zawodnicy zagrają w 1000 gier Yahtzee. Na koniec każdej gry zgłoszenie, które uzyskało najwyższy wynik, otrzyma 1 punkt. Po zakończeniu wszystkich gier wygrywa zgłoszenie z największą liczbą punktów. W przypadku remisu zostaną rozegrane dodatkowe gry tylko z remisami, dopóki remis nie zostanie rozstrzygnięty.

Kontroler

Pełny kod kontrolera można znaleźć w tym repozytorium GitHub . Oto publiczne interfejsy, z którymi gracze będą wchodzić w interakcje:

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

}

Ponadto istnieje kilka metod narzędziowych w Util.java. Są one głównie po to, aby uprościć kod kontrolera, ale mogą być używane przez graczy, jeśli chcą.

Zasady

  • Gracze nie mogą wchodzić w interakcje w żaden sposób, z wyjątkiem Scorecard.getScoresmetody sprawdzania bieżących wyników wszystkich graczy. Obejmuje to zmowę z innymi graczami lub sabotowanie innych graczy poprzez manipulowanie częściami systemu, które nie są częścią publicznego interfejsu.
  • Jeśli gracz wykona nielegalny ruch, nie będzie mógł wziąć udziału w turnieju. Wszelkie problemy powodujące nielegalne ruchy muszą zostać rozwiązane przed rozpoczęciem turnieju.
  • Jeśli po uruchomieniu turnieju zostaną przesłane dodatkowe zgłoszenia, nowy turniej zostanie przeprowadzony z nowymi zgłoszeniami, a zwycięskie zgłoszenie zostanie odpowiednio zaktualizowane. Nie gwarantuję jednak szybkiego przeprowadzenia nowego turnieju.
  • Zgłoszenia nie mogą wykorzystywać żadnych błędów w kodzie kontrolera, które powodują odbieganie od rzeczywistych zasad gry. Wskaż mi błędy (w komentarzu i / lub w GitHubie), a naprawię je.
  • Korzystanie z narzędzi do refleksji Javy jest zabronione.
  • Można użyć dowolnego języka, który działa na JVM lub można go skompilować do kodu bajtowego Java lub JVM (takiego jak Scala lub Jython), pod warunkiem, że podasz dodatkowy kod potrzebny do połączenia go z Javą.

Ostatnie komentarze

Jeśli istnieje jakakolwiek metoda użyteczności, którą chciałbym dodać do kontrolera, po prostu zapytaj w komentarzach i / lub zrób problem w GitHub, a ja dodam ją, zakładając, że nie pozwala ona na łamanie reguł lub ujawnianie informacji którzy gracze nie są wtajemniczeni. Jeśli chcesz napisać to sam i utworzyć żądanie ściągnięcia na GitHub, jeszcze lepiej!

Mego
źródło
ACES? Masz na myśli ONES? To są kości, a nie karty.
mbomb007
Nie przypominam sobie, żeby widziałem, jak to się nazywa, kiedy go grałem, ale dobrze.
mbomb007,
Czy istnieje metoda uzyskania wyniku dla danej kategorii, biorąc pod uwagę zestaw kości?
mbomb007,
@ mbomb007 Nie, ale z pewnością mogę to zrobić :)
Mego

Odpowiedzi:

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

}

Ten odtwarzacz ma służyć jako podstawowy zarys korzystania z narzędzi obecnych w kontrolerze Yahtzee. W miarę możliwości wybiera Yahtzee i dokonuje losowych wyborów, zachowując przy tym ścisłe zasady jokera.

Mego
źródło
1

Asy i ósemki

Cóż, zajęło mi to znacznie więcej czasu, niż chciałbym, dzięki temu, jak bardzo byłem ostatnio zajęty.

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

Bot szuka w kostkach wzorów, które mogłyby pasować do niektórych kategorii i przechowuje niezbędne. Natychmiast wybiera kategorię dopasowania o wysokim priorytecie, jeśli zostanie znaleziona; w przeciwnym razie wybiera kategorię, która daje najwyższy wynik. Otrzymuje średnio prawie 200 punktów na grę.

TNT
źródło