大开脑洞:完美的棋盘是什么样的
潘达按:本文作者 @萤-时光灯 ,写这篇文章的灵感来自于一次阅读刘慈欣小说时候突发的畅想。本文首发于微信公众号:奇略研究所。潘达参与编辑本文。
————————————————————————————————————————
从AIphaGo到超现实数,围棋始终是工程师们的沙盘,数学宝宝们的游乐场。不过,这些科学家研究的对象都是下棋的策略。今天就让我们来返璞归真一下,站在巨人的肩上,从数学的角度来解读一下承载这种美丽游戏的背景——棋盘吧。
围棋的棋盘是一个19x19的正方形,我们叫它19路棋盘。古代的时候也有用17路棋盘的。小朋友在刚学棋的时候也会用到13路和9路的棋盘。我们日常会用到的围棋盘都是这些正方形的。也有些人创造性地使用地图形状的棋盘,但是在我看来,这些都不是最完美的棋盘。
完美的棋盘,首先应该没有“特殊”的交叉点——每个交叉点都应该恰好与另外四个交叉点相邻,不多不少。下面这张同心圆的棋盘就可以满足此要求。
那么,可以更给力一点吗?
当然是可以的 。
上面那张同心圆的棋盘,虽然每个点确实只有四个邻点,而且没有了“角”,但还有里、外两条“边”,仍不够对称。更完美的棋盘,在没有棋子的时候,应该是混沌一片,处处都是对称的。无论第一手下在哪里,都是一样的。第一手过后才有了两极,有了策略,有了胜负。
想要这样的棋盘其实很容易。只需要把这个正方形的棋盘卷一卷,卷成一个圆柱型,然后再折一折,折成一个甜甜圈就可以了。这里我们定义1=19, A=T,这样一个19路的普通棋盘就变成了18路的甜甜圈棋盘。
当然,已经有会玩的棋友想在我前面了。在LittleGolem这个网站上(感谢 @龚渠成 的推荐),就有一场正在进行的甜甜圈围棋锦标赛,有几十位棋友参加。(https://littlegolem.net/jsp/in/tournament.jsp?gtid=go19&variant=TOROID)上图的这张棋谱是两位高水平的爱好者在甜甜圈棋谱上的实战。请读者注意,这张棋盘实际上只有中间橙色的11路,周边灰色的部分只是辅助对弈者直观理解甜甜圈棋盘的。
可以看到,如果棋子下在了某一个角上,棋盘的灰色部分也对应地放上三枚棋子。假设棋子下在了边上,我们要同时在对面的边上也放上一样颜色的棋子。当然还有计算边上和角上的棋子的气的时候,要把对面的那一气也算进去。在网络围棋的时代,要实现这样一个棋盘太容易了,只需要用上面的定义,就可以把一个看上去方方正正的棋盘卷成一个甜甜圈棋盘。当然,这样还有很多不足,比如很多人会习惯性的忽略对手从棋盘的另一面搞突袭。这都是棋子间的距离不够直观导致的。也许正是因为这样的不足,所以这种理论上完美的棋盘只有少数人在玩。
(潘达按:别吐槽我离题啊,这段已经点题了!具体的甜甜圈围棋战略,可以看LittleGolem上的棋谱。)
那么,可以更更给力一点吗?
当然是可以的。
有没有可能把甜甜圈围棋盘搬到现实中来呢?有一位不愿意透露姓名的西方围棋爱好者,兼能工巧匠,就做出了一个真正的甜甜圈棋盘。
三十八条交叉的圆形铁丝,组成了纵横十九道的甜甜圈棋盘。这里的棋子也是特制的,在中心划了十字槽,可以嵌在交叉点上。
可惜,如此漂亮的实物甜甜圈棋盘,仍有几处瑕疵。在这个甜甜圈棋盘上,里圈的格子特别小,而棋盘的纵线也会比横线短很多,因此每个格子都不再是正方形的了。棋盘格子大小不相等,给棋手直观理解棋局带来了困难。比如离某颗棋子最远的地方在哪里,这种简单的策略也需要额外的思考时间。
那么,可以更更更给力一点吗?
当然还是可以的。
数学上已经有前辈把我们所遇到的这些难题通通解决了。这个人就是约翰·纳什(John Nash)。你可能在经济学领域或者是在他对博弈论的贡献里听说过他的名字。你也可能看过以他的传记拍成的电影《美丽心灵》。电影里有他和好基友在校园里下围棋的场景。纳什就是这样,润物细无声地帮我们解决了上文提到的所有问题,但是却没有向大家透露,他解决的这些问题与围棋有什么关系。不过我想,懂一点围棋和数学的人看到他的嵌入理论(Nash Embedding Theorems)的时候,不难联想到围棋的棋盘。
纳什的嵌入理论就是做了两件事(为了不引入更多术语,我们只讨论甜甜圈棋盘这一种情况。嵌入理论可以应用在更广泛的领域上)。第一件事,嵌入理论可以把平面的正方形棋盘‘折’成一个类似甜甜圈的形状,使得棋盘上的每一个格子仍然保持相等的大小。这一步用到的具体定理又叫Nash-Kuiper定理,它保证了这种折叠的存在。而近年,一个法国数学家团队完成了这种折叠的具体构造,像是一个螺纹装饰的甜甜圈,见下图。
实现螺纹甜甜圈的技术挺难的,需要在普通甜甜圈的基础上做数次名为“起皱”的迭代,相当于一层层地给甜甜圈刻上螺纹。甜甜圈上螺纹的作用是,使得甜甜圈棋盘上每一格的长宽都相等。像这样保持原有图形距离关系的折叠,数学术语叫做等距映射(isometry)。
下图给出了折叠前的正方形棋盘,与折叠后螺纹甜甜圈棋盘上曲线的对应关系。正方形棋盘上原本的纵线(下图左方黑色纵线),在折叠后变成了螺纹甜甜圈上的一个截面的边缘(下图右方黑色的“雪花形”曲线)。熟悉数学的读者可能已经看出来,这条雪花形曲线是一种分形(fractal)。
那么,可以更更更更给力一点吗?
当然仍然是可以的。
我们历经艰险做出了螺纹甜甜圈形棋盘,但它仍有一个明显的缺陷:不够光滑。光滑在数学上的含义是,这个曲面上每个点的切面都是可以算出来的。但是螺纹甜甜圈棋盘,首先它视觉上皱皱巴巴的我们人类不太可能用它下棋。其次,它数学上也不完美,虽然它每个点的切面都能算出来,但是某些局部的弯曲程度却是不连续的。用数学语言说,这些局部的二次导数是无法定义的。综上所述,我们必须通过第二步来完善这个棋盘。当然,得到这个结果已经很厉害了,手残党如我折出的棋盘有棱有角,切面都不能保证存在的呀。
第二步,把甜甜圈上任意多的点放到一个高维度的欧几里得空间,并且保留了原有的甜甜圈上每个点之间的距离关系。在这个新的高维的棋盘上,棋盘的面确实是平滑的,你可以对每个点算导数算到地老天荒。而且视觉上,我们上一步被折的皱皱巴巴的棋盘,在这一步重新得以平展开了。纳什证明了对于甜甜圈这样的m=2曲面来说,寻找嵌入所需要的维度数不会超过 m^2+5m+3=17维。不要被这么高的维度吓到,这只是一个上限。可以作用于任何形状的甜甜圈上任意的点。而我们的棋盘可能更特殊,所以幸运的话可能不需要那么多维度就可以解决问题了。
光说不练假把式。下面就让我们从4路开始,来体验一下这个高(维度)大(脑洞)上(档次)的甜甜圈棋盘吧。那我们先从5x5的正方形棋盘开始,给每个点都标上坐标。注意边和角的坐标都是重复的。一共有16个坐标。
然后,我们算一下每两个点之间的距离,从白色方块开始我们有可能走出并,尖,单关跳,小飞和象步。但是大飞和拆二就不行了。
然后,我们拿出一个四维的立方体。把立方体的 16个顶点依次标上原来的坐标。一个闪闪发光的四维甜甜圈棋盘就做好了。
验证一下,在四维立方体上,同样任意两点之间的距离,同样起于白色方块我们可以得到和平面上一样的结果。
那么,还可以更更更更更给力一点吗?
额…… 这个问题,我也遇到困难了。刚才我示范的是四路棋盘,但更高路数的棋盘,我也不知道答案。
3路棋盘在三维里的光滑嵌入我猜测是行不通的。如果4路棋盘需要4维空间实现嵌入,那么5路棋盘需要几维空间呢?17路,19路棋盘,是不是就需要用到最高的17维才能嵌入了呢?我在这里抛砖引玉,希望读者中有大牛能完美地解答这个开放性问题。
不过,无论是这个完美的棋盘是需要嵌入四维空间,还是十七维空间,想在这个棋盘上下棋,首先我们得进入高维空间呢。读者朋友们,如果做出了这张完美棋盘,那我们就在高维空间见面下棋吧!
最后附上福利——
先看具体的例子感受一下:
上图局面在正常棋盘下,黑棋总是差一气,最终被提吃,但是在环面 的情况又是如何呢?
黑长,我们看到黑棋实际上有三口气,
我们知道白棋暂时提子无望。不过我想让白棋强杀一下黑棋:
这个时候,白棋被叫吃,只好反叫吃,黑棋弃子逃跑,
这个时候出现了奇观,黑棋出路太多,跑得太快,白棋吃子失败。
至于征吃:
黑棋沿着 上同胚于 的闭合曲线逃跑,这当然回魂乏术了。
正如上一位网友回答,是紧致无边流形,所以每一个点都属于环面的内点,由于环面高度对称性,所以“战略要点”就失去了意义,因为每一个点都是一样的。
反观我们通常的棋盘,正是由于角部、边界的特殊性,所以在吃子、围空提供了普通内点不具有的先天优势,于是变成了“兵家必争”。
回到环面。所以,单纯地吃子恐怕是很困难的,因为逃跑太容易了,这个与我们在通常棋盘打吃中腹的子一样困难。所以策略是围空优先,顺便侵消对方的实地。这个时候考验的是玩家对中腹能力的掌控。
最后说贴目,这个时候先手优势太小了,肯定不能贴七目半了,我感觉先手价值应该是两子。
我之前写过一个围棋程序,在那个基础上稍加改动即可得到环面上的围棋(R 语言)——
GoT2 <- function(n=19) { ###初始设置 ##构造棋盘 if (!interactive()) return() par(mar = rep(0, 4),bg = rgb(0.9296875, 0.6757812, 0.0546875)) plot(1:n, type = "n", xlim = c(1, n), ylim = c(0, n), axes = FALSE, xlab = "", ylab = "", bty = "o", lab = c(n, n, 1)) text( 3,0,"落子无悔",cex=1) text(17,0,"一生好走",cex=1) segments(1, 1:n, n, 1:n) segments(1:n, 1, 1:n, n) points(rep(c(4, 10, 16), 3), rep(c(4, 10, 16), each = 3), pch = 19, cex = 1.2) box() ##四大列表初始设置 playedlist <- list(c(),c()) #当前盘面 vplay <-c() history <- list(list()) #历史记录 re <- 0 #悔棋指标 conset <- list(list(),list()) #连通分支列表,是二重嵌套列表,第一子列表属黑棋,第二属白棋 bouset <- list(list(0+0i),list(0+0i)) #连通分支边界列表,同上 hiscon <- list(list()) hisbou <- list(list()) ncb=c(0,0) #分支数 ntake <- c(0,0);eat <- 0 hisncb <- list() hisntake <- list() ##棋盘特殊位置 SW<-1+1i #棋盘四角 SE<-19+1i NE<-19+19i NW<-1+19i S<- 2:18+1i #棋盘四边(不含角) E<- complex(0,19,2:18) N<- 2:18+19i W<- complex(0,1,2:18) dire<-c(1i,-1i,-1,1) #棋盘四合 ##边界函数Bd(从中去掉历史位置即可得到“气”) Bd<-function(A) { B=c() for(a in A) { pa <- a+dire pa <- complex(0, Re(pa)%%19, Im(pa)%%19) for(i in 1:4) { if(Re(pa[i])==0)pa[i]<-complex(0,19,Im(pa[i])) if(Im(pa[i])==0)pa[i]<-complex(0,Re(pa[i]),19) } for(i in pa) if(!is.element(i,A)) B=c(B,i) } B } ##提子 take <- function(A) { if(is.element(SE,A)) { points(19,1, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(18.5,1,19,1) segments(19,1,19,1.5) A <- setdiff(A,SE) } if(is.element(SW,A)) { points(1,1, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(1,1,1.5,1) segments(1,1,1,1.5) A <- setdiff(A,SW) } if(is.element(NE,A)) { points(19,19, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(18.5,19,19,19) segments(19,18.5,19,19) A <- setdiff(A,NE) } if(is.element(NW,A)) { points(1,19, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(1,19,1.5,19) segments(1,18.5,1,19) A <- setdiff(A,NW) } EE <- intersect(E,A);WW <- intersect(W,A) SS <- intersect(S,A);NN <- intersect(N,A) A <- setdiff(A,c(EE,WW,SS,NN)) if(length(A)>0) { x <- Re(A);y <- Im(A) points(x,y, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(x-0.5,y,x+0.5,y) segments(x,y-0.5,x,y+0.5) } if(length(EE)>0) { x <- Re(EE);y <- Im(EE) points(x,y, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(x-0.5,y,x,y) segments(x,y-0.5,x,y+0.5) } if(length(WW)>0) { x <- Re(WW);y <- Im(WW) points(x,y, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(x,y,x+0.5,y) segments(x,y-0.5,x,y+0.5) } if(length(SS)>0) { x <- Re(SS);y <- Im(SS) points(x,y, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(x-0.5,y,x+0.5,y) segments(x,y,x,y+0.5) } if(length(NN)>0) { x <- Re(NN);y <- Im(NN) points(x,y, cex = 3, pch = 19, col = rgb(0.9296875, 0.6757812, 0.0546875)) segments(x-0.5,y,x+0.5,y) segments(x,y-0.5,x,y) } if(is.element(4+4i,A))points(4+4i, cex = 1.2, pch = 19) if(is.element(4+10i,A))points(4+10i, cex = 1.2, pch = 19) if(is.element(4+16i,A))points(4+16i, cex = 1.2, pch = 19) if(is.element(10+4i,A))points(10+4i, cex = 1.2, pch = 19) if(is.element(10+10i,A))points(10+10i, cex = 1.2, pch = 19) if(is.element(10+16i,A))points(10+16i, cex = 1.2, pch = 19) if(is.element(16+4i,A))points(16+4i, cex = 1.2, pch = 19) if(is.element(16+10i,A))points(16+10i, cex = 1.2, pch = 19) if(is.element(16+16i,A))points(16+16i, cex = 1.2, pch = 19) } ##气的提示 qi <- function() { tp1 <- matrix(,ncb[1],2) for(i in 1:ncb[1]) tp1[i,] <- c(length(bouset[[1]][[i]]),conset[[1]][[i]][1]) d1 <- tp1[order(tp1[,1]),] d1 <- data.frame(黑方气数=Re(d1[,1]),分支位置=d1[,2]) print(d1) tp2 <- matrix(,ncb[2],2) for(i in 1:ncb[2]) tp2[i,] <- c(length(bouset[[2]][[i]]),conset[[2]][[i]][1]) d2 <- tp2[order(tp2[,1]),] d2 <- data.frame(白方气数=Re(d2[,1]),分支位置=d2[,2]) print(d2) } ###进入对弈 k <- 0 repeat { for (j in 1:2) ##黑白交替落子 { repeat { l <- locator(1) #获得落子坐标 l$x <- round(l$x) l$y <- round(l$y) xy <- complex(0,l$x,l$y) if (!is.element(xy, vplay))break #禁走历史位置 } ##悔棋 Cex <- 1 if(k>99)Cex <- 0.8 if(l$y<0.5) { take(vplay) history[[k]] <- NULL k <- k-1 playedlist <- history[[k]] vplay <- unlist(playedlist) conset <- hiscon[[k]] bouset <- hisbou[[k]] ncb <- hisncb[[k]] ntake <- hisntake[[k]] Bx <- Re(history[[k]][[1]]);By <- Im(history[[k]][[1]]) Wx <- Re(history[[k]][[2]]);Wy <- Im(history[[k]][[2]]) points(Bx, By, cex = 3, pch = 19, bg = "black") points(Wx, Wy, cex = 3, pch = 21, bg = "white") text(Bx,By+0.06,seq(1,k,2),cex=Cex,col="white") text(Wx,Wy+0.06,seq(2,k,2),cex=Cex,col="black") if(min(ncb)>2)qi() re <- 1 } if(re == 1){re <- 0;next} #落子 k <- k+1 points(l, cex = 3, pch = c(19, 21)[j], bg = c("black", "white")[j]) text(l$x,l$y+0.06,k,cex=Cex,col=c("white","black")[j]) ##损气提吃 s=1 while(s<=ncb[3-j]) { opp <- bouset[[3-j]][[s]] if(is.element(xy,opp)) { if(length(opp)==1) { playedlist[[3-j]] <- setdiff(playedlist[[3-j]],conset[[3-j]][[s]]) vplay <-unlist(playedlist) take(conset[[3-j]][[s]]) #提子 ntake[j] <- ntake[j]+length(conset[[3-j]][[s]]);eat <- 1 bouset[[3-j]][[s]] <- NULL conset[[3-j]][[s]]<-NULL ncb[3-j] <- ncb[3-j]-1 #对方分支数减少 for(t in 1:ncb[j]) { bouset[[j]][[t]] <- setdiff(Bd(conset[[j]][[t]]),vplay) } s <- s-1 }else{ bouset[[3-j]][[s]] <- setdiff(opp,xy) } } s <- s+1 } ###构造三大列表(历史、连通分支、边界) p=0 #分支构建指标(p>0分支合并、p=0新建分支) U <- c() #合并分支 i <- 1;r<- c() while(i<=ncb[j]) { if(ncb[j]==0) break if(is.element(xy, bouset[[j]][[i]])) #新下的棋属于哪个连通分支 { p <- p+1 if(p==1) { q <- i U <- conset[[j]][[i]] conset[[j]][[i]]<-NULL bouset[[j]][[i]]<-NULL ncb[[j]] <- ncb[[j]]-1 i <- i-1 } if(p>1) { U <- union(U,conset[[j]][[i]]) conset[[j]][[i]]<-NULL bouset[[j]][[i]]<-NULL i=i-1 ncb[j] <- ncb[j]-1 } } i <- i+1 } ncb[j] <- ncb[j]+1 if(p==0){ if(ncb[j]==0) { conset[[j]] <- list(xy) #孤子建新支 bouset[[j]] <- list(setdiff(Bd(xy),vplay)) }else{ conset[[j]][[ncb[j]]] <- xy bouset[[j]][[ncb[j]]] <- setdiff(Bd(xy),vplay) } }else if(p==1){ if(ncb[j]>0) { conset[[j]][[ncb[j]]] <- c(U,xy) bouset[[j]][[ncb[j]]] <- setdiff(Bd(c(U,xy)),vplay) }else{ conset[[j]] <- list(c(U,xy)) bouset[[j]] <- list(setdiff(Bd(c(U,xy)),vplay)) } }else{ conset[[j]][[ncb[j]]] <- c(U,xy) bouset[[j]][[ncb[j]]] <- setdiff(Bd(c(U,xy)),vplay) } playedlist[[j]] <- c(playedlist[[j]], xy) vplay <-unlist(playedlist) history[[k]] <- playedlist hiscon[[k]] <- conset hisbou[[k]] <- bouset hisncb[[k]] <- ncb hisntake[[k]] <- ntake if(min(ncb)>2)qi() if(eat>0) { print(list(黑方提子数=ntake[1],白方提子数=ntake[2]));eat <- 0 } if (k >= n^2)break ###满盘结束(满盘皆输555~) } if(k >= n^2) break } } GoT2()
为了表现环面的特征,下了一个智障的棋谱,大家可以品一品。另外,点击“落子无悔”可以悔棋哦~