Estratégia Mastermind


19

Eu só consegui encontrar desafios de código-golfe para o Mastermind, então aqui está uma versão de código-desafio que eu gostaria de enfrentar.

Uma estratégia ideal para o jogo Mastermind normal, MM (4,6), foi encontrada por Koyama e Lai em 1993, com um número médio de suposições = 5625/1296 ~ 4,34. MM (5,8) ainda não foi resolvido, mas estima-se que tenha um número médio de suposições ~ 5,5.

Sua tarefa é criar uma estratégia MM (5,8), com 5 furos e 8 cores, cobrindo todas pow(8,5) = 32768as soluções distintas possíveis. Obviamente, não precisa ser o ideal. Você tem duas opções:

  1. Poste um programa determinístico que gere a estratégia. O programa deve ser compilável / executável no Windows 7, Mac OS X ou Linux sem nenhum software extra gratuito.
  2. Publique sua estratégia (junto com o seu nome StackExchange) em algum lugar da Internet e publique o URL aqui.

Nos dois casos, indique a pontuação (veja abaixo) no cabeçalho da resposta.

A estratégia deve ser codificada de acordo com a seguinte gramática:

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'

O algoritmo usado para decidir o número de pinos de teclas preto / branco está descrito em http://en.wikipedia.org/wiki/Mastermind_(board_game)

Observe que a resposta "50" (ou seja, palpite correto) está implícita e não faz parte da gramática.

Pontuação: N = a soma do número de suposições para cada um dos 32768 caminhos / soluções. A estratégia com o N mais baixo ganha. Primeiro tie-break: o menor número máximo de palpites. Segundo tie-break: a primeira resposta postada. A competição termina em 1 de agosto de 2014 às 0:00 GMT .


Um exemplo de estratégia para MM (2,3) com pontuação = 21:

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

Usando esta estratégia, os 9 jogos possíveis serão assim:

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

Em breve, publicarei um verificador de estratégia MM (5,8) baseado em Java para sua conveniência.


Estou com dificuldades para entender como a estratégia de exemplo de MM (2,3) é aplicada. Você pode postar um exemplo de jogo explicando a estratégia?

@tolos eu adicionei todos os 9 :)
MrBackend

Seria ótimo se o seu verificador pudesse gerar uma pontuação também!
Não que Charles,

@ Charles vai fazer!
MrBackend

2
Você gostaria de alterar sua gramática para permitir a mesma resposta a várias combinações de pinos? Gosta {AB:{10|01:BB}}? Eu tenho uma resposta, mas é bastante ingênua e, devido à estrutura em árvore da gramática, ela não escala muito bem (4 furos, 3 cores, gera uma estratégia de 147 MB, que eu poderia reduzir significativamente combinando partes de a árvore).
Martin Ender

Respostas:


6

Java

Meu algoritmo para MM (5,8) pontua com 177902 178006 182798 182697 com uma profundidade máxima de 8 9 e precisa de apenas alguns segundos (no meu computador lento).

Um exemplo de saída de uma estratégia para MM (2,3) com score = 21 encontrada por esse algoritmo é semelhante a:

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

Não há nada empolgante com meu algoritmo. Nenhuma invenção. Eu apenas segui as receitas encontradas na rede e as comprimi neste código Java. A única otimização que fiz foi tentar otimizar as linhas de código (de certa forma). É assim:

  1. Crie o conjunto inicial S0 de todos os códigos possíveis para o conjunto atual S.
  2. O quebra-código encontra uma boa (gananciosa) suposição para S. Cada suposição leva a uma partição P de S, onde cada subconjunto S 'coleta todos os códigos (de S) com a mesma resposta na suposição. Um bom palpite tem uma boa partição, como a que fornece mais informações para o palpite.
  3. Adote o bom palpite e seu P. Para cada S 'vazio em P, aplique o quebra-código recursivo (etapa 2).

@ MrBackend: Escrever um verificador é difícil, eu acho. ;-)

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

Algumas observações:

  1. Nenhuma verificação de consistência é necessária porque os conjuntos S e suas partições são construídos de maneira (auto) consistente.
  2. Escolher um bom palpite de S0 (em vez de S) faz sentido. Mas eu não sigo essa abordagem no código atual.
  3. Minha busca gananciosa é podada artificialmente para 200 tentativas.
  4. Eu sei, "dar mais informações para o palpite" não é muito preciso. A idéia simples é escolher a partição com o maior número de subconjuntos.
  5. O resultado depende muito de como você calcula a resposta (..). Finalmente, adaptei a expressão de Donald Knuth.

A estratégia para MM(5,8) pode ser encontrada aqui . O GitHub tem alguns problemas ao exibir linhas que demoram tanto, então clique no botão Raw .


oi como imprimir o texto do github para que os resultados possam ser transformados em um guia de pesquisa .. desde o primeiro ponto de partida 'HHCAA' .. e na próxima etapa, dependendo da resposta ... e na próxima e assim por diante. O formato de texto bruto atual não é mais fácil para a digitalização manual. A técnica que eu estou procurando é como analisar os colchetes aninhados e obter um bom layout de tabela que seja mais fácil de seguir até o final, semelhante à lista com marcadores da pergunta para MM (2,3). Obrigado. Espero que você possa entender o que estou procurando. (prefira python para analisar str)
ihightower 30/12/17

2

Rubi

Edição: Adicionado alguma lógica para excluir suposições impossíveis. Portanto, as estratégias agora estão em conformidade com o formato fornecido e são muito mais gerenciáveis.

Então, aqui está uma tentativa de fazer isso acontecer. É bastante ingênuo (e não muito legível - ajuda a ler o ramo if / elsif / else de baixo para cima).

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

Primeiro, eu tento 5 de cada cor: AAAAA, BBBBB, etc. A partir desse figura I quais cores são realmente utilizados no padrão. E então simplesmente tento todas as permutações das cores fornecidas, omitindo aquelas que já foram descartadas pelos pinos pretos.

Aqui está a MM(2,3)estratégia:

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

A estratégia para MM(5,8)376KB pode ser encontrada aqui . O GitHub tem alguns problemas ao exibir linhas que demoram tanto, então clique no botão Raw .

Agora, se eu receber um verificador, também posso lhe dizer qual é minha pontuação real. :)


Sentindo-se mal com o verificador ainda não publicado, mas está a caminho ... Há algo errado com sua (primeira) estratégia MM (2,3), por exemplo, se a solução for AB: Guess = AA; resposta = 10; palpite = BB; reply = 10, para o qual não há estratégia. Analisarei sua sugestão de agrupar respostas, mas isso não deve ser necessário para boas estratégias, já que o conjunto de soluções possíveis não é adequado para respostas diferentes.
MrBackend

@ MrBackend Boa captura, eu pulei um caso lá. Deve ser corrigido agora. Quanto à gramática, é claro que não é necessário para boas estratégias, mas achei que você estivesse disposto a diminuir um pouco a fasquia. ;) Se as pessoas puderem enviar entradas mais simples (como a minha), você poderá ter um pouco mais de sorte com as submissões interessantes que melhoram gradualmente, em vez de precisar incluir todas as combinações desde o início.
Martin Ender

Aqui está o acordo: vou terminar o verificador atual e publicá-lo (como um aplicativo da Web - é muito grande para colar aqui). Infelizmente, pode ser muito rigoroso, pois considera respostas impossíveis um erro. Depois, vou adaptá-lo para suportar várias respostas e emitir apenas avisos para respostas impossíveis. Dito isto, sua estratégia de 1,3G MM (4,4) deve conter muitas respostas impossíveis e / ou suposições não redutoras, pois o tamanho estimado de uma estratégia decente de MM (5,8) é um punhado de megas.
precisa saber é o seguinte

@ MrBackend É claro que minhas estratégias contêm uma tonelada de respostas impossíveis. Isso é o que eu quis dizer com "não estou fazendo a combinatória". ;) Se for muito complicado para você apoiar isso e agrupar respostas, não se preocupe, então tentarei omitir suposições impossíveis.
Martin Ender

@MrBackend Boas notícias, eu as consertei. :) Espero que a estratégia seja válida agora. Deixe-me saber se ainda há algum problema.
Martin Ender
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.