r-statistics-fanの日記

統計好きの現場の臨床医の覚書のようなもの

頭脳王2014 三山崩しをRで解く(必敗型スタートの人と必勝型スタートの人がいた件)

#追記
#録画を見なおしたら衝撃的事実が発覚
#なんと敗者の初期値は必敗の組合せだった!
#ひでえ。放送事故やん

追記2
Nim - sigma425のブログ
すでに言及されていた。しかも、コメント欄が熱い!!!

追記3 本人のコメント?
https://twitter.com/sou_mizukami/status/542321017759092736



録画していた頭脳王を見た。

そこで、コンピュータと対戦するコーナーが有った。

3色のチェスの駒のかたまりがあり、そこから交互に任意の1色の
駒を好きなだけ取っていく。最後の1つを取ったら負け。
2色以上の駒は同時にとってはいけない。

Rで無理やり解いてみる。
勝つために相手(コンピュータ)に渡すべき組合せを探索する。

n <- 10   #コマの最大数
dat <- expand.grid(0:n,0:n,0:n)
dat <- dat[-1,]
nr <- nrow(dat)
row.names(dat) <- seq_len(nr)

flag2 <- flag <- numeric(nr)
flag3 <- 0
dat <- as.matrix(dat)

flag[apply(dat, 1, function(x) sum(x)==1)] <- 1 #flag1=win、1,0,0を相手に渡すと勝利確定 flag=2は負け確定 flag=0は未定

tsugi <- function(x){   #次に考えられる組合せ
      if (x[1]==0){
            temp1 <- NULL
      }else{
            temp1 <- expand.grid(0:(x[1]-1), x[2], x[3])
      }
      if (x[2]==0){
            temp2 <- NULL
      }else{
            temp2 <- expand.grid(x[1], 0:(x[2]-1), x[3])
      }
      if (x[3]==0){
            temp3 <- NULL      
      }else{
            temp3 <- expand.grid(x[1], x[2], 0:(x[3]-1))
      }
      return(rbind(temp1, temp2, temp3))
}

mae <-  function(x){   #前に考えられる組合せ
      if (x[1] == n){
            temp1 <- NULL
      }else{
            temp1 <- expand.grid((x[1]+1):n, x[2], x[3])
      }
      if (x[2] == n){
            temp2 <- NULL
      }else{
            temp2 <- expand.grid(x[1], (x[2]+1):n, x[3])
      }
      if (x[3] == n){
            temp3 <- NULL      
      }else{
            temp3 <- expand.grid(x[1], x[2], (x[3]+1):n)
      }
      return(rbind(temp1, temp2, temp3))
}

same <- function(y){
      which(apply(dat, 1, function(x) all(x == y)))
}

while(sum(flag==0) != 0){
      cat(sum(flag==0),"st")
 if (sum(flag == 1 & flag2 == 0) != 0){
 make.check <- dat[(flag == 1 & flag2 == 0),]
 flag2[flag == 1] <- 1
 temp1 <- NULL
 for (i in seq_len(nrow(make.check))){
       temp1 <- rbind(temp1, mae(make.check[i,])) 
 }
 temp1 <-  as.matrix(unique(temp1, 1, incomparables = FALSE))
 for (i in seq_len(nrow(temp1))){
    flag[apply(dat, 1, function(x) all(x == temp1[i,]))] <- 2
 }
 } ##ここまで絶対負けのチェック

 if (sum(flag==0) != 0){  #未確定が残っていれば
 temp3 <- dat[flag == 0,] #未確定のもの
 cat(sum(flag==0),"make", temp3[1,])
 for (k in seq_len(nrow(temp3))){
       temp4 <- tsugi(temp3[k,])
       if( all(flag[apply(temp4, 1, same)]  == 2)){  #次がすべて負の場合
             flag[same(temp3[k,])] <- 1 #勝ちフラグ達成
       }
} 
}
}

win <- dat[flag==1,]  ##勝ち
lose <- dat[flag==2,]  ##負け
dat[flag==0,]  ##未定

win2 <- (apply(win, 1, sort))
win2 <- unique(win2, 2, incomparables = FALSE)
win2


相手に渡すと必ず勝てる組合せ

> win2
     1 24 36 48 60 72 84 96 108 120 133 146 170 194 218 292 304 340 414 424 472
[1,] 0  0  0  0  0  0  0  0   0   0   1   1   1   1   1   2   2   2   3   3   3
[2,] 0  2  3  4  5  6  7  8   9  10   1   2   4   6   8   4   5   8   4   5   9
[3,] 1  2  3  4  5  6  7  8   9  10   1   3   5   7   9   6   7  10   7   6  10


コンピュータに上記0,2,2とか0,3,3などの組み合わせになるように
うまく取って渡すことを繰り返せば勝てる。

解いた後に、これは三山崩しという有名なゲームだと知った。
必勝法はここに詳しい
http://www.ise.chuo-u.ac.jp/ISE/outline/Gmajor/matsui/contents03.html
つまり、これは必勝法を知っているかどうかの知識問題だったわけだ。
うろ覚えだった場合、当然後の人ほど思い出す時間があるので有利。
その場で必勝法を作ったとしたら、まさに宇宙人と言えよう。
でも彼らなら有り得そうだ。

ググって、必勝法がわかったので、そっちの方向で検証する

library(oro.dicom)
bi2 <- Vectorize(dec2base)
bi3 <- function(x) bi2(x,2,4)

res <- matrix(ncol = ncol(win2), nrow = 3)
for (i in seq_len(ncol(win2))){
      res[,i] <- sapply(win2[,i], bi3) 
}
res

> res
     [,1]   [,2]   [,3]   [,4]   [,5]   [,6]   [,7]   [,8]   [,9]   [,10]  [,11]  [,12]  [,13]  [,14] 
[1,] "0000" "0000" "0000" "0000" "0000" "0000" "0000" "0000" "0000" "0000" "0001" "0001" "0001" "0001"
[2,] "0000" "0010" "0011" "0100" "0101" "0110" "0111" "1000" "1001" "1010" "0001" "0010" "0100" "0110"
[3,] "0001" "0010" "0011" "0100" "0101" "0110" "0111" "1000" "1001" "1010" "0001" "0011" "0101" "0111"
     [,15]  [,16]  [,17]  [,18]  [,19]  [,20]  [,21] 
[1,] "0001" "0010" "0010" "0010" "0011" "0011" "0011"
[2,] "1000" "0100" "0101" "1000" "0100" "0101" "1001"
[3,] "1001" "0110" "0111" "1010" "0111" "0110" "1010"
tasu <- function(x) sum(as.numeric(x))
apply(res, 2, tasu)

> apply(res, 2, tasu)
 [1]    1   20   22  200  202  220  222 2000 2002 2020    3   22  202  222 2002  220  222 2020  222  222 2022
>

#たしかに、自明の0,0,1と1,1,1以外各桁が偶数だ。

#各桁が偶数になる組み合わせ全部を探索する

res2 <- dat
for (i in seq_len(nrow(res2))){
      res2[i,] <- sapply(dat[i,], bi3) 
}

tasu <- function(x) sum(as.numeric(x))

ni <- apply(res2, 1, tasu)

bal <- function(x) {
      temp <- NULL
      guu <- function(y) y %% 2 == 0
      temp[1] <- guu(x %/% 1000)
      temp[2] <- guu(x %/% 100)
      temp[3] <- guu(x %/% 10)
      temp[4] <- guu(x %% 10)
      all(temp == TRUE)
}

ans <- dat[sapply(ni, bal),]
ans2 <- (apply(ans, 1, sort))
ans2 <- unique(ans2, 2, incomparables = FALSE)
ans2

> ans2
     12 24 36 48 60 72 84 96 108 120 146 170 194 218 292 304 340 414 424 472
[1,]  0  0  0  0  0  0  0  0   0   0   1   1   1   1   2   2   2   3   3   3
[2,]  1  2  3  4  5  6  7  8   9  10   2   4   6   8   4   5   8   4   5   9
[3,]  1  2  3  4  5  6  7  8   9  10   3   5   7   9   6   7  10   7   6  10

> win2
     1 24 36 48 60 72 84 96 108 120 133 146 170 194 218 292 304 340 414 424 472
[1,] 0  0  0  0  0  0  0  0   0   0   1   1   1   1   1   2   2   2   3   3   3
[2,] 0  2  3  4  5  6  7  8   9  10   1   2   4   6   8   4   5   8   4   5   9
[3,] 1  2  3  4  5  6  7  8   9  10   1   3   5   7   9   6   7  10   7   6  10

うむ、自明の0,0,1と1,1,1以外一致した。完璧。

しかし、時間がかかりすぎる。もっと良いアルゴリズムはあるだろう。

そして、録画を見なおした所衝撃的事実が判明

敗者の初期値
3,9,10

えっ3,9,10!????
相手に渡すと必勝の組合せじゃん。
これが自分に回ってきているとなると、自分必敗。
相手コンピュータだし絶対敗けるじゃん。
と思ったら、わざとミスするプログラムっぽい。
しかし、人によって必敗スタートとはいかがなものか。
ちょっと公平じゃないんじゃね。

人3,9,10(スタートが必敗型)から1,9,10
com1,9,10から1,9,8(必勝)
人1,9,8から1,7,8
com1,7,8から1,7,6(必勝)
人1,7,6から1,5,6(訂正:ビデオ見なおしたら数字が変わっていただけだった。勘違い)
1,5,6から以降カットされて分からず


勝者は
人4,5,9(スタートが必勝に持ち込める型)から4,5,1(必勝)
com4,5,1から4,3,1
人4,3,1から2,3,1(必勝)
com2,3,1から2,3,0
人2,3,0から2,2,0(必勝)
com2,2,0から2,1,0
人2,1,0から0,1,0(必勝)