2048遊戲最多能玩到多大的數字?最多能玩多少分?
2048大家都玩過,也有很多人考慮過,最高到底能玩到多大的數字。
也有的人會問,最高能玩到多大的分數,但是這個明顯要複雜很多。
有個線上的2048做的還不錯,手機電腦都可以玩,
而且只要開啟,後面就再也不需要網路了,火車上面玩很方便
我的成績:
我的手機裡面還安裝了一個可以悔一步的版本的2048
可以悔一步的版本,比不能悔的版本要簡單很多。
比如說,當只有一個空格的時候,很多時候,出現2就會game over,出現4就可以繼續玩。
這樣,利用悔一步的功能,可以不斷嘗試,直到出現4再繼續玩。
當然,有的版本是game over之後就不能悔一步了
那就比這個版本稍微難一些,但是還是比不能悔的版本要簡單一些。
我的成績:
下面討論2048問題的簡化版
簡化一:假設系統只會給出2,不會給出4
簡化二:假設16個格子沒有位置的限制,任何2個格子只要數相等就可以合併。
重定義操作:每次操作,可以同時合併若干對數,比如將2,2,4,4,8合併成4,8,8,
如果沒有可以合併的數,就不合並,算一次不做任何改變的操作。
每次操作都會產生1個新的2
問題:這樣的遊戲最多可以玩出多大的數字?
首先列出2的冪,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072
131072=2^17
下面我將證明,永遠不可能玩到131072
定義一個全域性狀態:所有格子的數的和S
很明顯,剛開始的時候S是4,每合併1次,S的值就會加2
假設某個時候出現了131072,那麼S至少為131072
那麼一定有某個時候,S=131070,表示成二進位制是1111 1111 1111 1111 0
這個數,要想表示成2的冪的和,至少需要16個數即從2到65536這16個數。
而這個時候,沒有多餘的空格了,所以說,不可能玩到131072。
而且,65536是可以達到的。
下面討論真正的2048,到底能玩到多大的數字。
按照上面的論述可以推出,如果2048永遠只出2的話,是不可能玩到131072的。
但是真正的2048,是有可能出現4的,所以說,
為了研究131072到底能不能達到,我意識到只能悔一步的版本無法滿足我的要求,於是我開始玩第三種版本——無限悔棋版(下面是我的成績)
其實,可以悔一步的版本和可以悔任意多步的版本,在理論上是效果一樣的。
當我們考慮2048最多可以玩到多大的數字的時候,是這麼想的:
每次出現的是2還是4是隨機的,出現的位置也是隨機的。
我們只需要考慮在所有情況中,對我們最有利的那一種即可。
也就是說,可以這樣考慮,每次出現的是2還是4是由我們來定,每次出現的位置也由我們來定,這樣的情況下最多可以玩到多大的數字。
不難發現,無論是可以悔一步的版本的2048,還是可以悔任意多步的,都和這個模型的等價的。
這個圖說明131072是可以玩到的,也讓人很容易相信,131072便是能玩出的最大數字了,要證明也簡單,就像我上面證明的那樣。
下面討論最多能玩多少分。
雖然我已經玩到了終點,已經無法再玩了,但是分數卻不是最高的。
2048的計分規則是,玩家通過合併得到1個數N,這個數就計分N
比如說,2個2合併得到4,就加4分,2個1024合併得到2048,就加2048分
所以要計算最高分需要注意一個問題,同樣是4,如果是玩家合併2個2得到的,那就是4分,但是如果是自動給出的4,那是沒有分的,也就是說,單看這16個格子裡面有哪些數是無法判斷準確的得分的。
不過,最高分對應的局面只有2種情況,最小的格子可能是2也可能是4,另外15個格子就一定是8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072無疑了。
為了計算最高分,可以再次假設每次給出的數都是2,如果還能這樣玩出上述的局面的話,那就一定能得到最高分。
具體計算方法是,2沒分,4對應4分,8對應16分,16對應48分,32對應128分,64對應320分,128對應768分,256對應1792分,512對應4096分,1024對應9216分,2048對應20480分,4096對應45056分,8192對應98304分,16384對應212992分,32768對應458752分,65536對應983040分,131072對應2097152分
所以從8到131072這15個格子的總分是3932160分,但是這個分數是不可達到的,因為如果每次都出2的話,是不可能玩出131072的,也就是
在玩出131072之前至少出1個4,
同理在玩出131072之後,在玩出65536之前至少出1個4,
在玩出131072和65536之後,在玩出32768之前至少出一個4
。。。。。。
在玩出131072,65536,32768......128,64,32,16之後,在玩出8之前至少出1個4,
所以2048的理論最高分是3932160-15*4=3932100分
這個應該是可達到的,但是很難在理論上證明。
下面我將程式設計+構造證明,3932100分是可以達到的,這樣便證明了2048的最高分是3932100分。
我的證明工作分為3步:
一,構造資料
構造出1個多行資料,每一行對應2048的一個狀態,第一行是初始狀態(只有2個2),最後一行是最終狀態(16個數分別是4,8,16......131072)
二,驗證資料正確性
編寫一個程式驗證每一行到下一行的轉化都是對的,符合2048的規則
三,計算得分
其實只要驗證一共只出現過16個4,那麼得分就是最高分3932100
一,構造資料
要想構造資料,首先需要自己編寫一個2048的程式,只需要1個控制檯的程式即可
0,需要的主要功能如下:
(1)對記事本進行讀寫,把每一個狀態和操作寫到記事本的一行,下一次玩的時候讀取記事本即可接著玩
(2)顯示遊戲介面,讀取鍵盤的上下左右鍵,如果操作合法的話,進行相應的操作
(3)每次操作之後,要給出1個數,包含3個數:行i,列j,在i行j列出2還是4
1,規定資料結構
每一行放20個數,對應int s[21];的後面20個數,第0個數廢棄不用
第1-16分別對應遊戲介面中的16個數,0代表空格
例如,當s[1]到s[16]分別為2 0 0 0 2 0 0 0 16 0 0 0 16 2 0 0時,對應的狀態就是
s[17]對應玩家操作,也就是上下左右,-4是上,4是下,-1是左,1是右
具體說來,s[17]代表的是玩家從上一行對應的狀態變成本行對應的狀態所進行的操作
s[18]是行,範圍是0-3,s[19]是列,範圍是0-3,s[20]是新出的數。
也就是說,自上一行前面16個數對應的狀態開始,玩家進行了s[17]對應的操作,然後系統在s[18]、s[19]對應的位置新出了一個數s[20],然後狀態就變成了本行前面16個數對應的狀態。
以我玩的結果舉例,前2行是
0 0 0 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 4 0 0 2
(第一行的後面4個數沒有意義)
也就是說初始狀態是
第二行後面的4 0 0 2表示,我進行了4對應的下操作,即往下滑,然後系統自動在第0行第0列新出1個2
那麼此時新的狀態就是
也就是對應第二行前面的16個數。
2,判斷新出的數的位置和大小
首先大小,是2還是4,其實很容易確定,
整個遊戲中出現的最後一個數,無論是2還是4都不重要,都無法得分,在此之前有15次是必須要出4,其他的時候都必須要出2,這樣才能達到最高分。
這15個狀態和其他所有的狀態很容易區分出來,即如果有1個空格和15個不同的數,而且其中沒有2,那麼此時就必須出4
程式碼:
int newNum(int *p)
{
int s = 0, k, num0 = 0;
for (int i = 1; i <= 16; i++)
{
k = *(p + i);
if ((s^k) < s)return 2;//非0數不重複,否則出2
if ((s^k) == s)num0++;//統計空格數量
if (s % 4)return 2;//沒有2,否則出2
s ^= k;
}
if (num0 == 1)return 4;
return 2;
}
然後是位置
需要申明的是,這個程式並不是一般的2048遊戲,但是也符合2048的規則,
這個程式的唯一作用就是用來構造資料。
所以,這個程式不需要隨機演算法來決定在哪個格子出新數。
只需要按照s[1],s[2],s[3]......s[16]的順序依次檢視哪裡有空格,第一個發現的空格作為出新數的地方即可。
根據我玩2048的經驗,這樣應該是可以玩到最高分的,這一點我並沒有去驗證,因為這樣的話構造資料的時間代價比較高,我引入了flag這個變數,這是在整個構造資料的過程中唯一需要修改程式碼的地方。
實際上,在構造整個資料的過程中,並不要求程式一直是不變的,在需要的時候,隨時都可以修改程式然後接著構造資料。
3,讀取鍵盤
鍵盤的方向鍵和數字字母鍵不同,數字字母鍵每個按鍵對應1個字元,而方向鍵每個按鍵對應2個字元,根據後面那個字元可以判斷是哪個方向
程式碼:
char direction()//讀取鍵盤的方向鍵
{
int c1 = _getch(), c2 = _getch();
if (c2 == 72)return 'u';
if (c2 == 80)return 'd';
if (c2 == 75)return 'l';
if (c2 == 77)return 'r';
return ' ';
}
其他的程式碼細節就不再解釋了。
4,完整程式碼
#include<iostream>
#include <fstream>
#include<conio.h>
#include<string>
using namespace std;
void display(int *p)//顯示遊戲介面
{
for (int i = 0; i < 4; i++)
{
printf("\n\n\n");
for (int j = 0; j < 4; j++)printf("%8d", *(p + i * 4 + j + 1));
}
printf("\n\n\n");
}
char direction()//讀取鍵盤的方向鍵
{
int c1 = _getch(), c2 = _getch();
if (c2 == 72)return 'u';
if (c2 == 80)return 'd';
if (c2 == 75)return 'l';
if (c2 == 77)return 'r';
return ' ';
}
int dif(char c)//每個方向的運動偏移
{
if (c == 'u')return -4;
if (c == 'd')return 4;
if (c == 'l')return -1;
if (c == 'r')return 1;
return 0;
}
bool move(int *p, int dif)//p,p+d,p+2d,p+3d
{
int a = *(p), b = *(p + dif), c = *(p + dif * 2), d = *(p + dif * 3); //往d的方向移動
for (int i = 0; i < 3; i++)//先把0集中在起點處,然後就只有相鄰的數才能合併了
{
if (d == 0)d = c, c = b, b = a, a = 0;
if (c == 0)c = b, b = a, a = 0;
if (b == 0)b = a, a = 0;
}
if (a == b && c == d)d *= 2, c = b * 2, b = 0, a = 0;
else if (c == d)d *= 2, c = b, b = a, a = 0;
else if (b == c)c *= 2, b = a, a = 0;
else if (a == b)b *= 2, a = 0;
//上面計算了移動之後的數值,下面和移動之前比較,看是否真的有移動
if (*(p) == a && *(p + dif) == b && *(p + dif * 2) == c && *(p + dif * 3) == d)return false;
*(p) = a, *(p + dif) = b, *(p + dif * 2) = c, *(p + dif * 3) = d;
return true;
}
void move(int *p)//滑動
{
int d = dif(direction());
*(p + 17) = d;
bool b = false;
if (d == 4)for (int i = 1; i <= 4; i++)if (move(p + i, d))b = true;
if (d == -4)for (int i = 13; i <= 16; i++)if (move(p + i, d))b = true;
if (d == 1)for (int i = 1; i <= 13; i += 4)if (move(p + i, d))b = true;
if (d == -1)for (int i = 4; i <= 16; i += 4)if (move(p + i, d))b = true;
if (!b)move(p);
}
int newNum(int *p)
{
int s = 0, k, num0 = 0;
for (int i = 1; i <= 16; i++)
{
k = *(p + i);
if ((s^k) < s)return 2;//非0數不重複,否則出2
if ((s^k) == s)num0++;//統計空格數量
if (s % 4)return 2;//沒有2,否則出2
s ^= k;
}
if (num0 == 1)return 4;
return 2;
}
void new_num(int *p)//判斷哪裡可以出1個新數
{
bool flag = false;//唯一可以修改的地方
int sum = 0;
for (int i = 1; i <= 16; i++)if (*(p + i) == 0)sum++;//統計空格數量
if (sum < 2)flag = false;
for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)if (*(p + i * 4 + j + 1) == 0)
{
if (flag)
{
flag = false;
continue;
}
*(p + 18) = i, *(p + 19) = j;
*(p