当前系列历史文章:
这一部分就开始讲实现的代码啦!因为之前写代码的时候没什么经验,所以可能有不少冗余的部分和可以简化的地方命名也不是很规范,欢迎大家讨论交流和改进。
这一部分代码并不适合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. 运行程序
其实也算不上什么设计啦,就是跟大家讲讲主要的一些思路。
游戏主要的界面应该用于显示25个卡牌 CardButton,我们称这个区域为为 area_cards;
在游戏开始前,我们有两个功能按钮 Button 分别用于用于换词和开始游戏,这两个按钮我们将放在 prepare_btn_grp 中;
游戏开始后,我们在显示卡牌的区域下显示一些游戏状态信息,我们称这个区域为 area_tips;
游戏开始后,每一方回合开始时,需要队长说出一个词和一个数量,这一数量需要输入到游戏中,以便我们的游戏程序可以正常的实施游戏规则逻辑,输入数量时我们用显示1到9的9个按钮 ButtonKeyboard 辅助输入,这些按钮放在 input_card_grp 中
以下是一些实际截图
准备时
游戏开始队长选择此轮猜词个数
猜词阶段
游戏结束
因为游戏中我们将分为红蓝两队,卡牌有黑、黄、蓝、红四个颜色,于是我们分别用 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
这一小部门讲讲上面提到的各类按钮如何实现. 先来一个基本的按钮类
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)
用于选择 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)
用于展示卡牌的按钮:
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)
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()
if __name__ == '__main__':
py.init()
screen = py.display.set_mode((1000, 1000))
py.display.set_caption('行动代号')
menu_game()
下一部分链接:
Pygame 实战(行动代号(单机版)):(三). 程序的完善补充