Стратегия Mastermind

19

Я мог найти только вызовы для игры в гольф для Mastermind, так что вот версия для кода, которую мне хотелось бы взять на себя.

Оптимальная стратегия для нормальной игры Mastermind, MM (4,6), была найдена Коямой и Лаем в 1993 году, имея среднее количество догадок = 5625/1296 ~ 4,34. ММ (5,8) до сих пор не решен, но, по оценкам, имеет среднее количество предположений ~ 5,5.

Ваша задача - создать стратегию MM (5,8) для 5 лунок и 8 цветов, охватывающую все pow(8,5) = 32768возможные различные решения. Очевидно, это не должно быть оптимальным. У вас есть два варианта:

  1. Опубликовать детерминистическую программу, которая генерирует стратегию. Программа должна быть компилируемой / запускаемой в Windows 7, Mac OS X или Linux без каких-либо дополнительных несвободных программ.
  2. Опубликуйте свою стратегию (вместе с именем StackExchange) где-нибудь в Интернете и опубликуйте URL-адрес здесь.

В обоих случаях укажите оценку (см. Ниже) в заголовке ответа.

Стратегия должна быть закодирована в соответствии со следующей грамматикой:

strategy : guessing-strategy | known-solution-strategy
guessing-strategy : '{' guess ':' branches '}'
known-solution-strategy : guess
guess : color color color color color
color : 'A'..'H'
branches : '{' branch (',' branch)* '}'
branch : reply ':' strategy
reply : number-of-blacks number-of-whites
number-of-blacks : number-of-key-pegs
number-of-whites : number-of-key-pegs
number-of-key-pegs : '0'..'5'

Алгоритм, используемый для определения количества чёрно-белых колышков клавиш, описан в http://en.wikipedia.org/wiki/Mastermind_(board_game)

Обратите внимание, что ответ «50» (т.е. правильное предположение) подразумевается и не является частью грамматики.

Оценка: N = сумма количества предположений для каждого из 32768 путей / решений. Стратегия с самым низким N выигрывает. Первый тай-брейк: наименьшее максимальное количество догадок. Второй тай-брейк: первый опубликованный ответ. Конкурс заканчивается 1 августа 2014 года в 0:00 по Гринвичу .


Пример стратегии для MM (2,3) со счетом = 21:

{AB:{10:{AC:{10:AA,01:CB,00:BB}},02:BA,01:{BC:{01:CA}},00:CC}}

Используя эту стратегию, 9 возможных игр будут выглядеть так:

  • AB 20
  • AB 10, AC 20
  • AB 10, AC 10, AA 20
  • AB 10, AC 01, CB 20
  • AB 10, AC 00, BB 20
  • AB 02, BA 20
  • AB 01, BC 20
  • AB 01, BC 01, CA 20
  • AB 00, CC 20

Скоро я опубликую для вашего удобства верификатор стратегии MM (5,8) на основе Java.

MrBackend
источник
Мне трудно понять, как применяется пример стратегии ММ (2,3). Можете ли вы опубликовать пример игры, объясняющей стратегию?
@tolos Я добавил все 9 :)
MrBackend
Было бы здорово, если бы ваш верификатор тоже мог выводить результаты!
Не то, что Чарльз
@ Чарльз подойдет!
MrBackend
2
Желаете ли вы изменить свою грамматику, чтобы разрешить один и тот же ответ на несколько комбинаций клавиш? Как {AB:{10|01:BB}}? У меня есть ответ, но он довольно наивный, и из-за древовидной структуры грамматики он не масштабируется вообще (4 отверстия, 3 цвета, генерирует стратегию 147 МБ, которую я мог бы значительно сократить , комбинируя части дерево).
Мартин Эндер

Ответы:

6

Джава

Мой алгоритм для MM (5,8) дает 177902, 178006, 182798, 182697 с максимальной глубиной 8 9 и требует всего несколько секунд (на моем медленном компьютере).

Пример вывода стратегии для MM (2,3) со счетом = 21, найденной этим алгоритмом, выглядит следующим образом:

{BC:{00:AA,01:AB:{01:CA},02:CB,10:AC:{00:BB,01:BA,10:CC}}}

В моем алгоритме нет ничего захватывающего. Нет изобретения. Я просто следовал рецептам, найденным в сети, и сжал их в этот код Java. Единственная оптимизация, которую я сделал, - это попытка оптимизировать строки кода (в некотором смысле). Это выглядит так:

  1. Создайте начальный набор S0 всех возможных кодов, чтобы быть текущим набором S.
  2. Разрушитель кодов находит (жадное) хорошее предположение для S. Каждое предположение приводит к разделу P из S, где каждое подмножество S 'собирает все коды (из S), имеющие одинаковый ответ на предположение. У хорошего предположения есть хороший раздел, так как тот, который дает больше информации для предположения.
  3. Возьмите правильное предположение и его P. Для каждого непустого S 'в P примените рекурсивный взломщик кода (шаг 2).

@MrBackend: Думаю, написать верификатор сложно. ;-)

import java.util.TreeMap;
import java.util.Vector;

public class MM {
    Vector<String> codeset = new Vector<String>();
    String guess;
    TreeMap<Integer, MM> strategy = new TreeMap<Integer, MM>();

    public String toString() {
        String list="";
        for (Integer reply: strategy.keySet()) {
            if (strategy.get(reply)!=null) list+=(list.length()>0?",":"")+(reply<10?"0":"")+reply+":"+strategy.get(reply);
        }
        if (list.length()>0) return guess+":{"+list+"}"; else return guess;
    }

    MM() { }

    MM(int h, int c) {
        for (int i = 0; i < Math.pow(c, h); i++) {
            String code = "";
            for (int j = 0, p=i; j < h; j++) {
                code+="ABCDEFGH".charAt(p%c);
                p/=c;
            }
            codeset.add(code);
        }
    }

    int replyAccordingToDonaldKnuth(String secret, String guess) {
        int black=0;
        int totalHitsBlackAndWhite=0;
        for (char n = 'A'; n <= 'H'; n++) {
            int a=0, b=0;
            for (int i = 0; i < secret.length(); i++) {
                if (secret.charAt(i)==n) a++;
                if ( guess.charAt(i)==n) b++;
            }
            totalHitsBlackAndWhite+=Math.min(a, b);
        }
        for (int i = 0; i < secret.length(); i++) {
            if (secret.charAt(i) == guess.charAt(i)) black++;
        }
        return 10 * black + (totalHitsBlackAndWhite-black);
    }

    int reply(String secret, String guess) {
        return replyAccordingToDonaldKnuth(secret, guess);
    }

    MM codebreaker(Vector<String> permuts) {
        int fitness=0;
        MM protostrategy=null;
        for (int greedy = 0; greedy < Math.min(permuts.size(), 200); greedy++) {
            MM tmp=partition(permuts, permuts.get(greedy));
            int value=tmp.strategy.size();
            if (fitness<=value) {
                fitness=value;
                protostrategy=tmp;
                protostrategy.guess=permuts.get(greedy);
            }
        }
        if (protostrategy!=null) {
            for (Integer reply: protostrategy.strategy.keySet()) {
                protostrategy.strategy.put(reply, codebreaker(protostrategy.strategy.get(reply).codeset));
            }
        }
        return protostrategy;
    }

    MM partition(Vector<String> permuts, String code) {
        MM protostrategy=new MM();
        for (int c = 0; c < permuts.size(); c++) {
            int reply=reply(permuts.get(c), code);
            if (!protostrategy.strategy.containsKey(reply)) protostrategy.strategy.put(reply, new MM());
            if (permuts.get(c)!=code) protostrategy.strategy.get(reply).codeset.add(permuts.get(c));
        }
        return protostrategy;
    }

    public static void main(String[] args) {
        MM mm = new MM(5,8);
        System.out.println("{"+mm.codebreaker(mm.codeset)+"}");
    }
}

Некоторые замечания:

  1. Проверка согласованности не требуется, поскольку наборы S и их разделы строятся (автоматически) согласованным образом.
  2. Выбор правильного предположения из S0 (вместо S) имеет смысл. Но я не придерживаюсь этого подхода в текущем коде.
  3. Мой жадный поиск искусственно сокращен до 200 попыток.
  4. Я знаю, «давать большую информацию для догадки» не очень точно. Простая идея - выбрать раздел с наибольшим количеством подмножеств.
  5. Результат сильно зависит от того, как вы рассчитываете ответ (..). Наконец я адаптировал выражение лица Дональда Кнута.

Стратегию для MM(5,8) можно найти здесь . У GitHub есть некоторые проблемы с отображением длинных строк, поэтому нажмите на кнопку Raw .

Боб Геном
источник
Привет, как правильно печатать текст на github, чтобы результаты можно было превратить в справочник по поиску ... из первой начальной точки "HHCAA" ... и следующего шага в зависимости от ответа ... и до следующего и так далее. Текущий формат необработанного текста не так удобен для ручного сканирования. Техника, которую я использую, заключается в том, как разобрать вложенные скобки и получить красивую разметку таблицы, которую легче довести до конца, аналогично маркированному списку в вопросе. для ММ (2,3). Спасибо. Надеюсь, ты сможешь понять, что я хочу. (предпочитаю python для разбора str)
ihightower
2

Рубин

РЕДАКТИРОВАТЬ: Добавлена ​​логика, чтобы исключить невозможные догадки. Следовательно, стратегии теперь соответствуют данному формату и намного более управляемы.

Итак, вот одна попытка, чтобы начать это. Это довольно наивно (и не очень разборчиво - это помогает читать ветку if / elsif / else снизу вверх).

Holes, Colors = ARGV.map &:to_i

ColorChars = ('A'..'H').to_a

def is_possible(guess, blacks, result)
    blacks == guess.chars.zip(result.chars).count {|chars| chars[0] == chars[1]}
end

def print_strategy(known_colors, remaining_permutations, next_color)
    char = ColorChars[next_color]
    if remaining_permutations
        guess = remaining_permutations[0]
        print guess
        if remaining_permutations.length > 1
            print ':{'
            (Holes-1).times do |i|
                new_permutations = (remaining_permutations - [guess]).select { |perm| is_possible(guess, i, perm) }
                next if new_permutations.empty?
                print "#{i}#{Holes-i}:"                
                print '{' if new_permutations.length > 1
                print_strategy(known_colors, new_permutations, next_color)
                print '}' if new_permutations.length > 1
                print ',' if i < Holes-2
            end
            print '}'
        end
    elsif known_colors.length == Holes
        print_strategy(known_colors, known_colors.chars.permutation.map(&:join).uniq, next_color)
    elsif next_color == Colors-1
        print_strategy(known_colors+char*(Holes - known_colors.length), remaining_permutations, next_color+1)
    else
        print char*Holes, ':{'

        (Holes - known_colors.length + 1).times do |i|
            break if i == Holes
            print "#{i}0:"
            print '{' if next_color < Colors-2 || i > 0 || known_colors.length > 0
            print_strategy(
                known_colors+char*i,
                remaining_permutations,
                next_color+1
            )
            print '}' if next_color < Colors-2 || i > 0 || known_colors.length > 0
            print ',' if i < (Holes - known_colors.length) && i < Holes-1
        end
        print '}'
    end
end

print '{'
print_strategy('', nil, 0)
puts '}'

Во- первых, я стараюсь 5 каждого цвета: AAAAA, BBBBBи т.д. Из этого я выясняю , какие цвета фактически используются в шаблоне. И тогда я просто пробую все перестановки данных цветов, опуская те, которые уже были исключены черными колышками.

Вот MM(2,3)стратегия:

{AA:{00:{BB:{00:CC,10:{BC:{02:CB}}}},10:{BB:{00:{AC:{02:CA}},10:{AB:{02:BA}}}}}}

Стратегия для MM(5,8)занимает 376KB и можно найти здесь . У GitHub есть некоторые проблемы с отображением длинных строк, поэтому нажмите на кнопку Raw .

Теперь, если я получу верификатор, я также могу сказать вам, каков мой реальный счет. :)

Мартин Эндер
источник
Плохое чувство по поводу того, что верификатор еще не опубликован, но он уже в пути ... Что-то не так с вашей (первой) стратегией MM (2,3), например, если решение AB: Guess = AA; ответ = 10; догадываюсь = BB; reply = 10, для которого нет стратегии. Я рассмотрю ваше предложение о группировке ответов, но оно не должно быть необходимым для хороших стратегий, поскольку множество возможных решений не пересекается для разных ответов.
MrBackend
@MrBackend Хороший улов, я пропустил дело там. Это должно быть исправлено сейчас. Что касается грамматики, конечно, это не обязательно для хороших стратегий, но я подумал, что вы, возможно, захотите немного снизить планку. ;) Если люди могут отправлять более простые записи (например, мои), вам, возможно, повезет больше, если вы получите интересные предложения, которые постепенно улучшаются, вместо того, чтобы включать все комбинаторики с самого начала.
Мартин Эндер
Вот в чем дело: я закончу текущий верификатор и опубликую его (как веб-приложение - оно слишком велико для вставки). К сожалению, он может быть слишком строгим, так как считает невозможными ответы об ошибках. После этого я адаптирую его для поддержки нескольких ответов и просто выдаю предупреждения о невозможных ответах. Сказав это, ваша стратегия 1,3G MM (4,4) должна содержать много невозможных ответов и / или неубедительных догадок, так как предполагаемый размер приличной стратегии MM (5,8) - это горстка мег.
MrBackend
@MrBackend Конечно, мои стратегии содержат массу невозможных ответов. Вот что я имел в виду под «я не занимаюсь комбинаторикой». ;) Если вам слишком сложно поддерживать это и группировать ответы, не волнуйтесь, тогда я постараюсь исключить невозможные догадки.
Мартин Эндер
@MrBackend Хорошие новости, я исправил это. :) Надеюсь, стратегия верна сейчас. Дайте мне знать, если есть еще проблемы с этим.
Мартин Эндер