原标题:200 行代码实现 2048 游戏
作者:Guolanzhe
原文: http://www.guolanzhe.com/?p=522创建游戏文件 2048.py
首先导入需要的包:
importcurses
fromrandomimportrandrange,choice
fromcollectionsimportdefaultdict主逻辑 用户行为
所有的有效输入都可以转换为"上,下,左,右,游戏重置,退出"这六种行为,用 actions表示
actions=['Up','Left','Down','Right','Restart','Exit']
有效输入键是最常见的 W(上),A(左),S(下),D(右),R(重置),Q(退出),这里要考虑到大写键开启的情况,获得有效键值列表:
letter_codes=[ord(ch)forchin'WASDRQwasdrq']
将输入与行为进行关联:
actionsdict = dict(zip(lettercodes, actions * 2))
状态机
处理游戏主逻辑的时候我们会用到一种十分常用的技术:状态机,或者更准确的说是有限状态机(FSM)
你会发现 2048 游戏很容易就能分解成几种状态的转换。
state存储当前状态, state_actions这个词典变量作为状态转换的规则,它的 key 是状态,value 是返回下一个状态的函数:
Init: init()
Game: game()
Win: lambda: not_game('Win')
Gameover: lambda: not_game('Gameover')
Exit: 退出循环
状态机会不断循环,直到达到 Exit 终结状态结束程序。
下面是经过提取的主逻辑的代码,会在后面进行补全:
defmain(stdscr):
definit():
#重置游戏棋盘
return'Game'
defnot_game(state):
#画出 GameOver 或者 Win 的界面
#读取用户输入得到action,判断是重启游戏还是结束游戏
responses=defaultdict(lambda:state)#默认是当前状态,没有行为就会一直在当前界面循环
responses['Restart'],responses['Exit']='Init','Exit'#对应不同的行为转换到不同的状态
returnresponses[action]
defgame():
#画出当前棋盘状态
#读取用户输入得到action
ifaction=='Restart':
return'Init'
ifaction=='Exit':
return'Exit'
#if 成功移动了一步:
if游戏胜利了:
return'Win'
if游戏失败了:
return'Gameover'
return'Game'
state_actions={
'Init':init,
'Win':lambda:not_game('Win'),
'Gameover':lambda:not_game('Gameover'),
'Game':game
}
state='Init'
#状态机开始循环
whilestate!='Exit':
state=state_actions[state]()用户输入处理
阻塞+循环,直到获得用户有效输入才返回对应行为:
defget_user_action(keyboard):
char="N"
whilecharnotinactions_dict:
char=keyboard.getch()
returnactions_dict[char]矩阵转置与矩阵逆转
加入这两个操作可以大大节省我们的代码量,减少重复劳动,看到后面就知道了。
矩阵转置:
deftranspose(field):
return[list(row)forrowinzip(*field)]
矩阵逆转(不是逆矩阵):
definvert(field):
return[row[::-1]forrowinfield]创建棋盘
初始化棋盘的参数,可以指定棋盘的高和宽以及游戏胜利条件,默认是最经典的 4×4~2048。
classGameField(object):
def__init__(self,height=4,width=4,win=2048):
self.height=height#高
self.width=width#宽
self.win_value=2048#过关分数
self.score=0#当前分数
self.highscore=0#最高分
self.reset()#棋盘重置棋盘操作 随机生成一个 2 或者 4
defspawn(self):
new_element=4ifrandrange(100)>89else2
(i,j)=choice([(i,j)foriinrange(self.width)forjinrange(self.height)ifself.field[i][j]==0])
self.field[i][j]=new_element
#### 重置棋盘
defreset(self):
ifself.score>self.highscore:
self.highscore=self.score
self.score=0
self.field=[[0foriinrange(self.width)]forjinrange(self.height)]
self.spawn()
self.spawn()
#### 一行向左合并
(注:这一操作是在move内定义的,拆出来是为了方便阅读)
defmove_row_left(row):
deftighten(row):# 把零散的非零单元挤到一块
new_row=[iforiinrowifi!=0]
new_row+=[0foriinrange(len(row)-len(new_row))]
returnnew_row
defmerge(row):# 对邻近元素进行合并
pair=False
new_row=[]
foriinrange(len(row)):
ifpair:
new_row.append(2*row[i])
self.score+=2*row[i]
pair=False
else:
ifi+1
pair=True
new_row.append(0)
else:
new_row.append(row[i])
assertlen(new_row)==len(row)
returnnew_row
#先挤到一块再合并再挤到一块
returntighten(merge(tighten(row)))棋盘走一步
通过对矩阵进行转置与逆转,可以直接从左移得到其余三个方向的移动操作
defmove(self,direction):
defmove_row_left(row):
#一行向左合并
moves={}
moves['Left']=lambdafield:[move_row_left(row)forrowinfield]
moves['Right']=lambdafield:invert(moves['Left'](invert(field)))
moves['Up']=lambdafield:transpose(moves['Left'](transpose(field)))
moves['Down']=lambdafield:transpose(moves['Right'](transpose(field)))
ifdirectioninmoves:
ifself.move_is_possible(direction):
self.field=moves[direction](self.field)
self.spawn()
returnTrue
else:
returnFalse判断输赢
defis_win(self):
returnany(any(i>=self.win_valueforiinrow)forrowinself.field)
defis_gameover(self):
returnnotany(self.move_is_possible(move)formoveinactions)
#### 判断能否移动
defmove_is_possible(self,direction):
defrow_is_left_movable(row):
defchange(i):
ifrow[i]==0androw[i+1]!=0:# 可以移动
returnTrue
ifrow[i]!=0androw[i+1]==row[i]:# 可以合并
returnTrue
returnFalse
returnany(change(i)foriinrange(len(row)-1))
check={}
check['Left']=lambdafield:any(row_is_left_movable(row)forrowinfield)
check['Right']=lambdafield:check['Left'](invert(field))
check['Up']=lambdafield:check['Left'](transpose(field))
check['Down']=lambdafield:check['Right'](transpose(field))
ifdirectionincheck:
returncheck[direction](self.field)
else:
returnFalse绘制游戏界面
defdraw(self,screen):
help_string1='(W)Up (S)Down (A)Left (D)Right'
help_string2=' (R)Restart (Q)Exit'
gameover_string=' GAME OVER'
win_string=' YOU WIN!'
defcast(string):
screen.addstr(string+'n')
#绘制水平分割线
defdraw_hor_separator():
line='+'+('+------'*self.width+'+')[1:]
separator=defaultdict(lambda:line)
ifnothasattr(draw_hor_separator,"counter"):
draw_hor_separator.counter=0
cast(separator[draw_hor_separator.counter])
draw_hor_separator.counter+=1
defdraw_row(row):
cast(''.join('|{: ^5} '.format(num)ifnum>0else'| 'fornuminrow)+'|')
screen.clear()
cast('SCORE: '+str(self.score))
if0!=self.highscore:
cast('HGHSCORE: '+str(self.highscore))
forrowinself.field:
draw_hor_separator()
draw_row(row)
draw_hor_separator()
ifself.is_win():
cast(win_string)
else:
ifself.is_gameover():
cast(gameover_string)
else:
cast(help_string1)
cast(help_string2)完成主逻辑
完成以上工作后,我们就可以补完主逻辑了!
defmain(stdscr):
definit():
#重置游戏棋盘
game_field.reset()
return'Game'
defnot_game(state):
#画出 GameOver 或者 Win 的界面
game_field.draw(stdscr)
#读取用户输入得到action,判断是重启游戏还是结束游戏
action=get_user_action(stdscr)
responses=defaultdict(lambda:state)#默认是当前状态,没有行为就会一直在当前界面循环
responses['Restart'],responses['Exit']='Init','Exit'#对应不同的行为转换到不同的状态
returnresponses[action]
defgame():
#画出当前棋盘状态
game_field.draw(stdscr)
#读取用户输入得到action
action=get_user_action(stdscr)
ifaction=='Restart':
return'Init'
ifaction=='Exit':
return'Exit'
ifgame_field.move(action):# move successful
ifgame_field.is_win():
return'Win'
ifgame_field.is_gameover():
return'Gameover'
return'Game'
state_actions={
'Init':init,
'Win':lambda:not_game('Win'),
'Gameover':lambda:not_game('Gameover'),
'Game':game
}
curses.use_default_colors()
game_field=GameField(win=32)
state='Init'
#状态机开始循环
whilestate!='Exit':
state=state_actions[state]()运行
填上最后一行代码:
curses.wrapper(main)
完整版代码地址:https://github.com/JLUNeverMore/easy_2048-in-200-lines返回搜狐,查看更多
责任编辑: