Pygame 实战(行动代号(单机版)):(二). 游戏编程

当前系列历史文章:

  • Pygame 实战(行动代号(单机版)):(一). 游戏简介

这一部分就开始讲实现的代码啦!因为之前写代码的时候没什么经验,所以可能有不少冗余的部分和可以简化的地方命名也不是很规范,欢迎大家讨论交流和改进。


这一部分代码并不适合Pygame初学者,所以如果对Pygame还不熟的人可以自行学习一下基本的 Surface, Rect, Sprite, SpriteGroup,event等等,这些资源在网上有好多我就不一一叙说了,当然有什么问题也是可以向我提问哒~


废话不多说,直接开始讲吧。

目录

1. 游戏实现

1.1. 代码设计

1.1.1. 展示部分

游戏demo

1.1.2. 代号(命名)设计

1.2. 功能实现

Button

ButtonKeyboard

CardButton

初始化词和颜色布局

1.3. 主程序

1.4. 运行程序


1. 游戏实现

1.1. 代码设计

其实也算不上什么设计啦,就是跟大家讲讲主要的一些思路。

1.1.1. 展示部分

游戏主要的界面应该用于显示25个卡牌 CardButton,我们称这个区域为为 area_cards

在游戏开始前,我们有两个功能按钮 Button 分别用于用于换词和开始游戏,这两个按钮我们将放在 prepare_btn_grp 中;

游戏开始后,我们在显示卡牌的区域下显示一些游戏状态信息,我们称这个区域为 area_tips;

游戏开始后,每一方回合开始时,需要队长说出一个词和一个数量,这一数量需要输入到游戏中,以便我们的游戏程序可以正常的实施游戏规则逻辑,输入数量时我们用显示1到9的9个按钮 ButtonKeyboard 辅助输入,这些按钮放在 input_card_grp 

游戏demo

以下是一些实际截图


准备时

Pygame 实战(行动代号(单机版)):(二). 游戏编程_第1张图片


游戏开始队长选择此轮猜词个数

Pygame 实战(行动代号(单机版)):(二). 游戏编程_第2张图片


猜词阶段

Pygame 实战(行动代号(单机版)):(二). 游戏编程_第3张图片


游戏结束

Pygame 实战(行动代号(单机版)):(二). 游戏编程_第4张图片

 

1.1.2. 代号(命名)设计

因为游戏中我们将分为红蓝两队,卡牌有黑、黄、蓝、红四个颜色,于是我们分别用 id = 0,1,2,3带指这四个颜色,当 id = 2 时游戏信息显示中对应“蓝方”,id = 3时游戏信息显示中对应“红方”

ID_COLOR = {0: (0, 0, 0), 1: (255, 255, 0), 2: (0, 0, 255), 3: (255, 0, 0)}
ID_TIPS = {2: '蓝方', 3: '红方'}

游戏有三个状态,一个是开始前的准备阶段 phase_prepare ,一个是开始后的游戏阶段 phase_gaming ,和一个一整盘游戏是否结束的状态 game_over

# 初始时的状态初始值
phase_prepare = True
phase_gaming = False
game_over = False

开始游戏后,第一个回合的队伍对应 begin_id, 当前队伍对应 now, 我们用 DISCOVERED 代表当前没展示颜色的卡牌数量,方便 area_tips 中的信息展示

DISCOVERED = {0: 1, 1: 7, begin_id: 9, 2 if begin_id == 3 else 3: 8} # 初始化DISCOVERED

1.2. 功能实现

这一小部门讲讲上面提到的各类按钮如何实现. 先来一个基本的按钮类

Button

class Button(py.sprite.Sprite):
    def __init__(self, text, font=None, size=(100, 40), position=(0, 0), id=None, name=None, text_color=(0, 0, 0),
                 clicked_color=(192, 192, 192), bg_color=(255, 255, 255), shadow_color=(0, 0, 0), shadow=True):
        '''
        :param text: 文本
        :param font: 字体
        :param size: 大小
        :param position: 位置
        :param id: id
        :param name: 按钮名
        :param text_color: 文本颜色
        :param clicked_color: 点击时背景颜色
        :param bg_color: 背景颜色
        :param shadow_color: 边框颜色
        :param shadow: 是否有边框
        '''
        py.sprite.Sprite.__init__(self)
        self.id = id
        self.name = name
        self.width, self.height = size
        self.text_color = text_color
        self.bg_color = bg_color
        self.clicked_color = clicked_color
        self.shadow_color = shadow_color
        self.shadow = shadow
        self.clicked = False
        self.text = text
        self.font = font
        self.load(text, font=font, position=position)

    def _set_pos(self, pos):
        self.rect.topleft = pos

    def _get_pos(self):
        return self.rect.topleft

    def _set_x(self, x):
        self.rect.x = x

    def _get_x(self):
        return self.rect.x

    def _set_y(self, y):
        self.rect.y = y

    def _get_y(self):
        return self.rect.y

    position = property(_get_pos, _set_pos)
    x = property(_get_x, _set_x)
    y = property(_get_y, _set_y)

    def load(self, text, font=None, position=(0, 0)):
        # 渲染按钮图片
        if not font:
            self.font = py.font.Font(None, 20)
        else:
            self.font = font
        self.rect = Rect(position[0], position[1], self.width, self.height)
        image_text = self.font.render(text, True, self.text_color)
        centerx, centery = image_text.get_rect().center
        coord_image_text = (self.width // 2 - centerx, self.height // 2 - centery)
        image = py.Surface(self.rect.size)
        image.fill(self.bg_color)
        image.blit(image_text, coord_image_text)
        image_clicked = py.Surface(self.rect.size)
        image_clicked.fill(self.clicked_color)
        image_clicked.blit(image_text, coord_image_text)

        if self.shadow:
            py.draw.rect(image, self.shadow_color, (0, 0, self.width, self.height), 2)
            py.draw.rect(image_clicked, self.shadow_color, (0, 0, self.width, self.height), 2)
        # self.images = [原始图片,被点击时的图片]
        self.images = [image, image_clicked]

    def update(self):
        if self.clicked:
            self.image = self.images[1]
        else:
            self.image = self.images[0]

    def collide_mouse(self, x, y):
        return self.rect.collidepoint(x, y)

ButtonKeyboard

用于选择 1~9 的按钮:

class ButtonKeyboard(Button):
    def __init__(self, text, size=(20, 20), position=(0, 0), font=None, id=None, name=None):
        Button.__init__(self, text, size=size, font=font, position=position, id=id, name=name)

CardButton

用于展示卡牌的按钮:

class CardButton(Button):
    def __init__(self, text, size=(20, 20), position=(0, 0), font=None, id=0, name=None, shadow=False, locked=False):
        if id not in [0, 1, 2, 3]:
            raise ValueError
        Button.__init__(self, text, size=size, font=font, position=position, id=id, name=name, shadow=shadow)
        self.reload(text, font)
        self.locked = locked  # 卡牌是否被猜到

    def reload(self, text, font=None):
        # 渲染游戏过程中,卡牌被猜到时显示的图片
        if not font:
            self.font = py.font.Font(None, 20)
        else:
            self.font = font
        # 黑色卡牌需要用白字,其它卡牌用黑字
        if self.text_color == ID_COLOR[self.id]:
            if self.text_color == (0, 0, 0):
                image_text = self.font.render(text, True, (255, 255, 255))
            else:
                image_text = self.font.render(text, True, (0, 0, 0))
        else:
            image_text = self.font.render(text, True, self.text_color)
        centerx, centery = image_text.get_rect().center
        coord_image_text = (self.width // 2 - centerx, self.height // 2 - centery)

        image_locked = py.Surface(self.rect.size)
        image_locked.fill(ID_COLOR[self.id])
        image_locked.blit(image_text, coord_image_text)

        if self.shadow:
            py.draw.rect(image_locked, self.shadow_color, (0, 0, self.width, self.height), 2)

        self.images.append(image_locked)

    def update(self):
        if self.locked:
            self.image = self.images[2]
        elif self.clicked:
            self.image = self.images[1]
        else:
            self.image = self.images[0]

初始化词和颜色布局

我们将所有词语文件都存放 words_dir 中,词语文件中词语用utf-8编码“,”作为分隔符

words_dir = './dat'


class HandleWords():
    def __init__(self):
        try:
            self.word_list = []
            for filename in os.listdir(words_dir):
                try:
                    with open(os.path.join(words_dir, filename), 'r', encoding='utf-8') as f:
                        self.word_list.extend(f.read().split(','))
                except:
                    pass
        except:
            raise FileNotFoundError

    def get_words(self):
        words_index = set()
        while len(words_index) < 25:
            words_index.add(randint(0, len(self.word_list) - 1))
        words = [self.word_list[i] for i in words_index]
        shuffle(words)
        return words

为每一个词生成一个 id

def get_maps():
    begin_id = randint(2, 3)
    second_id = 2 if begin_id == 3 else 3
    maps = [0] + [1] * 7 + [second_id] * 8 + [begin_id] * 9
    shuffle(maps)
    generate_map_image(maps)
    return maps, begin_id


def generate_map_image(maps):
    # 生成图像
    global ID_COLOR
    image_file_path = 'img/text.png'
    size_img = 600, 600
    img = py.Surface(size_img)
    img.fill((255, 255, 255))
    padx, pady = 30, 30
    int_x, int_y = 10, 10
    w, h = 100, 100
    i = 0
    py.draw.rect(img, (0, 0, 0), (padx // 2, pady // 2, size_img[0] - padx, size_img[1] - pady), 1)
    while i < 25:
        x, y = i % 5, i // 5
        temp_surf = py.Surface((w, h))
        temp_surf.fill(ID_COLOR[maps[i]])
        img.blit(temp_surf, (padx + x * w + x * int_x, pady + y * h + y * int_y, w, h))
        i += 1
    py.image.save(img, image_file_path)

1.3. 主程序

import pygame as py
from pygame.locals import *
import sys
from random import randint, shuffle
import os
def menu_game():
    global screen, cards_grp, tips_font, area_tips_position, pady
    global phase_prepare, phase_gaming, game_over, cursor_click, cursor_click_object, init, now, waiting_input, count, total, continuer, DISCOVERED
    global word_generator
    # 生成词语和颜色图
    word_generator = HandleWords()
    words = get_words()
    maps, begin_id = get_maps()
    # 初始化相关大小参数
    card_font = py.font.Font('SimHei.ttf', 50)
    card_size = (150, 80)
    blank_x, blank_y = card_size[1] // 2, card_size[1] // 2
    padx, pady = 5, 5
    area_cards_bg = (255, 228, 181)
    prepare_btn_size = (100, 60)
    prepare_btn_font = py.font.Font('SimHei.ttf', 30)
    prepare_btn_blank_x = prepare_btn_size[0] // 10

    area_cards_size = padx * 2 + card_size[0] * 5 + blank_x * 4, pady * 2 + card_size[1] * 5 + blank_y * 4
    area_cards_position = (screen.get_rect().width - area_cards_size[0]) // 2, (
            screen.get_rect().height - area_cards_size[1]) // 5
    area_cards = py.Surface(area_cards_size)

    # 初始化25个词语按钮
    cards_grp = py.sprite.Group()
    start_x, start_y = padx, pady
    i = 0
    y = start_y
    while i < 25:
        for x in range(start_x, area_cards_size[0], card_size[0] + blank_x):
            if i < 25:
                cards_grp.add(CardButton(words[i], size=card_size, font=card_font, position=(x, y), id=maps[i]))
                i += 1
        y += card_size[1] + blank_y

    area_tips_position = area_cards_position[0], area_cards_position[1] + area_cards_size[1] + 10 * pady
    tips_font = py.font.Font('SimHei.ttf', 20)

    # 初始化“准备”、“换牌”按钮
    prepare_btn_grp = py.sprite.Group()
    btn_change_cards_position = (
        area_tips_position[0] + area_cards_size[0] - prepare_btn_size[0], area_tips_position[1])
    btn_prepare_position = (
        btn_change_cards_position[0] - prepare_btn_size[0] - prepare_btn_blank_x, area_tips_position[1])
    prepare_btn_grp.add(
        Button('换牌', font=prepare_btn_font, size=prepare_btn_size, position=btn_change_cards_position, id='change'))
    prepare_btn_grp.add(
        Button('准备', font=prepare_btn_font, size=prepare_btn_size, position=btn_prepare_position, id='prepare'))

    # 初始化 input_card_grp 
    input_card_grp = py.sprite.Group()
    input_card_size = (40, 40)
    pad_input_card = 10
    start_x, start_y = area_cards_position[0] + padx, area_tips_position[1] + tips_font.size('一')[1] + pady
    x = start_x
    for i in range(1, 10):
        input_card_grp.add(ButtonKeyboard(str(i), size=input_card_size, position=(x, start_y), id=i))
        x += pad_input_card + input_card_size[0]

    def change_side():
        # 当前方回合结束,换边
        global now, init, waiting_input
        if not init:
            now = 2 if now == 3 else 3
        else:  # 若游戏刚开始,则换边后行动方为 begin_id
            init = False
        waiting_input = True

    def change_words():
        # 点击换牌按钮
        global cards_grp
        words = get_words()
        for card, word in zip(cards_grp, words):
            card.text = word
            card.load(card.text, card.font, card.position)
            card.reload(card.text, card.font, card.position)

    def init_game():
        # 重新初始化游戏
        global phase_prepare, phase_gaming, game_over, cursor_click, cursor_click_object, init, now, waiting_input, count, total, cards_grp, continuer, DISCOVERED
        maps, begin_id = get_maps()
        phase_prepare = True
        phase_gaming = False
        game_over = False
        cursor_click = False
        cursor_click_object = None
        now = begin_id
        init = True
        continuer = False
        count = 0
        total = 0
        waiting_input = False
        for c, m in zip(cards_grp, maps):
            c.id = m
            c.locked = False
        DISCOVERED = {0: 1, 1: 7, begin_id: 9, 2 if begin_id == 3 else 3: 8}

    def show_current_info():
        # 可视化DISCOVERED信息
        global tips_font, DISCOVERED, screen, pady
        assert isinstance(tips_font, py.font.FontType)
        tips_height = tips_font.size('一')[1]
        x, y = area_tips_position[0], area_tips_position[1] + tips_height + pady
        for id in [2, 3]:
            screen.blit(tips_font.render(ID_TIPS[id] + '剩余%d' % DISCOVERED[id], True, ID_COLOR[id]), (x, y))
            y += tips_height + pady

    phase_prepare = True
    phase_gaming = False
    game_over = False
    cursor_click = False
    cursor_click_object = None
    now = begin_id
    init = True
    count = 0
    total = 0
    waiting_input = False
    continuer = False
    DISCOVERED = {0: 1, 1: 7, begin_id: 9, 2 if begin_id == 3 else 3: 8}

    img_copyright = tips_font.render('Contact : CSDN: Apoca——20200202, Email:[email protected]',
                                     True, (255, 255, 255), (0, 0, 0)).convert()
    while True:
        if not game_over:
            for event in py.event.get():
                if event.type in (QUIT,):
                    sys.exit()
                elif event.type == MOUSEBUTTONDOWN:
                    mouse_x, mouse_y = event.pos
                    if phase_prepare:
                        for b in prepare_btn_grp:
                            if b.collide_mouse(mouse_x, mouse_y):
                                b.clicked = True
                                cursor_click_object = b
                                cursor_click = True
                                break
                    elif phase_gaming:
                        if waiting_input:
                            for b in input_card_grp:
                                if b.collide_mouse(mouse_x, mouse_y):
                                    b.clicked = True
                                    cursor_click_object = b
                                    cursor_click = True
                                    break
                        else:
                            for b in cards_grp:
                                if b.collide_mouse(mouse_x - area_cards_position[0], mouse_y - area_cards_position[1]):
                                    if not b.locked:
                                        b.clicked = True
                                        cursor_click_object = b
                                        cursor_click = True
                                        break
                elif event.type == MOUSEBUTTONUP:
                    if cursor_click:
                        cursor_click = False
                        cursor_click_object.clicked = False
                        if phase_gaming:
                            # 游戏阶段
                            if waiting_input:
                                # 等待玩家选择本轮猜词个数阶段
                                if b.collide_mouse(mouse_x, mouse_y):
                                    # 玩家选择本轮猜 b.id个词
                                    total = b.id
                                    b.clicked = False
                                    count = 0
                                    waiting_input = False
                            else:
                                # 猜词阶段
                                # 此时回合进行方为 now,队长指定猜total个词,已经猜对了count个,目前队员猜词b(b为一个CardButton)
                                if b.collide_mouse(mouse_x - area_cards_position[0], mouse_y - area_cards_position[1]):
                                    b.locked = True
                                    DISCOVERED[b.id] -= 1
                                    # 猜词结束后的逻辑
                                    if now == b.id:
                                        # 若猜词为本方颜色词
                                        if DISCOVERED[b.id] == 0:
                                            # 若所有本方颜色都已经被猜到,游戏结束
                                            game_over = True
                                            game_over_message = tips_font.render(ID_TIPS[b.id] + '胜利', True,
                                                                                 ID_COLOR[b.id])
                                        else:
                                            count += 1
                                            if count == total:
                                                # 若已达到此回合最大猜词个数,则换边
                                                change_side()
                                    else:
                                        # 若猜词不为本方颜色词
                                        if b.id == 0:
                                            # 若猜词为黑色,游戏结束
                                            game_over = True
                                            game_over_message = tips_font.render(ID_TIPS[now] + '错误识别黑色暗号,游戏失败!', True,
                                                                                 ID_COLOR[now])
                                        else:
                                            if DISCOVERED[b.id] == 0 and b.id != 1:
                                                # 若猜到了对方颜色词,且对方颜色的所有词都被揭露,游戏结束
                                                game_over = True
                                                game_over_message = tips_font.render(ID_TIPS[b.id] + '胜利', True,
                                                                                     ID_COLOR[b.id])
                                            else:
                                                # 其余猜错词的情况,换边
                                                change_side()
                        elif phase_prepare:
                            # 准备阶段
                            if b.collide_mouse(mouse_x, mouse_y):
                                b.clicked = False
                                if b.id == 'prepare':  # 点击准备按钮
                                    # 游戏开始
                                    phase_prepare = False
                                    phase_gaming = True
                                    change_side()
                                elif b.id == 'change':  # 点击换牌按钮
                                    change_words()
                        cursor_click_object = None
                elif event.type == MOUSEMOTION:
                    mouse_x, mouse_y = event.pos
        else:
            # 整局游戏结束,按任意键将重新初始化游戏
            for event in py.event.get():
                if event.type in (MOUSEBUTTONUP, KEYUP):
                    continuer = True
        
        # 绘制屏幕
        screen.fill((0, 0, 0))
        # 作显示词语的区域
        cards_grp.update()
        area_cards.fill(area_cards_bg)
        cards_grp.draw(area_cards)
        screen.blit(area_cards, area_cards_position)
        if phase_prepare:
            # 准备阶段显示“准备”和“换边”按钮
            prepare_btn_grp.update()
            prepare_btn_grp.draw(screen)
        elif phase_gaming:
            # 游戏阶段
            if waiting_input:
                # 回合开始等待选择此轮猜词个数,显示input_card_grp
                screen.blit(tips_font.render(ID_TIPS[now] + '选择此轮猜词个数', True, ID_COLOR[now]), area_tips_position)
                input_card_grp.update()
                input_card_grp.draw(screen)
            else:
                # 猜词阶段
                if not game_over:
                    # 若游戏未结束,显示当前游戏信息
                    screen.blit(
                        tips_font.render(ID_TIPS[now] + ' : ' + '此轮剩余个数(%d) !' % (total - count), True, ID_COLOR[now]),
                        area_tips_position)
                    show_current_info()
                else:
                    # 游戏结束
                    if not continuer:
                        # 显示胜利提示
                        screen.blit(py.font.SysFont('SimHei', 100).render('按任意键继续', True, (139, 139, 122)), (200, 500))
                        screen.blit(
                            game_over_message, area_tips_position)
                    else:
                        # 重新初始化游戏
                        init_game()
        screen.blit(img_copyright, (0, 0))
        py.display.update()

1.4. 运行程序

if __name__ == '__main__':
    py.init()
    screen = py.display.set_mode((1000, 1000))
    py.display.set_caption('行动代号')
    menu_game()

 

下一部分链接:

Pygame 实战(行动代号(单机版)):(三). 程序的完善补充

 

你可能感兴趣的:(pygame,Python游戏,Python游戏,pygame,桌游,行动代号)