767 lines
33 KiB
Python
767 lines
33 KiB
Python
import pygame
|
||
import sys
|
||
import random
|
||
import time
|
||
|
||
pygame.init()
|
||
|
||
# --- ПАРАМЕТРЫ ПОЛЯ ---
|
||
CELL_SIZE = 32
|
||
GRID_WIDTH = 30
|
||
GRID_HEIGHT = 30
|
||
PANEL_WIDTH = 200 # Ширина правой панели
|
||
WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH + PANEL_WIDTH
|
||
WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT
|
||
|
||
# --- ЦВЕТА ---
|
||
COLOR_BG = (30, 30, 30)
|
||
COLOR_GRID = (50, 50, 50)
|
||
COLOR_FOG = (20, 20, 20)
|
||
COLOR_SELECTED = (255, 0, 0)
|
||
COLOR_MOVE = (0, 255, 0)
|
||
|
||
COLOR_WHITE_PIECE = (255, 255, 255) # Заливка белых фигур
|
||
COLOR_BLACK_PIECE = (0, 0, 0) # Заливка чёрных фигур
|
||
|
||
# Новые цвета для бонусов
|
||
COLOR_REGEN = (0, 255, 0) # Зелёный
|
||
COLOR_HP_UPGRADE = (0, 128, 255) # Синий
|
||
COLOR_DAMAGE = (255, 0, 0) # Красный
|
||
COLOR_KING_HP_UPGRADE = (255, 215, 0) # Золотой
|
||
COLOR_ADD_PIECE = (255, 255, 255) # Белый (с плюсом)
|
||
COLOR_ADD_PIECE_PINK = (255, 192, 203) # Розовый
|
||
|
||
# Цвет текущего хода
|
||
COLOR_TURN_TEXT = (255, 255, 255) # Белый
|
||
|
||
# Новый цвет для пометки клеток, которые скоро уйдут в туман войны
|
||
COLOR_PRE_FOG = (169, 169, 169) # Светло-серый
|
||
|
||
# Цвет таймера
|
||
COLOR_TIMER_NORMAL = (255, 255, 255) # Белый
|
||
COLOR_TIMER_WARNING = (255, 0, 0) # Красный
|
||
|
||
# Цвет торговца
|
||
COLOR_MERCHANT = (139, 69, 19) # Коричневый
|
||
|
||
# Цвет монеты
|
||
COLOR_COIN = (255, 165, 0) # Оранжевый
|
||
|
||
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
|
||
pygame.display.set_caption("Шахматы с Открытым Миром 1.1.1")
|
||
|
||
# --- Шрифты ---
|
||
font = pygame.font.SysFont(None, CELL_SIZE // 2)
|
||
victory_font = pygame.font.SysFont(None, 60)
|
||
turn_font = pygame.font.SysFont(None, 30)
|
||
counter_font = pygame.font.SysFont(None, 30)
|
||
timer_font = pygame.font.SysFont(None, 25)
|
||
shop_font = pygame.font.SysFont(None, 24)
|
||
|
||
# --- Глобальное множество открытых клеток (туман войны) ---
|
||
global_revealed = set()
|
||
|
||
# --- Словарь символов (фолбэк, если спрайт не найден) ---
|
||
piece_symbols = {
|
||
'rook': 'Л',
|
||
'knight': 'К',
|
||
'bishop': 'С',
|
||
'queen': 'Ф',
|
||
'king': 'К', # Король
|
||
'pawn': 'П'
|
||
}
|
||
|
||
# === МЕХАНИКА БОНУСОВ ===
|
||
bonus_cells = {}
|
||
|
||
base_bonus_probabilities = {
|
||
'regen': 0.42, # 42%
|
||
'hp_upgrade': 0.32, # 32%
|
||
'damage': 0.2, # 20%
|
||
'king_hp_upgrade': 0.05, # 5%
|
||
'add_piece': 0.01 # 1%
|
||
}
|
||
|
||
bonus_probabilities = base_bonus_probabilities.copy()
|
||
|
||
def choose_bonus_type():
|
||
rand = random.random()
|
||
cumulative = 0
|
||
for bonus_type, prob in bonus_probabilities.items():
|
||
cumulative += prob
|
||
if rand < cumulative:
|
||
return bonus_type
|
||
return 'regen' # По умолчанию
|
||
|
||
# === МЕХАНИКА БОЯ ===
|
||
def resolve_combat(attacker, defender):
|
||
a = attacker.hp
|
||
b = defender.hp
|
||
if a < b:
|
||
defender.hp = b - a
|
||
attacker.hp = 0
|
||
return (False, True)
|
||
elif a > b:
|
||
attacker.hp = a - b
|
||
defender.hp = 0
|
||
return (True, False)
|
||
else:
|
||
attacker.hp = 1
|
||
defender.hp = 0
|
||
return (True, False)
|
||
|
||
# === КЛАСС ФИГУРЫ ===
|
||
class Piece:
|
||
def __init__(self, name, color, x, y):
|
||
self.name = name
|
||
self.color = color # 'white' или 'black'
|
||
self.x = x
|
||
self.y = y
|
||
self.selected = False
|
||
self.has_moved = False # Для ограничения повторного хода за один ход
|
||
|
||
# Инициализация HP
|
||
if self.name == 'king':
|
||
self.max_hp = 5
|
||
self.hp = 5
|
||
elif self.name in ['rook', 'knight', 'bishop', 'queen']:
|
||
self.max_hp = 3
|
||
self.hp = 3
|
||
elif self.name == 'pawn':
|
||
self.max_hp = 1
|
||
self.hp = 1
|
||
|
||
def get_possible_moves(self, pieces):
|
||
moves = []
|
||
if self.name == 'pawn':
|
||
directions = [(-1,-1), (0,-1), (1,-1),
|
||
(-1, 0), (1, 0),
|
||
(-1, 1), (0, 1), (1, 1)]
|
||
for dx, dy in directions:
|
||
nx = self.x + dx
|
||
ny = self.y + dy
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
blocking = next((p for p in pieces if p.x == nx and p.y == ny), None)
|
||
if not blocking or blocking.color != self.color:
|
||
moves.append((nx, ny))
|
||
elif self.name in ['rook', 'bishop', 'queen']:
|
||
max_steps = self.move_range if hasattr(self, 'move_range') else 3
|
||
if self.name == 'rook':
|
||
directions = [(-1,0),(1,0),(0,-1),(0,1)]
|
||
elif self.name == 'bishop':
|
||
directions = [(-1,-1),(1,-1),(1,1),(-1,1)]
|
||
elif self.name == 'queen':
|
||
directions = [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,-1),(1,1),(-1,1)]
|
||
for dx, dy in directions:
|
||
for step in range(1, max_steps + 1):
|
||
nx = self.x + dx * step
|
||
ny = self.y + dy * step
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
blocking = next((p for p in pieces if p.x == nx and p.y == ny), None)
|
||
if blocking:
|
||
if blocking.color != self.color:
|
||
moves.append((nx, ny))
|
||
break
|
||
moves.append((nx, ny))
|
||
else:
|
||
break
|
||
elif self.name == 'knight':
|
||
deltas = [(-2,-1),(-1,-2),(1,-2),(2,-1),
|
||
(2,1),(1,2),(-1,2),(-2,1)]
|
||
for dx, dy in deltas:
|
||
nx, ny = self.x + dx, self.y + dy
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
blocking = next((p for p in pieces if p.x == nx and p.y == ny), None)
|
||
if not blocking or blocking.color != self.color:
|
||
moves.append((nx, ny))
|
||
elif self.name == 'king':
|
||
directions = [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,-1),(1,1),(-1,1)]
|
||
for dx, dy in directions:
|
||
nx, ny = self.x + dx, self.y + dy
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
blocking = next((p for p in pieces if p.x == nx and p.y == ny), None)
|
||
if not blocking or blocking.color != self.color:
|
||
moves.append((nx, ny))
|
||
return moves
|
||
|
||
# --- Инициализация фигур ---
|
||
def initialize_pieces():
|
||
"""Чёрные: y=2 (back), y=3 (pawns). Белые: y=27 (back), y=26 (pawns)."""
|
||
pieces = []
|
||
cx = GRID_WIDTH // 2
|
||
# Чёрные
|
||
black_back = ['rook','knight','bishop','king','queen','bishop','knight','rook']
|
||
for i, pname in enumerate(black_back):
|
||
x = cx - 3 + i
|
||
y = 2
|
||
pieces.append(Piece(pname, 'black', x, y))
|
||
for i in range(8):
|
||
x = cx - 3 + i
|
||
y = 3
|
||
pieces.append(Piece('pawn', 'black', x, y))
|
||
# Белые
|
||
white_back = ['rook','knight','bishop','king','queen','bishop','knight','rook']
|
||
for i, pname in enumerate(white_back):
|
||
x = cx - 3 + i
|
||
y = 27
|
||
pieces.append(Piece(pname, 'white', x, y))
|
||
for i in range(8):
|
||
x = cx - 3 + i
|
||
y = 26
|
||
pieces.append(Piece('pawn', 'white', x, y))
|
||
return pieces
|
||
|
||
pieces = initialize_pieces()
|
||
|
||
selected_piece = None
|
||
possible_moves = []
|
||
game_over = False
|
||
winner = None
|
||
|
||
# --- Туман войны ---
|
||
cell_counters = {} # {(x,y): turns_since_last_revealed}
|
||
# Каждая новая клетка имеет 10% шанс стать бонусной.
|
||
BONUS_GENERATION_CHANCE = 0.10
|
||
|
||
# --- Система ходов и многоперемещений ---
|
||
current_turn = 'white' # Начинаем с белых
|
||
turn_count = 0
|
||
moves_remaining = 3 # За один ход игрок может сделать 3 перемещения
|
||
|
||
# --- Механика повышения вероятности красных полей ---
|
||
def update_bonus_probabilities():
|
||
global bonus_probabilities
|
||
if turn_count <= 100:
|
||
bonus_probabilities = base_bonus_probabilities.copy()
|
||
else:
|
||
extra_turns = turn_count - 100
|
||
max_extra = 200
|
||
increase_per_turn = (0.50 - base_bonus_probabilities['damage']) / max_extra
|
||
new_damage_prob = min(base_bonus_probabilities['damage'] + increase_per_turn * extra_turns, 0.50)
|
||
remaining_prob = 1 - new_damage_prob
|
||
total_other_probs = sum(base_bonus_probabilities[bt] for bt in base_bonus_probabilities if bt != 'damage')
|
||
bonus_probabilities = {}
|
||
for bt, prob in base_bonus_probabilities.items():
|
||
if bt == 'damage':
|
||
bonus_probabilities[bt] = new_damage_prob
|
||
else:
|
||
bonus_probabilities[bt] = prob / total_other_probs * remaining_prob
|
||
|
||
# --- Механика времени на ход ---
|
||
TURN_TIME_LIMIT = 120 # seconds
|
||
timer_start_time = time.time()
|
||
timer_expired = False
|
||
player_timeouts = {'white': 0, 'black': 0}
|
||
|
||
def reset_timer():
|
||
global timer_start_time, timer_expired
|
||
timer_start_time = time.time()
|
||
timer_expired = False
|
||
|
||
def get_time_left():
|
||
elapsed = time.time() - timer_start_time
|
||
return max(0, TURN_TIME_LIMIT - int(elapsed))
|
||
|
||
def check_timer():
|
||
global timer_expired, game_over, winner
|
||
time_left = get_time_left()
|
||
if time_left <= 0 and not timer_expired:
|
||
timer_expired = True
|
||
player_timeouts[current_turn] += 1
|
||
if player_timeouts[current_turn] >= 2:
|
||
winner = 'black' if current_turn == 'white' else 'white'
|
||
game_over = True
|
||
else:
|
||
switch_turn()
|
||
|
||
# При переключении хода сбрасываем число оставшихся ходов и флаг has_moved у фигур текущего игрока.
|
||
def switch_turn():
|
||
global current_turn, turn_count, timer_expired, moves_remaining
|
||
current_turn = 'black' if current_turn == 'white' else 'white'
|
||
turn_count += 1
|
||
reset_timer()
|
||
timer_expired = False
|
||
update_bonus_probabilities()
|
||
moves_remaining = 3
|
||
for p in pieces:
|
||
if p.color == current_turn:
|
||
p.has_moved = False
|
||
|
||
# После успешного перемещения фигуры вызываем эту функцию.
|
||
def end_move(moved_piece):
|
||
global moves_remaining
|
||
moved_piece.has_moved = True
|
||
moves_remaining -= 1
|
||
if moves_remaining <= 0:
|
||
switch_turn()
|
||
|
||
# --- Обновление тумана войны и генерация бонусов/монет ---
|
||
def update_fog():
|
||
global global_revealed, bonus_cells
|
||
previous_revealed = global_revealed.copy()
|
||
new_revealed = set()
|
||
for p in pieces:
|
||
for dx in range(-2, 3):
|
||
for dy in range(-2, 3):
|
||
nx = p.x + dx
|
||
ny = p.y + dy
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
new_revealed.add((nx, ny))
|
||
for merchant_pos in merchant_positions:
|
||
new_revealed.add(merchant_pos)
|
||
newly_revealed = new_revealed - previous_revealed
|
||
for y in range(GRID_HEIGHT):
|
||
for x in range(GRID_WIDTH):
|
||
pos = (x, y)
|
||
if pos in new_revealed:
|
||
cell_counters[pos] = 0
|
||
global_revealed.add(pos)
|
||
else:
|
||
if pos in cell_counters:
|
||
cell_counters[pos] += 1
|
||
else:
|
||
cell_counters[pos] = 1
|
||
if cell_counters[pos] >= 5:
|
||
if pos in global_revealed:
|
||
global_revealed.remove(pos)
|
||
if pos in bonus_cells and bonus_cells[pos]['type'] != 'damage':
|
||
del bonus_cells[pos]
|
||
for pos in newly_revealed:
|
||
if pos not in bonus_cells and pos not in coin_cells and random.random() < BONUS_GENERATION_CHANCE:
|
||
if random.random() < 0.90:
|
||
bonus_type = choose_bonus_type()
|
||
if not any(p.x == pos[0] and p.y == pos[1] for p in pieces):
|
||
bonus_cells[pos] = {'type': bonus_type}
|
||
else:
|
||
if not any(p.x == pos[0] and p.y == pos[1] for p in pieces):
|
||
coin_cells.add(pos)
|
||
|
||
# --- Добавление торговцев ---
|
||
central_line_y = GRID_HEIGHT // 2
|
||
merchant_positions = [(0, central_line_y), (GRID_WIDTH - 1, central_line_y)]
|
||
global_revealed.update(merchant_positions)
|
||
|
||
# --- Монеты на поле ---
|
||
coin_cells = set()
|
||
|
||
# --- Счет монет для игроков ---
|
||
player_coins = {'white': 0, 'black': 0}
|
||
|
||
# --- Механика магазина ---
|
||
# Повышенные цены: 6 монет за покупку
|
||
shop_open = False
|
||
shop_items = {
|
||
'extend_move': {'name': 'Расширить ход', 'cost': 6},
|
||
'increase_hp': {'name': 'Увеличить HP', 'cost': 6},
|
||
'damage_enemy': {'name': 'Уменьшить HP врага', 'cost': 6},
|
||
'curse': {'name': 'Проклятие', 'cost': 6}
|
||
}
|
||
|
||
# --- Реализация спрайтов фигур ---
|
||
# Ожидается, что в папке "sprites" находятся файлы: wK.png, wQ.png, wR.png, wB.png, wN.png, wP.png для белых,
|
||
# и bK.png, bQ.png, bR.png, bB.png, bN.png, bP.png для чёрных.
|
||
piece_sprites = {}
|
||
|
||
def load_piece_sprites():
|
||
piece_names_map = {
|
||
'king': 'K',
|
||
'queen': 'Q',
|
||
'rook': 'R',
|
||
'bishop': 'B',
|
||
'knight': 'N',
|
||
'pawn': 'P'
|
||
}
|
||
colors = ['white', 'black']
|
||
for color in colors:
|
||
letter = 'w' if color == 'white' else 'b'
|
||
for name in piece_names_map:
|
||
filename = f"sprites/{letter}{piece_names_map[name]}.png"
|
||
try:
|
||
image = pygame.image.load(filename).convert_alpha()
|
||
image = pygame.transform.scale(image, (CELL_SIZE, CELL_SIZE))
|
||
piece_sprites[(color, name)] = image
|
||
except Exception as e:
|
||
print(f"Ошибка загрузки спрайта {filename}: {e}")
|
||
piece_sprites[(color, name)] = None
|
||
|
||
load_piece_sprites()
|
||
|
||
# --- Функции отрисовки ---
|
||
def draw_grid():
|
||
for x in range(0, CELL_SIZE * GRID_WIDTH, CELL_SIZE):
|
||
pygame.draw.line(screen, COLOR_GRID, (x, 0), (x, CELL_SIZE * GRID_HEIGHT))
|
||
for y in range(0, CELL_SIZE * GRID_HEIGHT, CELL_SIZE):
|
||
pygame.draw.line(screen, COLOR_GRID, (0, y), (CELL_SIZE * GRID_WIDTH, y))
|
||
|
||
def draw_fog():
|
||
for y in range(GRID_HEIGHT):
|
||
for x in range(GRID_WIDTH):
|
||
pos = (x, y)
|
||
if pos not in global_revealed:
|
||
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
|
||
pygame.draw.rect(screen, COLOR_FOG, rect)
|
||
else:
|
||
if pos in merchant_positions:
|
||
continue
|
||
if cell_counters.get(pos, 0) == 4:
|
||
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
|
||
pygame.draw.rect(screen, COLOR_PRE_FOG, rect)
|
||
|
||
def draw_bonus_cells():
|
||
for (bx, by), info in bonus_cells.items():
|
||
if info['type'] != 'damage' and (bx, by) not in global_revealed:
|
||
continue
|
||
cx = bx * CELL_SIZE + CELL_SIZE // 2
|
||
cy = by * CELL_SIZE + CELL_SIZE // 2
|
||
bonus_type = info['type']
|
||
if bonus_type == 'regen':
|
||
color = COLOR_REGEN
|
||
pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE // 4)
|
||
elif bonus_type == 'hp_upgrade':
|
||
color = COLOR_HP_UPGRADE
|
||
pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE // 4)
|
||
elif bonus_type == 'damage':
|
||
color = COLOR_DAMAGE
|
||
pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE // 4)
|
||
elif bonus_type == 'king_hp_upgrade':
|
||
color = COLOR_KING_HP_UPGRADE
|
||
pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE // 4)
|
||
elif bonus_type == 'add_piece':
|
||
possible = False
|
||
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,-1),(1,1),(-1,1)]:
|
||
nx = bx + dx
|
||
ny = by + dy
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
if not any(p.x == nx and p.y == ny for p in pieces):
|
||
possible = True
|
||
break
|
||
color = COLOR_ADD_PIECE if possible else COLOR_ADD_PIECE_PINK
|
||
pygame.draw.circle(screen, color, (cx, cy), CELL_SIZE // 4)
|
||
pygame.draw.line(screen, (0, 0, 0), (cx - CELL_SIZE // 8, cy), (cx + CELL_SIZE // 8, cy), 2)
|
||
pygame.draw.line(screen, (0, 0, 0), (cx, cy - CELL_SIZE // 8), (cx, cy + CELL_SIZE // 8), 2)
|
||
|
||
def draw_coin_cells():
|
||
for pos in coin_cells:
|
||
if pos not in global_revealed:
|
||
continue
|
||
x, y = pos
|
||
cx = x * CELL_SIZE + CELL_SIZE // 2
|
||
cy = y * CELL_SIZE + CELL_SIZE // 2
|
||
size = CELL_SIZE // 3
|
||
point1 = (cx, cy - size)
|
||
point2 = (cx - size, cy + size)
|
||
point3 = (cx + size, cy + size)
|
||
pygame.draw.polygon(screen, COLOR_COIN, [point1, point2, point3])
|
||
dollar_text = font.render('$', True, (0, 0, 0))
|
||
text_rect = dollar_text.get_rect(center=(cx, cy))
|
||
screen.blit(dollar_text, text_rect)
|
||
|
||
def draw_merchants():
|
||
for pos in merchant_positions:
|
||
x, y = pos
|
||
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
|
||
pygame.draw.rect(screen, COLOR_MERCHANT, rect)
|
||
points = [
|
||
(x * CELL_SIZE + CELL_SIZE // 2, y * CELL_SIZE + CELL_SIZE // 4),
|
||
(x * CELL_SIZE + CELL_SIZE // 4, y * CELL_SIZE + 3 * CELL_SIZE // 4),
|
||
(x * CELL_SIZE + 3 * CELL_SIZE // 4, y * CELL_SIZE + 3 * CELL_SIZE // 4)
|
||
]
|
||
pygame.draw.polygon(screen, (255, 255, 255), points)
|
||
|
||
def draw_pieces(pieces):
|
||
for p in pieces:
|
||
x = p.x * CELL_SIZE
|
||
y = p.y * CELL_SIZE
|
||
sprite = piece_sprites.get((p.color, p.name))
|
||
if sprite:
|
||
screen.blit(sprite, (x, y))
|
||
else:
|
||
rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
|
||
if p.color == 'white':
|
||
pygame.draw.rect(screen, COLOR_WHITE_PIECE, rect)
|
||
else:
|
||
pygame.draw.rect(screen, COLOR_BLACK_PIECE, rect)
|
||
symbol = piece_symbols.get(p.name, '?')
|
||
text = font.render(symbol, True, (255, 0, 0))
|
||
text_rect = text.get_rect(center=rect.center)
|
||
screen.blit(text, text_rect)
|
||
# Отрисовка HP с полупрозрачным фоном для лучшей читаемости:
|
||
hp_str = f"{p.hp}/{p.max_hp}"
|
||
# Определяем цвета: для белых фигур текст будет черным, для черных – белым;
|
||
# фон выбираем противоположный
|
||
if p.color == 'white':
|
||
text_color = (0, 0, 0)
|
||
bg_color = (255, 255, 255)
|
||
else:
|
||
text_color = (255, 255, 255)
|
||
bg_color = (0, 0, 0)
|
||
hp_text = font.render(hp_str, True, text_color)
|
||
text_rect = hp_text.get_rect(topleft=(x + 2, y + 2))
|
||
# Создаем временную поверхность для фона с альфа-каналом
|
||
bg_surf = pygame.Surface(text_rect.size)
|
||
bg_surf.set_alpha(200) # степень прозрачности (0-255)
|
||
bg_surf.fill(bg_color)
|
||
screen.blit(bg_surf, text_rect.topleft)
|
||
screen.blit(hp_text, text_rect.topleft)
|
||
if p.selected:
|
||
rect = pygame.Rect(x, y, CELL_SIZE, CELL_SIZE)
|
||
pygame.draw.rect(screen, COLOR_SELECTED, rect, 2)
|
||
for (mx, my) in possible_moves:
|
||
r = pygame.Rect(mx * CELL_SIZE, my * CELL_SIZE, CELL_SIZE, CELL_SIZE)
|
||
pygame.draw.rect(screen, COLOR_MOVE, r, 2)
|
||
|
||
def draw_statistics_panel():
|
||
panel_rect = pygame.Rect(CELL_SIZE * GRID_WIDTH, 0, PANEL_WIDTH, WINDOW_HEIGHT)
|
||
pygame.draw.rect(screen, (50, 50, 50), panel_rect)
|
||
title_text = shop_font.render("Статистика", True, (255, 255, 255))
|
||
screen.blit(title_text, (CELL_SIZE * GRID_WIDTH + 10, 10))
|
||
turn_text = turn_font.render(f"Ход: {current_turn.capitalize()}", True, COLOR_TURN_TEXT)
|
||
screen.blit(turn_text, (CELL_SIZE * GRID_WIDTH + 10, 50))
|
||
counter_text = counter_font.render(f"Ходов: {turn_count}", True, (255, 255, 255))
|
||
screen.blit(counter_text, (CELL_SIZE * GRID_WIDTH + 10, 80))
|
||
time_left = get_time_left()
|
||
minutes = time_left // 60
|
||
seconds = time_left % 60
|
||
timer_text_str = f"Время: {minutes:02}:{seconds:02}"
|
||
color = COLOR_TIMER_WARNING if timer_expired and player_timeouts[current_turn] >= 1 else COLOR_TIMER_NORMAL
|
||
timer_text = timer_font.render(timer_text_str, True, color)
|
||
screen.blit(timer_text, (CELL_SIZE * GRID_WIDTH + 10, 110))
|
||
coins_text_white = counter_font.render(f"Белые монеты: {player_coins['white']}", True, (255, 255, 0))
|
||
coins_text_black = counter_font.render(f"Чёрные монеты: {player_coins['black']}", True, (255, 255, 0))
|
||
screen.blit(coins_text_white, (CELL_SIZE * GRID_WIDTH + 10, 140))
|
||
screen.blit(coins_text_black, (CELL_SIZE * GRID_WIDTH + 10, 170))
|
||
moves_text = counter_font.render(f"Осталось ходов: {moves_remaining}", True, (255, 255, 255))
|
||
screen.blit(moves_text, (CELL_SIZE * GRID_WIDTH + 10, 200))
|
||
if shop_open:
|
||
draw_shop()
|
||
|
||
def draw_shop():
|
||
shop_x = CELL_SIZE * GRID_WIDTH + 10
|
||
shop_y = 240
|
||
shop_title = shop_font.render("Магазин", True, (255, 255, 0))
|
||
screen.blit(shop_title, (shop_x, shop_y))
|
||
for idx, (key, item) in enumerate(shop_items.items()):
|
||
item_y = shop_y + 40 + idx * 60
|
||
button_rect = pygame.Rect(shop_x, item_y, PANEL_WIDTH - 20, 50)
|
||
button_color = (0, 255, 0) if player_coins[current_turn] >= item['cost'] else (128, 128, 128)
|
||
pygame.draw.rect(screen, button_color, button_rect)
|
||
item_text = shop_font.render(f"{item['name']} - {item['cost']} мон.", True, (0, 0, 0))
|
||
screen.blit(item_text, (shop_x + 5, item_y + 15))
|
||
shop_items[key]['button_rect'] = button_rect
|
||
|
||
def draw_victory(winner):
|
||
text = victory_font.render(f"{winner.capitalize()} победил!", True, (255, 255, 255))
|
||
text_rect = text.get_rect(center=(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2))
|
||
screen.blit(text, text_rect)
|
||
|
||
def apply_bonus(piece, bonus_type, pieces):
|
||
if bonus_type == 'regen':
|
||
if piece.name != 'king':
|
||
amt = random.randint(1, 2)
|
||
piece.hp = min(piece.hp + amt, piece.max_hp)
|
||
elif bonus_type == 'hp_upgrade':
|
||
if piece.name != 'king':
|
||
piece.max_hp += 1
|
||
elif bonus_type == 'king_hp_upgrade':
|
||
if piece.name != 'king':
|
||
king = next((p for p in pieces if p.name == 'king' and p.color == piece.color), None)
|
||
if king:
|
||
if king.hp == king.max_hp:
|
||
king.max_hp += 1
|
||
king.hp = king.max_hp
|
||
else:
|
||
king.hp = min(king.hp + 1, king.max_hp)
|
||
elif bonus_type == 'damage':
|
||
pass
|
||
elif bonus_type == 'add_piece':
|
||
if piece.name != 'king':
|
||
directions = [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(1,-1),(1,1),(-1,1)]
|
||
random.shuffle(directions)
|
||
for dx, dy in directions:
|
||
nx = piece.x + dx
|
||
ny = piece.y + dy
|
||
if 0 <= nx < GRID_WIDTH and 0 <= ny < GRID_HEIGHT:
|
||
if not any(p.x == nx and p.y == ny for p in pieces) and (nx, ny) not in bonus_cells and (nx, ny) not in merchant_positions:
|
||
new_piece = Piece(piece.name, piece.color, nx, ny)
|
||
pieces.append(new_piece)
|
||
break
|
||
|
||
def handle_shop_purchase(key):
|
||
if player_coins[current_turn] < shop_items[key]['cost']:
|
||
return
|
||
player_coins[current_turn] -= shop_items[key]['cost']
|
||
if key == 'extend_move':
|
||
for p in pieces:
|
||
if p.color == current_turn:
|
||
if p.name in ['rook', 'bishop', 'queen']:
|
||
setattr(p, 'move_range', getattr(p, 'move_range', 3) + 2)
|
||
elif p.name == 'pawn':
|
||
setattr(p, 'move_range', getattr(p, 'move_range', 1) + 1)
|
||
elif key == 'increase_hp':
|
||
for p in pieces:
|
||
if p.color == current_turn and p.name != 'king':
|
||
p.max_hp += 3
|
||
p.hp = min(p.hp + 3, p.max_hp)
|
||
elif key == 'damage_enemy':
|
||
enemy_color = 'black' if current_turn == 'white' else 'white'
|
||
for p in pieces:
|
||
if p.color == enemy_color:
|
||
if p.hp > 3:
|
||
p.hp -= 3
|
||
elif p.hp > 1:
|
||
p.hp = 1
|
||
else:
|
||
p.hp = 1
|
||
elif key == 'curse':
|
||
bonus_probabilities['regen'] = max(bonus_probabilities['regen'] - 0.10, 0)
|
||
bonus_probabilities['hp_upgrade'] = max(bonus_probabilities['hp_upgrade'] - 0.10, 0)
|
||
total_prob = sum(bonus_probabilities.values())
|
||
for bt in bonus_probabilities:
|
||
bonus_probabilities[bt] /= total_prob if total_prob > 0 else 1
|
||
|
||
# --- Инициализируем туман войны в начале игры ---
|
||
update_fog()
|
||
|
||
# --- Основной цикл игры ---
|
||
running = True
|
||
clock = pygame.time.Clock()
|
||
|
||
while running:
|
||
clock.tick(30)
|
||
check_timer()
|
||
for event in pygame.event.get():
|
||
if event.type == pygame.QUIT:
|
||
running = False
|
||
elif event.type == pygame.MOUSEBUTTONDOWN and not game_over:
|
||
mx, my = pygame.mouse.get_pos()
|
||
if mx < CELL_SIZE * GRID_WIDTH and my < CELL_SIZE * GRID_HEIGHT:
|
||
gx = mx // CELL_SIZE
|
||
gy = my // CELL_SIZE
|
||
clicked_piece = next((p for p in pieces if p.x == gx and p.y == gy), None)
|
||
if selected_piece:
|
||
if (gx, gy) in possible_moves:
|
||
if (gx, gy) in bonus_cells:
|
||
bonus_type = bonus_cells[(gx, gy)]['type']
|
||
apply_bonus(selected_piece, bonus_type, pieces)
|
||
del bonus_cells[(gx, gy)]
|
||
if (gx, gy) in coin_cells:
|
||
player_coins[current_turn] += 1
|
||
coin_cells.remove((gx, gy))
|
||
if (gx, gy) in merchant_positions:
|
||
shop_open = True
|
||
defender = next((p for p in pieces if p.x == gx and p.y == gy), None)
|
||
if defender:
|
||
attacker = selected_piece
|
||
attacker_alive, defender_alive = resolve_combat(attacker, defender)
|
||
if not defender_alive:
|
||
if defender.name == 'king':
|
||
winner = attacker.color
|
||
game_over = True
|
||
pieces.remove(defender)
|
||
if not attacker_alive:
|
||
pieces.remove(attacker)
|
||
selected_piece = None
|
||
possible_moves.clear()
|
||
update_fog()
|
||
switch_turn()
|
||
continue
|
||
else:
|
||
path = []
|
||
if attacker.name in ['rook', 'bishop', 'queen']:
|
||
dx = gx - attacker.x
|
||
dy = gy - attacker.y
|
||
if dx != 0:
|
||
dx //= abs(dx)
|
||
if dy != 0:
|
||
dy //= abs(dy)
|
||
for step in range(1, max(abs(gx - attacker.x), abs(gy - attacker.y))):
|
||
path_x = attacker.x + dx * step
|
||
path_y = attacker.y + dy * step
|
||
path.append((path_x, path_y))
|
||
survived = True
|
||
for pos in path:
|
||
if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage':
|
||
damage = random.randint(1, 5)
|
||
attacker.hp = max(attacker.hp - damage, 0)
|
||
del bonus_cells[pos]
|
||
if attacker.hp == 0:
|
||
pieces.remove(attacker)
|
||
selected_piece = None
|
||
possible_moves.clear()
|
||
survived = False
|
||
switch_turn()
|
||
break
|
||
if survived:
|
||
attacker.x = gx
|
||
attacker.y = gy
|
||
attacker.selected = False
|
||
end_move(attacker)
|
||
selected_piece = None
|
||
possible_moves.clear()
|
||
else:
|
||
path = []
|
||
if selected_piece.name in ['rook', 'bishop', 'queen']:
|
||
dx = gx - selected_piece.x
|
||
dy = gy - selected_piece.y
|
||
if dx != 0:
|
||
dx //= abs(dx)
|
||
if dy != 0:
|
||
dy //= abs(dy)
|
||
for step in range(1, max(abs(gx - selected_piece.x), abs(gy - selected_piece.y))):
|
||
path_x = selected_piece.x + dx * step
|
||
path_y = selected_piece.y + dy * step
|
||
path.append((path_x, path_y))
|
||
survived = True
|
||
for pos in path:
|
||
if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage':
|
||
damage = random.randint(1, 5)
|
||
selected_piece.hp = max(selected_piece.hp - damage, 0)
|
||
del bonus_cells[pos]
|
||
if selected_piece.hp == 0:
|
||
pieces.remove(selected_piece)
|
||
selected_piece = None
|
||
possible_moves.clear()
|
||
survived = False
|
||
switch_turn()
|
||
break
|
||
if survived:
|
||
selected_piece.x = gx
|
||
selected_piece.y = gy
|
||
selected_piece.selected = False
|
||
end_move(selected_piece)
|
||
selected_piece = None
|
||
possible_moves.clear()
|
||
update_fog()
|
||
else:
|
||
selected_piece.selected = False
|
||
selected_piece = None
|
||
possible_moves.clear()
|
||
else:
|
||
if clicked_piece and (gx, gy) in global_revealed and clicked_piece.color == current_turn and not clicked_piece.has_moved:
|
||
selected_piece = clicked_piece
|
||
selected_piece.selected = True
|
||
possible_moves = selected_piece.get_possible_moves(pieces)
|
||
else:
|
||
if shop_open:
|
||
for key, item in shop_items.items():
|
||
if 'button_rect' in item and item['button_rect'].collidepoint(mx, my):
|
||
if player_coins[current_turn] >= item['cost']:
|
||
handle_shop_purchase(key)
|
||
elif event.type == pygame.KEYDOWN:
|
||
if event.key == pygame.K_ESCAPE:
|
||
shop_open = False
|
||
screen.fill(COLOR_BG)
|
||
draw_grid()
|
||
draw_fog()
|
||
draw_merchants()
|
||
draw_bonus_cells()
|
||
draw_coin_cells()
|
||
draw_pieces(pieces)
|
||
draw_statistics_panel()
|
||
if game_over and winner:
|
||
draw_victory(winner)
|
||
pygame.display.flip()
|
||
|
||
pygame.quit()
|
||
sys.exit()
|