欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[实验楼] Python 实现 2048 游戏

程序员文章站 2023-08-24 17:28:15
参考:https://www.shiyanlou.com/courses/368本节实验中将学习和实践以下知识点:Python 基本知识curses 终端图形编程库random 随机数模块collections 容器数据类型库状态机的概念项目样式如下(main函数中)state 存储当前状态, state_actions 这个词典变量作为状态转换的规则,它的 key 是状态,value 是返回下一个状态的函数:与原代码相比,添加了上下左右键处理(原来只有wasd键),胜利后按任意键(除...

参考:https://www.shiyanlou.com/courses/368

本节实验中将学习和实践以下知识点:
Python 基本知识
curses 终端图形编程库
random 随机数模块
collections 容器数据类型库
状态机的概念

项目样式如下
[实验楼] Python 实现 2048 游戏
(main函数中)state 存储当前状态, state_actions 这个词典变量作为状态转换的规则,它的 key 是状态,value 是返回下一个状态的函数:[实验楼] Python 实现 2048 游戏
与原代码相比,添加了上下左右键处理(原来只有wasd键),胜利后按任意键(除了r,q)继续游戏,把一部分函数改的更亲民了(比如move_is_possible,not_game,原来的好变态…不,是高深)


import curses  # curses 用来在终端上显示图形界面
from random import randrange, choice # random 模块用来生成随机数

actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit']

letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] + [259,260,258,261] 
# ord转换为ASCII码  后面四个是上左下右
#[87, 65, 83, 68, 82, 81, 119, 97, 115, 100, 114, 113, 259, 260, 258, 261]

win_continue = False #胜利后是否继续
actions_dict = dict(zip(letter_codes, actions * 2 + actions[:4]))
#{87: 'Up', 65: 'Left', 83: 'Down', 68: 'Right', 82: 'Restart', 81: 'Exit', 119: 'Up', 97: 'Left', 115: 'Down', 100: 'Right', 114: 'Restart', 113: 'Exit', 259: 'Up', 260: 'Left', 258: 'Down', 261: 'Right'}

#对角置换
def transpose(field):
    return [list(row) for row in zip(*field)]

#水平反置
def invert(field):
    return [row[::-1] for row in field]

# 用户输入
def get_user_action(keybord):
    char = '#'
    while char not in actions_dict:
        char = keybord.getch()  # 返回按下键位的 ASCII 码值
    return actions_dict[char]

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()

    # 随机生成一个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(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)):
                    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):
        global win_continue
        if win_continue:
             return False
        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_str1 = '(W)Up (S)Down (A)Left (D)Right'
        help_str2 = '       (R)Restart (Q)Exit        '
        gameover_str = '        Game Over     '
        win_str = '         You Win       '

        # 绘制函数
        def cast(string):
            # addstr()将传入的内容展示到屏幕上
            screen.addstr(string + '\n')

        # 水平线
        def draw_hor_separator():
            line =  '+------' * self.width + '+'
            cast(line)
     
       
        def draw_row(row):
            cast(''.join('|{:^6}'.format(num) if num > 0 else '!      ' for num in row) + '|')

        screen.clear()
        cast('SCORE: ' + str(self.score))
        if self.highscore != 0:
            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_str)
        else:
            if self.is_gameover():
                cast(gameover_str)
            else:
                cast(help_str1)
        cast(help_str2)

    #判断某个方向能否移动
    #有0一定可以,否则需要这个方向上有相邻且相同的数
    def move_is_possible(self, direction):
        if any(any(num==0 for num in row) for row in self.field):
            return True
        if direction == 'Left' or direction == 'Right':
            for i in range(0, self.height):
                for j in range(1, self.width):
                    if self.field[i][j] == self.field[i][j-1]:
                        return True
        else:
            for i in range(1, self.height):
                for j in range(0, self.width):
                    if self.field[i][j] == self.field[i-1][j]:
                        return True
        return False

def main(stdscr):
    #标准屏幕 来自curses库
    def init():
        # 初始化棋盘
        global win_continue
        win_continue = False
        game_field.reset()
        return 'Game'

    def not_game(state):
        # 绘制界面
        game_field.draw(stdscr)
        if state == 'Win':
            stdscr.addstr('press other key to continue\n')
        # 获取用户键盘输入
        action = get_user_action(stdscr)            
        if action == 'Exit':
            return 'Exit'
        if action == 'Restart':
            return 'Init'
        if state == 'Win':
            global win_continue
            win_continue = True
            return 'Game'
        return state

    def game():
        game_field.draw(stdscr)
        action = get_user_action(stdscr)

        if action == 'Exit':
            return 'Exit'
        if action == 'Restart':
            return 'Init'
        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()
    game_field = GameField(win=2048)

    state = 'Init'

    while state != 'Exit':
        state = state_actions[state]()

curses.wrapper(main) 
"""
首先, curses.wrapper 函数会激活并初始化终端进入 'curses 模式'。
在这个模式下会禁止输入的字符显示在终端上、禁止终端程序的行缓冲(line buffering),即字符在输入时就可以使用,不需要遇到换行符或回车。
接着,curses.wrapper 函数需要传一个函数作为参数,这个传进去的函数必须满足第一个参数为主窗体(main window) stdscr。 在前面的代码里,可以看到我们给 curses.wrapper(main) 的 main 函数中传入了一个 stdscr。
最后,stdscr 作为 window.addstr(str)、window.clear() 方法的调用需要窗体对象(window object),在 game_field.draw(stdscr) 中传入 draw 方法中。
"""

本文地址:https://blog.csdn.net/qq_33831360/article/details/107550841

相关标签: Python Lab 2048