找回密码
 立即注册
查看: 237|回复: 0

用python实现四个完整游戏开发小项目

[复制链接]
发表于 2022-2-15 14:06 | 显示全部楼层 |阅读模式
1、2048

1. 游戏简介
2048 是一款比较流行的数字游戏。游戏规则:每次可按上、下、左、右方向键滑动数字,每滑动一次,所有数字都会往滑动方向靠拢,同时在空白位置随机出现一个数字,相同数字在靠拢时会相加。不断叠加最终拼出 2048 这个数字算成功。
2048 最早于 2014年3月20日发行。原版 2048 首先在 GitHub 上发布,原作者是 Gabriele Cirulli,后被移植到各个平台。
本例难度为初级,适合具有 Python 基础和 Pygame 编程知识的用户学习。
2. 设计原理
这个游戏的本质是二维列表,就以 4*4 的二位列表来分析关键的逻辑以及实现。二维列表如下图:



所有的操作都是对这个二维列表的数据的操作。分为上下左右四个方向。先说向左的方向(如图)。



向左操作的结果如下图;当向左的方向是,所有的数据沿着水平方向向左跑。


水平说明操作的是二维列表的一行,而垂直操作的则是二位列表的一列。这样就可以将二维列表的操作变成遍历后对一维列表的操作。向左说明数据的优先考虑的位置是从左开始的。这样就确定了一维列表的遍历开始的位置。
上面第 2 个图共四行,每一个行都能得到一个列表。
list1:[0,0,2,0]
list2:[0,4,2,0]
list3:[0,0,4,4]
list4:[2,0,2,0]这样一来向左的方向就变成。从上到下获得每一行的列表,方向向左。参数(row,left)。


其他的三个方向在开始的时候记住是怎样获得以为列表的,等操作完才放回去这样就能实现了。
3. 示例效果


4. 示例源码
import random
import sys
import pygame
from pygame.locals import *

PIXEL = 150
SCORE_PIXEL = 100
SIZE = 4


# 地图的类


class Map:
    def __init__(self, size):
        self.size = size
        self.score = 0
        self.map = [[0 for i in range(size)] for i in range(size)]
        self.add()
        self.add()

    # 新增2或4,有1/4概率产生4
    def add(self):
        while True:
            p = random.randint(0, self.size * self.size - 1)
            if self.map[int(p / self.size)][int(p % self.size)] == 0:
                x = random.randint(0, 3) > 0 and 2 or 4
                self.map[int(p / self.size)][int(p % self.size)] = x
                self.score += x
                break

    # 地图向左靠拢,其他方向的靠拢可以通过适当旋转实现,返回地图是否更新
    def adjust(self):
        changed = False
        for a in self.map:
            b = []
            last = 0
            for v in a:
                if v != 0:
                    if v == last:
                        b.append(b.pop() << 1)
                        last = 0
                    else:
                        b.append(v)
                        last = v
            b += [0] * (self.size - len(b))
            for i in range(self.size):
                if a != b:
                    changed = True
            a[:] = b
        return changed

    # 逆时针旋转地图90度
    def rotate90(self):
        self.map = [[self.map[c][r]
                     for c in range(self.size)] for r in reversed(range(self.size))]

    # 判断游戏结束
    def over(self):
        for r in range(self.size):
            for c in range(self.size):
                if self.map[r][c] == 0:
                    return False
        for r in range(self.size):
            for c in range(self.size - 1):
                if self.map[r][c] == self.map[r][c + 1]:
                    return False
        for r in range(self.size - 1):
            for c in range(self.size):
                if self.map[r][c] == self.map[r + 1][c]:
                    return False
        return True

    def moveUp(self):
        self.rotate90()
        if self.adjust():
            self.add()
        self.rotate90()
        self.rotate90()
        self.rotate90()

    def moveRight(self):
        self.rotate90()
        self.rotate90()
        if self.adjust():
            self.add()
        self.rotate90()
        self.rotate90()

    def moveDown(self):
        self.rotate90()
        self.rotate90()
        self.rotate90()
        if self.adjust():
            self.add()
        self.rotate90()

    def moveLeft(self):
        if self.adjust():
            self.add()


# 更新屏幕


def show(map):
    for i in range(SIZE):
        for j in range(SIZE):
            # 背景颜色块
            screen.blit(map.map[j] == 0 and block[(i + j) % 2]
                        or block[2 + (i + j) % 2], (PIXEL * j, PIXEL * i))
            # 数值显示
            if map.map[j] != 0:
                map_text = map_font.render(
                    str(map.map[j]), True, (106, 90, 205))
                text_rect = map_text.get_rect()
                text_rect.center = (PIXEL * j + PIXEL / 2,
                                    PIXEL * i + PIXEL / 2)
                screen.blit(map_text, text_rect)
    # 分数显示
    screen.blit(score_block, (0, PIXEL * SIZE))
    score_text = score_font.render((map.over(
    ) and "Game over with score " or "Score: ") + str(map.score), True, (106, 90, 205))
    score_rect = score_text.get_rect()
    score_rect.center = (PIXEL * SIZE / 2, PIXEL * SIZE + SCORE_PIXEL / 2)
    screen.blit(score_text, score_rect)
    pygame.display.update()


map = Map(SIZE)
pygame.init()
screen = pygame.display.set_mode((PIXEL * SIZE, PIXEL * SIZE + SCORE_PIXEL))
pygame.display.set_caption("2048")
block = [pygame.Surface((PIXEL, PIXEL)) for i in range(4)]
# 设置颜色
block[0].fill((152, 251, 152))
block[1].fill((240, 255, 255))
block[2].fill((0, 255, 127))
block[3].fill((225, 255, 255))
score_block = pygame.Surface((PIXEL * SIZE, SCORE_PIXEL))
score_block.fill((245, 245, 245))
# 设置字体
map_font = pygame.font.Font(None, int(PIXEL * 2 / 3))
score_font = pygame.font.Font(None, int(SCORE_PIXEL * 2 / 3))
clock = pygame.time.Clock()
show(map)

while not map.over():
    # 12为实验参数
    clock.tick(12)
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
    # 接收玩家操作
    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_w] or pressed_keys[K_UP]:
        map.moveUp()
    elif pressed_keys[K_s] or pressed_keys[K_DOWN]:
        map.moveDown()
    elif pressed_keys[K_a] or pressed_keys[K_LEFT]:
        map.moveLeft()
    elif pressed_keys[K_d] or pressed_keys[K_RIGHT]:
        map.moveRight()
    show(map)

# 游戏结束
pygame.time.delay(3000)2、贪吃蛇

1. 案例介绍
贪吃蛇是一款经典的益智游戏,简单又耐玩。该游戏通过控制蛇头方向吃蛋,从而使得蛇变得越来越长。
通过上下左右方向键控制蛇的方向,寻找吃的东西,每吃一口就能得到一定的积分,而且蛇的身子会越吃越长,身子越长玩的难度就越大,不能碰墙,不能咬到自己的身体,更不能咬自己的尾巴,等到了一定的分数,就能过关,然后继续玩下一关。
本例难度为中级,适合具有 Python 基础和 Pygame 编程知识的用户学习。
2. 设计要点
游戏是基于 PyGame 框架制作的,程序核心逻辑如下:
游戏界面分辨率是 640*480,蛇和食物都是由 1 个或多个 20*20 像素的正方形块儿(为了方便,下文用点表示 20*20 像素的正方形块儿) 组成,这样共有 32*24 个点,使用 pygame.draw.rect 来绘制每一个点;
初始化时蛇的长度是 3,食物是 1 个点,蛇初始的移动的方向是右,用一个数组代表蛇,数组的每个元素是蛇每个点的坐标,因此数组的第一个坐标是蛇尾,最后一个坐标是蛇头;游戏开始后,根据蛇的当前移动方向,将蛇运动方向的前方的那个点 append 到蛇数组的末位,再把蛇尾去掉,蛇的坐标数组就相当于往前挪了一位;如果蛇吃到了食物,即蛇头的坐标等于食物的坐标,那么在第 2 点中蛇尾就不用去掉,就产生了蛇长度增加的效果;食物被吃掉后,随机在空的位置(不能与蛇的身体重合) 再生成一个;通过 PyGame 的 event 监控按键,改变蛇的方向,例如当蛇向右时,下一次改变方向只能向上或者向下;当蛇撞上自身或墙壁,游戏结束,蛇头装上自身,那么蛇坐标数组里就有和舌头坐标重复的数据,撞上墙壁则是蛇头坐标超过了边界,都很好判断;其他细节:做了个开始的欢迎界面;食物的颜色随机生成;吃到实物的时候有声音提示等。
3. 示例效果


4. 示例源码
import pygame
from os import path
from sys import exit
from time import sleep
from random import choice
from itertools import product
from pygame.locals import QUIT, KEYDOWN


def direction_check(moving_direction, change_direction):
    directions = [['up', 'down'], ['left', 'right']]
    if moving_direction in directions[0] and change_direction in directions[1]:
        return change_direction
    elif moving_direction in directions[1] and change_direction in directions[0]:
        return change_direction
    return moving_direction


class Snake:
    colors = list(product([0, 64, 128, 192, 255], repeat=3))[1:-1]

    def __init__(self):
        self.map = {(x, y): 0 for x in range(32) for y in range(24)}
        self.body = [[100, 100], [120, 100], [140, 100]]
        self.head = [140, 100]
        self.food = []
        self.food_color = []
        self.moving_direction = 'right'
        self.speed = 4
        self.generate_food()
        self.game_started = False

    def check_game_status(self):
        if self.body.count(self.head) > 1:
            return True
        if self.head[0] < 0 or self.head[0] > 620 or self.head[1] < 0 or self.head[1] > 460:
            return True
        return False

    def move_head(self):
        moves = {
            'right': (20, 0),
            'up': (0, -20),
            'down': (0, 20),
            'left': (-20, 0)
        }
        step = moves[self.moving_direction]
        self.head[0] += step[0]
        self.head[1] += step[1]

    def generate_food(self):
        self.speed = len(
            self.body) // 16 if len(self.body) // 16 > 4 else self.speed
        for seg in self.body:
            x, y = seg
            self.map[x // 20, y // 20] = 1
        empty_pos = [pos for pos in self.map.keys() if not self.map[pos]]
        result = choice(empty_pos)
        self.food_color = list(choice(self.colors))
        self.food = [result[0] * 20, result[1] * 20]


def main():
    key_direction_dict = {
        119: 'up',  # W
        115: 'down',  # S
        97: 'left',  # A
        100: 'right',  # D
        273: 'up',  # UP
        274: 'down',  # DOWN
        276: 'left',  # LEFT
        275: 'right',  # RIGHT
    }

    fps_clock = pygame.time.Clock()
    pygame.init()
    pygame.mixer.init()
    snake = Snake()
    sound = False
    if path.exists('eat.wav'):
        sound_wav = pygame.mixer.Sound("eat.wav")
        sound = True
    title_font = pygame.font.SysFont('simsunnsimsun', 32)
    welcome_words = title_font.render(
        '贪吃蛇', True, (0, 0, 0), (255, 255, 255))
    tips_font = pygame.font.SysFont('simsunnsimsun', 20)
    start_game_words = tips_font.render(
        '点击开始', True, (0, 0, 0), (255, 255, 255))
    close_game_words = tips_font.render(
        '按ESC退出', True, (0, 0, 0), (255, 255, 255))
    gameover_words = title_font.render(
        '游戏结束', True, (205, 92, 92), (255, 255, 255))
    win_words = title_font.render(
        '蛇很长了,你赢了!', True, (0, 0, 205), (255, 255, 255))
    screen = pygame.display.set_mode((640, 480), 0, 32)
    pygame.display.set_caption('贪吃蛇')
    new_direction = snake.moving_direction
    while 1:
        for event in pygame.event.get():
            if event.type == QUIT:
                exit()
            elif event.type == KEYDOWN:
                if event.key == 27:
                    exit()
                if snake.game_started and event.key in key_direction_dict:
                    direction = key_direction_dict[event.key]
                    new_direction = direction_check(
                        snake.moving_direction, direction)
            elif (not snake.game_started) and event.type == pygame.MOUSEBUTTONDOWN:
                x, y = pygame.mouse.get_pos()
                if 213 <= x <= 422 and 304 <= y <= 342:
                    snake.game_started = True
        screen.fill((255, 255, 255))
        if snake.game_started:
            snake.moving_direction = new_direction  # 在这里赋值,而不是在event事件的循环中赋值,避免按键太快
            snake.move_head()
            snake.body.append(snake.head[:])
            if snake.head == snake.food:
                if sound:
                    sound_wav.play()
                snake.generate_food()
            else:
                snake.body.pop(0)
            for seg in snake.body:
                pygame.draw.rect(screen, [0, 0, 0], [
                    seg[0], seg[1], 20, 20], 0)
            pygame.draw.rect(screen, snake.food_color, [
                snake.food[0], snake.food[1], 20, 20], 0)
            if snake.check_game_status():
                screen.blit(gameover_words, (241, 310))
                pygame.display.update()
                snake = Snake()
                new_direction = snake.moving_direction
                sleep(3)
            elif len(snake.body) == 512:
                screen.blit(win_words, (33, 210))
                pygame.display.update()
                snake = Snake()
                new_direction = snake.moving_direction
                sleep(3)
        else:
            screen.blit(welcome_words, (240, 150))
            screen.blit(start_game_words, (246, 310))
            screen.blit(close_game_words, (246, 350))
        pygame.display.update()
        fps_clock.tick(snake.speed)


if __name__ == '__main__':
    main()3、俄罗斯方块

1. 案例介绍
俄罗斯方块是由 4 个小方块组成不同形状的板块,随机从屏幕上方落下,按方向键调整板块的位置和方向,在底部拼出完整的一行或几行。这些完整的横条会消失,给新落下来的板块腾出空间,并获得分数奖励。没有被消除掉的方块不断堆积,一旦堆到顶端,便告输,游戏结束。
本例难度为高级,适合具有 Python 进阶和 Pygame 编程技巧的用户学习。
2. 设计要点
边框――由 15*25 个空格组成,方块就落在这里面。
盒子――组成方块的其中小方块,是组成方块的基本单元。
方块――从边框顶掉下的东西,游戏者可以翻转和改变位置。每个方块由 4 个盒子组成。
形状――不同类型的方块。这里形状的名字被叫做 T, S, Z ,J, L, I , O。如下图所示:





模版――用一个列表存放形状被翻转后的所有可能样式。全部存放在变量里,变量名字如 S or J。
着陆――当一个方块到达边框的底部或接触到在其他的盒子话,就说这个方块着陆了。那样的话,另一个方块就会开始下落。
3. 示例效果


4. 示例源码
import pygame
import random
import os

pygame.init()

GRID_WIDTH = 20
GRID_NUM_WIDTH = 15
GRID_NUM_HEIGHT = 25
WIDTH, HEIGHT = GRID_WIDTH * GRID_NUM_WIDTH, GRID_WIDTH * GRID_NUM_HEIGHT
SIDE_WIDTH = 200
SCREEN_WIDTH = WIDTH + SIDE_WIDTH
WHITE = (0xff, 0xff, 0xff)
BLACK = (0, 0, 0)
LINE_COLOR = (0x33, 0x33, 0x33)

CUBE_COLORS = [
    (0xcc, 0x99, 0x99), (0xff, 0xff, 0x99), (0x66, 0x66, 0x99),
    (0x99, 0x00, 0x66), (0xff, 0xcc, 0x00), (0xcc, 0x00, 0x33),
    (0xff, 0x00, 0x33), (0x00, 0x66, 0x99), (0xff, 0xff, 0x33),
    (0x99, 0x00, 0x33), (0xcc, 0xff, 0x66), (0xff, 0x99, 0x00)
]

screen = pygame.display.set_mode((SCREEN_WIDTH, HEIGHT))
pygame.display.set_caption("俄罗斯方块")
clock = pygame.time.Clock()
FPS = 30

score = 0
level = 1

screen_color_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]

# 设置游戏的根目录为当前文件夹
base_folder = os.path.dirname(__file__)


def show_text(surf, text, size, x, y, color=WHITE):
    font_name = os.path.join(base_folder, 'font/font.ttc')
    font = pygame.font.Font(font_name, size)
    text_surface = font.render(text, True, color)
    text_rect = text_surface.get_rect()
    text_rect.midtop = (x, y)
    surf.blit(text_surface, text_rect)


class CubeShape(object):
    SHAPES = ['I', 'J', 'L', 'O', 'S', 'T', 'Z']
    I = [[(0, -1), (0, 0), (0, 1), (0, 2)],
         [(-1, 0), (0, 0), (1, 0), (2, 0)]]
    J = [[(-2, 0), (-1, 0), (0, 0), (0, -1)],
         [(-1, 0), (0, 0), (0, 1), (0, 2)],
         [(0, 1), (0, 0), (1, 0), (2, 0)],
         [(0, -2), (0, -1), (0, 0), (1, 0)]]
    L = [[(-2, 0), (-1, 0), (0, 0), (0, 1)],
         [(1, 0), (0, 0), (0, 1), (0, 2)],
         [(0, -1), (0, 0), (1, 0), (2, 0)],
         [(0, -2), (0, -1), (0, 0), (-1, 0)]]
    O = [[(0, 0), (0, 1), (1, 0), (1, 1)]]
    S = [[(-1, 0), (0, 0), (0, 1), (1, 1)],
         [(1, -1), (1, 0), (0, 0), (0, 1)]]
    T = [[(0, -1), (0, 0), (0, 1), (-1, 0)],
         [(-1, 0), (0, 0), (1, 0), (0, 1)],
         [(0, -1), (0, 0), (0, 1), (1, 0)],
         [(-1, 0), (0, 0), (1, 0), (0, -1)]]
    Z = [[(0, -1), (0, 0), (1, 0), (1, 1)],
         [(-1, 0), (0, 0), (0, -1), (1, -1)]]
    SHAPES_WITH_DIR = {
        'I': I, 'J': J, 'L': L, 'O': O, 'S': S, 'T': T, 'Z': Z
    }

    def __init__(self):
        self.shape = self.SHAPES[random.randint(0, len(self.SHAPES) - 1)]
        # 骨牌所在的行列
        self.center = (2, GRID_NUM_WIDTH // 2)
        self.dir = random.randint(0, len(self.SHAPES_WITH_DIR[self.shape]) - 1)
        self.color = CUBE_COLORS[random.randint(0, len(CUBE_COLORS) - 1)]

    def get_all_gridpos(self, center=None):
        curr_shape = self.SHAPES_WITH_DIR[self.shape][self.dir]
        if center is None:
            center = [self.center[0], self.center[1]]

        return [(cube[0] + center[0], cube[1] + center[1])
                for cube in curr_shape]

    def conflict(self, center):
        for cube in self.get_all_gridpos(center):
            # 超出屏幕之外,说明不合法
            if cube[0] < 0 or cube[1] < 0 or cube[0] >= GRID_NUM_HEIGHT or \
                    cube[1] >= GRID_NUM_WIDTH:
                return True

            # 不为None,说明之前已经有小方块存在了,也不合法
            if screen_color_matrix[cube[0]][cube[1]] is not None:
                return True

        return False

    def rotate(self):
        new_dir = self.dir + 1
        new_dir %= len(self.SHAPES_WITH_DIR[self.shape])
        old_dir = self.dir
        self.dir = new_dir
        if self.conflict(self.center):
            self.dir = old_dir
            return False

    def down(self):
        # import pdb; pdb.set_trace()
        center = (self.center[0] + 1, self.center[1])
        if self.conflict(center):
            return False

        self.center = center
        return True

    def left(self):
        center = (self.center[0], self.center[1] - 1)
        if self.conflict(center):
            return False
        self.center = center
        return True

    def right(self):
        center = (self.center[0], self.center[1] + 1)
        if self.conflict(center):
            return False
        self.center = center
        return True

    def draw(self):
        for cube in self.get_all_gridpos():
            pygame.draw.rect(screen, self.color,
                             (cube[1] * GRID_WIDTH, cube[0] * GRID_WIDTH,
                              GRID_WIDTH, GRID_WIDTH))
            pygame.draw.rect(screen, WHITE,
                             (cube[1] * GRID_WIDTH, cube[0] * GRID_WIDTH,
                              GRID_WIDTH, GRID_WIDTH),
                             1)


def draw_grids():
    for i in range(GRID_NUM_WIDTH):
        pygame.draw.line(screen, LINE_COLOR,
                         (i * GRID_WIDTH, 0), (i * GRID_WIDTH, HEIGHT))

    for i in range(GRID_NUM_HEIGHT):
        pygame.draw.line(screen, LINE_COLOR,
                         (0, i * GRID_WIDTH), (WIDTH, i * GRID_WIDTH))

    pygame.draw.line(screen, WHITE,
                     (GRID_WIDTH * GRID_NUM_WIDTH, 0),
                     (GRID_WIDTH * GRID_NUM_WIDTH, GRID_WIDTH * GRID_NUM_HEIGHT))


def draw_matrix():
    for i, row in zip(range(GRID_NUM_HEIGHT), screen_color_matrix):
        for j, color in zip(range(GRID_NUM_WIDTH), row):
            if color is not None:
                pygame.draw.rect(screen, color,
                                 (j * GRID_WIDTH, i * GRID_WIDTH,
                                  GRID_WIDTH, GRID_WIDTH))
                pygame.draw.rect(screen, WHITE,
                                 (j * GRID_WIDTH, i * GRID_WIDTH,
                                  GRID_WIDTH, GRID_WIDTH), 2)


def draw_score():
    show_text(screen, u'得分:{}'.format(score), 20, WIDTH + SIDE_WIDTH // 2, 100)


def remove_full_line():
    global screen_color_matrix
    global score
    global level
    new_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]
    index = GRID_NUM_HEIGHT - 1
    n_full_line = 0
    for i in range(GRID_NUM_HEIGHT - 1, -1, -1):
        is_full = True
        for j in range(GRID_NUM_WIDTH):
            if screen_color_matrix[j] is None:
                is_full = False
                continue
        if not is_full:
            new_matrix[index] = screen_color_matrix
            index -= 1
        else:
            n_full_line += 1
    score += n_full_line
    level = score // 20 + 1
    screen_color_matrix = new_matrix


def show_welcome(screen):
    show_text(screen, u'俄罗斯方块', 30, WIDTH / 2, HEIGHT / 2)
    show_text(screen, u'按任意键开始游戏', 20, WIDTH / 2, HEIGHT / 2 + 50)


running = True
gameover = True
counter = 0
live_cube = None
while running:
    clock.tick(FPS)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if gameover:
                gameover = False
                live_cube = CubeShape()
                break
            if event.key == pygame.K_LEFT:
                live_cube.left()
            elif event.key == pygame.K_RIGHT:
                live_cube.right()
            elif event.key == pygame.K_DOWN:
                live_cube.down()
            elif event.key == pygame.K_UP:
                live_cube.rotate()
            elif event.key == pygame.K_SPACE:
                while live_cube.down() == True:
                    pass
            remove_full_line()

    # level 是为了方便游戏的难度,level 越高 FPS // level 的值越小
    # 这样屏幕刷新的就越快,难度就越大
    if gameover is False and counter % (FPS // level) == 0:
        # down 表示下移骨牌,返回False表示下移不成功,可能超过了屏幕或者和之前固定的
        # 小方块冲突了
        if live_cube.down() == False:
            for cube in live_cube.get_all_gridpos():
                screen_color_matrix[cube[0]][cube[1]] = live_cube.color
            live_cube = CubeShape()
            if live_cube.conflict(live_cube.center):
                gameover = True
                score = 0
                live_cube = None
                screen_color_matrix = [[None] * GRID_NUM_WIDTH for i in range(GRID_NUM_HEIGHT)]
        # 消除满行
        remove_full_line()
    counter += 1
    # 更新屏幕
    screen.fill(BLACK)
    draw_grids()
    draw_matrix()
    draw_score()
    if live_cube is not None:
        live_cube.draw()
    if gameover:
        show_welcome(screen)
    pygame.display.update()4、连连看

1. 案例介绍
连连看是一款曾经非常流行的小游戏。游戏规则:

  • 点击选中两个相同的方块。
  • 两个选中的方块之间连接线的折点不超过两个(接线由X轴和Y轴的平行线组成)。
  • 每找出一对,它们就会自动消失。
  • 连线不能从尚未消失的图案上经过。
  • 把所有的图案全部消除即可获得胜利。
2. 设计思路

  • 生成成对的图片元素。
  • 将图片元素打乱排布。
  • 定义什么才算 相连(两张图片的连线不多于3跟直线,或者说转角不超过2个)。
  • 实现 相连 判断算法。
  • 消除图片元素并判断是否消除完毕。
3. 示例效果


4. 示例源码
from tkinter import *
from tkinter.messagebox import *
from threading import Timer
import time
import random


class Point:
    # 点类
    def __init__(self, x, y):
        self.x = x
        self.y = y


# --------------------------------------


'''
判断选中的两个方块是否可以消除
'''


def IsLink(p1, p2):
    if lineCheck(p1, p2):
        return True
    if OneCornerLink(p1, p2):  # 一个转弯(折点)的联通方式
        return True
    if TwoCornerLink(p1, p2):  # 两个转弯(折点)的联通方式
        return True
    return False


# ---------------------------
def IsSame(p1, p2):
    if map[p1.x][p1.y] == map[p2.x][p2.y]:
        print("clicked at IsSame")
        return True
    return False


def callback(event):  # 鼠标左键事件代码
    global Select_first, p1, p2
    global firstSelectRectId, SecondSelectRectId

    # print ("clicked at", event.x, event.y,turn)
    x = (event.x) // 40  # 换算棋盘坐标
    y = (event.y) // 40
    print("clicked at", x, y)

    if map[x][y] == " ":
        showinfo(title="提示", message="此处无方块")
    else:

        if Select_first == False:
            p1 = Point(x, y)
            # 画选定(x1,y1)处的框线
            firstSelectRectId = cv.create_rectangle(x * 40, y * 40, x * 40 + 40, y * 40 + 40, width=2, outline="blue")
            Select_first = True
        else:
            p2 = Point(x, y)
            # 判断第二次点击的方块是否已被第一次点击选取,如果是则返回。
            if (p1.x == p2.x) and (p1.y == p2.y):
                return
            # 画选定(x2,y2)处的框线
            print('第二次点击的方块', x, y)
            # SecondSelectRectId=cv.create_rectangle(100,20,x*40+40,y*40+40,width=2,outline="yellow")
            SecondSelectRectId = cv.create_rectangle(x * 40, y * 40, x * 40 + 40, y * 40 + 40, width=2,
                                                     outline="yellow")
            print('第二次点击的方块', SecondSelectRectId)
            cv.pack()

            # 判断是否连通
            if IsSame(p1, p2) and IsLink(p1, p2):
                print('连通', x, y)
                Select_first = False
                # 画选中方块之间连接线
                drawLinkLine(p1, p2)
                # clearTwoBlock()
                # time.sleep(0.6)
                # clearFlag=True
                t = Timer(timer_interval, delayrun)  # 定时函数
                t.start()


            else:  # 重新选定第一个方块
                # 清除第一个选定框线
                cv.delete(firstSelectRectId)
                cv.delete(SecondSelectRectId)
                # print('清除第一个选定框线')
                # firstSelectRectId=SecondSelectRectId
                # p1=Point(x,y)           #设置重新选定第一个方块的坐标
                Select_first = False


timer_interval = 0.3  # 0.3秒


# --------------------------------------
def delayrun():
    clearTwoBlock()  # 清除连线及方块


def clearTwoBlock():  # 清除连线及方块
    # 延时0.1秒
    # time.sleep(0.1)
    # 清除第一个选定框线
    cv.delete(firstSelectRectId)
    # 清除第2个选定框线
    cv.delete(SecondSelectRectId)
    # 清空记录方块的值
    map[p1.x][p1.y] = " "
    cv.delete(image_map[p1.x][p1.y])
    map[p2.x][p2.y] = " "
    cv.delete(image_map[p2.x][p2.y])
    Select_first = False
    undrawConnectLine()  # 清除选中方块之间连接线


def drawQiPan():  # 画棋盘
    for i in range(0, 15):
        cv.create_line(20, 20 + 40 * i, 580, 20 + 40 * i, width=2)
    for i in range(0, 15):
        cv.create_line(20 + 40 * i, 20, 20 + 40 * i, 580, width=2)
    cv.pack()


def print_map():  # 输出map地图
    global image_map
    for x in range(0, Width):  # 0--14
        for y in range(0, Height):  # 0--14
            if (map[x][y] != ' '):
                img1 = imgs[int(map[x][y])]
                id = cv.create_image((x * 40 + 20, y * 40 + 20), image=img1)
                image_map[x][y] = id
    cv.pack()
    for y in range(0, Height):  # 0--14
        for x in range(0, Width):  # 0--14
            print(map[x][y], end=' ')
        print(",", y)


'''
* 同行同列情况消除方法 原理:如果两个相同的被消除元素之间的 空格数
spaceCount等于他们的(行/列差-1)则 两者可以联通消除
* x代表列,y代表行
* param p1 第一个保存上次选中点坐标的点对象
* param p2 第二个保存上次选中点坐标的点对象
'''


# 直接连通
def lineCheck(p1, p2):
    absDistance = 0
    spaceCount = 0
    if (p1.x == p2.x or p1.y == p2.y):  # 同行同列的情况吗?
        print("同行同列的情况------")
        # 同列的情况
        if (p1.x == p2.x and p1.y != p2.y):
            print("同列的情况")
            # 绝对距离(中间隔着的空格数)
            absDistance = abs(p1.y - p2.y) - 1
            # 正负值
            if p1.y - p2.y > 0:
                zf = -1
            else:
                zf = 1
            for i in range(1, absDistance + 1):
                if (map[p1.x][p1.y + i * zf] == " "):
                    # 空格数加1
                    spaceCount += 1
                else:
                    break;  # 遇到阻碍就不用再探测了

        # 同行的情况
        elif (p1.y == p2.y and p1.x != p2.x):
            print(" 同行的情况")
            absDistance = abs(p1.x - p2.x) - 1
            # 正负值
            if p1.x - p2.x > 0:
                zf = -1
            else:
                zf = 1
            for i in range(1, absDistance + 1):
                if (map[p1.x + i * zf][p1.y] == " "):
                    # 空格数加1
                    spaceCount += 1
                else:
                    break;  # 遇到阻碍就不用再探测了
        if (spaceCount == absDistance):
            # 可联通
            print(absDistance, spaceCount)
            print("行/列可直接联通")
            return True
        else:
            print("行/列不能消除!")
            return False
    else:
        # 不是同行同列的情况所以直接返回false
        return False;

    # --------------------------------------


# 第二种,直角连通
'''
直角连接,即X,Y坐标都不同的,可以用这个方法尝试连接
param first:选中的第一个点
param second:选中的第二个点
'''


def OneCornerLink(p1, p2):
    # 第一个直角检查点,如果这里为空则赋予相同值供检查
    checkP = Point(p1.x, p2.y)
    # 第二个直角检查点,如果这里为空则赋予相同值供检查
    checkP2 = Point(p2.x, p1.y);
    # 第一个直角点检测
    if (map[checkP.x][checkP.y] == " "):
        if (lineCheck(p1, checkP) and lineCheck(checkP, p2)):
            linePointStack.append(checkP)
            print("直角消除ok", checkP.x, checkP.y)
            return True
    # 第二个直角点检测
    if (map[checkP2.x][checkP2.y] == " "):
        if (lineCheck(p1, checkP2) and lineCheck(checkP2, p2)):
            linePointStack.append(checkP2)
            print("直角消除ok", checkP2.x, checkP2.y)
            return True
    print("不能直角消除")
    return False;


# -----------------------------------------
'''
#第三种,双直角连通
双直角联通判定可分两步走:
1. 在p1点周围4个方向寻找空格checkP
2. 调用OneCornerLink(checkP, p2)
3. 即遍历 p1 4 个方向的空格,使之成为 checkP,然后调用 OneCornerLink(checkP,
p2)判定是否为真,如果为真则可以双直角连同,否则当所有的空格都遍历完而没有找
到一个checkP使OneCornerLink(checkP, p2)为真,则两点不能连同
具体代码:

双直角连接方法
@param p1 第一个点
@param p2 第二个点
'''


def TwoCornerLink(p1, p2):
    checkP = Point(p1.x, p1.y)
    # 四向探测开始
    for i in range(0, 4):
        checkP.x = p1.x
        checkP.y = p1.y
        # 向下
        if (i == 3):
            checkP.y += 1
            while ((checkP.y < Height) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("下探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.y += 1
            print("ssss", checkP.y, Height - 1)
            # 补充两个折点都在游戏区域底侧外部
            if checkP.y == Height:  # 出了底部,则仅需判断p2能否也达到底部边界
                z = Point(p2.x, Height - 1)  # 底部边界点
                if lineCheck(z, p2):  # 两个折点在区域外部的底侧
                    linePointStack.append(Point(p1.x, Height))
                    linePointStack.append(Point(p2.x, Height))
                    print("下探测到游戏区域外部OK")
                    return True
        # 向右
        elif (i == 2):
            checkP.x += 1
            while ((checkP.x < Width) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("右探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.x += 1
            # 补充两个折点都在游戏区域右侧外部
            if checkP.x == Width:  # 出了右侧,则仅需判断p2能否也达到右部边界
                z = Point(Width - 1, p2.y)  # 右部边界点
                if lineCheck(z, p2):  # 两个折点在区域外部的底侧
                    linePointStack.append(Point(Width, p1.y))
                    linePointStack.append(Point(Width, p2.y))
                    print("右探测到游戏区域外部OK")
                    return True
        # 向左
        elif (i == 1):
            checkP.x -= 1
            while ((checkP.x >= 0) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("左探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.x -= 1
        # 向上
        elif (i == 0):
            checkP.y -= 1
            while ((checkP.y >= 0) and map[checkP.x][checkP.y] == " "):
                linePointStack.append(checkP)
                if (OneCornerLink(checkP, p2)):
                    print("上探测OK")
                    return True
                else:
                    linePointStack.pop()
                checkP.y -= 1

    # 四个方向都寻完都没找到适合的checkP点
    print("两直角连接没找到适合的checkP点")
    return False;


# ---------------------------
# 画连接线
def drawLinkLine(p1, p2):
    if (len(linePointStack) == 0):
        Line_id.append(drawLine(p1, p2))
    else:
        print(linePointStack, len(linePointStack))
    if (len(linePointStack) == 1):
        z = linePointStack.pop()
        print("一折连通点z", z.x, z.y)
        Line_id.append(drawLine(p1, z))
        Line_id.append(drawLine(p2, z))
    if (len(linePointStack) == 2):
        z1 = linePointStack.pop()
        print("2折连通点z1", z1.x, z1.y)
        Line_id.append(drawLine(p2, z1))
        z2 = linePointStack.pop()
        print("2折连通点z2", z2.x, z2.y)
        Line_id.append(drawLine(z1, z2))
        Line_id.append(drawLine(p1, z2))


# 删除连接线
def undrawConnectLine():
    while len(Line_id) > 0:
        idpop = Line_id.pop()
        cv.delete(idpop)


def drawLine(p1, p2):
    print("drawLine p1,p2", p1.x, p1.y, p2.x, p2.y)
    # cv.create_line( 40+20, 40+20,200,200,width=5,fill='red')
    id = cv.create_line(p1.x * 40 + 20, p1.y * 40 + 20, p2.x * 40 + 20, p2.y * 40 + 20, width=5, fill='red')
    # cv.pack()
    return id


# --------------------------------------
def create_map():  # 产生map地图
    global map
    # 生成随机地图
    # 将所有匹配成对的动物物种放进一个临时的地图中
    tmpMap = []
    m = (Width) * (Height) // 10
    print('m=', m)
    for x in range(0, m):
        for i in range(0, 10):  # 每种方块有10个
            tmpMap.append(x)
    random.shuffle(tmpMap)
    for x in range(0, Width):  # 0--14
        for y in range(0, Height):  # 0--14
            map[x][y] = tmpMap[x * Height + y]


# --------------------------------------
def find2Block(event):  # 自动查找
    global firstSelectRectId, SecondSelectRectId
    m_nRoW = Height
    m_nCol = Width
    bFound = False;
    # 第一个方块从地图的0位置开始
    for i in range(0, m_nRoW * m_nCol):
        # 找到则跳出循环
        if (bFound):
            break

        # 算出对应的虚拟行列位置
        x1 = i % m_nCol
        y1 = i // m_nCol
        p1 = Point(x1, y1)
        # 无图案的方块跳过
        if (map[x1][y1] == ' '):
            continue
        # 第二个方块从前一个方块的后面开始
        for j in range(i + 1, m_nRoW * m_nCol):
            # 算出对应的虚拟行列位置
            x2 = j % m_nCol
            y2 = j // m_nCol
            p2 = Point(x2, y2)
            # 第二个方块不为空 且与第一个方块的动物相同
            if (map[x2][y2] != ' ' and IsSame(p1, p2)):
                # 判断是否可以连通
                if (IsLink(p1, p2)):
                    bFound = True
                    break
    # 找到后自动消除
    if (bFound):  # p1(x1,y1)与p2(x2,y2)连通
        print('找到后', p1.x, p1.y, p2.x, p2.y)
        # 画选定(x1,y1)处的框线
        firstSelectRectId = cv.create_rectangle(x1 * 40, y1 * 40, x1 * 40 + 40, y1 * 40 + 40, width=2, outline="red")
        # 画选定(x2,y2)处的框线
        secondSelectRectId = cv.create_rectangle(x2 * 40, y2 * 40, x2 * 40 + 40, y2 * 40 + 40, width=2, outline="red")
        # t=Timer(timer_interval,delayrun)#定时函数
        # t.start()

    return bFound


# 游戏主逻辑
root = Tk()
root.title("Python连连看 ")
imgs = [PhotoImage(file='images\\bar_0' + str(i) + '.gif') for i in range(0, 10)]  # 所有图标图案
Select_first = False  # 是否已经选中第一块
firstSelectRectId = -1  # 被选中第一块地图对象
SecondSelectRectId = -1  # 被选中第二块地图对象
clearFlag = False
linePointStack = []
Line_id = []
Height = 10
Width = 10
map = [[" " for y in range(Height)] for x in range(Width)]
image_map = [[" " for y in range(Height)] for x in range(Width)]
cv = Canvas(root, bg='green', width=440, height=440)
# drawQiPan( )
cv.bind("<Button-1>", callback)  # 鼠标左键事件
cv.bind("<Button-3>", find2Block)  # 鼠标右键事件
cv.pack()
create_map()  # 产生map地图
print_map()  # 打印map地图
root.mainloop()Python最经典的四个小游戏,适合新手小白练习,源码已经打包好了,希望能帮到大家!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-21 14:34 , Processed in 0.090288 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表