福利片在线一区二区,久久国产免费,欧美aa一级,日韩三级精品

焦點日報:保存象棋棋盤信息,需要多少比特?我只用139-167位二進制
2023-04-23 14:54:26 來源:騰訊云 編輯:

如何存儲當前棋局

方案有3種:

象棋一共32個棋子,每個棋子有91種狀態:死亡或位于0-89中任一位置。所以用長度為32的列表即可,每個數的值域是0-90,其中90代表死亡。死亡的棋子不再占用空間,使用類似map的結構,key是棋子id,value是棋子位置(0-89)。壓縮空間的方案:將帥個子有9個可能在的位置,只需要0-9即可表示,需要至多5位二進制。士有5種位置,每個士只需要至多3位二進制。以此類推……占用空間最少。

分析方案一:數組長度為32,每個數組項目是個uint8,總共8 * 32 = 256 位。

分析方案二:在棋子多的時候,占用空間較多,所以存儲空間的大小不太穩定。


(資料圖片)

方案三占用空間少,但是開發成本也較高,需要開發者去拼接二進制位。

今天我們探討方案三。

前綴碼

引入一個概念:Prefix-Free Code,也可以叫 Prefix Code,它來源于信息論學科,維基百科:en.wikipedia.org/wiki/Prefix… 描述如下:

A prefix codeis a type of code system distinguished by its possession of the "prefix property", which requires that there is no whole code word in the system that is a prefix (initial segment) of any other code word in the system. It is trivially true for fixed-length code, so only a point of consideration in variable-length code.

For example, a code with code words {9, 55} has the prefix property; a code consisting of {9, 5, 59, 55} does not, because "5" is a prefix of "59" and also of "55". A prefix code is a uniquely decodable code: given a complete and accurate sequence, a receiver can identify each word without requiring a special marker between words. However, there are uniquely decodable codes that are not prefix codes; for instance, the reverse of a prefix code is still uniquely decodable (it is a suffix code), but it is not necessarily a prefix code.

它舉了個例子,針對集合{9, 5, 59, 55}就不是 prefix code,因為「5」有二義性,遇到5后,不知道該結束流程,還是繼續讀取后面的9或5。

哈夫曼編碼 Huffman Coding

信息論中有個經典問題:給定一篇文章,如何用最短的二進制編碼它。

解決方案就是:找出出現的所有單詞集合(例如:I am good good good,出現了3個單詞),計算每個單詞出現頻率,以某種方式,構造每個單詞對應的二進制編碼,滿足條件:基于前綴就能知道它代表哪個單詞。然后我們把這些前綴拼在一起,就成功編碼了(并且是可以解碼的)。

例如這種編碼 good = 0, I = 10, am = 11,文章就表示為1011000。

這是最簡短的編碼了。構造方法就是通過構造一顆哈夫曼樹,算法如下:

針對每一個單詞(或組合),都有一個對應的頻數,作為頻數表。如果當前只有1個,就進入4,否則進入2。找到頻數最低的2個,作為表示一個組合,他們對應的頻數就是兩個單詞(或組合)的頻數之和,加入頻數表(同時刪除這2個單詞或組合各自的頻數)。選取的2個單詞(或組合),分別作為左子樹和右子樹,組成一個樹。進入1。現在得到了一個二叉樹(叫做哈夫曼樹),每個葉子結點代表一個單詞。規定左分叉為0,右分叉為1,這個單詞對應的 Prefix Code就是根節點到它的路徑。

例如上述編碼對應的哈夫曼樹就是:

對于象棋的啟發

回到象棋棋盤狀態的問題:

將帥有10個位置(包括死亡狀態)。士有6個位置(包括死亡狀態)。象有8個位置(包括死亡狀態)。馬有91個位置(包括死亡狀態)。車有91個位置(包括死亡狀態)。炮有91個位置(包括死亡狀態)。兵有48個位置(包括死亡狀態)。

不妨假設他們出現在各個位置的頻率都一致,不難構造出對應的編碼。(這樣的編碼是比較穩定的,無論棋局變成什么樣子,存儲占用空間都不會太大)

10個位置,需要3-4位。6個位置,需要2-3位。8個位置,需要3位。48個位置,需要4-5位。91個位置,需要6-7位。

這樣算下來,保存一個象棋的棋子位置信息,最少需要:

(3+2*2+3*2+6*6+4*5)*2=138位,再用1位保存該誰下棋了,總共至少需要139位。至多需要(4+3*2+3*2+7*6+5*5)*2=166位,再用1位保存該誰下棋了,總共至多需要167位。

有辦法實現嗎?

上面說的很理想,如何實現呢?

我們以10個位置的情況,來探討通用的編碼生成方法。首先根據哈夫曼樹,可以構造這樣的編碼:

000代表0001代表1010代表2011代表3100代表4101代表51100代表61110代表71101代表81111代表9

隨后容易發現這樣的規律:

至于0-5,用3位二進制編碼即可。至于6-7,我們需要在3位的6(110)7(111)末尾新增0。至于8-9我們需要在3位的67末尾新增1。

可以利用數學歸納法,歸納總結出這樣的算法:

針對X個位置的情況,計算Log2(X),分別向下取整和向上取整,得到A和B。如果A=B,則用A位二進制表示這X個數即可,直接轉換進制。如果A0至2^A-1-(X-2^A);用B位二進制表示其它位置;針對位置2^A-(X-2^A)2^A-1,編碼為A位的進制轉換,并在末尾拼接一位0(共計B位);針對其它位置,編碼為位置減去(X-2^A)再轉換二進制,并在末尾拼接一位1(共計B位)。

可以發現,這種算法,位置編號小的比位置編號大的少了一位。也就是說,我們應該盡量把出現頻率較高的位置放在前面。

生成各棋子的位置列表

const RedAllCandidates = new Array(90).fill(0).map((a, i) => 89 - i);const BlackAllCandidates = new Array(90).fill(0).map((a, i) => i);const RedSoliderCandidates = new Array(45).fill(0).map((a, i) => 44 - i);const BlackSoliderCandidates = new Array(45).fill(0).map((a, i) => 45 + i);// 分別是將、士、士、……const PieceCandidates = [  [85, 86, 84, 76, 77, 75, 67, 68, 66, 127],  [127, 86, 84, 76, 68, 66],  [127, 84, 86, 76, 66, 68],  [127, 87, 67, 71, 51, 83, 47, 63],  [127, 83, 67, 63, 47, 87, 51, 71],  [127, ...RedAllCandidates],  [127, ...RedAllCandidates],  [127, ...RedAllCandidates],  [127, ...RedAllCandidates],  [127, ...RedAllCandidates],  [127, ...RedAllCandidates],  [127, 62, 53, ...RedSoliderCandidates],  [127, 60, 51, ...RedSoliderCandidates],  [127, 58, 49, ...RedSoliderCandidates],  [127, 56, 47, ...RedSoliderCandidates],  [127, 54, 45, ...RedSoliderCandidates],  [4, 3, 5, 13, 12, 14, 22, 21, 23, 127],  [127, 3, 5, 13, 21, 23],  [127, 5, 3, 13, 23, 21],  [127, 2, 22, 18, 38, 6, 42, 26],  [127, 6, 22, 26, 42, 2, 38, 18],  [127, ...BlackAllCandidates],  [127, ...BlackAllCandidates],  [127, ...BlackAllCandidates],  [127, ...BlackAllCandidates],  [127, ...BlackAllCandidates],  [127, ...BlackAllCandidates],  [127, 27, 36, ...BlackSoliderCandidates],  [127, 29, 38, ...BlackSoliderCandidates],  [127, 31, 40, ...BlackSoliderCandidates],  [127, 33, 42, ...BlackSoliderCandidates],  [127, 35, 44, ...BlackSoliderCandidates],];

解釋:

我可以把將帥的「死亡」(127)調整到了最后一位,因為他們死亡是非常罕見的,這樣可以節約2bit空間。我刻意把棋子常見位置放在了數組前幾位,尤其是將帥、士、兵,這樣可以節約幾bit空間。兵的位置,紅色和黑色不同,剛過河的一排放在前面,離河遠的位置放在后面,可以節約幾bit空間。

提前計算log

為了提高效率,我應該避免在JS中計算Math.log2,而要提前定義好運算結果。

const ceilLog2Map = new Map([  [1, 0],  [2, 1],  [3, 2],  [4, 2],  [6, 3],  [8, 3],  [10, 4],  [17, 5],  [48, 6],  [91, 7],]);const floorLog2Map = new Map([  [1, 0],  [2, 1],  [3, 1],  [4, 2],  [6, 2],  [8, 3],  [10, 3],  [17, 4],  [48, 5],  [91, 6],]);

按照編碼規則encode

基于文章《JS 按自定義格式 拼接二進制串 解析二進制串》提到的concatBits函數,我寫了concatFlexibleBits函數:

function concatFlexibleBits(current: number, offset: number, candidateIndex: number, candidateLength: number): [number, number, number[]] {  const floorLog = floorLog2Map.get(candidateLength)!;  const ceilLog = ceilLog2Map.get(candidateLength)!;  const last = 2 ** floorLog;  const beyond = candidateLength - last;  if (floorLog === ceilLog || candidateIndex < last - beyond) {    return concatBits(current, offset, candidateIndex, floorLog);  }  let newCurrent = current;  let newOffset = offset;  const array: number[] = [];  let newUint8: number[];  if (candidateIndex < last) {    [newCurrent, newOffset, newUint8] = concatBits(newCurrent, newOffset, candidateIndex, floorLog);    array.push(...newUint8);    [newCurrent, newOffset, newUint8] = concatBits(newCurrent, newOffset, 0, 1);    array.push(...newUint8);  } else {    [newCurrent, newOffset, newUint8] = concatBits(newCurrent, newOffset, candidateIndex - beyond, floorLog);    array.push(...newUint8);    [newCurrent, newOffset, newUint8] = concatBits(newCurrent, newOffset, 1, 1);    array.push(...newUint8);  }  return [newCurrent, newOffset, array];}

這里encode規則,就是按照上面提到的算法實現的。不過多解釋了。

按照編碼規則decode

基于文章《JS 按自定義格式 拼接二進制串 解析二進制串》的readBits函數,我寫了readFlexibleBits函數:

function readFlexibleBits(array: Uint8Array, bitsOffset: number, candidateLength: number) {  const floorLog = floorLog2Map.get(candidateLength)!;  const ceilLog = ceilLog2Map.get(candidateLength)!;  const last = 2 ** floorLog;  const beyond = candidateLength - last;  const [number, offset] = readBits(array, bitsOffset, floorLog);  if (floorLog === ceilLog || number < last - beyond) {    return [number, offset];  }  const [current, offset2] = readBits(array, offset, 1);  if (current) {    return [number + beyond, offset2];  }  return [number, offset2];}

這里decode規則,是按照上面算法解析的。先讀取floorLog位,如果總位置數就是2的次方,則結束。如果讀取到的數比較小,也結束。如果讀取到的數超過某個臨界值,就需要多讀取一位,決定它代表誰。

結論

方案三可以實現,并且比方案一節約了35%-45%的空間。

關于性能:編碼、解碼邏輯全都位于用戶瀏覽器中執行,對服務器無影響,瀏覽器也會在人感知不到的耗時內運算完。

有什么用?

我在開發《象棋》時,期望通過URL來分享棋局。我希望分享的URL能永久有效,而且不喜歡給服務器太多債務(不采用token+數據庫存儲棋盤信息)。那么URL中必須包含完整的棋盤信息。

如果把棋盤信息存到URL中,那么URL越短,越好。

例如:game.hullqin.cn/xq?p=gSQCL5P5oIDhCFJCIBJ4eQCAkX8&r=86pU6-4nbSh38OCojLarcupWOb1rXw&s=1 ,點開后就能立馬展現某場對局。

這個URL里,保存了棋盤所有棋子信息、所有歷史記錄(10個回合即20步)。方便大家保存、分享。

保存歷史記錄,也是通過類似的手段實現的,占用空間非常小(長度兩百的字符串,足夠存儲大部分常規對局的歷史記錄)。

歡迎觀看 【可以「旋轉棋盤」的聯機象棋】 - 嗶哩嗶哩

寫在最后

我是HullQin,公眾號線下聚會游戲的作者(歡迎關注我,交個朋友)。轉發本文前需獲得作者HullQin授權。我獨立開發了《聯機桌游合集》,是個網頁,可以很方便的跟朋友聯機玩UNO、飛行棋、斗地主、五子棋、一夜狼、狼人殺、象棋、德國心臟病、達芬奇密碼等游戲,不收費無廣告。還開發了《Dice Crush》參加Game Jam 2022。喜歡可以關注我噢~我有空了會分享做游戲的相關技術,會在這個專欄里分享:《教你做小游戲》。

關鍵詞:

相關閱讀
分享到:
版權和免責申明

凡注有"環球傳媒網 - 環球資訊網 - 環球生活門戶"或電頭為"環球傳媒網 - 環球資訊網 - 環球生活門戶"的稿件,均為環球傳媒網 - 環球資訊網 - 環球生活門戶獨家版權所有,未經許可不得轉載或鏡像;授權轉載必須注明來源為"環球傳媒網 - 環球資訊網 - 環球生活門戶",并保留"環球傳媒網 - 環球資訊網 - 環球生活門戶"的電頭。

福利片在线一区二区,久久国产免费,欧美aa一级,日韩三级精品
日韩精品免费一区二区三区| 国产精品videosex极品| 999久久久亚洲| 五月天久久久| 日韩中文字幕不卡| 国产精品一区二区99| 麻豆精品av| 久久三级视频| 免费在线看一区| 国产精品porn| 欧美日韩国产一区二区三区不卡 | 欧美亚洲色图校园春色| 精品国产亚洲一区二区在线观看| 成人啊v在线| 中文亚洲免费| 国产欧美激情| 精品日韩视频| 日韩av一区二区在线影视| 另类专区亚洲| 亚洲精品影视| 伊人久久视频| 亚洲精品在线a| 蜜臀国产一区| 91成人在线网站| 午夜日韩av| 精品三级在线| 日韩一区二区三区精品视频第3页 日韩一区二区三区免费视频 | 欧美日韩亚洲一区| 日韩欧美不卡| 91亚洲无吗| 亚洲男女av一区二区| 国产剧情在线观看一区| 国产精品av久久久久久麻豆网| 国产毛片精品| 六月婷婷一区| 日本欧美不卡| 国产精品一区二区av日韩在线 | 宅男噜噜噜66国产日韩在线观看| 麻豆一区二区三区| 免费在线观看成人| 久久69成人| 亚洲最大av| 久久久久国产精品一区三寸| 欧美日本不卡高清| 视频一区视频二区中文| 亚洲综合电影| 国产精品网在线观看| 蜜芽一区二区三区| 91精品99| 欧美天堂视频| 久久三级中文| 欧美私人啪啪vps| 亚洲伊人影院| 亚洲日产av中文字幕| 中文字幕日韩欧美精品高清在线| 国产精品1区在线| 最新日韩av| 精品视频91| 偷拍亚洲精品| 精品欧美视频| 欧美精品一区二区三区精品| 日韩高清中文字幕一区| 久久久久久黄| 国产精品分类| 美女久久一区| 成人亚洲欧美| 欧美视频二区| 91精品二区| 成人国产精品一区二区网站| 日韩精品久久理论片| 日韩在线高清| 久久99久久人婷婷精品综合| 蜜臀久久久99精品久久久久久| 国产欧洲在线| 日韩国产在线一| 九九综合在线| 精品中文在线| 四虎在线精品| 久久精品九色| 美日韩精品视频| 国产亚洲一级| 黑丝一区二区| 国产一级久久| 美女精品网站| 日韩中文字幕不卡| 亚洲免费一区二区| 黄色av日韩| 日韩视频在线一区二区三区| 狠狠久久婷婷| 午夜一区在线| 最新亚洲国产| 97在线精品| 欧美激情另类| 色偷偷偷在线视频播放| 黑森林国产精品av| 日韩国产欧美一区二区| 欧美日韩免费看片| 播放一区二区| 亚洲婷婷免费| 国产精品社区| 亚洲欧美日韩国产一区| 亚洲综合国产| 亚洲精品大片| 欧美一区影院| 国产精品99久久免费| 国产一区二区三区不卡av | 伊人久久亚洲热| 亚洲欧美日韩综合国产aⅴ| 三级在线观看一区二区| 亚洲人亚洲人色久| 久久国产麻豆精品| 九九久久国产| 伊人久久在线| 久久九九电影| 麻豆精品91| 911精品国产| 精品视频一区二区三区在线观看 | 波多视频一区| 欧美亚洲日本精品| 欧洲一区二区三区精品| 九色porny丨国产首页在线| 在线天堂资源www在线污| 国产高潮在线| 国产麻豆久久| 亚洲精品va| 免费久久99精品国产| 麻豆精品网站| 日韩在线麻豆| 欧美在线看片| 欧美91在线|欧美| 成人亚洲一区二区| 免费福利视频一区二区三区| 亚洲成人不卡| jiujiure精品视频播放| 国产亚洲综合精品| 日韩一区二区三区四区五区| 欧美亚洲tv| 日韩不卡免费高清视频| 精品国产亚洲一区二区三区| av一区在线| 鲁鲁在线中文| 国产精品精品| 欧美日韩一区二区三区四区在线观看 | 中文字幕一区二区三区日韩精品| 日韩欧美中文字幕一区二区三区| 精品中文字幕一区二区三区| 亚洲先锋成人| 欧美中文高清| 日韩精品91| 亚洲一区免费| 九九久久国产| 亚洲自拍另类| 精品福利久久久| 免费高清在线一区| 成午夜精品一区二区三区软件| 在线视频精品| 久久只有精品| 久久午夜精品| 欧美国产偷国产精品三区| 视频一区二区三区入口| 精品五月天堂| 中文一区一区三区免费在线观| 日本一区二区高清不卡| 亚洲精品裸体| 日本欧美国产| 日本中文字幕不卡| 久久久久久久久久久妇女| 国产调教精品| 伊人成人在线视频| 国产成人免费| 日本亚州欧洲精品不卡| 久久人人99| 麻豆一区二区三区| 亚洲人成毛片在线播放女女| 日本高清不卡一区二区三区视频| 青青草91久久久久久久久| 在线日韩一区| 精品午夜久久| 日韩精选在线| 久久在线免费| 国内揄拍国内精品久久| 婷婷综合一区| 久久九九电影| 精品国产aⅴ| 日韩av一二三| 99精品99| caoporn视频在线| 国产探花一区| 亚洲精一区二区三区| 免费不卡中文字幕在线| 成人影视亚洲图片在线| 欧美一区自拍| 亚洲人成网77777色在线播放| 亚洲二区精品| 狠狠久久伊人| 欧美激情日韩| 久久国内精品| 亚洲精品第一| 麻豆成人在线| 尤物精品在线|