Forum: Offtopic Wer gewinnt dieses Spiel?


von D. I. (Gast)


Lesenswert?

Ich grüble derzeit über eine kleine Programmieraufgabe, wo ich mir 
ziemlich sicher bin dass sie sich mithilfe dynamischer Programmierung 
lösen lässt und das auch nicht zu schwierig, aber ich scheitere daran 
die richtige Rekursion dafür aufzustellen. Vielleicht mag ja jemand mit 
einsteigen.

Problem:

Es handelt sich um ein 2-Spieler Spiel. Gegeben ist eine natürliche Zahl 
n (n <= 2.000.000.000). Das Spiel beginnt mit k = 1 und die Spieler 
(beginnend mit Spieler 1) können die Zahl k entweder mit 2 oder 9 
multiplizieren (nach jeder Multiplikation ist der jeweils andere Spieler 
dran). Wer zuerst die Zahl n erreicht oder überschreitet gewinnt das 
Spiel. Nun soll man zu einem gegebenen n den Gewinner ermitteln.

Meine Ideen bisher:

- Wenn eine Strategie für P1 existiert die nicht von P2s Entscheidungen 
abhängt gewinnt P1, ansonsten P2.

- Wer den anderen dazu bringen kann auf n/9 zu kommen, gewinnt das Spiel 
da er dann nur noch mit 9 multiplizieren muss.

- Den aktuellen Zustand kodiere ich mit S(i,j) = 2^i * 9^j ist also wohl 
ein 2-dimensionales Problem

- Wenn S(i,j+1) >= n, gewinnt der aktuelle Spieler, ansonsten muss ich 
die bessere Wahl zwischen S(i+1,j) oder S(i,j+1) treffen und das ist der 
Punkt an dem ich scheitere

Hat jemand Input für mich?

von A. $. (mikronom)


Lesenswert?

Ich bin gerade zu faul zum nachdenken, deshalb ein ähnliches Beispiel.

Zwei Spieler und es liegen 20 Streichhölzer auf dem Tisch. Sie nehmen 
abwechselnd 1, 2 oder 3 Streichhölzer weg. Wer das letzte nehmen muss 
hat verloren.

Das ist das gleiche mit Addition und runterzählen und schon allein 
dadurch viel einfacher. Pro Runde können 2 bis 6 Hölzer weggenommen 
werden. Der clevere Spieler sorgt dafür, dass es immer 4 sind. Vielleich 
tkommst du ja damit weiter.

von D. I. (Gast)


Lesenswert?

Jo das ist die einfache Version die mir auch schonmal in den Sinn 
gekommen ist. Leider hat es für den zündenden Funken nich nicht 
gereicht.

Im Wesentlichen kann man sich das ja wie ein Schachbrett vorstellen. 
Landet man auf einem "schwarzen" Feld dass >= n ist gewinnt der eine 
Spieler, ist es "weiß" der andere.
Der zweite Spieler hat immer nur die Möglichkeit in einer die Runde die 
vom ersten Spieler vorgegebene richtung beizubehalten oder sie Richtung 
Diagonale wieder auszugleichen.
Mh...

von Andreas W. (andy_w)


Lesenswert?

Hallo,
das kann man tatsächlich auf das Streichholzspiel zurückführen, einfach 
logarithmieren. Also als Startwert log(Startzahl) und jeder darf 
entweder log(2) oder log(9) abziehen. Wer Null erreicht oder einen 
negativen Wert, gewinnt.

Eine Gewinnstellung ist dann die Zahl log(2)+log(9), egal, was der 
Gegner macht, er erreicht nicht das Ende, man selber danach aber schon. 
Um diese Stellung sicher zu erreichen, ist 2*(log(2)+log(9)) auch eine 
Gewinnstellung usw.

Also sollte man im Originalspiel n/18 erreichen, vorher n/(18*18), 
n/(18*18*18) usw. Sind die genauen Zahlen keine ganzen Zahlen, ist die 
nächst höhere anzustreben.

Viel interessanter ist dagegen das NIM-Spiel, mehrere Haufen 
Streichhölzer (oder Münzen oder sonst was), von einem dieser Haufen kann 
man soviel wegnehmen, wie man will, mindestens eins und maximal alle. 
Wer das letzte vom letzten Haufen nehmen muß, verliert. Für ein 
Computerprogramm ist die Sache extrem einfach, aber man muß erst einmal 
auf die Strategie kommen...

Gruß
Andy_W

von D. I. (Gast)


Lesenswert?

Narf, manchmal sieht man den Baum vor lauter Wäldern nicht. Es ist 
eigentlich ganz einfach, die Rekursion aufzubauen: Entweder ich kann mit 
dem nächsten Zug gewinnen oder der nächste Zug sorgt dafür dass der 
andere nicht gewinnt. Wenn beides nicht zutrifft gewinnt der andere.

Ich hab das mal in ein kleines Ruby-Programm gegossen. Ich denke das 
sollte passen, was meint ihr?
1
@table = nil
2
3
def rec(i, j, n)
4
  return @table[i][j] if !@table[i][j].nil?
5
  
6
  if (2**i) * (9**j) * 9 >= n #can we win now?
7
    @table[i][j] = true
8
  else #can we make a move thus the other player doesn't win?
9
    @table[i][j] = !rec(i+1, j, n) || !rec(i, j+1, n) 
10
  end
11
  return @table[i][j]
12
end
13
14
def first_player_wins?(n)
15
  @table = Array.new(100) { Array.new(100) { nil } }
16
  return rec(0, 0, n)
17
end
18
19
n = ARGV[0].to_i
20
puts (first_player_wins?(n) ? "Spieler 1 gewinnt" : "Spieler 2 gewinnt")

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wenn folgender Ausdruck eine gerade Zahl ergibt, gewinnt der erste
Spieler, sonst der zweite:

Für 2≤n≤9 gewinnt der erste Spieler, und für n=1 ist es eine Frage der
Definition, da das Spiel ja endet, bevor überhaupt jemand einen Zug
gemacht hat. Ich würde sagen, der zweite Spieler gewinnt in diesem Fall.

Man sollte allerdings die Logarithmen nicht mit FP-Funktionen berechnen,
da durch die Aufrundfunktion schon kleine Rundungsfehler zu falschen
Ergebnissen führen. Besser ist es, ganzzahlige Potenzen in einer Schlei-
fe hochzuzählen, bis das Argument erreicht oder überschritten ist, z.B.
so:
1
int gewinner(unsigned int n) {
2
  unsigned long long n4 = 4ULL*n, n36=36ULL*n, p;
3
  int i, ret;
4
5
  if(n == 1)
6
    ret = 2;
7
  else if(n < 10)
8
    ret = 1;
9
  else {
10
    p = 1;
11
    while(p < n4)
12
      p *= 18;
13
    i = 0;
14
    while(p < n36) {
15
      p *= 2;
16
      i++;
17
    }
18
    ret = i&1 ? 2 : 1;
19
  }
20
  return ret;
21
}

Die Spielstrategie:

Wenn es für die aktuelle Zahl k ein i gibt, so dass

dann multipliziere die Zahl mit 9, sonst mit 2. Vielleicht gibt es etwas
einfacheres, mir ist aber nichts eingefallen

D. I. schrieb:
> Ich hab das mal in ein kleines Ruby-Programm gegossen. Ich denke das
> sollte passen, was meint ihr?

Das scheint mit meinen Ergebnissen zusammenzupassen:
1
        von         bis   Gewinner
2
——————————————————————————————————
3
          1           1      2
4
          2           9      1
5
         10          18      2
6
         19          36      1
7
         37          72      2
8
         73         162      1
9
        163         324      2
10
        325         648      1
11
        649        1296      2
12
       1297        2916      1
13
       2917        5832      2
14
       5833       11664      1
15
      11665       23328      2
16
      23329       52488      1
17
      52489      104976      2
18
     104977      209952      1
19
     209953      419904      2
20
     419905      944784      1
21
     944785     1889568      2
22
    1889569     3779136      1
23
    3779137     7558272      2
24
    7558273    17006112      1
25
   17006113    34012224      2
26
   34012225    68024448      1
27
   68024449   136048896      2
28
  136048897   306110016      1
29
  306110017   612220032      2
30
  612220033  1224440064      1
31
 1224440065  2448880128      2
32
 2448880129  4294967295      1
33
——————————————————————————————————

Das Ganze geht auch mit dynamischer Programmierung, wie du es ursprüng-
lich vorgehabt hast. Du macht eine Tabelle mit allen möglichen Zahlen-
werten
1
   |  9⁰   9¹   9²   9³ ...
2
———————————————————————————
3
2⁰ |   1    9   81  729 ...
4
   |
5
2¹ |   2   18  162 1458 ...
6
   |
7
2² |   4   36  324 2916 ...
8
   |
9
2³ |   8   72  648 5832 ...
10
 : |   :    :    :    :

und markierst alle Zahlen, die größer oder gleich n sind, als "Gewinn-
zahlen". Danach werden die restlichen Zahlen von rechts nach links und
von unten nach oben ebenfalls als Gewinn- oder Verlustzahlen markiert:
Eine Zahl ist genau dann eine Gewinnzahl, wenn der rechte und der untere
Nachbar beide Verlustzahlen sind.

Als letztes wird die 1 markiert. Ist sie eine Gewinnzahl, gewinnt der
zweite Spieler, sonst der erste.

Letztendlich ist das genau das gleiche wie dein rekursiver Algorithmus,
nur von hinten aufgezäumt und eben nicht rekursiv.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.