Strategia di Mastermind


19

Ho potuto trovare solo sfide di code-golf per Mastermind, quindi ecco una versione di code-challenge che mi sarebbe piaciuto affrontare.

Una strategia ottimale per il normale gioco Mastermind, MM (4,6), è stata trovata da Koyama e Lai nel 1993, con un numero medio di ipotesi = 5625/1296 ~ 4.34. MM (5,8) è ancora irrisolto, ma si stima che abbia un numero medio di ipotesi ~ 5,5.

Il tuo compito è quello di creare una strategia MM (5,8), ovvero per 5 fori e 8 colori, che copra tutte le pow(8,5) = 32768possibili soluzioni distinte. Ovviamente, non deve essere ottimale. Hai due scelte:

  1. Pubblica un programma deterministico che genera la strategia. Il programma deve essere compilabile / eseguibile su Windows 7, Mac OS X o Linux senza software aggiuntivo non libero.
  2. Pubblica la tua strategia (insieme al tuo nome StackExchange) da qualche parte su Internet e pubblica qui l'URL.

In entrambi i casi, indica il punteggio (vedi sotto) nell'intestazione della risposta.

La strategia deve essere codificata secondo la seguente grammatica:

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'

L'algoritmo utilizzato per decidere il numero di pioli di tasti bianco / nero è descritto in http://it.wikipedia.org/wiki/Mastermind_(board_game)

Si noti che la risposta "50" (ovvero ipotesi corretta) è implicita e non fa parte della grammatica.

Punteggio: N = la somma del numero di ipotesi per ciascuno dei 32768 percorsi / soluzioni. Vince la strategia con la N più bassa. Primo pareggio: il numero massimo più basso di ipotesi. Secondo tie-break: la prima risposta pubblicata. La competizione termina il 1 agosto 2014 alle 0:00 GMT .


Un esempio di strategia per MM (2,3) con punteggio = 21:

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

Usando questa strategia, i 9 giochi possibili andranno così:

  • 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

Presto pubblicherò un verificatore di strategia MM (5,8) basato su Java per vostra comodità.


Sto avendo difficoltà a capire come viene applicata la strategia di esempio di MM (2,3). Puoi pubblicare un esempio di gioco che spiega la strategia?

@tolos Ho aggiunto tutti i 9 :)
MrBackend il

Sarebbe bello se anche il tuo verificatore potesse generare un punteggio!
Non che Charles, il

@Charles Will!
MrBackend,

2
Saresti disposto a modificare la grammatica per consentire la stessa risposta a più combinazioni di pioli di tasti? Come {AB:{10|01:BB}}? Ho una risposta, ma è abbastanza ingenuo e, a causa della struttura ad albero della grammatica, non si ridimensiona affatto bene (4 fori, 3 colori, genera una strategia di 147 MB, che potrei ridurre significativamente combinando parti di l'albero).
Martin Ender,

Risposte:


6

Giava

Il mio algoritmo per MM (5,8) segna 177902 178006 182798 182697 con una profondità massima di 8 9 e richiede solo pochi secondi (sul mio computer lento).

Un esempio di output di una strategia per MM (2,3) con punteggio = 21 trovato da questo algoritmo è simile al seguente:

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

Non c'è niente di eccitante con il mio algoritmo. Nessuna invenzione Ho appena seguito le ricette trovate in rete e le ho compresse in questo codice Java. L'unica ottimizzazione che ho fatto è stato cercare di ottimizzare le righe di codice (in un certo senso). Va così:

  1. Creare il set iniziale S0 di tutti i codici possibili per essere il set corrente S.
  2. Codebreaker trova un'ipotesi (avida) buona per S. Ogni ipotesi porta a una partizione P di S, dove ogni sottoinsieme S 'raccoglie tutti i codici (da S) che hanno la stessa risposta sull'ipotesi. Una buona ipotesi ha una buona partizione, come quella che fornisce la maggior parte delle informazioni per l'ipotesi.
  3. Prendi la buona ipotesi e la sua P. Per ogni S 'vuoto in P applica codebreaker ricorsivo (passaggio 2).

@MrBackend: scrivere un verificatore è difficile, immagino. ;-)

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

Alcune osservazioni:

  1. Non è necessario un controllo di coerenza perché gli insiemi S e le loro partizioni sono costruiti in modo (auto) coerente.
  2. Scegliere una buona ipotesi su S0 (anziché su S) ha senso. Ma non seguo questo approccio nel codice attuale.
  3. La mia ricerca avida è potata artificialmente a 200 tentativi.
  4. So che "dare la maggior parte delle informazioni per indovinare" non è molto preciso. L'idea semplice è scegliere la partizione con il maggior numero di sottoinsiemi.
  5. Il risultato dipende fortemente da come si calcola la risposta (..). Alla fine ho adattato l'espressione di Donald Knuth.

La strategia per MM(5,8) può essere trovata qui . GitHub ha dei problemi con la visualizzazione di righe così lunghe, quindi fai clic sul pulsante Raw .


ciao come stampare graziosamente il testo di github in modo che i risultati possano essere trasformati in una guida di ricerca .. dal primo punto di partenza 'HHCAA' .. e il passaggio successivo a seconda della risposta ... e al successivo e così via. L'attuale formato di testo non elaborato non è più facile per la scansione manuale. La tecnica che sto seguendo è come analizzare le parentesi annidate e ottenere un bel layout di tabella che sia più facile da seguire fino alla fine, simile all'elenco puntato nella domanda per MM (2,3). Grazie. Spero che tu possa capire cosa sto cercando. (preferisco python per analizzare str)
ihightower

2

Rubino

EDIT: aggiunta una logica per escludere ipotesi impossibili. Quindi, le strategie ora sono conformi al formato indicato e sono molto più gestibili.

Quindi ecco un tentativo per farlo andare avanti. È abbastanza ingenuo (e non molto leggibile - aiuta a leggere il ramo if / elsif / else dal basso verso l'alto).

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

In primo luogo, provo 5 di ogni colore: AAAAA, BBBBB, ecc Da quella cifra io quali colori sono effettivamente utilizzati nel modello. E poi provo semplicemente tutte le permutazioni dei colori indicati, omettendo quelli che sono già stati esclusi dai pioli neri.

Ecco la MM(2,3)strategia:

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

La strategia per MM(5,8)prende 376 KB e può essere trovata qui . GitHub ha dei problemi con la visualizzazione di righe così lunghe, quindi fai clic sul pulsante Raw .

Ora se ottengo un verificatore, posso anche dirti qual è il mio punteggio reale. :)


Sentirsi male con il verificatore non ancora pubblicato, ma sta arrivando ... C'è qualcosa di sbagliato nella tua (prima) strategia MM (2,3), ad esempio se la soluzione è AB: Guess = AA; risposta = 10; Immagino = BB; risposta = 10, per cui non esiste una strategia. Esaminerò il tuo suggerimento di raggruppare le risposte, ma non dovrebbe essere necessario per buone strategie, poiché l'insieme delle possibili soluzioni è disgiunto per le diverse risposte.
MrBackend,

@MrBackend Buona cattura, ho saltato un caso lì. Ora dovrebbe essere risolto. Per quanto riguarda la grammatica, ovviamente non è necessario per buone strategie, ma ho pensato che potresti essere disposto ad abbassare un po 'la barra. ;) Se le persone possono inviare voci più semplici (come la mia), potresti avere un po 'più di fortuna nell'ottenere proposte interessanti che migliorano gradualmente, invece di dover includere tutte le combinazioni combinate fin dall'inizio.
Martin Ender,

Ecco l'affare: finirò il verificatore corrente e lo pubblicherò (come app Web - è troppo grande per incollarlo qui). Sfortunatamente, potrebbe essere troppo severo, in quanto considera impossibili le risposte. Successivamente, lo adatterò per supportare più risposte ed emetterò avvisi per risposte impossibili. Detto questo, la tua strategia da 1,3 G MM (4,4) deve contenere molte risposte impossibili e / o ipotesi non riduttive, poiché la dimensione stimata di una strategia MM (5,8) decente è una manciata di mega.
MrBackend,

@MrBackend Naturalmente le mie strategie contengono moltissime risposte impossibili. Questo è ciò che intendevo per "non sto facendo la combinatoria". ;) Se è troppo fastidioso per te supportare e raggruppare le risposte, non preoccuparti, allora darò un'occhiata all'omissione di ipotesi impossibili.
Martin Ender,

@MrBackend Buone notizie, l'ho risolto. :) Spero che la strategia sia valida ora. Fammi sapere se ci sono ancora problemi con esso.
Martin Ender,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.