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

以下是一些實際截圖


準備時


遊戲開始隊長選擇此輪猜詞個數


猜詞階段


遊戲結束

 

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 實戰(行動代號(單機版)):(三). 程序的完善補充

 

發佈了6 篇原創文章 · 獲贊 6 · 訪問量 7602
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章