Python實現小游戲--2048

uznh1227 7年前發布 | 20K 次閱讀 Python 游戲開發 Python開發

開篇語

今天是筆者奶奶生日,昨天為此準備了不少事情,也因此花費了很多時間,今天上午接待客人,下午送我年邁的姑奶奶回家。等我抽出身來看代碼已經是三點的事情了。再加上還有個“問題xx”要教導,真正開始認真看代碼,已經是四點咯。然后看到五點多,爸爸叫我吃飯,我去,還沒跑步,所以又是一番雞飛狗跳。跑完回來一邊壓腿,一邊看家人吃飯,也是醉醉的。雖然有點跑題,但是我還是要拿我的跑步的圖來放一放~~~

這是定向越野~地面泥濘,公路上大大小小的石子,跑起來真是百曲千回~

第二天了,繼續堅持!yeah!

正文

本次是我對于Python實現2048這個曾經風靡一時的小游戲的代碼解讀

工作環境

一、主邏輯圖

邏輯圖解:黑色是邏輯層,藍色是外部方法,紅色是類內方法,稍后即可知道~

狀態機。。。游戲概念,出自實驗樓

下面容我逐行解釋主邏輯main()函數,并且在其中穿叉外部定義的函數與類。

二、主邏輯代碼解讀(完整代碼見文末)

主邏輯main如下,之后的是對主函數中的一些方法的解讀:

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)
    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): # move successful
        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()
game_field = GameField(win=32)

state = 'Init'

#狀態機開始循環
while state != 'Exit':
    state = state_actions[state]()</code></pre> 

逐條解讀(代碼框內會標注是來自外部,無標注則是來自內部):定義主函數

def main(stdscr):
def init():
        #重置游戲棋盤
        game_field.reset()

reset出自外部定義的類, game_field=GameField 的一個方法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()
#其中highscore為程序初始化過程中定義的一個變量。記錄你win游戲的最高分數記錄。
return 'Game'

返回一個游戲進行中的狀態。 game_field=GameField 狀態在后面有定義:

主函數底部定義:
 state_actions = {
            'Init': init,
            'Win': lambda: not_game('Win'),
            'Gameover': lambda: not_game('Gameover'),
            'Game': game
        }
def not_game(state):
        #畫出 GameOver 或者 Win 的界面
        game_field.draw(stdscr)

draw是導入的類 game_field=GameField 中的方法:

#來自外部類
    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!'
#定義各個字符串
        def cast(string):
            screen.addstr(string + '\n')

        def draw_hor_separator():
            line = '+' + ('+------' * self.width + '+')[1:]
            separator = defaultdict(lambda: line)
            if not hasattr(draw_hor_separator, "counter"):
                draw_hor_separator.counter = 0
            cast(separator[draw_hor_separator.counter])
            draw_hor_separator.counter += 1

        def draw_row(row):
            cast(''.join('|{: ^5} '.format(num) if num > 0 else '|      ' for num in row) + '|')

        screen.clear()
        cast('SCORE: ' + str(self.score))
        if 0 != self.highscore:
            cast('HGHSCORE: ' + 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)
#這里面的draw方法的字函數我就不做多的解釋了,很簡單的一些概念。
#但是又運用到了很優秀的精簡代碼。
#有的地方建議去查一下python的一些高級概念,我就不做多的介紹了。

這里面的draw方法的字函數我就不做多的解釋了,很簡單的一些概念。

但是又運用到了很優秀的精簡代碼。

有的地方建議去查一下python的一些高級概念,我就不做多的介紹了。

#讀取用戶輸入得到action,判斷是重啟游戲還是結束游戲
        action = get_user_action(stdscr)

讀取用戶行為,函數來自于代碼初始的定義

#來自外部定義的函數
def get_user_action(keyboard):    
    char = "N"
    while char not in actions_dict:    
        char = keyboard.getch()
    return actions_dict[char]

在結尾處,也即是主函數執行的第三步,定義了 state = state_actions[state]() 這一實例:

#主函數底部:
    state = 'Init'

    #狀態機開始循環
    while state != 'Exit':
        state = state_actions[state]()
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): # move successful
            if game_field.is_win():
                return 'Win'
            if game_field.is_gameover():
                return 'Gameover'
        return 'Game'
#game()函數的定義類似于上面已經講過的not_game(),只是game()有了內部循環
#即如果不是Restart/Exit或者對move之后的狀態進行判斷,如果不是結束游戲,就一直在game()內部循環。

game()函數的定義類似于上面已經講過的not_game(),只是game()有了內部循環,即如果不是Restart/Exit或者對move之后的狀態進行判斷,如果不是結束游戲,就一直在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'

    #狀態機開始循環
    while state != 'Exit':
        state = state_actions[state]()
#此處的意思是:state=state_actions[state] 可以看做是:
#state=init()或者state=not_game(‘Win’)或者是另外的not_game(‘Gameover’)/game()

此處的意思是:state=state_actions[state] 可以看做是:state=init()或者state=not_game(‘Win’)或者是另外的not_game(‘Gameover’)/game()

結束語

廢話不多說,上一個我的成功的圖,另外,可以通過設置最后幾行中的 win=32 來決定你最終獲勝的條件!

2048.gif

三、完整代碼

#-*- coding:utf-8 -*-
import curses
from random import randrange, choice # generate and place new tile
from collections import defaultdict
letter_codes = [ord(ch) for ch in 'WASDRQwasdrq']
actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit']
actions_dict = dict(zip(letter_codes, actions * 2))
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): # squeese non-zero elements together
                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)):
                    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 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!'
        def cast(string):
            screen.addstr(string + '\n')

        def draw_hor_separator():
            line = '+' + ('+------' * self.width + '+')[1:]
            separator = defaultdict(lambda: line)
            if not hasattr(draw_hor_separator, "counter"):
                draw_hor_separator.counter = 0
            cast(separator[draw_hor_separator.counter])
            draw_hor_separator.counter += 1

        def draw_row(row):
            cast(''.join('|{: ^5} '.format(num) if num > 0 else '|      ' for num in row) + '|')

        screen.clear()
        cast('SCORE: ' + str(self.score))
        if 0 != self.highscore:
            cast('HGHSCORE: ' + 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)

    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): # true if there'll be change in i-th tile
                if row[i] == 0 and row[i + 1] != 0: # Move
                    return True
                if row[i] != 0 and row[i + 1] == row[i]: # Merge
                    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)
        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): # move successful
            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()
    game_field = GameField(win=32)
    state = 'Init'
    #狀態機開始循環
    while state != 'Exit':
        state = state_actions[state]()
curses.wrapper(main)

 

 

來自:http://www.jianshu.com/p/7a3a7545d2fb

 

 本文由用戶 uznh1227 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!