You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
8.0 KiB
238 lines
8.0 KiB
|
|
import pygame
|
|
import sys
|
|
import json
|
|
import os
|
|
|
|
pygame.init()
|
|
pygame.mixer.init()
|
|
|
|
pygame.mixer.music.load("./bgm.mp3")
|
|
pygame.mixer.music.play(-1)
|
|
place_sound = pygame.mixer.Sound("./sound_click_btn.mp3")
|
|
|
|
w, h = 900, 900
|
|
n = 15
|
|
s = w // n
|
|
black = (0, 0, 0)
|
|
white = (255, 255, 255)
|
|
bg = (200, 150, 100)
|
|
red = (200, 0, 0)
|
|
|
|
scr = pygame.display.set_mode((w, h))
|
|
pygame.display.set_caption("五子棋")
|
|
|
|
f_big = pygame.font.SysFont("fangsong", 80)
|
|
f_small = pygame.font.SysFont("fangsong", 60)
|
|
f_button = pygame.font.SysFont("fangsong", 40)
|
|
|
|
ranking_file = "rankings.json"
|
|
|
|
def load_rankings():
|
|
if os.path.exists(ranking_file):
|
|
with open(ranking_file, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
return {}
|
|
|
|
def save_rankings(data):
|
|
with open(ranking_file, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
rankings = load_rankings()
|
|
|
|
def record_win(name):
|
|
if name not in rankings:
|
|
rankings[name] = {"wins": 0, "games": 0}
|
|
rankings[name]["wins"] += 1
|
|
rankings[name]["games"] += 1
|
|
|
|
def record_loss(name):
|
|
if name not in rankings:
|
|
rankings[name] = {"wins": 0, "games": 0}
|
|
rankings[name]["games"] += 1
|
|
|
|
class TextInputBox:
|
|
def __init__(self, x, y, w, h, label):
|
|
self.rect = pygame.Rect(x, y, w, h)
|
|
self.color = (200, 200, 200)
|
|
self.text = ''
|
|
self.label = label
|
|
self.txt_surface = pygame.font.SysFont("fangsong", 40).render(self.text, True, black)
|
|
self.active = False
|
|
|
|
def handle_event(self, event):
|
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
self.active = self.rect.collidepoint(event.pos)
|
|
elif event.type == pygame.KEYDOWN and self.active:
|
|
if event.key == pygame.K_BACKSPACE:
|
|
self.text = self.text[:-1]
|
|
else:
|
|
self.text += event.unicode
|
|
self.txt_surface = pygame.font.SysFont("fangsong", 40).render(self.text, True, black)
|
|
|
|
def draw(self, screen):
|
|
label_surface = pygame.font.SysFont("fangsong", 40).render(self.label, True, black)
|
|
screen.blit(label_surface, (self.rect.x, self.rect.y - 30))
|
|
pygame.draw.rect(screen, self.color, self.rect, 2)
|
|
screen.blit(self.txt_surface, (self.rect.x + 5, self.rect.y + 8))
|
|
|
|
game_state = "menu"
|
|
input_boxes = [
|
|
TextInputBox(w//2 - 150, h//2 - 30, 300, 40, "黑棋玩家:"),
|
|
TextInputBox(w//2 - 150, h//2 + 60, 300, 40, "白棋玩家:")
|
|
]
|
|
player1, player2 = "", ""
|
|
p = 1
|
|
b = [[0 for _ in range(n)] for _ in range(n)]
|
|
over = False
|
|
|
|
def draw_board():
|
|
scr.fill(bg)
|
|
for i in range(n):
|
|
pygame.draw.line(scr, black, (s // 2, s // 2 + i * s), (w - s // 2, s // 2 + i * s), 1)
|
|
pygame.draw.line(scr, black, (s // 2 + i * s, s // 2), (s // 2 + i * s, h - s // 2), 1)
|
|
pygame.draw.rect(scr, (230, 180, 150), (w - 160, 20, 140, 40))
|
|
t = f_button.render("返回主菜单", True, black)
|
|
scr.blit(t, (w - 150, 25))
|
|
|
|
def draw_chess():
|
|
for y in range(n):
|
|
for x in range(n):
|
|
cx, cy = x * s + s // 2, y * s + s // 2
|
|
r = s // 2 - 4
|
|
if b[y][x] == 1:
|
|
pygame.draw.circle(scr, (30, 30, 30), (cx, cy), r)
|
|
pygame.draw.circle(scr, black, (cx - r // 3, cy - r // 3), r // 2)
|
|
elif b[y][x] == 2:
|
|
pygame.draw.circle(scr, white, (cx, cy), r)
|
|
pygame.draw.circle(scr, (200, 200, 200), (cx - r // 3, cy - r // 3), r // 2)
|
|
|
|
def check_win(x, y, who):
|
|
def count(dx, dy):
|
|
c = 1
|
|
for d in [1, -1]:
|
|
nx, ny = x, y
|
|
while True:
|
|
nx += dx * d
|
|
ny += dy * d
|
|
if 0 <= nx < n and 0 <= ny < n and b[ny][nx] == who:
|
|
c += 1
|
|
else:
|
|
break
|
|
return c
|
|
for dx, dy in [(1, 0), (0, 1), (1, 1), (1, -1)]:
|
|
if count(dx, dy) >= 5:
|
|
return True
|
|
return False
|
|
|
|
def draw_text_center(text, font, color, y_offset=0):
|
|
t = font.render(text, True, color)
|
|
r = t.get_rect(center=(w // 2, h // 2 + y_offset))
|
|
scr.blit(t, r)
|
|
return r
|
|
|
|
def draw_menu():
|
|
scr.fill((240, 220, 180))
|
|
draw_text_center("五子棋小游戏", f_big, black, -150)
|
|
start_btn = draw_text_center("开始游戏", f_small, black, -20)
|
|
rank_btn = draw_text_center("查看排行榜", f_small, black, 50)
|
|
quit_btn = draw_text_center("退出游戏", f_small, red, 120)
|
|
return start_btn, rank_btn, quit_btn
|
|
|
|
def draw_name_input():
|
|
scr.fill((220, 220, 200))
|
|
draw_text_center("输入玩家姓名", f_big, black, -200)
|
|
for box in input_boxes:
|
|
box.draw(scr)
|
|
draw_text_center("按 Enter 开始游戏", f_button, red, 150)
|
|
|
|
def draw_win_message():
|
|
winner = player1 if p == 2 else player2
|
|
draw_text_center(f"{winner} 获胜!点击任意处重新开始", f_small, black)
|
|
|
|
def draw_rankings():
|
|
scr.fill((240, 240, 220))
|
|
draw_text_center("排行榜", f_big, black, -300)
|
|
y = 150
|
|
for name, data in sorted(rankings.items(), key=lambda x: x[1]["wins"], reverse=True):
|
|
win = data["wins"]
|
|
games = data["games"]
|
|
rate = (win / games * 100) if games else 0
|
|
txt = f"{name}: 胜场 {win} / 对局 {games} - 胜率 {rate:.1f}%"
|
|
t = f_small.render(txt, True, black)
|
|
scr.blit(t, (100, y))
|
|
y += 50
|
|
draw_text_center("点击任意处返回菜单", f_small, red, 350)
|
|
|
|
while True:
|
|
scr.fill(bg)
|
|
|
|
if game_state == "menu":
|
|
start_btn, rank_btn, quit_btn = draw_menu()
|
|
elif game_state == "input_name":
|
|
draw_name_input()
|
|
elif game_state == "play":
|
|
draw_board()
|
|
draw_chess()
|
|
if over:
|
|
draw_win_message()
|
|
elif game_state == "rank":
|
|
draw_rankings()
|
|
|
|
pygame.display.flip()
|
|
|
|
for e in pygame.event.get():
|
|
if e.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
if game_state == "menu":
|
|
if e.type == pygame.MOUSEBUTTONDOWN:
|
|
mx, my = pygame.mouse.get_pos()
|
|
if start_btn.collidepoint(mx, my):
|
|
game_state = "input_name"
|
|
elif rank_btn.collidepoint(mx, my):
|
|
game_state = "rank"
|
|
elif quit_btn.collidepoint(mx, my):
|
|
pygame.quit()
|
|
sys.exit()
|
|
elif game_state == "input_name":
|
|
for box in input_boxes:
|
|
box.handle_event(e)
|
|
if e.type == pygame.KEYDOWN and e.key == pygame.K_RETURN:
|
|
player1 = input_boxes[0].text.strip() or "玩家1"
|
|
player2 = input_boxes[1].text.strip() or "玩家2"
|
|
b = [[0 for _ in range(n)] for _ in range(n)]
|
|
p = 1
|
|
over = False
|
|
game_state = "play"
|
|
elif game_state == "play":
|
|
if e.type == pygame.MOUSEBUTTONDOWN:
|
|
mx, my = pygame.mouse.get_pos()
|
|
if w - 160 <= mx <= w - 20 and 20 <= my <= 60:
|
|
b = [[0 for _ in range(n)] for _ in range(n)]
|
|
p = 1
|
|
over = False
|
|
game_state = "menu"
|
|
elif not over:
|
|
gx, gy = mx // s, my // s
|
|
if 0 <= gx < n and 0 <= gy < n and b[gy][gx] == 0:
|
|
b[gy][gx] = p
|
|
if place_sound:
|
|
place_sound.play()
|
|
if check_win(gx, gy, p):
|
|
winner = player1 if p == 1 else player2
|
|
loser = player2 if p == 1 else player1
|
|
record_win(winner)
|
|
record_loss(loser)
|
|
save_rankings(rankings)
|
|
over = True
|
|
else:
|
|
p = 2 if p == 1 else 1
|
|
elif over:
|
|
b = [[0 for _ in range(n)] for _ in range(n)]
|
|
p = 1
|
|
over = False
|
|
elif game_state == "rank":
|
|
if e.type == pygame.MOUSEBUTTONDOWN:
|
|
game_state = "menu"
|