OpenWorldChess/chess_open_world.py
2025-02-01 21:33:42 +03:00

886 lines
40 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pygame
import sys
import random
import time
pygame.init()
# ========= Глобальные настройки по умолчанию =========
CELL_SIZE = 32
PANEL_WIDTH = 200
# Значения по умолчанию для поля и шанса бонусов (меню позволит их изменять)
default_GRID_WIDTH = 30
default_GRID_HEIGHT = 30
default_BONUS_GENERATION_CHANCE = 0.10 # 10%
# ========= Цвета =========
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_TIMER_NORMAL = (255, 255, 255)
COLOR_TIMER_WARNING = (255, 0, 0)
COLOR_MERCHANT = (139, 69, 19)
COLOR_COIN = (255, 165, 0)
# ========= Шрифты =========
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)
# ========= Меню настроек =========
def main_menu():
menu_running = True
menu_width, menu_height = 600, 400
menu_screen = pygame.display.set_mode((menu_width, menu_height))
pygame.display.set_caption("Настройки игры")
# Начальные значения из настроек по умолчанию
grid_width = default_GRID_WIDTH
grid_height = default_GRID_HEIGHT
bonus_chance = default_BONUS_GENERATION_CHANCE # 0.10 = 10%
# Подготавливаем тексты для кнопок
minus_text = shop_font.render("-", True, (0, 0, 0))
plus_text = shop_font.render("+", True, (0, 0, 0))
while menu_running:
menu_screen.fill((50, 50, 50))
# Заголовок
title_text = shop_font.render("Настройки игры", True, (255,255,255))
title_rect = title_text.get_rect(center=(menu_width//2, 40))
menu_screen.blit(title_text, title_rect)
# Размер поля
field_text = shop_font.render("Размер поля:", True, (255,255,255))
menu_screen.blit(field_text, (50, 80))
# Ширина поля
width_text = shop_font.render(f"Ширина: {grid_width}", True, (255,255,255))
menu_screen.blit(width_text, (100, 130))
width_minus_rect = pygame.Rect(50, 130, 40, 30)
width_plus_rect = pygame.Rect(250, 130, 40, 30)
pygame.draw.rect(menu_screen, (200,200,200), width_minus_rect)
pygame.draw.rect(menu_screen, (200,200,200), width_plus_rect)
menu_screen.blit(minus_text, minus_text.get_rect(center=width_minus_rect.center))
menu_screen.blit(plus_text, plus_text.get_rect(center=width_plus_rect.center))
# Высота поля
height_text = shop_font.render(f"Высота: {grid_height}", True, (255,255,255))
menu_screen.blit(height_text, (100, 180))
height_minus_rect = pygame.Rect(50, 180, 40, 30)
height_plus_rect = pygame.Rect(250, 180, 40, 30)
pygame.draw.rect(menu_screen, (200,200,200), height_minus_rect)
pygame.draw.rect(menu_screen, (200,200,200), height_plus_rect)
menu_screen.blit(minus_text, minus_text.get_rect(center=height_minus_rect.center))
menu_screen.blit(plus_text, plus_text.get_rect(center=height_plus_rect.center))
# Шанс бонусов (выводим значение в процентах)
bonus_label = shop_font.render("Шанс бонусов (%):", True, (255,255,255))
menu_screen.blit(bonus_label, (100, 230))
bonus_value = shop_font.render(f"{int(bonus_chance*100)}", True, (255,255,255))
menu_screen.blit(bonus_value, (300, 230))
bonus_minus_rect = pygame.Rect(350, 225, 40, 30)
bonus_plus_rect = pygame.Rect(450, 225, 40, 30)
pygame.draw.rect(menu_screen, (200,200,200), bonus_minus_rect)
pygame.draw.rect(menu_screen, (200,200,200), bonus_plus_rect)
menu_screen.blit(minus_text, minus_text.get_rect(center=bonus_minus_rect.center))
menu_screen.blit(plus_text, plus_text.get_rect(center=bonus_plus_rect.center))
# Кнопка "Начать игру"
start_rect = pygame.Rect(menu_width//2 - 75, 300, 150, 50)
pygame.draw.rect(menu_screen, (0,255,0), start_rect)
start_text = shop_font.render("Начать игру", True, (0,0,0))
menu_screen.blit(start_text, start_text.get_rect(center=start_rect.center))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mx, my = pygame.mouse.get_pos()
if width_minus_rect.collidepoint(mx, my):
grid_width = max(8, grid_width - 1)
elif width_plus_rect.collidepoint(mx, my):
grid_width = min(100, grid_width + 1)
elif height_minus_rect.collidepoint(mx, my):
grid_height = max(8, grid_height - 1)
elif height_plus_rect.collidepoint(mx, my):
grid_height = min(100, grid_height + 1)
elif bonus_minus_rect.collidepoint(mx, my):
bonus_chance = max(0.0, bonus_chance - 0.01)
elif bonus_plus_rect.collidepoint(mx, my):
bonus_chance = min(1.0, bonus_chance + 0.01)
elif start_rect.collidepoint(mx, my):
menu_running = False
pygame.display.flip()
return grid_width, grid_height, bonus_chance
# ========= Вызываем меню настроек и обновляем глобальные параметры =========
GRID_WIDTH, GRID_HEIGHT, BONUS_GENERATION_CHANCE = main_menu()
WINDOW_WIDTH = CELL_SIZE * GRID_WIDTH + PANEL_WIDTH
WINDOW_HEIGHT = CELL_SIZE * GRID_HEIGHT
# Теперь устанавливаем главное окно игры
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Шахматы с Открытым Миром 1.1.1")
# ========= Глобальные переменные =========
global_revealed = set()
bonus_cells = {}
base_bonus_probabilities = {
'regen': 0.42,
'hp_upgrade': 0.32,
'damage': 0.2,
'king_hp_upgrade': 0.05,
'add_piece': 0.01
}
bonus_probabilities = base_bonus_probabilities.copy()
piece_symbols = {
'rook': 'Л',
'knight': 'К',
'bishop': 'С',
'queen': 'Ф',
'king': 'К',
'pawn': 'П'
}
# ========= Класс фигур =========
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
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 = getattr(self, 'move_range', 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 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)
# ========= Функция инициализации фигур =========
def initialize_pieces():
pieces = []
cx = GRID_WIDTH // 2
offset = 3 # для базовой линии 8 фигур
# Вычисляем вертикальные позиции пропорционально высоте поля:
black_back_y = max(0, int(GRID_HEIGHT * 0.1))
black_pawn_y = min(GRID_HEIGHT - 1, black_back_y + 1)
white_back_y = GRID_HEIGHT - 1 - int(GRID_HEIGHT * 0.1)
white_pawn_y = max(0, white_back_y - 1)
black_back = ['rook','knight','bishop','king','queen','bishop','knight','rook']
for i, pname in enumerate(black_back):
x = cx - offset + i
if 0 <= x < GRID_WIDTH:
pieces.append(Piece(pname, 'black', x, black_back_y))
for i in range(8):
x = cx - offset + i
if 0 <= x < GRID_WIDTH:
pieces.append(Piece('pawn', 'black', x, black_pawn_y))
white_back = ['rook','knight','bishop','king','queen','bishop','knight','rook']
for i, pname in enumerate(white_back):
x = cx - offset + i
if 0 <= x < GRID_WIDTH:
pieces.append(Piece(pname, 'white', x, white_back_y))
for i in range(8):
x = cx - offset + i
if 0 <= x < GRID_WIDTH:
pieces.append(Piece('pawn', 'white', x, white_pawn_y))
return pieces
# ========= Спрайты для фигур =========
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()
# ========= Спрайты для окружающей среды =========
try:
open_cell_sprite = pygame.image.load("sprites/open_cell.png").convert_alpha()
open_cell_sprite = pygame.transform.scale(open_cell_sprite, (CELL_SIZE, CELL_SIZE))
except Exception as e:
print(f"Ошибка загрузки open_cell.png: {e}")
open_cell_sprite = None
try:
fog_sprite = pygame.image.load("sprites/fog.png").convert_alpha()
fog_sprite = pygame.transform.scale(fog_sprite, (CELL_SIZE, CELL_SIZE))
except Exception as e:
print(f"Ошибка загрузки fog.png: {e}")
fog_sprite = None
# ========= Механика бонусов =========
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'
# ========= Торговцы, монеты и магазин =========
merchant_positions = [(0, GRID_HEIGHT // 2), (GRID_WIDTH - 1, GRID_HEIGHT // 2)]
global_revealed.update(merchant_positions)
coin_cells = set()
player_coins = {'white': 0, 'black': 0}
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}
}
# ========= Система ходов =========
current_turn = 'white'
turn_count = 0
moves_remaining = 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 # секунд
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()
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
# Если фигура погибла, этот ход всё равно расходуется
moves_remaining -= 1
if moves_remaining <= 0:
switch_turn()
# ========= Обновление тумана (с удалением монет при старении) =========
cell_counters = {} # {(x,y): turns_since_last_revealed}
def update_fog():
global global_revealed, bonus_cells, coin_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]
if pos in coin_cells:
del coin_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:
# 95% шанс бонус, 5% монета
if random.random() < 0.95:
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)
# ========= Функции отрисовки =========
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_environment():
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
pos = (x, y)
rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE)
if pos in global_revealed:
if open_cell_sprite:
screen.blit(open_cell_sprite, rect)
else:
pygame.draw.rect(screen, (30, 30, 30), rect)
if cell_counters.get(pos, 0) == 4 and (pos not in bonus_cells) and (pos not in coin_cells):
if fog_sprite:
overlay = fog_sprite.copy()
overlay.set_alpha(128)
screen.blit(overlay, rect)
else:
overlay = pygame.Surface((CELL_SIZE, CELL_SIZE))
overlay.set_alpha(128)
overlay.fill((169, 169, 169))
screen.blit(overlay, rect)
else:
if fog_sprite:
screen.blit(fog_sprite, rect)
else:
pygame.draw.rect(screen, COLOR_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_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)
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':
damage = random.randint(1, 5)
piece.hp -= damage
print(f"Бонус DAMAGE: Фигура получила {damage} урона, осталось {piece.hp} HP")
if piece.hp <= 0:
try:
pieces.remove(piece)
except ValueError:
pass
return 'dead'
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
# ========= Инициализация фигур =========
pieces = initialize_pieces()
selected_piece = None
possible_moves = []
game_over = False
winner = None
# ========= Инициализация тумана =========
cell_counters = {} # {(x,y): turns_since_last_revealed}
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:
# Если на клетке есть бонус damage по пути
if selected_piece.name in ['rook', 'bishop', 'queen']:
# Если фигура двигается на несколько клеток, проверяем промежуточный путь
path = []
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
# Считаем, что ход потрачен, уменьшаем moves_remaining
moves_remaining -= 1
break
if not survived:
if moves_remaining <= 0:
switch_turn()
continue
# Если бонус (damage или другой) находится непосредственно на целевой клетке
if (gx, gy) in bonus_cells:
bonus_type = bonus_cells[(gx, gy)]['type']
result = apply_bonus(selected_piece, bonus_type, pieces)
del bonus_cells[(gx, gy)]
if result == 'dead':
# Если фигура погибла, засчитываем ход, уменьшаем счетчик и остаёмся на том же игроке
moves_remaining -= 1
if moves_remaining <= 0:
switch_turn()
selected_piece = None
possible_moves.clear()
continue
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()
moves_remaining -= 1
if moves_remaining <= 0:
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
moves_remaining -= 1
break
if not survived:
if moves_remaining <= 0:
switch_turn()
continue
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
moves_remaining -= 1
break
if not survived:
if moves_remaining <= 0:
switch_turn()
continue
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']:
# Здесь можно добавить логику покупки
pass
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
shop_open = False
screen.fill(COLOR_BG)
draw_environment()
draw_grid()
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()