“實驗樓"中的2048小遊戲復現
阿新 • • 發佈:2018-11-20
#-*- coding:utf-8 -*- import curses from random import randrange,choice from collections import defaultdict #所有的有效輸入轉換為“上下左右,重置,退出”這六種行為,用actions表示 actions=['Up','Left','Down','Right','Restart','Exit'] #有效輸入的值的列表 # ord()獲取字元的ASCII碼 letter_codes=[ord(ch) for ch in 'WASDRQwasdrq'] #將輸入與行為進行關聯 # zip()將元組打包為列表 actions_dict=dict(zip(letter_codes,actions*2)) def get_user_action(keyboard): char='N' while char not in actions_dict: char=keyboard.getch() return actions_dict[char] def transpose(field): return [list(row) for row in zip(*field)] def invert(field): return [row[::-1] for row in field] class GameField(object): #初始化棋盤的引數 def __init__(self,height=4,width=4,win=2048): self.height=height self.width=width self.win_value=win self.score=0 self.highscore=0 self.reset() #重置棋盤 def reset(self): if self.score>self.highscore: self.highscore=self.score self.score=0 self.field=[[0 for i in range(self.width)] for j in range(self.height)] self.spawn() self.spawn() #棋盤走一步 def move(self,direction): # 一行向左合併 def move_row_left(row): # 把零散的非零單元擠到一塊 def tighten(row): new_row = [i for i in row if i != 0] new_row += [0 for i in range(len(row) - len(new_row))] return new_row # 對鄰近元素進行合併 def merge(row): pair = False new_row = [] for i in range(len(row)): # pair用來標誌是否需要合併 # 有兩個一樣的數的話,第二個位置上存和 if pair: new_row.append(2 * row[i]) self.score += 2 * row[i] pair = False else: # 有兩個一樣的數的話,第一個位置置零 if i + 1 < len(row) and row[i] == row[i + 1]: pair = True new_row.append(0) # 若一行僅有一個數,則將那個數新增到新行 else: new_row.append(row[i]) # assert語句是一種插入除錯斷點到程式的一種便捷的方式 assert len(new_row) == len(row) return new_row # 先擠到一塊再合併再擠到一塊 return tighten(merge(tighten(row))) moves={} moves['Left']=lambda field:[move_row_left(row) for row in field] moves['Right']=lambda field:invert(moves['Left'](invert(field))) moves['Up']=lambda field:transpose(moves['Left'](transpose(field))) moves['Down']=lambda field:transpose(moves['Right'](transpose(field))) if direction in moves: if self.move_is_possible(direction): self.field=moves[direction](self.field) self.spawn() return True else: return False #判斷輸贏 def is_win(self): return any(any(i>self.win_value for i in row) for row in self.field) def is_gameover(self): return not any(self.move_is_possible(move) for move in actions) #繪製遊戲介面 def draw(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!' #輸出string後換行 def cast(string): screen.addstr(string + '\n') #繪製水平分割線 def draw_hor_separator(): line='+'+('+-----'*self.width+'+')[1:] separator=defaultdict(lambda :line) #hasattr(object,name)函式用於判斷物件是否包含對應的屬性 if not hasattr(draw_hor_separator,"counter"): draw_hor_separator.counter=0 #每行後呼叫cast()進行換行,並用drao_hor_separator.counter計有多少條水平分割線 cast(separator[draw_hor_separator.counter]) draw_hor_separator.counter+=1 #繪製數字所在的那行 def draw_row(row): #join()方法用於將序列中的元素以指定的字元(’’)連線生成一個新的字串 #format函式用於字串的格式化 cast(''.join('|{:^5}'.format(num) if num >0 else '| ' for num in row)+'|') #清空screen這個列表 screen.clear() #繪製整個框架 cast('SCORE:'+str(self.score)) if 0!=self.highscore: cast('HIGHSCORE:'+str(self.highscore)) #迴圈繪製分割線和數字行 for row in self.field: draw_hor_separator() draw_row(row) #多輸出個分割線做邊框 draw_hor_separator() if self.is_win(): cast(win_string) else: if self.is_gameover(): cast(gameover_string) else: cast(help_string1) cast(help_string2) # 隨機生成一個2或4 def spawn(self): new_element = 4 if randrange(100) > 89 else 2 (i, j) = choice([(i, j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) self.field[i][j] = new_element def move_is_possible(self,direction): def row_is_left_movable(row): #可以移動的兩種情況 def change(i): if row[i]==0 and row[i+1]!=0: return True if row[i]!=0 and row[i+1]==row[i]: return True return False return any(change(i) for i in range(len(row)-1)) check={} check['Left']=lambda field:any(row_is_left_movable(row) for row in field) check['Right']=lambda field:check['Left'](invert(field)) check['Up']=lambda field:check['Left'](transpose(field)) check['Down']=lambda field:check['Right'](transpose(field)) if direction in check: return check[direction](self.field) else: return False def main(stdscr): def init(): #重置遊戲棋盤 game_field.reset() return 'Game' def not_game(state): #畫出GameOver或win的介面 game_field.draw(stdscr) #讀取使用者輸入得到action,判斷是重啟還是結束 action=get_user_action(stdscr) #defaultdict的功能與dict相同,但會為一個不存在的鍵提供預設值,從而避免KeyError異常。 responses=defaultdict(lambda :state)#預設是當前狀態,沒有行為就會一直在當前介面迴圈 responses['Restart'],responses['Exit']='Init','Exit'#對應不同的行為轉換到不同的狀態 return responses[action] def game(): #畫出當前棋盤狀態 game_field.draw(stdscr) #讀取使用者輸入得到action action=get_user_action(stdscr) if action=='Restart': return 'Init' if action=='Exit': return 'Exit' #移動成功 if game_field.move(action): if game_field.is_win(): return 'Win' if game_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() #設定終結狀態最大數值為2048 game_field=GameField(win=2048) state='Init' #狀態機開始迴圈 while state!='Exit': #? state=state_actions[state]() curses.wrapper(main)
程式碼直接在pycharm中執行會有錯誤,後直接在命令列視窗執行正確。