當前系列歷史文章:
這一部分就開始講實現的代碼啦!因爲之前寫代碼的時候沒什麼經驗,所以可能有不少冗餘的部分和可以簡化的地方命名也不是很規範,歡迎大家討論交流和改進。
這一部分代碼並不適合Pygame初學者,所以如果對Pygame還不熟的人可以自行學習一下基本的 Surface, Rect, Sprite, SpriteGroup,event等等,這些資源在網上有好多我就不一一敘說了,當然有什麼問題也是可以向我提問噠~
廢話不多說,直接開始講吧。
目錄
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 實戰(行動代號(單機版)):(三). 程序的完善補充