基于Python和Pygame打造一個有趣的化學(xué)配對記憶游戲
這篇文章主要介紹了一款使用Python和Pygame開發(fā)的化學(xué)配對教育游戲,通過創(chuàng)新的記憶配對機(jī)制幫助學(xué)生學(xué)習(xí)化學(xué)知識。游戲包含元素和化合物兩種模式,分別涵蓋28種化學(xué)元素和32種常見化合物,玩家需要在網(wǎng)格中點(diǎn)擊匹配中文名稱與對應(yīng)化學(xué)式。游戲設(shè)計了三個難度級別(4×4、6×6、8×8網(wǎng)格),具有自適應(yīng)界面布局、智能字體調(diào)整、震動反饋效果和完整的游戲流程管理,既能讓學(xué)生在輕松互動中掌握化學(xué)術(shù)語,又展示了如何將編程技術(shù)應(yīng)用于教育領(lǐng)域,是一款兼具教育價值和編程學(xué)習(xí)意義的開源項(xiàng)目,因?yàn)榛衔镏械南聵?biāo)弄不出來,所以H?O這種是H2O,也可以自己增加元素和化合物的種類。
游戲效果如下:

完整代碼如下:
import pygame
import random
import sys
import math
import os
from pygame.locals import *
# 初始化pygame
pygame.init()
# 獲取屏幕尺寸并設(shè)置窗口居中
screen_width = 1000
screen_height = 800
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("化學(xué)配對游戲")
# 顏色定義
BACKGROUND = (240, 245, 249)
GRID_BG = (230, 240, 255)
TEXT_COLOR = (30, 45, 70)
SELECTED_COLOR = (100, 180, 255)
CORRECT_COLOR = (100, 220, 150)
WRONG_COLOR = (255, 150, 150)
EMPTY_COLOR = (220, 230, 240)
BUTTON_COLOR = (70, 130, 180)
BUTTON_HOVER = (90, 150, 200)
TITLE_COLOR = (20, 60, 120)
# 單元格邊距
CELL_MARGIN = 8
# 字體加載函數(shù)
def load_font(font_size=32):
"""安全加載字體,返回一個可用的字體對象"""
try:
# 嘗試加載系統(tǒng)字體
font_names = [
'Microsoft YaHei', # Windows 中文
'SimHei', # Windows 黑體
'SimSun', # Windows 宋體
'Arial Unicode MS', # 通用Unicode字體
'DejaVu Sans', # Linux通用字體
'Arial' # 通用字體
]
for font_name in font_names:
try:
font = pygame.font.SysFont(font_name, font_size)
# 測試字體是否能渲染
test_surface = font.render("測試", True, (0, 0, 0))
if test_surface.get_width() > 0:
return font
except:
continue
# 如果都失敗,使用默認(rèn)字體
return pygame.font.Font(None, font_size)
except:
# 終極備用方案
return pygame.font.Font(None, font_size)
# 加載字體
try:
chinese_font = load_font(32)
chinese_font_small = load_font(24)
font_medium = load_font(36)
font_small = load_font(28)
except:
# 如果字體加載失敗,使用默認(rèn)字體
chinese_font = pygame.font.Font(None, 32)
chinese_font_small = pygame.font.Font(None, 24)
font_medium = pygame.font.Font(None, 36)
font_small = pygame.font.Font(None, 28)
# 化學(xué)元素數(shù)據(jù) - 中文名稱和對應(yīng)的化學(xué)式
elements = [
{"chinese": "氫", "formula": "H"},
{"chinese": "氦", "formula": "He"},
{"chinese": "鋰", "formula": "Li"},
{"chinese": "鈹", "formula": "Be"},
{"chinese": "硼", "formula": "B"},
{"chinese": "碳", "formula": "C"},
{"chinese": "氮", "formula": "N"},
{"chinese": "氧", "formula": "O"},
{"chinese": "氟", "formula": "F"},
{"chinese": "氖", "formula": "Ne"},
{"chinese": "鈉", "formula": "Na"},
{"chinese": "鎂", "formula": "Mg"},
{"chinese": "鋁", "formula": "Al"},
{"chinese": "硅", "formula": "Si"},
{"chinese": "磷", "formula": "P"},
{"chinese": "硫", "formula": "S"},
{"chinese": "氯", "formula": "Cl"},
{"chinese": "氬", "formula": "Ar"},
{"chinese": "鉀", "formula": "K"},
{"chinese": "鈣", "formula": "Ca"},
{"chinese": "鐵", "formula": "Fe"},
{"chinese": "銅", "formula": "Cu"},
{"chinese": "銀", "formula": "Ag"},
{"chinese": "金", "formula": "Au"},
{"chinese": "汞", "formula": "Hg"},
{"chinese": "鉛", "formula": "Pb"},
{"chinese": "錫", "formula": "Sn"},
{"chinese": "鋅", "formula": "Zn"},
]
# 化合物數(shù)據(jù) - 使用標(biāo)準(zhǔn)字符,避免特殊字符
compounds = [
{"chinese": "水", "formula": "H2O"},
{"chinese": "二氧化碳", "formula": "CO2"},
{"chinese": "食鹽", "formula": "NaCl"},
{"chinese": "氨氣", "formula": "NH3"},
{"chinese": "甲烷", "formula": "CH4"},
{"chinese": "硫酸", "formula": "H2SO4"},
{"chinese": "葡萄糖", "formula": "C6H12O6"},
{"chinese": "乙醇", "formula": "C2H5OH"},
{"chinese": "碳酸鈣", "formula": "CaCO3"},
{"chinese": "氫氧化鈉", "formula": "NaOH"},
{"chinese": "鹽酸", "formula": "HCl"},
{"chinese": "硝酸", "formula": "HNO3"},
{"chinese": "醋酸", "formula": "CH3COOH"},
{"chinese": "氧化鐵", "formula": "Fe2O3"},
{"chinese": "硫酸銅", "formula": "CuSO4"},
{"chinese": "氯化銀", "formula": "AgCl"},
{"chinese": "氧化鋁", "formula": "Al2O3"},
{"chinese": "硫化氫", "formula": "H2S"},
{"chinese": "氫氧化鈣", "formula": "Ca(OH)2"},
{"chinese": "碳酸鈉", "formula": "Na2CO3"},
{"chinese": "氯化鉀", "formula": "KCl"},
{"chinese": "硫酸鈉", "formula": "Na2SO4"},
{"chinese": "硝酸銀", "formula": "AgNO3"},
{"chinese": "硫酸亞鐵", "formula": "FeSO4"},
{"chinese": "氧化銅", "formula": "CuO"},
{"chinese": "二氧化硅", "formula": "SiO2"},
{"chinese": "氯化鈣", "formula": "CaCl2"},
{"chinese": "氯化鎂", "formula": "MgCl2"},
{"chinese": "硫酸鎂", "formula": "MgSO4"},
{"chinese": "氯化鋅", "formula": "ZnCl2"},
{"chinese": "硫酸鋅", "formula": "ZnSO4"},
{"chinese": "硝酸鉀", "formula": "KNO3"},
]
class GameCell:
def __init__(self, x, y, width, height, text, cell_type, pair_id):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.cell_type = cell_type # 'chinese' 或 'formula'
self.pair_id = pair_id # 配對ID,相同ID的可以配對
self.selected = False
self.matched = False
def draw(self, screen):
if self.matched:
color = EMPTY_COLOR
elif self.selected:
color = SELECTED_COLOR
else:
color = GRID_BG
pygame.draw.rect(screen, color, self.rect, border_radius=8)
pygame.draw.rect(screen, TEXT_COLOR, self.rect, 2, border_radius=8)
if self.text and not self.matched:
# 根據(jù)單元格大小選擇合適的字體大小
cell_size = min(self.rect.width, self.rect.height)
if cell_size > 100: # 大單元格
font_size = 32
elif cell_size > 70: # 中等單元格
font_size = 24
else: # 小單元格
font_size = 18
# 使用安全的字體加載
try:
font = load_font(font_size)
text_surface = font.render(self.text, True, TEXT_COLOR)
except:
# 如果字體渲染失敗,使用默認(rèn)字體
font = pygame.font.Font(None, font_size)
text_surface = font.render(self.text, True, TEXT_COLOR)
# 如果文本太長,縮小字體
max_width = self.rect.width - 10
while text_surface.get_width() > max_width and font_size > 12:
font_size -= 2
try:
font = load_font(font_size)
text_surface = font.render(self.text, True, TEXT_COLOR)
except:
font = pygame.font.Font(None, font_size)
text_surface = font.render(self.text, True, TEXT_COLOR)
text_rect = text_surface.get_rect(center=self.rect.center)
screen.blit(text_surface, text_rect)
def is_clicked(self, pos):
return self.rect.collidepoint(pos) and not self.matched
class Button:
def __init__(self, x, y, width, height, text):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.hovered = False
def draw(self, screen):
color = BUTTON_HOVER if self.hovered else BUTTON_COLOR
pygame.draw.rect(screen, color, self.rect, border_radius=8)
pygame.draw.rect(screen, (255, 255, 255), self.rect, 2, border_radius=8)
# 渲染按鈕文本
try:
text_surface = chinese_font_small.render(self.text, True, (255, 255, 255))
except:
font = pygame.font.Font(None, 24)
text_surface = font.render(self.text, True, (255, 255, 255))
text_rect = text_surface.get_rect(center=self.rect.center)
screen.blit(text_surface, text_rect)
def is_clicked(self, pos):
return self.rect.collidepoint(pos)
class DifficultyButton:
def __init__(self, x, y, width, height, text, difficulty):
self.rect = pygame.Rect(x, y, width, height)
self.text = text
self.difficulty = difficulty
self.hovered = False
self.selected = False
def draw(self, screen):
if self.selected:
color = (150, 200, 100)
elif self.hovered:
color = BUTTON_HOVER
else:
color = BUTTON_COLOR
pygame.draw.rect(screen, color, self.rect, border_radius=8)
pygame.draw.rect(screen, (255, 255, 255), self.rect, 2, border_radius=8)
# 渲染按鈕文本
try:
text_surface = chinese_font_small.render(self.text, True, (255, 255, 255))
except:
font = pygame.font.Font(None, 24)
text_surface = font.render(self.text, True, (255, 255, 255))
text_rect = text_surface.get_rect(center=self.rect.center)
screen.blit(text_surface, text_rect)
def is_clicked(self, pos):
return self.rect.collidepoint(pos)
class Game:
def __init__(self):
self.mode = "elements" # "elements" 或 "compounds"
self.difficulty = "easy" # "easy", "medium", "hard"
self.cells = []
self.selected_cells = []
self.matched_pairs = 0
self.total_pairs = 0
self.game_over = False
self.shake_time = 0
self.shake_offset = (0, 0)
# 創(chuàng)建按鈕
self.create_buttons()
self.init_game()
def calculate_cell_size(self):
"""根據(jù)難度計算單元格大小和網(wǎng)格大小"""
if self.difficulty == "easy":
self.grid_size = 4
available_width = screen_width - 100
available_height = screen_height - 250
cell_size = min(available_width // self.grid_size, available_height // self.grid_size)
self.cell_width = cell_size - CELL_MARGIN
self.cell_height = self.cell_width
elif self.difficulty == "medium":
self.grid_size = 6
available_width = screen_width - 100
available_height = screen_height - 250
cell_size = min(available_width // self.grid_size, available_height // self.grid_size)
self.cell_width = cell_size - CELL_MARGIN
self.cell_height = self.cell_width
else: # hard
self.grid_size = 8
available_width = screen_width - 100
available_height = screen_height - 250
cell_size = min(available_width // self.grid_size, available_height // self.grid_size)
self.cell_width = cell_size - CELL_MARGIN
self.cell_height = self.cell_width
def create_buttons(self):
"""創(chuàng)建游戲按鈕"""
button_width = 150
button_height = 40
button_margin = 20
button_y = screen_height - 70
self.restart_button = Button(
screen_width // 2 - button_width - button_margin // 2,
button_y,
button_width,
button_height,
"重新開始"
)
self.mode_button = Button(
screen_width // 2 + button_margin // 2,
button_y,
button_width,
button_height,
"切換模式"
)
# 創(chuàng)建難度按鈕
diff_button_width = 100
diff_button_margin = 10
diff_button_y = 90
self.easy_button = DifficultyButton(
screen_width // 2 - diff_button_width - diff_button_margin,
diff_button_y,
diff_button_width,
button_height,
"簡單",
"easy"
)
self.medium_button = DifficultyButton(
screen_width // 2,
diff_button_y,
diff_button_width,
button_height,
"中等",
"medium"
)
self.hard_button = DifficultyButton(
screen_width // 2 + diff_button_width + diff_button_margin,
diff_button_y,
diff_button_width,
button_height,
"困難",
"hard"
)
# 設(shè)置默認(rèn)難度按鈕選中狀態(tài)
self.easy_button.selected = True
def init_game(self):
self.cells = []
self.selected_cells = []
self.matched_pairs = 0
self.game_over = False
self.shake_time = 0
self.shake_offset = (0, 0)
# 根據(jù)模式和難度確定使用的數(shù)據(jù)
if self.mode == "elements":
data = elements
else:
data = compounds
# 計算單元格大小
self.calculate_cell_size()
# 根據(jù)網(wǎng)格大小確定需要的配對數(shù)量
total_cells = self.grid_size * self.grid_size
num_pairs_needed = total_cells // 2
# 確保有足夠的數(shù)據(jù)
if len(data) < num_pairs_needed:
# 如果數(shù)據(jù)不足,重復(fù)使用數(shù)據(jù)
selected_data = []
while len(selected_data) < num_pairs_needed:
remaining = num_pairs_needed - len(selected_data)
selected_data.extend(random.sample(data, min(remaining, len(data))))
else:
selected_data = random.sample(data, num_pairs_needed)
# 創(chuàng)建配對列表
pairs = []
for i, item in enumerate(selected_data):
pairs.append((item["chinese"], "chinese", i))
pairs.append((item["formula"], "formula", i))
# 隨機(jī)打亂順序
random.shuffle(pairs)
# 計算網(wǎng)格位置使其居中
grid_width = self.grid_size * (self.cell_width + CELL_MARGIN) + CELL_MARGIN
grid_height = self.grid_size * (self.cell_height + CELL_MARGIN) + CELL_MARGIN
start_x = max(50, (screen_width - grid_width) // 2) # 確保不超出左邊界
start_y = 150
# 創(chuàng)建單元格
self.cells = []
for i in range(self.grid_size):
for j in range(self.grid_size):
index = i * self.grid_size + j
if index < len(pairs):
text, cell_type, pair_id = pairs[index]
x = start_x + j * (self.cell_width + CELL_MARGIN) + CELL_MARGIN
y = start_y + i * (self.cell_height + CELL_MARGIN) + CELL_MARGIN
# 確保不超出屏幕邊界
if x + self.cell_width > screen_width - 50:
self.cell_width = (screen_width - 50 - x) - CELL_MARGIN
cell = GameCell(x, y, self.cell_width, self.cell_height, text, cell_type, pair_id)
self.cells.append(cell)
self.total_pairs = len(selected_data)
def handle_click(self, pos):
# 游戲結(jié)束后仍然可以點(diǎn)擊按鈕重新開始
# 檢查按鈕點(diǎn)擊(放在最前面,這樣游戲結(jié)束后也能點(diǎn)擊)
if self.restart_button.is_clicked(pos):
self.init_game()
return
if self.mode_button.is_clicked(pos):
self.mode = "compounds" if self.mode == "elements" else "elements"
self.init_game()
return
# 檢查難度按鈕點(diǎn)擊
if self.easy_button.is_clicked(pos) and self.difficulty != "easy":
self.difficulty = "easy"
self.easy_button.selected = True
self.medium_button.selected = False
self.hard_button.selected = False
self.init_game()
return
if self.medium_button.is_clicked(pos) and self.difficulty != "medium":
self.difficulty = "medium"
self.easy_button.selected = False
self.medium_button.selected = True
self.hard_button.selected = False
self.init_game()
return
if self.hard_button.is_clicked(pos) and self.difficulty != "hard":
self.difficulty = "hard"
self.easy_button.selected = False
self.medium_button.selected = False
self.hard_button.selected = True
self.init_game()
return
# 如果游戲結(jié)束,不再處理單元格點(diǎn)擊
if self.game_over:
return
# 檢查單元格點(diǎn)擊
for cell in self.cells:
if cell.is_clicked(pos):
if cell.selected or cell.matched:
return
cell.selected = True
self.selected_cells.append(cell)
# 如果選中了兩個單元格
if len(self.selected_cells) == 2:
cell1, cell2 = self.selected_cells
# 檢查是否配對成功
if cell1.pair_id == cell2.pair_id and cell1.cell_type != cell2.cell_type:
# 配對成功
cell1.matched = True
cell2.matched = True
self.selected_cells.clear()
self.matched_pairs += 1
# 檢查游戲是否結(jié)束
if self.matched_pairs == self.total_pairs:
self.game_over = True
else:
# 配對失敗,觸發(fā)震動效果
self.shake_time = pygame.time.get_ticks()
self.shake_offset = (random.randint(-10, 10), random.randint(-10, 10))
# 短暫顯示后取消選擇
pygame.time.set_timer(pygame.USEREVENT, 800)
return
def cancel_selection(self):
for cell in self.selected_cells:
cell.selected = False
self.selected_cells.clear()
def update(self):
current_time = pygame.time.get_ticks()
# 處理震動效果
if self.shake_time > 0:
shake_duration = 300
if current_time - self.shake_time < shake_duration:
elapsed = current_time - self.shake_time
progress = elapsed / shake_duration
intensity = 10 * (1 - progress)
angle = elapsed * 0.1
self.shake_offset = (
intensity * math.sin(angle * 2),
intensity * math.cos(angle * 3)
)
else:
self.shake_time = 0
self.shake_offset = (0, 0)
# 更新按鈕懸停狀態(tài)
mouse_pos = pygame.mouse.get_pos()
self.restart_button.hovered = self.restart_button.rect.collidepoint(mouse_pos)
self.mode_button.hovered = self.mode_button.rect.collidepoint(mouse_pos)
self.easy_button.hovered = self.easy_button.rect.collidepoint(mouse_pos)
self.medium_button.hovered = self.medium_button.rect.collidepoint(mouse_pos)
self.hard_button.hovered = self.hard_button.rect.collidepoint(mouse_pos)
def draw(self, screen):
# 應(yīng)用震動偏移
if self.shake_time > 0:
screen_scroll = screen.copy()
screen.fill(BACKGROUND)
screen.blit(screen_scroll, self.shake_offset)
else:
screen.fill(BACKGROUND)
# 繪制標(biāo)題 - 將模式和標(biāo)題合并顯示
if self.mode == "elements":
title = "化學(xué)元素配對游戲(模式:元素)"
else:
title = "化學(xué)化合物配對游戲(模式:化合物)"
try:
title_surface = chinese_font.render(title, True, TITLE_COLOR)
except:
font = pygame.font.Font(None, 32)
title_surface = font.render(title, True, TITLE_COLOR)
screen.blit(title_surface, (screen_width // 2 - title_surface.get_width() // 2, 20))
# 繪制進(jìn)度
progress_text = f"配對進(jìn)度: {self.matched_pairs}/{self.total_pairs}"
try:
progress_surface = chinese_font.render(progress_text, True, TEXT_COLOR)
except:
font = pygame.font.Font(None, 24)
progress_surface = font.render(progress_text, True, TEXT_COLOR)
screen.blit(progress_surface, (20, 20))
# 繪制難度按鈕
self.easy_button.draw(screen)
self.medium_button.draw(screen)
self.hard_button.draw(screen)
# 繪制所有單元格
for cell in self.cells:
cell.draw(screen)
# 繪制按鈕(在遮罩之前繪制,這樣按鈕不會被遮住)
self.restart_button.draw(screen)
self.mode_button.draw(screen)
# 如果游戲結(jié)束,顯示勝利消息
if self.game_over:
# 創(chuàng)建一個透明遮罩,但不要太暗,讓玩家能看到下面的按鈕
overlay = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 100)) # 降低透明度,讓按鈕可見
screen.blit(overlay, (0, 0))
win_text = "恭喜!游戲勝利!"
try:
win_surface = chinese_font.render(win_text, True, (255, 255, 255))
except:
font = pygame.font.Font(None, 32)
win_surface = font.render(win_text, True, (255, 255, 255))
screen.blit(win_surface, (screen_width // 2 - win_surface.get_width() // 2, screen_height // 2 - 50))
restart_text = "點(diǎn)擊重新開始按鈕或按Enter鍵再來一局"
try:
restart_surface = chinese_font.render(restart_text, True, (255, 255, 255))
except:
font = pygame.font.Font(None, 24)
restart_surface = font.render(restart_text, True, (255, 255, 255))
screen.blit(restart_surface,
(screen_width // 2 - restart_surface.get_width() // 2, screen_height // 2 + 30))
def main():
game = Game()
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1:
game.handle_click(event.pos)
elif event.type == KEYDOWN:
# 游戲結(jié)束后按任意鍵重新開始
if game.game_over:
game.init_game()
elif event.type == pygame.USEREVENT:
# 計時器事件,用于取消錯誤選擇
game.cancel_selection()
pygame.time.set_timer(pygame.USEREVENT, 0)
game.update()
game.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()到此這篇關(guān)于基于Python和Pygame打造一個有趣的化學(xué)配對記憶游戲的文章就介紹到這了,更多相關(guān)Python Pygame游戲開發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python中字符串列表的相互轉(zhuǎn)換實(shí)際應(yīng)用場景
在Python編程中,經(jīng)常會遇到需要將字符串列表相互轉(zhuǎn)換的情況,這涉及到將逗號分隔的字符串轉(zhuǎn)換為列表,或者將列表中的元素連接成一個字符串,本文將深入討論這些情景,并提供豐富的示例代碼,幫助讀者更全面地理解字符串列表的轉(zhuǎn)換操作2023-12-12
基于Python實(shí)現(xiàn)在線加密解密網(wǎng)站系統(tǒng)
在這個數(shù)字化時代,數(shù)據(jù)的安全和隱私變得越來越重要,所以本文小編就來帶大家實(shí)現(xiàn)一個簡單但功能強(qiáng)大的加密解密系統(tǒng),并深入探討它是如何工作的,有興趣的可以了解下2023-09-09
在Python中通過threshold創(chuàng)建mask方式
今天小編就為大家分享一篇在Python中通過threshold創(chuàng)建mask方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-02-02
Python學(xué)習(xí)筆記之解析json的方法分析
這篇文章主要介紹了Python解析json的方法,結(jié)合實(shí)例形式分析了常見的Python解析與轉(zhuǎn)換json格式數(shù)據(jù)相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
Python TensorFlow介紹與實(shí)戰(zhàn)
這篇文章介紹了Python TensorFlow介紹與實(shí)戰(zhàn),通過本文的介紹,我們不僅了解了TensorFlow的基本概念和安裝方法,還通過線性回歸和卷積神經(jīng)網(wǎng)絡(luò)的實(shí)例,深入探討了 TensorFlow 的使用技巧,TensorFlow 的強(qiáng)大功能和靈活性使其成為深度學(xué)習(xí)領(lǐng)域的重要工具,需要的朋友可以參考下2024-07-07

