Пограйте в гру Yahtzee


18

У грі Yahtzee гравці роблять по черзі кочення 5 шестигранних кісток до трьох разів за оборот, можливо, економлячи кістки між рулонами, а потім вибирають категорію, яку бажають використовувати для свого рулону. Це триває, поки не буде більше категорій (що відбувається після 13 витків). Потім підсумовуються підсумки гравців, і виграє гравець з найвищим балом.

Категорії наступні ("сума кубиків" означає додавання кількості піпсів на вказаних кістях):

  • Верхній Розділ
    • Тузи : сума кістки, що показує 1 піп
    • Двічі : сума кістки, що показує 2 піпси
    • Трійки : сума кістки, що показує 3 піпси
    • Четверть : сума кістки, що показує 4 піпси
    • П’ятірки : сума кістки, що показує 5 піпсів
    • Шістдесят : сума кістки, що показує 6 піпсів
  • Нижній Розділ
    • Три види : 3 кістки з однаковим значенням, оцінка - це сума всіх кісток
    • Чотири з видів : 4 кістки з однаковим значенням, оцінка - це сума всіх кісток
    • Full House : 3 кубики з одним значенням і 2 з іншим, оцінка - 25
    • Маленька пряма : 4 послідовні кубики, оцінка - 30
    • Велика пряма : 5 послідовних кісток, оцінка 40
    • Yahtzee : всі 5 кубиків з однаковим значенням, оцінка 50
    • Шанс : будь-яка комбінація кісток, оцінка - це сума всіх кісток

Існує кілька правил щодо вибору категорії:

  • Якщо гравець вибирає категорію, яка не відповідає їх рулону, він отримує бал 0 для цієї категорії.
  • Якщо гравець заробить бал принаймні 63 у верхній частині, він отримає 35 бонусних балів.
  • Якщо гравець прокатав Yahtzee, але категорія Yahtzee вже прийнята (інший Yahtzee - заповнення 0 за промах не зараховується), він отримує бонус у 100 балів. Цей бонус присуджується кожному Yahtzee після першого.
    • Крім того, гравець повинен все-таки вибрати, щоб заповнити категорію. Вони повинні вибрати категорію верхнього розділу, що відповідає їх рулону (наприклад, рулон 5 6-х повинен бути розміщений у категорії шістдесят). Якщо відповідна категорія верхнього розділу вже використана, Yahtzee може бути використаний для категорії нижнього розділу (у цьому випадку, вибираючи Full House, Small Straight або Large Straight присвоюється нормальна кількість балів, а не 0). Якщо взяти всі категорії нижнього розділу, то Yahtzee може бути застосований до невикористаної категорії верхнього розділу, з оцінкою 0.

Змагання

У цьому виклику конкуренти зіграють 1000 ігор Yahtzee. В кінці кожної гри представники, які набрали найбільшу суму, отримають 1 бал. Після закінчення всіх ігор, виграш з найбільшою кількістю очок виграє. Якщо є нічия, додаткові ігри будуть грати лише зв'язаними поданнями, поки не буде розірвано нічия.

Контролер

Повний код контролера можна знайти в цьому сховищі GitHub . Ось загальнодоступні інтерфейси, з якими гравці будуть взаємодіяти:

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

}

Крім того, є деякі корисні методи в Util.java. Вони в основному є для спрощення коду контролера, але їх можуть використовувати гравці, якщо вони бажають.

Правила

  • Гравцям заборонено взаємодіяти будь-яким способом, за винятком використання Scorecard.getScoresметоду для перегляду поточних результатів усіх гравців. Сюди входить домовленість з іншими гравцями або саботаж інших гравців за допомогою маніпулювання частинами системи, які не є частиною загальнодоступного інтерфейсу.
  • Якщо гравець зробить нелегальний хід, він не буде дозволений до участі в турнірі. Будь-які проблеми, що спричиняють нелегальні ходи, повинні бути вирішені до початку турніру.
  • Якщо після запуску турніру буде здійснено додаткове подання, новий турнір буде запущений з новими поданнями, і виграш подання буде оновлено відповідно. Однак я не даю жодних гарантій оперативності в проведенні нового турніру.
  • Подання можуть не використовувати жодних помилок у коді контролера, які спричиняють його відхилення від фактичних правил гри. Вкажіть на мене помилки (у коментарі та / або у випуску GitHub), і я їх виправлю.
  • Використання засобів відображення Java заборонено.
  • Будь-яка мова, що працює на JVM або може бути скомпільована в байт-код Java або JVM (наприклад, Scala або Jython), може бути використана, якщо ви надаєте будь-який додатковий код, необхідний для взаємодії з Java.

Заключні коментарі

Якщо є якийсь корисний метод, який ви хотіли б мені додати до контролера, просто запитайте у коментарях та / або зробіть проблему на GitHub, і я додам це, припускаючи, що він не дозволяє порушувати правила або викривати інформацію які гравці неприватні. Якщо ви хочете написати це самостійно і створити запит на тягнення на GitHub, ще краще!


ACES? Ви маєте на увазі ONES? Це кубики, а не картки.
mbomb007


Я не пам'ятаю, як це називали, коли я грав у нього, але добре.
mbomb007

Чи існує спосіб отримати бал за дану категорію з набором кісток?
mbomb007

@ mbomb007 Ні, але я, безумовно, можу зробити одне :)
Mego

Відповіді:


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

}

Цей плеєр тут служить основним контуром для використання інструментів, наявних у контролері Yahtzee. Він обирає Yahtzee, коли це можливо, і робить випадковий вибір інакше, дотримуючись суворих правил жарту.


1

Тузи та вісімки

Ну, це зайняло набагато більше часу, ніж мені хотілося б завдяки тому, наскільки я зайнятий останнім часом.

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

Бот шукає шаблони в кубиках, які могли б відповідати певним категоріям і вміщувати необхідні. Він негайно вибирає категорію відповідності з високим пріоритетом, якщо її знайдено; інакше він вибирає категорію, яка дає найвищий бал. В середньому набирає майже 200 балів за гру.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.