蛋疼之作:99行代码的2048

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

基于Python和numpy,自带基于Tk最简仿原生2048配色的图形界面。文件代码行数(Physical LOC)一共99,没有统计过逻辑行数,因为是Python。

本来是想写个最简单的内核然后用机器学习算策略的,可是写了几行又不想写了,索性加了个GUI做个最简版的2048,所以完全没有效率和Pythonic可言。不过只是作为2048模块的话10ms和1ms能有什么区别呢。总之这就是一个蛋疼的作品,写它最主要的原因是无聊。

对2048游戏规则的理解:

1) 方块合并时从滑动所指方向开始合并,合并不递归,比如:

|-|2|2|2|向右滑动后,最右两块合并,变为|-|-|2|4|

|2|2|2|2|向右滑动后,最右和最左两块分别合并,变为|-|-|4|4|

2) 每次滑动后如果方块发生了变化则生成新方块,新方块的取值集为{2, 4},4出现的概率是0.1,初始化游戏产生两个新方块。

3) 游戏的分数是累加每次合并得到的新方块的值。

代码分为两个模块:

1) 核心模块,用一个4x4矩阵表示游戏中方块的数值,没有方块的位置值为nan。基本思想是用concatenate函数实现方块的移动,用将矩阵扩展成5x4并错开一行和原矩阵相减得到列方向相邻相同的方块位置。

按照滑动方向先将矩阵中的非空元素全部排列到每行的最右,用numpy的concatenate函数和isnan函数实现

将得到的已经移动过的矩阵旋转,然后分别在第一行和行尾插入新的空行,得到两个5x4的矩阵

这两个矩阵相减之后相邻并等值的位置就会得到0,这里我们定义空值和任何值得减法仍然得到空值,在numpy中对应的是nan和任何float的加法

注意执行完上面的合并操作后,如果是一排4个方块等值,则新合成的两个方块不相邻,所以还需要再将矩阵转回去执行一遍移动操作才能确保一排4个值都相等的情况下正确的移动并合并方块。

模块代码如下:

1fromnumpyimportrandom, array, zeros, append, sum, concatenate, copy, ndenumerate, isnan, rot90, nan, int, float2DOWN, RIGHT, UP, LEFT = range(4)34classGame2048:5def__init__(self):6self._grid, self._score = zeros(16) +nan, 07self._grid[random.choice(16, 2, replace=False)] = random.choice([2]*9+[4], 2, replace=False)#init with 2 tiles8self._grid = self._grid.reshape((4, 4))#create 4x4 grid910@staticmethod11def_merge_down(grid):12merge = concatenate((grid, [zeros(4) + nan])) - concatenate(([zeros(4) + nan], grid))#find the mergable tiles13merge[2][merge[3]==0], merge[1][merge[2]==0] = nan, nan#remove redundant 0 by 3 same tiles14score = sum(grid[merge[:4] ==0])15grid[merge[:4] == 0], grid[merge[1:] == 0] = grid[merge[:4] == 0] * 2, nan#fill the merged with new number16returnscore1718def_create_tiles(self):19avail =isnan(self._grid)20ifavail[avail==True].size >0:21new_tiles = append(random.choice([20]*9+[40]), zeros(avail[avail==True].size - 1) +nan)22random.shuffle(new_tiles)23self._grid[avail] =new_tiles2425defstep(self, direction):26self._grid[self._grid%10==0] /= 1027merge_v, merge_h, grid_copy =copy(self._grid), copy(rot90(self._grid)), copy(self._grid)28map(Game2048._merge_down, [merge_v, merge_h])#try to merge tiles along two directions29ifmerge_v[isnan(merge_v)].sizeis0andmerge_h[isnan(merge_h)].sizeis0:#Check if game is over30returnFalse31self._grid = rot90(self._grid, RIGHT -direction)32self._grid = array([concatenate((x[isnan(x)], x[~isnan(x)]))forxinself._grid])#move tiles33self._grid = rot90(self._grid, -1)34self._score += Game2048._merge_down(self._grid)#merge tiles35self._grid = rot90(self._grid, 1)36self._grid = array([concatenate((x[isnan(x)], x[~isnan(x)]))forxinself._grid])#move tiles37self._grid = rot90(self._grid, direction -RIGHT)38ifnot((self._grid == grid_copy) | (isnan(self._grid) &isnan(grid_copy))).all():39self._create_tiles()40returnTrue4142defget_grid(self):43grid =copy(self._grid)44grid[grid%10==0] /= 1045returngrid4647defget_new_tiles(self):48grid = zeros((4, 4), int)49grid[self._grid%10==0] = 150returngrid5152defget_score(self):53returnself._score

2) GUI模块,直接捕捉键盘的上下左右4个按键移动方块,窗体标题栏显示游戏相关信息,无动画,新出现的方块用橙色表示,使用大多数Python版本中内置的GUI库Tk实现,代码如下:

1fromTkinterimportTk, Label, Frame, BOTH2fromtkFontimportFont3fromgame2048importGame2048, UP, DOWN, LEFT, RIGHT, ndenumerate, copy, isnan45key_map = {'Up': UP,'Down': DOWN,'Left': LEFT,'Right': RIGHT}6color_map = {2: ('#776e65','#eee4da'), 4: ('#776e65','#ede0c8'), 8: ('#f9f6f2','#f2b179'), 16: ('#f9f6f2','#f2b179'),732: ('#f9f6f2','#f67c5f'), 64: ('#f9f6f2','#f65e3b'), 128:('#f9f6f2','#edcf72'), 256: ('#f9f6f2','#edcc61'),8512: ('#f9f6f2','#edc850'), 1024: ('#f9f6f2','#edc53f'), 2048: ('#f9f6f2','#edc22e'),'base':'#ccc0b3'}9color_map.update(dict.fromkeys([2**xforxinrange(12, 18)], ('#f9f6f2','#3c3a32')))1011definput_listener(event=None, game=None, tk_root=None, labels=None):12key ='{}'.format(event.keysym)13ifkeyinkey_mapandgameandlabelsandtk_root:14ifgame.step(key_map[key]):15grid, new_tiles, score =game.get_grid(), game.get_new_tiles(), int(game.get_score())16max_tile = int(grid[~isnan(grid)].max())17tk_root.title('Move tiles to get {}! Score: {}'.format(2048ifmax_tile < 2048elsemax_tile * 2, score))18for(i, j), valueinndenumerate(grid):19text ='{}'.format(''ifisnan(grid[i][j])elseint(grid[i][j]))20font_color = color_map[32][1]ifnew_tiles[i][j]elsecolor_map['base']ifisnan(value)elsecolor_map[value][0]21labels[4*i+j].config(text=text, fg=font_color, bg=color_map['base']ifisnan(value)elsecolor_map[value][1])22else:23grid, new_tiles, score =game.get_grid(), game.get_new_tiles(), int(game.get_score())24max_tile = int(grid[~isnan(grid)].max())25[labels[i].config(text=''ifi < 4ori > 11else'GAMEOVER'[i-4], bg=color_map['base'])foriinxrange(16)]26tk_root.title('Game Over! Tile acheived: {}, Score: {}'.format(max_tile, score))2728if__name__=='__main__':29game, root, window_size = Game2048(), Tk(), 36030root.title('Move tiles to get 2048! Score: 0')31root.geometry('{0}x{0}+111+111'.format(window_size))32root.config(background='#bbada0')3334grid, labels =game.get_grid(), []35for(i, j), valueinndenumerate(grid):36frame = Frame(root, width=window_size/4-2, height=window_size/4-2)37font = Font(family='Helvetica', weight='bold', size=window_size/15)38frame.pack_PRopagate(0)39frame.place(x=j*window_size/4+1, y=i*window_size/4+1)40(text, color) = ('', color_map['base'])ifisnan(value)else('{}'.format(int(value)), color_map[value][0])41label = Label(frame, text=text, font=font, fg=color, bg=color_map['base']ifisnan(value)elsecolor_map[value][1])42label.pack(fill=BOTH, expand=True)43labels.append(label)4445root.bind_all('<Key>',lambdaevent: input_listener(event, game=game, tk_root=root, labels=labels))46root.mainloop()

发布在github上了,还没有完全测试过,欢迎fork。

GitHub: https://github.com/frombeijingwithlove/mini2048

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航