ProgrammieraufgabenHier wird regelmäßig eine neue Programmieraufgabe gestellt, die dann gelöst werden soll und in Zusammenarbeit mit den Moderatoren auch besprochen werden kann.
Mastermind-robot
Diskussion: Mastermind-robot im Forum Programmieraufgaben, in der Kategorie Code Kitchen; Anzeige
Eine Aufgabe von Chris:
Zitat:
Schreibe ein Computerprogramm, das Mastermind spielt. Es soll nach einem Code fragen und diesen ...
Schreibe ein Computerprogramm, das Mastermind spielt. Es soll nach einem Code fragen und diesen dann versuchen zu knacken. (Das Programm spielt beide Spieler, also der Anwender soll nicht die lästigen Bewertungen eingeben müssen, aber natürlich darf der "lösende Teil" des Programms nicht direkt auf den Code zugreifen!)
Es muss jede mögliche Kombination knacken können!
Das Programm soll die einzelnen Zeilen und die jeweilige Bewertung auch anzeigen.
Spielregeln: Es werden keine 'Löcher' und keine doppelten Farben benutzt. Es gibt acht Farben und vier Positionen. Das Brett hat 10 Zeilen. Die Bewertung der Versuche gibt nur die Zahl der schwarzen und weißen Pins an, nicht, auf welche Position sie sich beziehen.
Viel Spass ;)
__________________ Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.
Original von CDW
Spielregeln: Es werden keine 'Löcher' und keine doppelten Farben benutzt. Es gibt acht Farben und vier Positionen. Das Brett hat 10 Zeilen. Die Bewertung der Versuche gibt nur die Zahl der schwarzen und weißen Pins an, nicht, auf welche Position sie sich beziehen.
Dürfen Löcher bzw doppelte Farben beim Lösen benutzt werden?
Wäre es sinnvoll, acht Farben vorher festzulegen? (normalerweise gibts ja nur 6...) Der Einfachheit halber kann man ja auch Zeichen oder Ziffern benutzen...
im obigen "wiki"-Link steht, dass zur Strategie "logisches Denkvermögen" und "Kombinationsgabe" gehören. Allerdings sehe ich keinen Unterschied zwischen "Kombinationsgabe" und "Raten mit rel. hoher Trefferquote".
Deshalb als Anhang dabei eine Lösung die auf einer Bruteforce-Methode beruht.
Möglich ist das weil die Spielregeln von "Mastemind" cracker-technisch gesehen zwei schwere Bugs enthalten :
MindBug I
Bei 8 Farben mit Wiederholung gibt es nur 8 ^ 4 = 4096 Kombinationen. Schon die Eingabe einer beliebigen Zahl reduziert die Kombinationen um 75 %.
MindBug II
Beispiel :
code : 3712 (Zahlen repräsentieren die Farben ( 1..8 ))
1. eingabe : 2761
Das Spiel gibt zurück : sw-- (schwarz,weiss,leer,leer)
Nun umgekehrt :
code : 2761
1. eingabe : 3712
Das Spiel gibt zurück : sw--
"vorwärts" und "rückwärts" bringt das gleiche Ergebnis (sw--).
Viel Spass mit dem Anhang. Diesmal ist es eine "echte C++ -State-Of-The-Art- Konsole" :
Habe ganz vergessen meine Lösung mit anzugeben. Hab mal ein Programm geschrieben, daß einfach nach Brute-Force-Manier vorgeht und den Code knackt und dann noch eins, das beweist, daß alle Kombinationen mit dem Algorithmus lösbar sind (indem es einfach alle Kombinationen als Startwert vorgibt und dann ne Schleife laufen lääst).
Das Programm beginnt mit einer Kombination (0-1-2-3), lässt sie bewerten und "zählt" dann durch: Erhöht zuerst mal die letzte Ziffer um 1 und checkt dann, ob das Ergebnis mit der Bewertung der Vorhergehenden konsistent ist, indem es die vorhergehenden nach dem neuen Versuch bewertet und diese Bewertung mit der ursprünglichen vergleicht. Wenn diese gleich sind, ist neue Kombination konsistent und er macht sie dann. Sind sie nicht gleich, "zählt" es weiter...
Geschrieben habe ich das ganze mit ziemlich elementaren Befehlen in Pascal.
Zitat:
Dürfen Löcher bzw doppelte Farben beim Lösen benutzt werden?
Wäre es sinnvoll, acht Farben vorher festzulegen? (normalerweise gibts ja nur 6...) Der Einfachheit halber kann man ja auch Zeichen oder Ziffern benutzen...
Klar kannst du auch eine "erweiterte" Version schreiben... Das Originalspiel geht meines Wissens mit acht Farben; natürlich ist es sinnvoll, diese mit Zahlen durchzunummerieren. (Hoffe ich habe die Frage richtig verstanden!)
(Wo kann man eigentlich dieses "Hide" im Post eingügen?) durch [ SPOILER ="titel"] hidecode [ /SPOILER] (editiert von CDW)
Code
Code:
program Mastermind;
uses crt;
var code:array[0..3] of byte; { Der versteckte Code }
allesSchwarz:array[0..1] of byte; { Referenz für 4 Schwarze und 0 weiße }
brett:array[0..9,0..3] of byte; { Das Brett }
antworten:array[0..9,0..1] of byte; { Platz für Bewertungen }
i,line,m,below,p,q:byte;
gefunden,voll,raus,firstright,gleiche:boolean;
procedure Einlesen; { Begrüßung // Einlesen des versteckten Codes }
var eingabeok:boolean;
istzahl:boolean;
j:integer;
begin
writeln('-------------------------------------------------');
writeln('| Mastermind v0.2 |');
writeln('| author: Chris |');
writeln('| Published under GNU/GPL in current version |');
writeln('-------------------------------------------------');
repeat
writeln('Enter your hidden code (4 separate values');
writeln('from 0 to 7, no equal values!):');
write('#0: '); readln(code[0]);
write('#1: '); readln(code[1]);
write('#2: '); readln(code[2]);
write('#3: '); readln(code[3]);
eingabeok:=true;
{ Checke, ob alle Zahlen korrekt sind }
for i:=0 to 3 do
begin
istzahl:=false;
if code[i]=0 then istzahl:=true;
if code[i]=1 then istzahl:=true;
if code[i]=2 then istzahl:=true;
if code[i]=3 then istzahl:=true;
if code[i]=4 then istzahl:=true;
if code[i]=5 then istzahl:=true;
if code[i]=6 then istzahl:=true;
if code[i]=7 then istzahl:=true;
if istzahl=false then eingabeok:=false;
end;
{ Checke, dass keine gleichen dabei sind }
for i:=0 to 3 do for j:=0 to 3 do
if (code[i]=code[j]) and (i<>j) then eingabeok:=false;
until eingabeok=true;
end;
procedure AntwortenNull;
var j:integer;
begin
for i:=0 to 9 do
for j:=0 to 1 do
antworten[i,j]:=0;
end;
procedure Schreibe(znr:integer);
begin
writeln(znr,' || ',brett[znr,0],' ',brett[znr,1],' ',brett[znr,2],' ',brett[znr,3],' || white: ',antworten[znr,0],', black: ',antworten[znr,1]);
end;
procedure ErsteZeileSchreiben;
begin
for i:=0 to 4 do
brett[0,i]:=i;
end;
procedure bewerte(znr:integer);
var j:integer;
begin
for i:=0 to 3 do
begin
for j:=0 to 3 do
if (brett[znr,i]=code[j]) and (i<>j) then inc(antworten[znr,0]); { weiße setzen }
if brett[znr,i]=code[i] then inc(antworten[znr,1]); { schwarze setzen }
end;
end;
function konsistent(znr:integer):boolean;
var testantwort:array[0..1] of integer;
j,k,r,znrbelow:integer;
begin
konsistent:=true;
znrbelow:=znr-1;
for k:=0 to znrbelow do
begin
for r:=0 to 1 do testantwort[r]:=0;
{ Vergleiche kte Zeile mit dem in znr übergebenen Vorschlag }
for i:=0 to 3 do
begin
for j:=0 to 3 do
if (brett[k,i]=brett[znr,j]) and (i<>j) then inc(testantwort[0]); { weiße setzen }
if brett[k,i]=brett[znr,i] then inc(testantwort[1]); { schwarze setzen }
end;
for i:=0 to 1 do
if testantwort[i]<>antworten[k,i] then konsistent:=false; { Vergleiche }
end;
end;
{ Hauptprogramm }
begin
Einlesen; { Liest 'code' ein }
AntwortenNull; { Setzt alle Elemente der Antwortmatrix auf 0 }
ErsteZeileSchreiben; { Schreibt 0 1 2 3 in die erste Zeile }
allesSchwarz[0]:=0; { Setzt die Referenz für die Lösung }
allesSchwarz[1]:=4;
bewerte(0);
Schreibe(0);
{ Checke, ob schon die erste richtig war }
firstright:=true;
for i:=0 to 1 do
if antworten[0,i]<>allesSchwarz[i] then firstright:=false;
if firstright=false then
begin
line:=0;
repeat
inc(line);
below:=line-1;
{ Vorherige Zeile übernehmen }
for m:=0 to 3 do brett[line,m]:=brett[below,m];
raus:=false;
gefunden:=false;
voll:=false;
repeat
repeat
{ Durchlaufen }
inc(brett[line,3]);
{ Überträge }
if brett[line,3]=8 then
begin
brett[line,3]:=0;
inc(brett[line,2]);
end;
if brett[line,2]=8 then
begin
brett[line,2]:=0;
inc(brett[line,1]);
end;
if brett[line,1]=8 then
begin
brett[line,1]:=0;
inc(brett[line,0]);
end;
if brett[line,0]=8 then
begin
brett[line,0]:=0;
end;
{ Checken ob welche gleich sind }
gleiche:=false;
for p:=0 to 3 do for q:=0 to 3 do
if (brett[line,p]=brett[line,q]) and (p<>q) then gleiche:=true;
until gleiche=false;
until konsistent(line)=true;
bewerte(line);
Schreibe(line);
{ Gucken, obs schon die Lösung ist }
gefunden:=true;
for i:=0 to 1 do
if antworten[line,i]<>allesSchwarz[i] then gefunden:=false;
{ ...oder ob das Brett voll ist }
if line=9 then voll:=true;
if voll=true then raus:=true;
if gefunden=true then raus:=true;
until raus=true;
if voll=true then writeln('Das Brett ist voll.');
if gefunden=true then writeln('Got it in line ',line,'! :)')
else writeln('Didnt get it. :(');
end
else writeln('Got it in the first line! ;)');
end.
Output:
Code:
-------------------------------------------------
| Mastermind v0.2 |
| author: Chris |
| Published under GNU/GPL in current version |
-------------------------------------------------
Enter your hidden code (4 separate values
from 0 to 7, no equal values!):
#0: 6
#1: 4
#2: 3
#3: 2
0 || 0 1 2 3 || white: 2, black: 0
1 || 1 0 4 5 || white: 1, black: 0
2 || 2 3 5 6 || white: 3, black: 0
3 || 3 2 6 4 || white: 4, black: 0
4 || 4 6 3 2 || white: 2, black: 2
5 || 6 4 3 2 || white: 0, black: 4
Got it in line 5! :)
from random import choice
from itertools import count
def initialCombs():
def hasNoDoubles(comb):
return not any(map(lambda i: comb.count(i) > 1, range(8)))
return [ "%s%s%s%s" % (a,b,c,d) for a in range(8) for b in range(8) for c in range(8) for d in range(8) if hasNoDoubles([a,b,c,d]) ]
def rateComb(comb, number):
black, white = 0, 0
for i in range(4):
if comb[i] == number[i]:
black += 1
elif comb[i] in number:
white += 1
return (black, white)
def solve(answer, combs):
def statistics(idx, comb, black, white):
print "%i >> %s: %s%s%s" % (idx+1, " ".join(comb), black*'s', white*'w', (4-(black+white))*'-')
for idx in count():
comb = list(choice(combs))
black, white = rateComb(comb, answer)
combs = removeByRating(combs, comb, black, white)
statistics(idx, comb, black, white)
if black == 4:
return "=> Loesung gefunden!"
def removeByRating(combs, number, black, white):
return filter(lambda c: rateComb(c, number) == (black, white), combs)
if __name__ == "__main__":
print solve(raw_input("Geheime Kombination: "), initialCombs())
Wenn ich deinen Code richtig verstanden habe, machst du es dir zu einfach, da man nur die Anzahl der schwarzen/weißen Pins erhält, und nicht die Positionen dazu.
Hier meine Lösung in Haskell(funktioniert in dieser Fassung nur ohne doppelte Farben, benötigt sonst 2 kleine Änderungen):
code
Code:
import Control.Monad
import Data.Char
import Data.Map as M
import Data.List as L
import System.Random
checkAnswer :: Map Int Int -> Map Int Int -> (Int,Int)
checkAnswer code answer = let correctPos = filterWithKey (\k a -> code ! k == a) answer
remainingAnswer = answer M.\\ correctPos
remainingCode = code M.\\ correctPos
correctCols = filterWithKey (\k a -> not . M.null $ M.filter (==a) remainingCode) remainingAnswer
in (size correctPos,size correctCols)
initialCombs :: [Map Int Int]
initialCombs = [fromList $ zip [0..] [a,b,c,d] | a <- [0..7], b <- [0..7], c <- [0..7], d <- [0..7],
a /= b, a /= c, a /= d, b /= c, b /= d, c /= d]
--returns the number of exact matches in m and n
exactMatches m n = size $ filterWithKey (\k v -> m ! k == v) n
--returns the number of value matches on different positions in m and n
valMatches m n = size $ filterWithKey (\k v -> not . M.null $ filterWithKey (\k' v' -> k' /= k && v == v') m) n
--correct code is passed along as a parameter but is accessed only by the checking function
tryComb :: Map Int Int -> Map Int Int -> [Map Int Int] -> IO ()
tryComb a code combs = do
let res@(b,w) = checkAnswer code a
putStrLn $ "Trying answer: " ++ show (toList a) ++ " - " ++ show res
case b of
4 -> putStrLn $ "Solution found "++show (toList a)
otherwise -> do
let newcombs = L.filter (\c -> exactMatches a c == b && valMatches a c == w) combs
putStrLn $ "Remaining combinations: "++show (length newcombs)
n <- randomRIO (0,length newcombs - 1)
tryComb (newcombs !! n) code newcombs
main :: IO ()
main = do
putStrLn "Enter code"
code <- L.map digitToInt `liftM` getLine
ind <- randomRIO (0,length initialCombs - 1)
tryComb (initialCombs !! ind) (fromList $ zip [0..] code) initialCombs