蛋疼之作:99行代码的2048

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

Not Pythonic, PEP8 is not followed.

对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个值都相等的情况下正确的移动并合并方块。

模块代码如下:

 1 from numpy import random, array, zeros, append, sum, concatenate, copy, ndenumerate, isnan, rot90, nan, int, float
 2 DOWN, RIGHT, UP, LEFT = range(4)
 3
 4 class Game2048:
 5     def __init__(self):
 6         self._grid, self._score = zeros(16) + nan, 0
 7         self._grid[random.choice(16, 2, replace=False)] = random.choice([2]*9+[4], 2, replace=False) # init with 2 tiles
 8         self._grid = self._grid.reshape((4, 4))  # create 4x4 grid
 9
10     @staticmethod
11     def _merge_down(grid):
12         merge = concatenate((grid, [zeros(4) + nan])) - concatenate(([zeros(4) + nan], grid))  # find the mergable tiles
13         merge[2][merge[3]==0], merge[1][merge[2]==0] = nan, nan     # remove redundant 0 by 3 same tiles
14         score = sum(grid[merge[:4] == 0])
15         grid[merge[:4] == 0], grid[merge[1:] == 0] = grid[merge[:4] == 0] * 2, nan # fill the merged  with new number
16         return score
17
18     def _create_tiles(self):
19         avail = isnan(self._grid)
20         if avail[avail==True].size > 0:
21             new_tiles = append(random.choice([20]*9+[40]), zeros(avail[avail==True].size - 1) + nan)
22             random.shuffle(new_tiles)
23             self._grid[avail] = new_tiles
24
25     def step(self, direction):
26         self._grid[self._grid%10==0] /= 10
27         merge_v, merge_h, grid_copy = copy(self._grid), copy(rot90(self._grid)), copy(self._grid)
28         map(Game2048._merge_down, [merge_v, merge_h])       # try to merge tiles along two directions
29         if merge_v[isnan(merge_v)].size is 0 and merge_h[isnan(merge_h)].size is 0:         # Check if game is over
30             return False
31         self._grid = rot90(self._grid, RIGHT - direction)
32         self._grid = array([concatenate((x[isnan(x)], x[~isnan(x)])) for x in self._grid])  # move tiles
33         self._grid = rot90(self._grid, -1)
34         self._score += Game2048._merge_down(self._grid)                                     # merge tiles
35         self._grid = rot90(self._grid, 1)
36         self._grid = array([concatenate((x[isnan(x)], x[~isnan(x)])) for x in self._grid])  # move tiles
37         self._grid = rot90(self._grid, direction - RIGHT)
38         if not ((self._grid == grid_copy) | (isnan(self._grid) & isnan(grid_copy))).all():
39             self._create_tiles()
40         return True
41
42     def get_grid(self):
43         grid = copy(self._grid)
44         grid[grid%10==0] /= 10
45         return grid
46
47     def get_new_tiles(self):
48         grid = zeros((4, 4), int)
49         grid[self._grid%10==0] = 1
50         return grid
51
52     def get_score(self):
53         return self._score

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

 1 from Tkinter import Tk, Label, Frame, BOTH
 2 from tkFont import Font
 3 from game2048 import Game2048, UP, DOWN, LEFT, RIGHT, ndenumerate, copy, isnan
 4
 5 key_map = {‘Up‘: UP, ‘Down‘: DOWN, ‘Left‘: LEFT, ‘Right‘: RIGHT}
 6 color_map = {2: (‘#776e65‘, ‘#eee4da‘), 4: (‘#776e65‘, ‘#ede0c8‘), 8: (‘#f9f6f2‘, ‘#f2b179‘), 16: (‘#f9f6f2‘, ‘#f2b179‘),
 7              32: (‘#f9f6f2‘, ‘#f67c5f‘), 64: (‘#f9f6f2‘, ‘#f65e3b‘), 128:(‘#f9f6f2‘, ‘#edcf72‘), 256: (‘#f9f6f2‘, ‘#edcc61‘),
 8              512: (‘#f9f6f2‘, ‘#edc850‘), 1024: (‘#f9f6f2‘, ‘#edc53f‘), 2048: (‘#f9f6f2‘, ‘#edc22e‘), ‘base‘: ‘#ccc0b3‘}
 9 color_map.update(dict.fromkeys([2**x for x in range(12, 18)], (‘#f9f6f2‘, ‘#3c3a32‘)))
10
11 def input_listener(event=None, game=None, tk_root=None, labels=None):
12     key = ‘{}‘.format(event.keysym)
13     if key in key_map and game and labels and tk_root:
14         if game.step(key_map[key]):
15             grid, new_tiles, score = game.get_grid(), game.get_new_tiles(), int(game.get_score())
16             max_tile = int(grid[~isnan(grid)].max())
17             tk_root.title(‘Move tiles to get {}! Score: {}‘.format(2048 if max_tile < 2048 else max_tile * 2, score))
18             for (i, j), value in ndenumerate(grid):
19                 text = ‘{}‘.format(‘‘ if isnan(grid[i][j]) else int(grid[i][j]))
20                 font_color = color_map[32][1] if new_tiles[i][j] else color_map[‘base‘] if isnan(value) else color_map[value][0]
21                 labels[4*i+j].config(text=text, fg=font_color, bg=color_map[‘base‘] if isnan(value) else color_map[value][1])
22         else:
23             grid, new_tiles, score = game.get_grid(), game.get_new_tiles(), int(game.get_score())
24             max_tile = int(grid[~isnan(grid)].max())
25             [labels[i].config(text=‘‘ if i < 4 or i > 11 else ‘GAMEOVER‘[i-4], bg=color_map[‘base‘]) for i in xrange(16)]
26             tk_root.title(‘Game Over! Tile acheived: {}, Score: {}‘.format(max_tile, score))
27
28 if __name__ == ‘__main__‘:
29     game, root, window_size = Game2048(), Tk(), 360
30     root.title(‘Move tiles to get 2048! Score: 0‘)
31     root.geometry(‘{0}x{0}+111+111‘.format(window_size))
32     root.config(background=‘#bbada0‘)
33
34     grid, labels = game.get_grid(), []
35     for (i, j), value in ndenumerate(grid):
36         frame = Frame(root, width=window_size/4-2, height=window_size/4-2)
37         font = Font(family=‘Helvetica‘, weight=‘bold‘, size=window_size/15)
38         frame.pack_propagate(0)
39         frame.place(x=j*window_size/4+1, y=i*window_size/4+1)
40         (text, color) = (‘‘, color_map[‘base‘]) if isnan(value) else (‘{}‘.format(int(value)), color_map[value][0])
41         label = Label(frame, text=text, font=font, fg=color, bg=color_map[‘base‘] if isnan(value) else color_map[value][1])
42         label.pack(fill=BOTH, expand=True)
43         labels.append(label)
44
45     root.bind_all(‘<Key>‘, lambda event: input_listener(event, game=game, tk_root=root, labels=labels))
46     root.mainloop()

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

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

时间: 2024-10-20 23:54:43

蛋疼之作:99行代码的2048的相关文章

js280行代码写2048

2048 原作者就是用Js写的,一直想尝试,但久久未动手. 昨天教学生学习JS代码.不妨就做个有趣的游戏好了.2048这么火,是一个不错的选择. 思路: 1. 数组 ,2维数组4x4 2. 移动算法,移动后有数字的对齐,无数字(我用的0,但不显示)补齐. 移动前 移动后(注意程序合并了第一行2个2,并产生了新的2) 移动算法分2步: 第一步骤:移动 第二步骤:合并 移动代码参考: function left(t,i) { var j; var len = t[i].length; for (j=

一个只有99行代码的JS流程框架

最近一直在想一个问题,如何能让js代码写起来更语义化和更具有可读性. 上周末的时候突发奇想,当代码在运行的时候,其实跟我们做事情是类似的,都是做完一步接着下一步,并且这些事情有些是可规划的,有些是需要做完该步才知道下一步该做什么.想到这里一个js框架雏形在我大脑中慢慢形成,暂且命名为flowJS. 接着说说这个框架应该有哪些API? 1.可以预先规划好流程的每一步,如this.setNext('步骤A').setNext('步骤B')-- 2.可以在任何一步决定下一步做什么,如 this.set

一个只有99行代码的JS流程框架(二)

张镇圳,腾讯Web前端高级工程师,对内部系统前端建设有多年经验,喜欢钻研捣鼓各种前端组件和框架. 导语 前面写了一篇文章,叫<一个只有99行代码的JS流程框架>,虽然该框架基本已经能实现一个流程正常的逻辑流转,但是在分模块应用下还是缺少一定的能力,无法将一个页面中的不同模块很好的连接在一起,于是对之前的框架进行了升级,新增了子流程的概念. 子流程 什么是子流程?在这个升级后的框架里(当然代码已经不止99行了,不要在乎标题),每个步骤不但可以是一个function,还可以引用另一个流程,这个被引

200行Python代码实现2048

200行Python代码实现2048 一.实验说明 1. 环境登录 无需密码自动登录,系统用户名shiyanlou 2. 环境介绍 本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌面上的程序: LX终端(LXTerminal): Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令 GVim:非常好用的编辑器,最简单的用法可以参考课程Vim编辑器 3. 环境使用 使用GVim编辑器输入实验所需的代码及文件,使用LX终端(LXTerminal)运行所需命令进行操

280行代码:Javascript 写的2048游戏

2048 原作者就是用Js写的,一直想尝试,但久久未动手. 昨天教学生学习JS代码.不妨就做个有趣的游戏好了.2048这么火,是一个不错的选择. 思路: 1. 数组 ,2维数组4x4 2. 移动算法,移动后有数字的对齐,无数字(我用的0,但不显示)补齐. 移动前 移动后(注意程序合并了第一行2个2,并产生了新的2) 移动算法分2步: 第一步骤:移动 第二步骤:合并 移动代码参考: [html] view plaincopy function left(t,i) { var j; var len 

【转载】88行代码实现俄罗斯方块游戏(含讲解)

来源:http://misaka.blog.com/p/8 在正式阅读本文之前,请你记得你应该用娱乐的心态来看,本代码所使用到的技巧,在工作了的人眼里会觉得很纠结,很蛋疼,很不可理喻,很丑,注意,是你蛋疼,不关我的事 通常,写一个俄罗斯方块,往往动不动就几百行,甚至上千行,而这里只有88行正所谓头脑风暴,打破常规.这里将使用很多不平常的手段来减少代码 以下是Win-TC可以成功编译并执行的代码(代码保证单行长度不超过80字符,如果你是Win7系统,那请看后文): #include "graphi

分享一个开源的JavaScript统计图表库,40行代码实现专业统计图表

提升程序员工作效率的工具/技巧推荐系列 推荐一个功能强大的文件搜索工具SearchMyFiles 介绍一个好用的免费流程图和UML绘制软件-Diagram Designer 介绍Windows任务管理器的替代者-Process Explorer 介绍一个强大的磁盘空间检测工具Space Sniffer 如何在电脑上比较两个相似文件的差异 程序员工作效率提升系列-推荐一个JSON文件查看和修改的小工具 将Chrome调试器里的JavaScript变量保存成本地JSON文件 这可能是史上最简单易用的

100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] 简介 流程图 simplest_ffmpeg_player标准版代码 simplest_ffmpeg_player_suSU版代码 结果 FFMPEG相关学习资料 补充问题 ===================================================== 最简单的基于FFmp

Cocos2d-x 3.x 开发(十八)10行代码看自动Batch,10行代码看自动剔除

1.概述 在游戏的运行过程中,图形的绘制是非常大的开销.对于良莠不齐的Android手机市场,绘制优化较好的游戏,可以在更多的手机上运行,因此也是优化的重中之重.图形方面的优化主要体现在减少GUP的绘制次数上.这里我们分别从自动优化渲染批次和绘制剔除两个方面来看新版本在绘制上的优化. 2.自动batch 在Cocos2d-x 3.x中,抛弃了先前手动编写BatchNode,采用自动管理的方式.说起BatchNode,就难免涉及到显卡底层的绘制原理.简单的说,每提交一条绘制指令到显卡都会产生消耗,