Играть в игру Yahtzee

18

В игре Yahtzee игроки по очереди бросают по 5 шестигранных кубиков до трех раз за ход, возможно, сохраняя кубы между бросками, а затем выбирая категорию, которую они хотят использовать для своего броска. Это продолжается до тех пор, пока не останется больше категорий (что происходит после 13 ходов). Затем подсчитываются очки игроков, и побеждает игрок с наибольшим количеством очков.

Категории следующие («сумма игральных костей» означает суммирование количества пипсов в указанной кости):

  • Верхняя часть
    • Тузы : сумма кубиков, показывающих 1 пункт
    • Twos : сумма кубиков с 2 пипсами
    • Тройка : сумма кубиков с 3 пунктами
    • Четверки : сумма кубиков с 4 пунктами
    • Пятерки : сумма кубиков с 5 пунктами
    • Sixes : сумма кубиков с 6 пипсами
  • Нижняя часть
    • Три вида : 3 кубика с одинаковым значением, сумма очков - сумма всех кубиков
    • Четыре вида : 4 кубика с одинаковым значением, сумма очков - сумма всех кубиков.
    • Full House : 3 кубика с одним значением и 2 с другим, оценка 25
    • Small Straight : 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). Если взяты все категории нижнего раздела, то Яхтзе может быть применен к неиспользуемой категории верхнего раздела со счетом 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, даже лучше!

Mego
источник
ACES? Вы имеете в виду ONES? Это кости, а не карты.
mbomb007
@ 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 всякий раз, когда это возможно, и делает случайный выбор в противном случае, соблюдая при этом строгие правила джокера.

Mego
источник
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 очков за игру.

тротил
источник