我博客上的圍棋js程式

来源:https://www.cnblogs.com/Colin-Cai/archive/2018/03/04/8506691.html
-Advertisement-
Play Games

作為一個圍棋愛好者,就決定在博客裡加個圍棋js程式。於是,申請了博客的js許可權,美化美化我的博客。 好在js的語法像C系的,看了看,寫個程式應該還是可以的。 圍棋里,設計好基本的數據結構: 畫圖用canvas,之前並未接觸,一樣,baidu上搜搜,知道了畫圓、畫線、畫方塊的辦法,OK了,我畫圍棋說白 ...


  版權申明:本文為博主窗戶(Colin Cai)原創,歡迎轉帖。如要轉貼,必須註明原文網址

  http://www.cnblogs.com/Colin-Cai/p/8506691.html 

  作者:窗戶

  QQ:6679072

  E-mail:[email protected]

  作為一個圍棋愛好者,就決定在博客裡加個圍棋js程式。於是,申請了博客的js許可權,美化美化我的博客。

  好在js的語法像C系的,看了看,寫個程式應該還是可以的。

  圍棋里,設計好基本的數據結構:

  

//a是19X19數組,用來存放圍棋,每個位置0為空,1為黑,2為白
//b是檢測禁手、提子時臨時使用
var a = new Array(19);
var b = new Array(19);
for(var i=0;i<19;i++) {
    a[i] = new Array(19);
    b[i] = new Array(19);
    for(var j=0;j<19;j++) {
        a[i][j] = 0;
    }
}
//當前棋的步數,從0開始
var step = 0;
//選擇qa變數的第qipu_seq個棋譜
var qipu_seq = Math.floor(10000*Math.random())%qa.length;
//所有的棋譜,這個數據結構是本文重點,後面講
var qa;

 

  畫圖用canvas,之前並未接觸,一樣,baidu上搜搜,知道了畫圓、畫線、畫方塊的辦法,OK了,我畫圍棋說白了就是圓、線、方塊組成。過程中有個BUG,後來才知道,是我對moveTo、LineTo的理解有問題,最終畫棋盤、棋子的函數如下:

function draw_weiqi()
{
    var c=document.getElementById("myCanvas").getContext("2d");

    c.fillStyle="#808080";
    c.fillRect(0,0,8*20,8*20); 
    c.beginPath();
    c.strokeStyle="#000000";

    for(var i=0;i<19;i++) {    
        c.lineWidth = 1;
        c.moveTo(8+i*8,8);
        c.lineTo(8+i*8,19*8);
        c.stroke();
        c.moveTo(8,8+i*8);
        c.lineTo(19*8,8+i*8);
        c.stroke();
    }
    for(var i=0;i<19;i++) {
        for(var j=0;j<19;j++) {
            if(a[i][j] != 0) {
                if(a[i][j] == 1) {
                    c.fillStyle="#000000";
                } else {
                    c.fillStyle="#FFFFFF";
                }
                c.beginPath();
                c.arc(8+i*8,8+j*8,4,0,Math.PI*2,true);
                c.fill();
            }
        }
    }
    if(step!=0) {
        var n = qipu[Math.floor((step-1)/2)];
        if(step%2==1) {
            n = n%361;
        } else {
            n = Math.floor(n/361);
        }
        var x = Math.floor(n/19);
        var y = n%19;
        c.fillStyle="#FF0000";
        c.fillRect(6+x*8,6+y*8,4,4);
    }
        for(var i=0;i<9;i++) {
                var x = 3+6*(i%3);
                var y = 3+2*(i-i%3);
                if(a[x][y] == 0) {
                        c.fillStyle="#c0c0c0";
                        c.fillRect(8+x*8,8+y*8,1,1);
                }
        }
}

 

  我的想法是js預設的情況是反覆播放alphago master和alphago zero的二十局棋,滑鼠點上去之後可以手動下棋(但不支持AI),於是還要去考慮滑鼠的操作。

  滑鼠的操作網上有太多的文章來講,但講的似乎又不清晰,不過反覆調試,試驗下,滑鼠的支持還是沒問題了。

  var n = document.getElementById("myCanvas");

  var ev = event || window.event;
  var x = ev.PageX||ev.clientX;
  var y = ev.PageY||ev.clientY;
  x += document.documentElement.scrollLeft || document.body.scrollLeft;
  y += document.documentElement.scrollTop || document.body.scrollTop;

  x -= n.offsetLeft;
  y -= n.offsetTop;

  找到點擊的位置在畫布里的相對坐標為以上代碼,可是這裡還是有一個BUG,對於Firefox不支持,我也沒去找原因,如果有知道的,歡迎和我聯繫或者寫在評論里,讓我可以補掉這個BUG。

 

  圍棋的規則也沒什麼問題,我有篇文章(《圍棋規則的電腦實現》)里專門講圍棋的規則可以看成是一個連通圖遍歷,如此可以判斷有沒有氣,從而禁手、提子、打劫,乃至後面點掉死子、數子計算勝負都可以歸結於連通圖遍歷。

  

  放進去棋譜是個問題,這需要相對較大的數據量,我雖然只放20個棋譜,但是我的強迫症總覺得棋譜的數據多了。

  我拿到的sgf文件,是圍棋界的標準存儲文件,以master和zero的第一局為例,棋譜如下:

(;FF[4]CA[UTF-8]KM[7.5]OT[3x60 byo-yomi]PB[AlphaGo Master]PW[AlphaGo Zero]
RE[W+R]RU[Chinese]TM[7200];B[dd];W[pp];B[cp];W[pd];B[fp];W[cc];B[cd];W[dc];
B[fc];W[ec];B[ed];W[fb];B[dj];W[nq];B[mp];W[hq];B[qf];W[qk];B[nc];W[pf];B[pg];
W[of];B[qd];W[qc];B[qe];W[pc];B[qi];W[md];B[og];W[mc];B[gb];W[gc];B[fd];W[hb];
B[gd];W[ga];B[hp];W[ip];B[ho];W[gq];B[fq];W[io];B[np];W[mq];B[in];W[jq];B[jn];
W[op];B[lq];W[lr];B[lp];W[kq];B[ek];W[dp];B[dq];W[bj];B[bl];W[cq];B[me];W[le];
B[lf];W[mf];B[ne];W[nf];B[ke];W[ld];B[kd];W[nd];B[cr];W[ch];B[eh];W[be];B[bd];
W[cf];B[rn];W[rp];B[qo];W[qp];B[mr];W[nr];B[ir];W[iq];B[on];W[eg];B[dh];W[dg];
B[fg];W[bg];B[mh];W[lg];B[kf];W[nh];B[mj];W[mg];B[kg];W[lh];B[kh];W[hf];B[gf];
W[hd];B[hg];W[ml];B[nl];W[nm];B[om];W[ck];B[cl];W[nk];B[ol];W[mk];B[pj];W[eq];
B[dr];W[fr];B[fo];W[qm];B[qn];W[pn];B[po];W[oo];B[pk];W[er];B[ep];W[ri];B[rh];
W[do];B[es];W[gr];B[kj];W[pm];B[rm];W[ql];B[qj];W[hn];B[hm];W[dk];B[dl];W[ej];
B[co];W[rj];B[pl];W[fk];B[el];W[di];B[fj];W[ei];B[fi];W[fh];B[gh];W[rk];B[si];
W[ff];B[gg];W[ie];B[ge];W[kc];B[lj];W[ko];B[lm];W[nj];B[mi];W[ni];B[kr];W[ms];
B[jd];W[kk];B[nn];W[km];B[kn];W[ng];B[jk];W[jl];B[bk];W[cj];B[rc];W[rb];B[jc];
W[jj];B[ik];W[gi];B[gj];W[ij];B[hj];W[il];B[hk];W[ki];B[ji];W[gn];B[dn];W[hl];
B[gl];W[gm];B[kb];W[lb];B[mb];W[nb];B[bc];W[bb];B[ab];W[ba];B[da];W[db];B[ma];
W[na];B[ea];W[eb];B[sd];W[ln];B[lo];W[im];B[aj];W[ai];B[sb];W[ra];B[jr];W[hr];
B[ii];W[la];B[he];W[id];B[if];W[ro];B[rl];W[lk];B[li];W[sn];B[sk];W[fn];B[eo];
W[sm];B[sj];W[ib];B[jb];W[kp];B[jo];W[mm];B[mn];W[qb];B[jp];W[ks];B[fm];W[em];
B[fl];W[en];B[fs];W[sa];B[ef];W[ak];B[al];W[dm];B[cm];W[ee];B[fe];W[de];B[ae];
W[ce];B[af];W[sc];B[rd];W[ph];B[qg];W[oh];B[pe];W[oe];B[pr];W[rr];B[rq];W[qq];
B[so];W[sp];B[sl];W[so];B[ic];W[hc];B[ag];W[ah];B[gs];W[is];B[eh];W[je];B[jf];
W[ia];B[aj];W[bf];B[ad];W[ak];B[gp];W[js];B[aj];W[lc];B[no];W[ak];B[sb];W[aj])

  我不用解釋,應該也能猜的出來,前面是信息,B[dd];W[pp];B[cp];這樣的是一步一步的棋,採用坐標的方法,橫縱坐標都是a-s這19個字母中的一個。

  因為最開始對js不熟悉,想全部用整數存儲棋譜,每一步棋存成0~360的一個數字,方法是把a-s對應於0~18,然後X*19+Y(其中X,Y是橫縱坐標),然後搞成數組,20個棋譜的qa變數就是數組的數組。

  用shell設計出來就是這樣:

#!/bin/bash
export LANG=C
for i in *.sgf; do
        cat $i | tr -d '\r\n\t() ' | tr ';' '\n' | sed -nr '/^[BW]\[([a-s][a-s])\]$/!d;s//\1/;H;${x;s/\n//gp;}' | od -t u1 -An -v | gawk '
        BEGIN {n=0}
        {for(i=1;i<=NF;i++){a[n++]=$i-97;}}
        END {
                m=0;
                for(i=0;i+2<=n;i+=2)b[m++]=a[i]*19+a[i+1];
                for(i=0;i<m;i++) {
                        x = b[i];
                        if(i!=0)printf(",");
                        printf("%d",x);
                }
                printf("\n");
        }
        ' | sed -nr 's/^/\[/;s/$/\],/;p' | tr -d '\n'
done | sed -nr 's/,$//;s/^/var qa = \[/;s/$/\];/;p'
echo

var qa = [[60,300,53,288,110,40,41,59,97,78,79,96,66,263,243,149,309,314,249,290,291,271,307,306,308,287,312,231,272,230,115,116,98,134,117,114,148,167,147,130,111,166,262,244,165,187,184,281,225,226,224,206,86,72,73,28,30,54,232,213,214,233...

  因為js是解釋型語言,最終從伺服器上下下來的是代碼,一數,位元組數很多,19788位元組。強迫症犯了,要壓一下。

   於是每兩步棋合一起,比如前一步棋為A,後一步棋為B,AB範圍都為0~360,則用B*361+A表示兩步棋。如果整局棋的步數為奇數,最後一步棋找不到配的,那麼最後一個數字就是361*361+A(正常範圍是0~360,361並不是真實棋步,可以直接判斷出來這一步沒有)

  shell程式如下

#!/bin/bash
export LANG=C
for i in *.sgf; do
        cat $i | tr -d '\r\n\t() ' | tr ';' '\n' | sed -nr '/^[BW]\[([a-s][a-s])\]$/!d;s//\1/;H;${x;s/\n//gp;}' | od -t u1 -An -v | gawk '
        BEGIN {n=0}
        {for(i=1;i<=NF;i++){a[n++]=$i-97;}}
        END {
                m=0;
                for(i=0;i+4<=n;i+=4)b[m++]=a[i]*19+a[i+1]+(a[i+2]*19+a[i+3])*361;
                if(i+2<=n)b[m++]=a[i]*19+a[i+1]+361*361;
                for(i=0;i<m;i++) {
                        x = b[i];
                        if(i!=0)printf(",");
                        printf("%d",x);
                }
                printf("\n");
        }
        ' | sed -nr 's/^/\[/;s/$/\],/;p' | tr -d '\n'
done | sed -nr 's/,$//;s/^/var qa = \[/;s/$/\];/;p'
echo

  再來看一看,數據如下這樣:

var qa = [[108360,104021,14550,21340,28255,34735,95009,54032,113663,104939,98122,110773,103915,83703,83302,41991,48472,41271,60435,47077,60037,88346,67672,101625,81811,74590,26078,10181,19524,77125,84327,91223,76726,90443,16300,8386,15545,122354,115477,95549,60817,29881,22807...

  這裡的數據壓縮到16489位元組,還是很多。

  其實,當初有個很好的選擇,就是直接提出a-s的坐標,然後來表示,那麼master和zero第一盤棋就變成以下字元串

  “ddppcppdfpcccddcfcecedfbdjnqmphqqfqkncpfpgofqdqcqepcqimdogmcgbgcfdhbgdgahpiphogqfqionpmqinjqjnoplqlrlpkqekdpdqbjblcqmelelfmfnenfkeldkdndcrchehbebdcfrnrpqoqpmrnririqonegdhdgfgbgmhlgkfnhmjmgkglhkhhfgfhdhgmlnlnmomckclnkolmkpjeqdrfrfoqmqnpnpooopkereprirhdoesgrkjpmrmqlqjhnhmdkdlejcorjplfkeldifjeififhghrksiffggiegekcljkolmnjminikrmsjdkknnkmknngjkjlbkcjrcrbjcjjikgigjijhjilhkkijigndnhlglgmkblbmbnbbcbbabbadadbmanaeaebsdlnloimajaisbrajrhriilaheidifrorllklisnskfneosmsjibjbkpjommmnqbjpksfmemflenfssaefakaldmcmeefedeaeceafscrdphqgohpeoeprrrrqqqsospslsoichcagahgsisehjejfiaajbfadakgpjsajlcnoaksbaj”

  shell如下:

#!/bin/bash
export LANG=C
for i in *.sgf; do
        cat $i | tr -d '\r\n\t() ' | tr ';' '\n' | sed -nr '/^[BW]\[([a-s][a-s])\]$/!d;s//\1/;H;${x;s/\n//gp;}' | sed -nr 's/^/"/;s/$/",/;p'
done | sed -nr 's/,$//;s/^/var qa = \[/;s/$/\];/;p'
echo

 

  這個其實已經很不錯,10730位元組。只是最開始的時候對js不熟,不知道怎麼處理字元到整形,強迫症也沒犯。

  不過,這個10730位元組我還是不滿足,後來想了想,

  其實用一個整數來代表兩步棋,B*361+A(B為0~361,A為0~360),那麼這個整數範圍為0~130681,

  而217=131072>130681

  而且數值非常接近,

  於是決定每步棋編碼17bits,編成二進位數據,又因為js不是編譯型語言,需要給二進位數據一個編碼,base64是合適的,理論上可以再壓縮的多那麼一點點,比如不只64個可見字元,用的更多一些也可,只是程式比較複雜,因為不再是2的整數次方個不同的用來編碼的可見字元,壓縮率提高也有限。於是就此作罷。

  

#!/bin/bash
export LANG=C
for i in *.sgf; do
        cat $i | tr -d '\r\n\t() ' | tr ';' '\n' | sed -nr '/^[BW]\[([a-s][a-s])\]$/!d;s//\1/;H;${x;s/\n//gp;}' | od -t u1 -An -v | gawk '
        BEGIN {n=0}
        {for(i=1;i<=NF;i++){a[n++]=$i-97;}}
        END {
                m=0;
                for(i=0;i+4<=n;i+=4)b[m++]=a[i]*19+a[i+1]+(a[i+2]*19+a[i+3])*361;
                if(i+2<=n)b[m++]=a[i]*19+a[i+1]+361*361;
                for(i=0;i<m;i++) {
                        x = b[i];
                        y = 65536;
                        for(j=0;j<17;j++) {
                                if(x>=y) {
                                        x-=y
                                        print "1"
                                } else
                                        print "0"
                                y/=2;
                        }
                }
        }
        ' | gawk 'BEGIN{i=0;j=128;v=0;}
        {if($1==1)v+=j;j/=2;i++;if(i==8){printf("%c",v);i=0;j=128;v=0}}
        END{if(i!=0)printf("%c",v)}
        ' | base64 | tr -d '\n' | sed -nr 's/^/"/;s/$/",/;p'
done | sed -nr 's/,$//;s/^/var qa = \[/;s/$/\];/;p'
echo

  var qa = ["06RllUcaxTXDcvoevuZC0xDd/+Z67+lbC1yvXRveisykB16sKE3dgmt+V1QtZGoQsYz5n8nI14y7wnxSYiS1FpLPZFeV21hSx/WCDCHlz3fLhit1PXbInS5LIuI6aYDVm8aVzzFcYa+wD+oyVuPSJjWqaqFmguJPHW+CdK74wDQiZ0x4ytK5GdHMb2CZ42Xww8EPUYEqclwAO24kD3rByOYm46Q82kENad20QdbtY1bR3esshAVshMnKKX9AqysccI5mmL4qzymWrLIrlHDXp0OJIazCkB10zNnDOmd0EYLUeRpNRCk6y2xzt602Ifq6Jms+cY2SCQ4NTKdeFSYKPkGfevxW4OazCsHECKzQOz7yjudCsEX33f8FFxJP8aP3ZV98gnleB89xprhAh4QcOwsGlMoDx8HBAA==",...

  總算滿意了,數數位元組數,

# ./deal.sh | wc -c

7680

 

  7680位元組,算是交代了。

  最後,再把代碼中的一堆變數、函數名給用單個字元替換了,去掉所有的換行、空格,甚至括弧都沒有多餘的,

  遇到if(cnt1 != 0 && cnt2 == 0),都被我簡寫成了if(cnt&&!cnt2),當然,後面cnt、cnt2都被搞成了單字元。

  最後貼了出去,可讀性極差,不過代碼小,如果我自己需要改動,就用原本的代碼改動了。接下去想在此基礎上搞個簡易的AI,再看時間了。

  

  剛纔想了想,base64的解碼是從網上找過來然後修改的,我看那個字元串表示不是太長了一點,強迫症又來了,我是不是該再裁它一刀。

  

function base64_decode(c){
        var s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        var x="";
        var e=new Array(4);
        var i=0;
        c=c.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while(i<c.length){
                for(var j=0;j<4;j++)
                        e[j]=s.indexOf(c.charAt(i++));
                x+=String.fromCharCode((e[0]<<2)|(e[1]>>4));
                if (e[2]!=64)
                        x+=String.fromCharCode(((e[1]&15)<<4)|(e[2]>>2));
                if (e[3] != 64)
                        x+=String.fromCharCode(((e[2]&3)<<6)|e[3]);
        }
        return x;
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • iOS的記憶體管理機制 Objective-C在iOS中不支持GC(垃圾回收)機制,而是採用的引用計數的方式管理記憶體。 引用計數:在引用計數中,每一個對象負責維護對象所有引用的計數值。當一個新的引用指向對象時,引用計數器就遞增,當去掉一個引用時,引用計數就遞減。當引用計數到零時,該對象就將釋放占有的資 ...
  • 1.Activity的生命周期 生命周期這個事情,是android工程師最基礎的知識,所以今天要講的是一些不一樣的東西。 1.1 OnStart,OnResume,OnPause,OnStop 他們的區別是什麼? OnStart & OnStop, 這一對發生的時候,頁面已經顯示,但是處於backg ...
  • adb工具的使用、adb連接設備、adb連接設備失敗的處理、adb常用命令 ...
  • java.net.UnknownHostException 錯誤解決方向 1. 查看是否申明瞭網路的許可權。     在AndroidManifest中需要聲明網路許可權 ···java ··· 註意申明許可權的節點不要弄錯。 2. 測試真機或者模擬機是否連上了網路 ...
  • 報錯如圖 解決 在 路徑下,刪除所有文件夾,重新run android ps:網上搜了說是說是java解壓縮編碼格式問題什麼的,感覺不靠譜,自己試出來的,不知道對別人有沒有用。 有問題歡迎交流,謝謝閱讀 ...
  • 前言 早就聽過爬蟲,這幾天開始學習nodejs,寫了個爬蟲 demo ,爬取 博客園首頁的文章標題、用戶名、閱讀數、推薦數和用戶頭像,現做個小總結。 使用到這幾個點: 1、node的核心模塊-- 文件系統 2、用於http請求的第三方模塊 -- superagent 3、用於解析DOM的第三方模塊 ...
  • 思路:通過命令行修改瀏覽器啟動參數,使得瀏覽器不進行跨域檢查,從而允許跨域 方法:命令行參數啟動瀏覽器後添加參數--disable-web-security 例:chrome --disable-web-security --disabl-web-security參數的作用是禁止瀏覽器進行跨域檢查 ...
  • 今天把昨天遺留的問題解決了。 點擊過後終於出現效果了,以後儘量趕在11點睡吧。 大家晚安。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...