import pygame
import sys
import random
import time
import math
from pygame import gfxdraw
from typing import List, Tuple, Optional, Dict, Any
import json
import os
Инициализация pygame
pygame.init()
pygame.mixer.init()
Константы
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 700
BOARD_SIZE = 8
CELL_SIZE = 70
BOARD_OFFSET_X = (SCREEN_WIDTH - BOARD_SIZE * CELL_SIZE) // 2
BOARD_OFFSET_Y = 100
ANIMATION_SPEED = 0.2
FPS = 60
Цвета
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (34, 139, 34)
DARK_GREEN = (0, 100, 0)
GOLD = (255, 215, 0)
SILVER = (192, 192, 192)
BLUE = (30, 144, 255)
RED = (255, 69, 0)
GRAY = (128, 128, 128)
LIGHT_GRAY = (200, 200, 200)
Состояния игры
MENU = 0
PLAYING = 1
GAME_OVER = 2
SETTINGS = 3
class Particle:
def init(self, x: float, y: float, color: Tuple[int, int, int]):
self.x = x
self.y = y
self.color = color
self.size = random.randint(2, 5)
self.speed = random.uniform(1, 3)
self.angle = random.uniform(0, math.pi * 2)
self.life = random.uniform(0.5, 1.5)
self.age = 0
def update(self, dt: float) -> bool:
self.x += math.cos(self.angle) * self.speed * dt * 60
self.y += math.sin(self.angle) * self.speed * dt * 60
self.age += dt
return self.age < self.life
def draw(self, screen: pygame.Surface):
alpha = max(0, int(255 * (1 - self.age / self.life)))
color = (*self.color[:3], alpha)
pygame.gfxdraw.filled_circle(
screen,
int(self.x),
int(self.y),
int(self.size),
color
)
class Animation:
def init(self):
self.particles: List[Particle] = []
self.flip_animations: Dict[Tuple[int, int], float] = {}
self.last_flip_time: float = 0
self.move_indicator_pos: Optional[Tuple[int, int]] = None
self.move_indicator_alpha: int = 0
self.move_indicator_dir: int = 1
def add_flip_animation(self, row: int, col: int):
self.flip_animations[(row, col)] = 0
self.last_flip_time = time.time()
def add_particles(self, x: float, y: float, color: Tuple[int, int, int], count: int = 10):
for _ in range(count):
self.particles.append(Particle(x, y, color))
def update(self, dt: float):
Обновление частиц
self.particles = [p for p in self.particles if p.update(dt)]
Обновление анимации переворота
current_time = time.time()
if current_time - self.last_flip_time > ANIMATION_SPEED:
self.flip_animations = {}
Обновление индикатора хода
if self.move_indicator_pos:
self.move_indicator_alpha += self.move_indicator_dir * 150 * dt
if self.move_indicator_alpha > 255:
self.move_indicator_alpha = 255
self.move_indicator_dir = -1
elif self.move_indicator_alpha < 0:
self.move_indicator_alpha = 0
self.move_indicator_dir = 1
def draw(self, screen: pygame.Surface):
Рисование частиц
for particle in self.particles:
particle.draw(screen)
Рисование индикатора хода
if self.move_indicator_pos:
row, col = self.move_indicator_pos
x = BOARD_OFFSET_X + col * CELL_SIZE + CELL_SIZE // 2
y = BOARD_OFFSET_Y + row * CELL_SIZE + CELL_SIZE // 2
radius = int(CELL_SIZE * 0.3)
color = (*BLUE, self.move_indicator_alpha)
pygame.gfxdraw.filled_circle(screen, x, y, radius, color)
class SoundManager:
def init(self):
self.sounds: Dict[str, pygame.mixer.Sound] = {}
self.enabled = True
self.load_sounds()
def load_sounds(self):
try:
self.sounds["place"] = pygame.mixer.Sound(os.path.join("sounds", "place.wav"))
self.sounds["flip"] = pygame.mixer.Sound(os.path.join("sounds", "flip.wav"))
self.sounds["win"] = pygame.mixer.Sound(os.path.join("sounds", "win.wav"))
self.sounds["lose"] = pygame.mixer.Sound(os.path.join("sounds", "lose.wav"))
self.sounds["draw"] = pygame.mixer.Sound(os.path.join("sounds", "draw.wav"))
self.sounds["click"] = pygame.mixer.Sound(os.path.join("sounds", "click.wav"))
except:
Создаем простые звуки программно, если файлы не найдены
self.create_default_sounds()
def create_default_sounds(self):
Простые звуки для демонстрации
def make_beep(sound: pygame.mixer.Sound, frequency: int, duration: float):
sample_rate = 44100
samples = int(sample_rate * duration)
buffer = numpy.zeros((samples, 2), dtype=numpy.int16)
for i in range(samples):
t = float(i) / sample_rate
val = int(32767 * math.sin(2 * math.pi * frequency * t))
buffer[i][0] = val
buffer[i][1] = val
sound = pygame.mixer.Sound(buffer)
sound.set_volume(0.3)
return sound
try:
import numpy
self.sounds["place"] = make_beep("place", 440, 0.1)
self.sounds["flip"] = make_beep("flip", 220, 0.05)
self.sounds["win"] = make_beep("win", 880, 0.5)
self.sounds["lose"] = make_beep("lose", 110, 0.5)
self.sounds["draw"] = make_beep("draw", 440, 0.3)
self.sounds["click"] = make_beep("click", 660, 0.05)
except:
self.enabled = False
def play(self, sound_name: str):
if self.enabled and sound_name in self.sounds:
self.sounds[sound_name].play()
class ReversiGame:
def init(self):
self.board: List[List[Optional[int]]] = [[None for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
self.current_player: int = 1 # 1 - черные, 2 - белые
self.game_state: int = MENU
self.animation = Animation()
self.sound = SoundManager()
self.player_names = ["Черные", "Белые"]
self.scores = [2, 2]
self.valid_moves: List[Tuple[int, int]] = []
self.game_mode = "pvp" # 'pvp', 'pve', 'eve'
self.ai_difficulty = "medium" # 'easy', 'medium', 'hard'
self.theme = "classic" # 'classic', 'wood', 'modern'
self.board_colors = {
"classic": (GREEN, DARK_GREEN),
"wood": ((160, 82, 45), (139, 69, 19)),
"modern": ((70, 130, 180), (100, 149, 237))
}
self.piece_colors = {
"classic": (BLACK, WHITE),
"wood": ((50, 50, 50), (220, 220, 220)),
"modern": ((30, 30, 30), (240, 240, 240))
}
self.hint_enabled = True
self.undo_stack: List[Dict[str, Any]] = []
self.redo_stack: List[Dict[str, Any]] = []
self.move_history: List[str] = []
self.game_start_time: float = 0
self.game_duration: float = 0
self.player_times = [0, 0]
self.last_move_time: float = 0
self.winner: Optional[int] = None
self.consecutive_passes = 0
self.font_small = pygame.font.SysFont("Arial", 16)
self.font_medium = pygame.font.SysFont("Arial", 24)
self.font_large = pygame.font.SysFont("Arial", 48)
self.load_settings()
def load_settings(self):
try:
with open("reversi_settings.json", "r") as f:
settings = json.load(f)
self.player_names = settings.get("player_names", self.player_names)
self.game_mode = settings.get("game_mode", self.game_mode)
self.ai_difficulty = settings.get("ai_difficulty", self.ai_difficulty)
self.theme = settings.get("theme", self.theme)
self.hint_enabled = settings.get("hint_enabled", self.hint_enabled)
self.sound.enabled = settings.get("sound_enabled", self.sound.enabled)
except:
pass
def save_settings(self):
settings = {
"player_names": self.player_names,
"game_mode": self.game_mode,
"ai_difficulty": self.ai_difficulty,
"theme": self.theme,
"hint_enabled": self.hint_enabled,
"sound_enabled": self.sound.enabled
}
with open("reversi_settings.json", "w") as f:
json.dump(settings, f)
def reset_game(self):
self.board = [[None for _ in range(BOARD_SIZE)] for _ in range(BOARD_SIZE)]
self.board[3][3] = 2
self.board[3][4] = 1
self.board[4][3] = 1
self.board[4][4] = 2
self.current_player = 1
self.scores = [2, 2]
self.valid_moves = self.get_valid_moves(self.current_player)
self.undo_stack = []
self.redo_stack = []
self.move_history = []
self.game_start_time = time.time()
self.game_duration = 0
self.player_times = [0, 0]
self.last_move_time = time.time()
self.winner = None
self.consecutive_passes = 0
self.animation = Animation()
self.game_state = PLAYING
def get_valid_moves(self, player: int) -> List[Tuple[int, int]]:
valid_moves = []
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
if self.board[row][col] is None and self.is_valid_move(row, col, player):
valid_moves.append((row, col))
return valid_moves
def is_valid_move(self, row: int, col: int, player: int) -> bool:
if self.board[row][col] is not None:
return False
opponent = 3 - player
directions = [(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)]
for dr, dc in directions:
r, c = row + dr, col + dc
if 0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE and self.board[r][c] == opponent:
r += dr
c += dc
while 0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE and self.board[r][c] == opponent:
r += dr
c += dc
if 0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE and self.board[r][c] == player:
return True
return False
def make_move(self, row: int, col: int) -> bool:
if not self.is_valid_move(row, col, self.current_player):
return False
Сохраняем состояние для возможного отмены
self.save_state()
self.board[row][col] = self.current_player
self.sound.play("place")
self.animation.add_particles(
BOARD_OFFSET_X + col * CELL_SIZE + CELL_SIZE // 2,
BOARD_OFFSET_Y + row * CELL_SIZE + CELL_SIZE // 2,
self.piece_colors[self.theme][self.current_player - 1],
20
)
opponent = 3 - self.current_player
directions = [(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)]
flipped = 0
for dr, dc in directions:
r, c = row + dr, col + dc
to_flip = []
while 0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE and self.board[r][c] == opponent:
to_flip.append((r, c))
r += dr
c += dc
if 0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE and self.board[r][c] == self.current_player and to_flip:
for (fr, fc) in to_flip:
self.board[fr][fc] = self.current_player
self.animation.add_flip_animation(fr, fc)
self.sound.play("flip")
flipped += len(to_flip)
self.scores[self.current_player - 1] += flipped + 1
self.scores[opponent - 1] -= flipped
Обновляем историю ходов
move_str = f"{'Черные' if self.current_player == 1 else 'Белые'} -> {chr(ord('A') + col)}{row + 1}"
self.move_history.append(move_str)
Обновляем время игрока
current_time = time.time()
self.player_times[self.current_player - 1] += current_time - self.last_move_time
self.last_move_time = current_time
Переключаем игрока
self.current_player = opponent
self.valid_moves = self.get_valid_moves(self.current_player)
Проверяем условия окончания игры
if not self.valid_moves:
self.consecutive_passes += 1
if self.consecutive_passes >= 2:
self.end_game()
else:
Пропуск хода
self.current_player = opponent
self.valid_moves = self.get_valid_moves(self.current_player)
if not self.valid_moves:
self.end_game()
else:
self.consecutive_passes = 0
Если следующий ход ИИ, делаем его
if self.game_state == PLAYING and self.game_mode != "pvp":
if (self.game_mode == "pve" and self.current_player == 2) or self.game_mode == "eve":
self.ai_move()
return True
def ai_move(self):
if not self.valid_moves:
return
Простое ИИ
if self.ai_difficulty == "easy":
row, col = random.choice(self.valid_moves)
elif self.ai_difficulty == "medium":
Предпочитаем углы и края
best_score = -1
best_moves = []
for row, col in self.valid_moves:
score = 0
Углы самые ценные
if (row == 0 or row == BOARD_SIZE - 1) and (col == 0 or col == BOARD_SIZE - 1):
score += 10
Края тоже хороши
elif row == 0 or row == BOARD_SIZE - 1 or col == 0 or col == BOARD_SIZE - 1:
score += 2
Избегаем клеток рядом с углами (если угол не занят)
for corner_row, corner_col in [(0, 0), (0, BOARD_SIZE-1), (BOARD_SIZE-1, 0), (BOARD_SIZE-1, BOARD_SIZE-1)]:
if self.board[corner_row][corner_col] is None:
if abs(row - corner_row) <= 1 and abs(col - corner_col) <= 1:
score -= 5
if score > best_score:
best_score = score
best_moves = [(row, col)]
elif score == best_score:
best_moves.append((row, col))
row, col = random.choice(best_moves)
else: # hard
Минимакс с альфа-бета отсечением
def evaluate_board():
Простая оценка - разница в количестве фишек
return self.scores[1] - self.scores[0] # Белые - Черные
def minimax(depth: int, alpha: float, beta: float, maximizing_player: bool) -> float:
if depth == 0 or not self.valid_moves:
return evaluate_board()
if maximizing_player:
max_eval = -float('inf')
for row, col in self.valid_moves:
Сохраняем состояние
old_board = [r[:] for r in self.board]
old_scores = self.scores[:]
old_player = self.current_player
Делаем ход
self.make_move(row, col)
eval = minimax(depth - 1, alpha, beta, False)
Восстанавливаем состояние
self.board = old_board
self.scores = old_scores
self.current_player = old_player
self.valid_moves = self.get_valid_moves(self.current_player)
max_eval = max(max_eval, eval)
alpha = max(alpha, eval)
if beta <= alpha:
break
return max_eval
else:
min_eval = float('inf')
for row, col in self.valid_moves:
Сохраняем состояние
old_board = [r[:] for r in self.board]
old_scores = self.scores[:]
old_player = self.current_player
Делаем ход
self.make_move(row, col)
eval = minimax(depth - 1, alpha, beta, True)
Восстанавливаем состояние
self.board = old_board
self.scores = old_scores
self.current_player = old_player
self.valid_moves = self.get_valid_moves(self.current_player)
min_eval = min(min_eval, eval)
beta = min(beta, eval)
if beta <= alpha:
break
return min_eval
best_score = -float('inf')
best_moves = []
for row, col in self.valid_moves:
Сохраняем состояние
old_board = [r[:] for r in self.board]
old_scores = self.scores[:]
old_player = self.current_player
Делаем ход
self.make_move(row, col)
score = minimax(3, -float('inf'), float('inf'), False)
Восстанавливаем состояние
self.board = old_board
self.scores = old_scores
self.current_player = old_player
self.valid_moves = self.get_valid_moves(self.current_player)
if score > best_score:
best_score = score
best_moves = [(row, col)]
elif score == best_score:
best_moves.append((row, col))
row, col = random.choice(best_moves)
Добавляем небольшую задержку для реалистичности
pygame.time.delay(500)
self.make_move(row, col)
def end_game(self):
self.game_state = GAME_OVER
self.game_duration = time.time() - self.game_start_time
if self.scores[0] > self.scores[1]:
self.winner = 1
self.sound.play("win")
elif self.scores[1] > self.scores[0]:
self.winner = 2
self.sound.play("lose")
else:
self.winner = 0
self.sound.play("draw")
def save_state(self):
state = {
"board": [row[:] for row in self.board],
"current_player": self.current_player,
"scores": self.scores[:],
"valid_moves": self.valid_moves[:],
"move_history": self.move_history[:],
"consecutive_passes": self.consecutive_passes
}
self.undo_stack.append(state)
self.redo_stack = []
def undo(self):
if not self.undo_stack:
return
Сохраняем текущее состояние в стек redo
current_state = {
"board": [row[:] for row in self.board],
"current_player": self.current_player,
"scores": self.scores[:],
"valid_moves": self.valid_moves[:],
"move_history": self.move_history[:],
"consecutive_passes": self.consecutive_passes
}
self.redo_stack.append(current_state)
Восстанавливаем предыдущее состояние
state = self.undo_stack.pop()
self.board = [row[:] for row in state["board"]]
self.current_player = state["current_player"]
self.scores = state["scores"][:]
self.valid_moves = state["valid_moves"][:]
self.move_history = state["move_history"][:]
self.consecutive_passes = state["consecutive_passes"]
Обновляем время последнего хода
self.last_move_time = time.time()
Если игра была окончена, возвращаемся в состояние игры
if self.game_state == GAME_OVER:
self.game_state = PLAYING
self.winner = None
def redo(self):
if not self.redo_stack:
return
Сохраняем текущее состояние в стек undo
current_state = {
"board": [row[:] for row in self.board],
"current_player": self.current_player,
"scores": self.scores[:],
"valid_moves": self.valid_moves[:],
"move_history": self.move_history[:],
"consecutive_passes": self.consecutive_passes
}
self.undo_stack.append(current_state)
Восстанавливаем следующее состояние
state = self.redo_stack.pop()
self.board = [row[:] for row in state["board"]]
self.current_player = state["current_player"]
self.scores = state["scores"][:]
self.valid_moves = state["valid_moves"][:]
self.move_history = state["move_history"][:]
self.consecutive_passes = state["consecutive_passes"]
Обновляем время последнего хода
self.last_move_time = time.time()
Если игра была окончена, возвращаемся в состояние игры
if self.game_state == GAME_OVER:
self.game_state = PLAYING
self.winner = None
def draw(self, screen: pygame.Surface):
screen.fill(BLACK)
if self.game_state == MENU:
self.draw_menu(screen)
elif self.game_state == PLAYING or self.game_state == GAME_OVER:
self.draw_board(screen)
self.draw_info_panel(screen)
if self.game_state == GAME_OVER:
self.draw_game_over(screen)
elif self.game_state == SETTINGS:
self.draw_settings(screen)
self.animation.draw(screen)
def draw_menu(self, screen: pygame.Surface):
title = self.font_large.render("REVERSI DELUXE", True, GOLD)
screen.blit(title, (SCREEN_WIDTH // 2 - title.get_width() // 2, 100))
Кнопки
button_y = 250
button_height = 50
button_width = 300
button_margin = 20
Игрок vs Игрок
pygame.draw.rect(screen, GREEN,
(SCREEN_WIDTH // 2 - button_width // 2, button_y, button_width, button_height))
text = self.font_medium.render("Игрок vs Игрок", True, WHITE)
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, button_y + button_height // 2 - text.get_height() // 2))
button_y += button_height + button_margin
Игрок vs Компьютер
pygame.draw.rect(screen, GREEN,
(SCREEN_WIDTH // 2 - button_width // 2, button_y, button_width, button_height))
text = self.font_medium.render("Игрок vs Компьютер", True, WHITE)
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, button_y + button_height // 2 - text.get_height() // 2))
button_y += button_height + button_margin
Компьютер vs Компьютер
pygame.draw.rect(screen, GREEN,
(SCREEN_WIDTH // 2 - button_width // 2, button_y, button_width, button_height))
text = self.font_medium.render("Компьютер vs Компьютер", True, WHITE)
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, button_y + button_height // 2 - text.get_height() // 2))
button_y += button_height + button_margin
Настройки
pygame.draw.rect(screen, BLUE,
(SCREEN_WIDTH // 2 - button_width // 2, button_y, button_width, button_height))
text = self.font_medium.render("Настройки", True, WHITE)
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, button_y + button_height // 2 - text.get_height() // 2))
button_y += button_height + button_margin
Выход
pygame.draw.rect(screen, RED,
(SCREEN_WIDTH // 2 - button_width // 2, button_y, button_width, button_height))
text = self.font_medium.render("Выход", True, WHITE)
screen.blit(text, (SCREEN_WIDTH // 2 - text.get_width() // 2, button_y + button_height // 2 - text.get_height() // 2))
def draw_board(self, screen: pygame.Surface):
Рисуем доску
light, dark = self.board_colors[self.theme]
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
color = light if (row + col) % 2 == 0 else dark
pygame.draw.rect(screen, color,
(BOARD_OFFSET_X + col * CELL_SIZE,
BOARD_OFFSET_Y + row * CELL_SIZE,
CELL_SIZE, CELL_SIZE))
Рисуем подсказки для допустимых ходов
if self.hint_enabled and self.game_state == PLAYING:
for row, col in self.valid_moves:
x = BOARD_OFFSET_X + col * CELL_SIZE + CELL_SIZE // 2
y = BOARD_OFFSET_Y + row * CELL_SIZE + CELL_SIZE // 2
radius = int(CELL_SIZE * 0.2)
pygame.gfxdraw.filled_circle(screen, x, y, radius, (*BLUE, 100))
Рисуем фишки
for row in range(BOARD_SIZE):
for col in range(BOARD_SIZE):
if self.board[row][col] is not None:
x = BOARD_OFFSET_X + col * CELL_SIZE + CELL_SIZE // 2
y = BOARD_OFFSET_Y + row * CELL_SIZE + CELL_SIZE // 2
radius = int(CELL_SIZE * 0.4)
Анимация переворота
if (row, col) in self.animation.flip_animations:
progress = self.animation.flip_animations[(row, col)]
progress = min(1.0, progress + 0.1)
self.animation.flip_animations[(row, col)] = progress
if progress < 1.0:
Рисуем частично перевернутую фишку
player = self.board[row][col]
opponent = 3 - player
Определяем, какая часть фишки видна
if progress < 0.5:
Показываем больше старого цвета
main_color = self.piece_colors[self.theme][opponent - 1]
secondary_color = self.piece_colors[self.theme][player - 1]
ratio = progress / 0.5
else:
Показываем больше нового цвета
main_color = self.piece_colors[self.theme][player - 1]
secondary_color = self.piece_colors[self.theme][opponent - 1]
ratio = (progress - 0.5) / 0.5
Рисуем основную часть
pygame.gfxdraw.filled_circle(screen, x, y, radius, main_color)
pygame.gfxdraw.aacircle(screen, x, y, radius, main_color)
Рисуем переворачивающуюся часть
if progress < 0.5:
Старый цвет сверху
start_angle = math.pi * (0.5 - ratio)
end_angle = math.pi * (0.5 + ratio)
else:
Новый цвет сверху
start_angle = math.pi * (1.5 - ratio)
end_angle = math.pi * (1.5 + ratio)
pygame.gfxdraw.pie(screen, x, y, radius,
int(math.degrees(start_angle)),
int(math.degrees(end_angle)),
secondary_color)
continue
Обычная фишка
player = self.board[row][col]
color = self.piece_colors[self.theme][player - 1]
pygame.gfxdraw.filled_circle(screen, x, y, radius, color)
pygame.gfxdraw.aacircle(screen, x, y, radius, color)
Эффект "блика" на фишке
highlight_radius = int(radius * 0.6)
highlight_offset = int(radius * 0.3)
highlight_color = (min(color[0] + 50, 255),
min(color[1] + 50, 255),
min(color[2] + 50, 255))
pygame.gfxdraw.filled_circle(screen,
x - highlight_offset,
y - highlight_offset,
highlight_radius,
highlight_color)
Рисуем сетку
for i in range(BOARD_SIZE + 1):
Горизонтальные линии
pygame.draw.line(screen, BLACK,
(BOARD_OFFSET_X, BOARD_OFFSET_Y + i * CELL_SIZE),
(BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE, BOARD_OFFSET_Y + i * CELL_SIZE), 2)
Вертикальные линии
pygame.draw.line(screen, BLACK,
(BOARD_OFFSET_X + i * CELL_SIZE, BOARD_OFFSET_Y),
(BOARD_OFFSET_X + i * CELL_SIZE, BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE), 2)
Буквы и цифры для обозначения клеток
for i in range(BOARD_SIZE):
Буквы (A-H)
letter = chr(ord('A') + i)
text = self.font_small.render(letter, True, WHITE)
screen.blit(text, (BOARD_OFFSET_X + i * CELL_SIZE + CELL_SIZE // 2 - text.get_width() // 2,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 5))
Цифры (1-8)
number = str(i + 1)
text = self.font_small.render(number, True, WHITE)
screen.blit(text, (BOARD_OFFSET_X - 20,
BOARD_OFFSET_Y + i * CELL_SIZE + CELL_SIZE // 2 - text.get_height() // 2))
def draw_info_panel(self, screen: pygame.Surface):
Панель информации сверху
pygame.draw.rect(screen, DARK_GREEN, (0, 0, SCREEN_WIDTH, BOARD_OFFSET_Y - 10))
Имена игроков и счет
black_name = self.font_medium.render(f"{self.player_names[0]}: {self.scores[0]}", True, WHITE)
white_name = self.font_medium.render(f"{self.player_names[1]}: {self.scores[1]}", True, WHITE)
screen.blit(black_name, (50, 20))
screen.blit(white_name, (SCREEN_WIDTH - 50 - white_name.get_width(), 20))
Индикатор текущего игрока
if self.game_state == PLAYING:
indicator_text = self.font_medium.render("Ход:", True, WHITE)
screen.blit(indicator_text, (SCREEN_WIDTH // 2 - indicator_text.get_width() // 2 - 40, 20))
indicator_color = self.piece_colors[self.theme][self.current_player - 1]
pygame.gfxdraw.filled_circle(screen,
SCREEN_WIDTH // 2 + 20,
20 + indicator_text.get_height() // 2,
15,
indicator_color)
pygame.gfxdraw.aacircle(screen,
SCREEN_WIDTH // 2 + 20,
20 + indicator_text.get_height() // 2,
15,
indicator_color)
Время игры
if self.game_state == PLAYING:
game_time = time.time() - self.game_start_time
else:
game_time = self.game_duration
minutes = int(game_time // 60)
seconds = int(game_time % 60)
time_text = self.font_small.render(f"Время: {minutes:02d}:{seconds:02d}", True, WHITE)
screen.blit(time_text, (SCREEN_WIDTH // 2 - time_text.get_width() // 2, 50))
Время игроков
if self.game_state == PLAYING:
current_time = time.time()
if self.last_move_time:
self.player_times[self.current_player - 1] += current_time - self.last_move_time
self.last_move_time = current_time
for i in range(2):
minutes = int(self.player_times[i] // 60)
seconds = int(self.player_times[i] % 60)
player_time = self.font_small.render(f"{minutes:02d}:{seconds:02d}", True, WHITE)
if i == 0:
screen.blit(player_time, (50, 50))
else:
screen.blit(player_time, (SCREEN_WIDTH - 50 - player_time.get_width(), 50))
Кнопки управления
button_width = 100
button_height = 30
button_margin = 10
Кнопка "Меню"
pygame.draw.rect(screen, BLUE,
(SCREEN_WIDTH - button_width - 20, BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20,
button_width, button_height))
menu_text = self.font_small.render("Меню", True, WHITE)
screen.blit(menu_text,
(SCREEN_WIDTH - button_width - 20 + button_width // 2 - menu_text.get_width() // 2,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20 + button_height // 2 - menu_text.get_height() // 2))
Кнопка "Отменить"
if self.undo_stack:
pygame.draw.rect(screen, BLUE,
(SCREEN_WIDTH - 2 * button_width - 20 - button_margin,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20,
button_width, button_height))
undo_text = self.font_small.render("Отменить", True, WHITE)
screen.blit(undo_text,
(SCREEN_WIDTH - 2 * button_width - 20 - button_margin + button_width // 2 - undo_text.get_width() // 2,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20 + button_height // 2 - undo_text.get_height() // 2))
Кнопка "Повторить"
if self.redo_stack:
pygame.draw.rect(screen, BLUE,
(SCREEN_WIDTH - 3 * button_width - 20 - 2 * button_margin,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20,
button_width, button_height))
redo_text = self.font_small.render("Повторить", True, WHITE)
screen.blit(redo_text,
(SCREEN_WIDTH - 3 * button_width - 20 - 2 * button_margin + button_width // 2 - redo_text.get_width() // 2,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20 + button_height // 2 - redo_text.get_height() // 2))
def draw_game_over(self, screen: pygame.Surface):
Затемнение экрана
overlay = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 180))
screen.blit(overlay, (0, 0))
Окно с результатом
result_width = 400
result_height = 200
pygame.draw.rect(screen, DARK_GREEN,
(SCREEN_WIDTH // 2 - result_width // 2,
SCREEN_HEIGHT // 2 - result_height // 2,
result_width, result_height))
pygame.draw.rect(screen, GOLD,
(SCREEN_WIDTH // 2 - result_width // 2,
SCREEN_HEIGHT // 2 - result_height // 2,
result_width, result_height), 3)
Текст результата
if self.winner == 0:
result_text = self.font_large.render("Ничья!", True, GOLD)
else:
winner_name = self.player_names[self.winner - 1]
result_text = self.font_large.render(f"Победитель: {winner_name}", True, GOLD)
screen.blit(result_text,
(SCREEN_WIDTH // 2 - result_text.get_width() // 2,
SCREEN_HEIGHT // 2 - result_height // 2 + 30))
Счет
score_text = self.font_medium.render(f"{self.scores[0]} - {self.scores[1]}", True, WHITE)
screen.blit(score_text,
(SCREEN_WIDTH // 2 - score_text.get_width() // 2,
SCREEN_HEIGHT // 2 - score_text.get_height() // 2 + 10))
Кнопки
button_width = 120
button_height = 40
button_margin = 20
Кнопка "Новая игра"
pygame.draw.rect(screen, GREEN,
(SCREEN_WIDTH // 2 - button_width - button_margin // 2,
SCREEN_HEIGHT // 2 + result_height // 2 - button_height - 20,
button_width, button_height))
new_game_text = self.font_medium.render("Новая игра", True, WHITE)
screen.blit(new_game_text,
(SCREEN_WIDTH // 2 - button_width - button_margin // 2 + button_width // 2 - new_game_text.get_width() // 2,
SCREEN_HEIGHT // 2 + result_height // 2 - button_height - 20 + button_height // 2 - new_game_text.get_height() // 2))
Кнопка "Меню"
pygame.draw.rect(screen, BLUE,
(SCREEN_WIDTH // 2 + button_margin // 2,
SCREEN_HEIGHT // 2 + result_height // 2 - button_height - 20,
button_width, button_height))
menu_text = self.font_medium.render("Меню", True, WHITE)
screen.blit(menu_text,
(SCREEN_WIDTH // 2 + button_margin // 2 + button_width // 2 - menu_text.get_width() // 2,
SCREEN_HEIGHT // 2 + result_height // 2 - button_height - 20 + button_height // 2 - menu_text.get_height() // 2))
def draw_settings(self, screen: pygame.Surface):
screen.fill(DARK_GREEN)
title = self.font_large.render("Настройки", True, GOLD)
screen.blit(title, (SCREEN_WIDTH // 2 - title.get_width() // 2, 50))
Настройки игроков
player_settings_y = 150
setting_margin = 50
Имя игрока 1
player1_text = self.font_medium.render("Имя игрока 1 (Черные):", True, WHITE)
screen.blit(player1_text, (100, player_settings_y))
pygame.draw.rect(screen, WHITE, (400, player_settings_y, 200, 30), 2)
player1_name = self.font_medium.render(self.player_names[0], True, WHITE)
screen.blit(player1_name, (410, player_settings_y + 2))
Имя игрока 2
player2_text = self.font_medium.render("Имя игрока 2 (Белые):", True, WHITE)
screen.blit(player2_text, (100, player_settings_y + setting_margin))
pygame.draw.rect(screen, WHITE, (400, player_settings_y + setting_margin, 200, 30), 2)
player2_name = self.font_medium.render(self.player_names[1], True, WHITE)
screen.blit(player2_name, (410, player_settings_y + setting_margin + 2))
Сложность ИИ
difficulty_text = self.font_medium.render("Сложность ИИ:", True, WHITE)
screen.blit(difficulty_text, (100, player_settings_y + 2 * setting_margin))
difficulty_options = ["Легкая", "Средняя", "Сложная"]
for i, option in enumerate(difficulty_options):
color = GREEN if (i == 0 and self.ai_difficulty == "easy") or \
(i == 1 and self.ai_difficulty == "medium") or \
(i == 2 and self.ai_difficulty == "hard") else BLUE
pygame.draw.rect(screen, color, (400 + i * 70, player_settings_y + 2 * setting_margin, 65, 30))
option_text = self.font_small.render(option, True, WHITE)
screen.blit(option_text, (400 + i * 70 + 32 - option_text.get_width() // 2,
player_settings_y + 2 * setting_margin + 15 - option_text.get_height() // 2))
Тема
theme_text = self.font_medium.render("Тема:", True, WHITE)
screen.blit(theme_text, (100, player_settings_y + 3 * setting_margin))
theme_options = ["Классика", "Дерево", "Модерн"]
for i, option in enumerate(theme_options):
color = GREEN if (i == 0 and self.theme == "classic") or \
(i == 1 and self.theme == "wood") or \
(i == 2 and self.theme == "modern") else BLUE
pygame.draw.rect(screen, color, (400 + i * 70, player_settings_y + 3 * setting_margin, 65, 30))
option_text = self.font_small.render(option, True, WHITE)
screen.blit(option_text, (400 + i * 70 + 32 - option_text.get_width() // 2,
player_settings_y + 3 * setting_margin + 15 - option_text.get_height() // 2))
Другие настройки
other_settings_y = player_settings_y + 4 * setting_margin
Подсказки
hints_text = self.font_medium.render("Показывать подсказки:", True, WHITE)
screen.blit(hints_text, (100, other_settings_y))
hints_color = GREEN if self.hint_enabled else RED
hints_status = "Вкл" if self.hint_enabled else "Выкл"
pygame.draw.rect(screen, hints_color, (400, other_settings_y, 65, 30))
status_text = self.font_small.render(hints_status, True, WHITE)
screen.blit(status_text, (400 + 32 - status_text.get_width() // 2,
other_settings_y + 15 - status_text.get_height() // 2))
Звук
sound_text = self.font_medium.render("Звук:", True, WHITE)
screen.blit(sound_text, (100, other_settings_y + setting_margin))
sound_color = GREEN if self.sound.enabled else RED
sound_status = "Вкл" if self.sound.enabled else "Выкл"
pygame.draw.rect(screen, sound_color, (400, other_settings_y + setting_margin, 65, 30))
sound_status_text = self.font_small.render(sound_status, True, WHITE)
screen.blit(sound_status_text, (400 + 32 - sound_status_text.get_width() // 2,
other_settings_y + setting_margin + 15 - sound_status_text.get_height() // 2))
Кнопки
button_width = 150
button_height = 40
button_margin = 20
Кнопка "Сохранить"
pygame.draw.rect(screen, GREEN,
(SCREEN_WIDTH // 2 - button_width - button_margin // 2,
SCREEN_HEIGHT - 100,
button_width, button_height))
save_text = self.font_medium.render("Сохранить", True, WHITE)
screen.blit(save_text,
(SCREEN_WIDTH // 2 - button_width - button_margin // 2 + button_width // 2 - save_text.get_width() // 2,
SCREEN_HEIGHT - 100 + button_height // 2 - save_text.get_height() // 2))
Кнопка "Отмена"
pygame.draw.rect(screen, RED,
(SCREEN_WIDTH // 2 + button_margin // 2,
SCREEN_HEIGHT - 100,
button_width, button_height))
cancel_text = self.font_medium.render("Отмена", True, WHITE)
screen.blit(cancel_text,
(SCREEN_WIDTH // 2 + button_margin // 2 + button_width // 2 - cancel_text.get_width() // 2,
SCREEN_HEIGHT - 100 + button_height // 2 - cancel_text.get_height() // 2))
def handle_click(self, pos: Tuple[int, int]):
if self.game_state == MENU:
self.handle_menu_click(pos)
elif self.game_state == PLAYING:
self.handle_game_click(pos)
elif self.game_state == GAME_OVER:
self.handle_game_over_click(pos)
elif self.game_state == SETTINGS:
self.handle_settings_click(pos)
def handle_menu_click(self, pos: Tuple[int, int]):
button_y = 250
button_height = 50
button_width = 300
button_margin = 20
Проверка кнопок
for i in range(4):
rect = pygame.Rect(SCREEN_WIDTH // 2 - button_width // 2,
button_y + i * (button_height + button_margin),
button_width, button_height)
if rect.collidepoint(pos):
self.sound.play("click")
if i == 0: # PvP
self.game_mode = "pvp"
self.reset_game()
elif i == 1: # PvE
self.game_mode = "pve"
self.reset_game()
elif i == 2: # EvE
self.game_mode = "eve"
self.reset_game()
elif i == 3: # Настройки
self.game_state = SETTINGS
Кнопка выхода (5-я кнопка)
rect = pygame.Rect(SCREEN_WIDTH // 2 - button_width // 2,
button_y + 4 * (button_height + button_margin),
button_width, button_height)
if rect.collidepoint(pos):
self.sound.play("click")
pygame.quit()
sys.exit()
def handle_game_click(self, pos: Tuple[int, int]):
Проверка хода на доске
if (BOARD_OFFSET_X <= pos[0] <= BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE and
BOARD_OFFSET_Y <= pos[1] <= BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE):
col = (pos[0] - BOARD_OFFSET_X) // CELL_SIZE
row = (pos[1] - BOARD_OFFSET_Y) // CELL_SIZE
if (row, col) in self.valid_moves:
self.make_move(row, col)
Проверка кнопок управления
button_width = 100
button_height = 30
Кнопка "Меню"
menu_rect = pygame.Rect(SCREEN_WIDTH - button_width - 20,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20,
button_width, button_height)
if menu_rect.collidepoint(pos):
self.sound.play("click")
self.game_state = MENU
Кнопка "Отменить"
if self.undo_stack:
undo_rect = pygame.Rect(SCREEN_WIDTH - 2 * button_width - 20 - 10,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20,
button_width, button_height)
if undo_rect.collidepoint(pos):
self.sound.play("click")
self.undo()
Кнопка "Повторить"
if self.redo_stack:
redo_rect = pygame.Rect(SCREEN_WIDTH - 3 * button_width - 20 - 20,
BOARD_OFFSET_Y + BOARD_SIZE * CELL_SIZE + 20,
button_width, button_height)
if redo_rect.collidepoint(pos):
self.sound.play("click")
self.redo()
def handle_game_over_click(self, pos: Tuple[int, int]):
button_width = 120
button_height = 40
button_margin = 20
Кнопка "Новая игра"
new_game_rect = pygame.Rect(SCREEN_WIDTH // 2 - button_width - button_margin // 2,
SCREEN_HEIGHT // 2 + 200 // 2 - button_height - 20,
button_width, button_height)
if new_game_rect.collidepoint(pos):
self.sound.play("click")
self.reset_game()
Кнопка "Меню"
menu_rect = pygame.Rect(SCREEN_WIDTH // 2 + button_margin // 2,
SCREEN_HEIGHT // 2 + 200 // 2 - button_height - 20,
button_width, button_height)
if menu_rect.collidepoint(pos):
self.sound.play("click")
self.game_state = MENU
def handle_settings_click(self, pos: Tuple[int, int]):
player_settings_y = 150
setting_margin = 50
button_width = 65
button_height = 30
Сложность ИИ
for i in range(3):
difficulty_rect = pygame.Rect(400 + i * 70, player_settings_y + 2 * setting_margin,
button_width, button_height)
if difficulty_rect.collidepoint(pos):
self.sound.play("click")
if i == 0:
self.ai_difficulty = "easy"
elif i == 1:
self.ai_difficulty = "medium"
else:
self.ai_difficulty = "hard"
Тема
for i in range(3):
theme_rect = pygame.Rect(400 + i * 70, player_settings_y + 3 * setting_margin,
button_width, button_height)
if theme_rect.collidepoint(pos):
self.sound.play("click")
if i == 0:
self.theme = "classic"
elif i == 1:
self.theme = "wood"
else:
self.theme = "modern"
Подсказки
hints_rect = pygame.Rect(400, player_settings_y + 4 * setting_margin,
button_width, button_height)
if hints_rect.collidepoint(pos):
self.sound.play("click")
self.hint_enabled = not self.hint_enabled
Звук
sound_rect = pygame.Rect(400, player_settings_y + 5 * setting_margin,
button_width, button_height)
if sound_rect.collidepoint(pos):
self.sound.play("click")
self.sound.enabled = not self.sound.enabled
Кнопки сохранения/отмены
button_width = 150
button_height = 40
button_margin = 20
Кнопка "Сохранить"
save_rect = pygame.Rect(SCREEN_WIDTH // 2 - button_width - button_margin // 2,
SCREEN_HEIGHT - 100,
button_width, button_height)
if save_rect.collidepoint(pos):
self.sound.play("click")
self.save_settings()
self.game_state = MENU
Кнопка "Отмена"
cancel_rect = pygame.Rect(SCREEN_WIDTH // 2 + button_margin // 2,
SCREEN_HEIGHT - 100,
button_width, button_height)
if cancel_rect.collidepoint(pos):
self.sound.play("click")
self.load_settings() # Восстанавливаем старые настройки
self.game_state = MENU
def main():
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Reversi Deluxe")
clock = pygame.time.Clock()
game = ReversiGame()
running = True
while running:
dt = clock.tick(FPS) / 1000.0 # Delta time в секундах
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Левая кнопка мыши
game.handle_click(event.pos)
game.animation.update(dt)
game.draw(screen)
pygame.display.flip()
pygame.quit()
sys.exit()
if name == "main":
main()