使用Python写俄罗斯方块,以游戏的方式学习编程

Posted 小杰666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Python写俄罗斯方块,以游戏的方式学习编程相关的知识,希望对你有一定的参考价值。

本文将用Python写一个终端版俄罗斯方块,用游戏的方式学习编程。

这个小游戏分享给学习Python的朋友,感受一下Python的魅力,锻炼编程思维。

更多小游戏请看 这里

演示效果图见下文。

代码如下:

# encoding:utf-8
# file: tetris.py
# author: Jason


# ##########################
# 终端俄罗斯方块Tetris
# ##########################


import time
import random
import sys
import select
import copy
import termios


# #################
# 全局变量定义
# #################


# 地图相关变量

# 下面的方块指7种基本方块 小方块指构成基本方块的最小单位
# 终端使用等宽字体时 小方块正好是个小正方形 1个小方块由2个英文字符组成
# 游戏区域即游戏地图的长与高
GAME_AREA_L, GAME_AREA_H = 16, 26
# 游戏区域内左上角坐标为坐标原点 X为横坐标 Y为纵坐标
GAME_AREA_X, GAME_AREA_Y = 2, 2
# 游戏边框图形 基本方块的小方块渲染图形
GAME_EDGE, GAME_SQUARE = '##', '  '
# 游戏区域背景色
GAME_BKGCOLOR = '\\033[40;30m'
# 游戏区域位图 即游戏地图
GAME_BITMAP = [[[0, GAME_BKGCOLOR] for y in range(GAME_AREA_L)] for x in range(GAME_AREA_H)]

# 方块相关变量

# 7方块初始状态位图 2,2位置为方块实际长与高 3,3位置为此方块颜色
BLOCK_DICT = 
    # 天蓝色
    'I': [[1, 1, 1, 1],
          [0, 0, 0, 0],
          [0, 0, [4, 1], 0],
          [0, 0, 0, '\\033[46;36m']],
    # 蓝色
    'J': [[1, 0, 0, 0],
          [1, 1, 1, 0],
          # [3,2]表示此方块长3高2 索引正好相反 x=2为两行y=3为三列
          # 3,2为游戏区域的坐标表示法的长与度 2,3为对应的二维列表索引表示法
          [0, 0, [3, 2], 0],
          [0, 0, 0, '\\033[44;34m']],
    # 白色
    'L': [[0, 0, 1, 0],
          [1, 1, 1, 0],
          [0, 0, [3, 2], 0],
          [0, 0, 0, '\\033[47;37m']],
    # 黄色
    'O': [[1, 1, 0, 0],
          [1, 1, 0, 0],
          [0, 0, [2, 2], 0],
          [0, 0, 0, '\\033[43;33m']],
    # 绿色
    'S': [[0, 1, 1, 0],
          [1, 1, 0, 0],
          [0, 0, [3, 2], 0],
          [0, 0, 0, '\\033[42;32m']],
    # 紫色
    'T': [[0, 1, 0, 0],
          [1, 1, 1, 0],
          [0, 0, [3, 2], 0],
          [0, 0, 0, '\\033[45;35m']],
    # 红色
    'Z': [[1, 1, 0, 0],
          [0, 1, 1, 0],
          [0, 0, [3, 2], 0],
          [0, 0, 0, '\\033[41;31m']]

# 方块存储位图
BLOCK_BITMAP = [[0, 0, [0, 0], 0] if i == 2 else [0, 0, 0, 0] for i in range(4)]
# 定义方块出生点的方块左上角的地图坐标
BLOCK_SX, BLOCK_SY = GAME_AREA_X + GAME_AREA_L // 2 - 2, GAME_AREA_Y
# 方块左上角地图坐标
BLOCK_COORD = 'x': BLOCK_SX, 'y': BLOCK_SY
# 生成方块计数 得分 方块类型
BLOCK_COUNT, GAME_SCORE, BLOCK_TYPE = 0, 0, '_'

# 按键和信息相关变量

# 按键变量 按键检测间隔 方块打印间隔
KEY_DEFAULT, KEY_PAUSE = '_', ' '
KEY, KEY_INTERVAL, PNT_INTERVAL = KEY_DEFAULT, 0.01, 0.2
# 计分板左上角坐标
INFO_AREA_X, INFO_AREA_Y = GAME_AREA_X + GAME_AREA_L + 2, GAME_AREA_Y + 1
# 计分板区域长与高
INFO_AREA_L, INFO_AREA_H = 8, GAME_AREA_H


# #################
# 函数定义
# #################


# 初始化与通用函数
# 包括坐标定位 绘制地图边框 填充地图背景色等函数


def exit_clear(txt, e_code=0):
    """
    退出时做一些清理工作
    :param txt: 文本
    :param e_code: 退出码
    :return:
    """
    print(txt)
    exit(e_code)


def goto_blockxy(x=1, y=1):
    """
    将终端光标移动到方块的左上角位置 即坐标定位函数
    :param x: 方块的坐标x
    :param y: 方块的坐标y
    :return: None
    """
    if x < 1 or y < 1:
        x, y = 1, 1
    # 1个小方块占2个英文字符宽度
    # 实际坐标_x与方块坐标x的转换
    _x = (x - 1) * 2 + 1
    print('\\033[;H'.format(y, _x), end='', sep='')


def _gotoxy_print(x=1, y=1, *args, **kwargs):
    """打印调试信息"""
    goto_blockxy(x, y)
    print(*args, **kwargs)


def _edge_block(x=1, y=1, ln=1, h=1, b=GAME_EDGE):
    """
    绘制边框小方块
    :param x: 起点x
    :param y: 起点y
    :param ln: 边框小方块长
    :param h: 边框小方块高
    :param b: 边框小方块字符
    :return: None
    """
    for _y in range(y, y + h):
        goto_blockxy(x, _y)
        print(''.format(b * ln))


def draw_edge(x=1, y=1):
    """
    绘制游戏边框
    """
    ln1 = GAME_AREA_L + INFO_AREA_L + 3
    ln2 = GAME_AREA_L + 1
    ln3 = GAME_AREA_L + INFO_AREA_L + 2
    h = GAME_AREA_H
    _edge_block(x, y, ln1, 1)
    _edge_block(x, y + 1, 1, h)
    _edge_block(x + ln2, y + 1, 1, h)
    _edge_block(x + ln3, y + 1, 1, h)
    _edge_block(x, y + h + 1, ln1, 1)


# 地图处理函数
# 包括地图中方块清除 地图中方块显示等


def _clear_map_area(x, y, ln, h):
    """
    清除地图位图
    :param x: 坐标表示法的x
    :param y: 坐标表示法的y
    :param ln: 清除区域长度
    :param h: 清除区域高度
    :return: None
    """
    map_x, map_y = x - GAME_AREA_X, y - GAME_AREA_Y
    for _x in range(map_x, map_x + ln):
        for _y in range(map_y, map_y + h):
            # 坐标表示法与索引表示法互换
            GAME_BITMAP[_y][_x] = [0, GAME_BKGCOLOR]


def _fill_map_point(x, y, color):
    """
    填充地图位图点
    :param x: 坐标x
    :param y: 坐标y
    :param color: 颜色字符串转义序列
    """
    map_x, map_y = x - GAME_AREA_X, y - GAME_AREA_Y
    GAME_BITMAP[map_y][map_x] = [1, color]


def _clear_blockline(lenght=1, block=GAME_SQUARE, bkg=GAME_BKGCOLOR):
    """
    填充指定长度方块为背景色 即清除方块
    :param lenght: 方块长度 每个小方块占2英文字符宽度
    :param block: 填充的小方块
    :param bkg: 填充的背景色
    :return: None
    """
    # 因为标准输出缓冲区的原因 不要传参end='' 否则打印内容可能不会立刻显示
    print('\\033[0m'.format(bkg, block * lenght))


def clear_area(x=GAME_AREA_X, y=GAME_AREA_Y, ln=GAME_AREA_L, h=GAME_AREA_H):
    """
    清除指定矩形区域 清除的最小单位是小方块 即2个英文字符 游戏区域为黑色背景
    :param x: 起始x坐标
    :param y: 起始y坐标
    :param ln: 区域长度
    :param h: 区域高度
    :return: None
    """
    for _y in range(y, y + h):
        goto_blockxy(x, _y)
        _clear_blockline(ln)
    _clear_map_area(x, y, ln, h)


def print_map_area(x, y, ln=GAME_AREA_L, h=GAME_AREA_H):
    """
    根据地图位图刷新游戏区域
    :param x: 地图起点坐标x
    :param y: 地图起点坐标y
    :param ln: 区域长
    :param h: 区域高
    :return: None
    """
    map_x, map_y = x - GAME_AREA_X, y - GAME_AREA_Y
    for _x in range(map_y, map_y + h):
        for _y in range(map_x, map_x + ln):
            goto_blockxy(_y + GAME_AREA_Y, _x + GAME_AREA_X)
            print('\\033[0m'.format(GAME_BITMAP[_x][_y][1], GAME_SQUARE))


def _print_map_bits():
    """
    打印地图点 用于调试
    :return: None
    """
    a, b = GAME_AREA_X + GAME_AREA_L + INFO_AREA_L + 3, GAME_AREA_Y
    for x in range(len(GAME_BITMAP)):
        goto_blockxy(a, b)
        for y in range(len(GAME_BITMAP[0])):
            if GAME_BITMAP[x][y][0] == 1:
                print('\\033[31m\\033[0m,'.format(GAME_BITMAP[x][y][0]), end='')
            else:
                print(','.format(GAME_BITMAP[x][y][0]), end='')
        b += 1


def draw_background():
    """游戏区域背景色填充
    """
    clear_area()


def restore_cursor():
    """恢复隐藏的光标
    """
    print('\\033[?25h')


def tetris_init():
    """初始化工作
    """
    # 清屏与隐藏光标
    print('\\033[2J\\033[?25l')
    # 绘制游戏区域边框
    draw_edge()
    # 地图填充背景色
    draw_background()


# 方块处理函数
# 包括选取方块 打印方块 方块旋转 方块平移等函数


def _pick_block(b_bitmap, b_type):
    """
    选取特定类型的方块
    :param b_bitmap: 方块位图
    :param b_type: 方块类型
    :return: None
    """
    for x in range(4):
        for y in range(4):
            b_bitmap[x][y] = BLOCK_DICT[b_type][x][y]


def print_block(x, y, b_bitmap, fill_flag=True):
    """
    打印一种方块
    :param x: 起始点x
    :param y: 起始点y
    :param b_bitmap: 方块位图
    :param fill_flag: 填充地图位图标记 False为单纯打印方块不填充
    :return: None
    """
    if not fill_flag:
        # 清除上个方块
        for _x in range(len(b_bitmap)):
            for _y in range(len(b_bitmap[0])):
                goto_blockxy(_y + x, _x + y)
                print(''.format(GAME_SQUARE))
    b_l, b_h = b_bitmap[-2][-2][0], b_bitmap[-2][-2][1]
    b_color = b_bitmap[-1][-1]
    # 这里的_x,_y为索引表示法 参数x,y为坐标表示法
    for _y in range(b_l):
        for _x in range(b_h):
            # 必须判断是否等于1 不要用True/False判断
            # b_bitmap坐标互换 让位图中所有1呈现的图像与实际打印的图像一致
            # 即b_bitmap中纵向扫描 print也纵向打印
            if b_bitmap[_x][_y] == 1:
                # 方块位图坐标转换游戏区域坐标 索引到坐标表示法需互换坐标
                goto_blockxy(_y + x, _x + y)
                if fill_flag:
                    _fill_map_point(_y + x, _x + y, b_color)
                # 打印行数超过终端窗口高度 可能会提前折行导致方块错乱
                print('\\033[0m'.format(b_color, GAME_SQUARE))


def _copy_rotatedblock(rotated_block, origin_block):
    """
    方块拷贝函数
    :param rotated_block: 旋转后的方块位图
    :param origin_block: 原始方块位图 必须是4x4方块
    :return: None
    """
    b_length, b_height = origin_block[-2][-2][0], origin_block[-2][-2][1]
    for _x in range(4):
        for _y in range(4):
            if _x < b_length and _y < b_height:
                # 原始方块长x高 => 位图[高][长] => 旋转后方块的 位图[长][高]
                # 所以这里_x是b_length _y是b_height
                origin_block[_x][_y] = rotated_block[_x][_y]
            elif _x == _y and _x == 2:
                # 长x高变换
                origin_block[_x][_y] = origin_block[_x][_y][::-1]
            elif _x == _y and _x == 3:
                # 颜色值不改变
                continue
            else:
                origin_block[_x][_y] = 0


def copy_block(fr_bitmap: list, to_bitmap: list):
    """
    拷贝方块存储位图
    :param fr_bitmap: 原始方块
    :param to_bitmap: 目的方块
    :return:
    """
    for i in range(4):
        for j in range(4):
            to_bitmap[i][j] = fr_bitmap[i][j]


def _rotate_block(flag=True):
    """
    逆时针旋转方块 即求NxM到MxN的转置矩阵
    :param flag: True生成旋转后的方块 False只旋转不生成旋转后方块
    :return: None
    """
    b_length, b_height = BLOCK_BITMAP[-2][-2][0], BLOCK_BITMAP[-2][-2][1]
    # b_lengthXb_height 方块索引表示法即 b_heightXb_length
    b_target = [[0 for _y in range(b_height)] for _x in range(b_length)]
    for _y in range(b_length):
        for _x in range(b_height):
            # 逆时针旋转90度
            offset = len(BLOCK_BITMAP[_x]) - b_length
            b_target[_y][_x] = BLOCK_BITMAP[_x][-_y - 1 - offset]
    # flag为真 生成旋转后方块
    if flag:
        _copy_rotatedblock(b_target, BLOCK_BITMAP)
    # 生成临时旋转后方块用于检测
    else:
        # 拷贝未旋转的方块为模板
        tmp_bitmap = copy.deepcopy(BLOCK_BITMAP)
        # 生成为临时方块
        _copy_rotatedblock(b_target, tmp_bitmap)
        return tmp_bitmap


def _edge_detect(x, y, b_bitmap):
    """
    方块是否超出游戏地图检测
    :param x: 方块左

以上是关于使用Python写俄罗斯方块,以游戏的方式学习编程的主要内容,如果未能解决你的问题,请参考以下文章

java写俄罗斯方块啥水平

只会C语言编程还要学哪些才能做俄罗斯方块这样的小游戏?

Android 安卓原生UI实现游戏《俄罗斯方块》,算法太多,把我写崩溃了,附源码

Android 安卓原生UI实现游戏《俄罗斯方块》,算法太多,把我写崩溃了,附源码

Android 安卓原生UI实现游戏《俄罗斯方块》,算法太多,把我写崩溃了,附源码

Android 安卓原生UI实现游戏《俄罗斯方块》,算法太多,把我写崩溃了,附源码