OpenWorldChess/chess_open_world.py
2025-01-30 23:14:08 +03:00

649 lines
29 KiB
Python
Raw 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
pygame.init()
# --- ПАРАМЕТРЫ ПОЛЯ ---
CELL_SIZE = 32
GRID_WIDTH = 30
GRID_HEIGHT = 30
WINDOW_WIDTH = CELL_SIZE * GRID_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) # Светло-серый
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("Шахматы с открытым миром 1.0")
# Шрифты
font = pygame.font.SysFont(None, CELL_SIZE // 2)
victory_font = pygame.font.SysFont(None, 60)
turn_font = pygame.font.SysFont(None, 30)
# --- Глобальное множество открытых клеток (туман войны) ---
global_revealed = set()
# --- Словарь символов ---
piece_symbols = {
'rook': 'Л',
'knight': 'К',
'bishop': 'С',
'queen': 'Ф',
'king': 'К', # Король (можно заменить на 'K' или другой символ)
'pawn': 'П'
}
# === МЕХАНИКА БОНУСОВ ===
# Храним в глобальном словаре bonus_cells: {(x,y): {'type': ...}}
bonus_cells = {}
# Вероятности появления различных типов бонусов
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%
}
# Функция для выбора бонуса на основе вероятностей
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 initialize_bonus_cells(pieces, num_each=10):
"""
Создаём случайно бонусных клеток.
Включает три типа изначально: 'hp_upgrade', 'regen', 'king_hp_upgrade'.
Бонусы не размещаются под стартовыми позициями фигур.
"""
global bonus_cells
all_positions = [(x, y) for x in range(GRID_WIDTH) for y in range(GRID_HEIGHT)]
random.shuffle(all_positions)
i_pos = 0
types = ['hp_upgrade', 'regen', 'king_hp_upgrade']
for t in types:
for _ in range(num_each):
while i_pos < len(all_positions):
(xx, yy) = all_positions[i_pos]
i_pos += 1
# Проверка, чтобы клетка не занята фигурой
if not any(p.x == xx and p.y == yy for p in pieces):
bonus_cells[(xx, yy)] = {'type': t}
break
def apply_bonus(piece, bonus_type, pieces):
"""Применяем эффект бонуса к фигуре piece в зависимости от типа."""
if bonus_type == 'regen':
# Регенерация (1..2) для НЕ короля
if piece.name != 'king':
amt = random.randint(1, 2)
piece.hp = min(piece.hp + amt, piece.max_hp)
elif bonus_type == 'hp_upgrade':
# Повышение max_hp только НЕ королю без восстановления HP
if piece.name != 'king':
piece.max_hp += 1
# piece.hp остается неизменным
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:
# Если король на полном здоровье,
# то увеличиваем max_hp на 1
king.max_hp += 1
king.hp = king.max_hp
else:
# Если король не на полном, то
# просто восстанавливаем HP до max
king.hp = min(king.hp + 1, king.max_hp)
elif bonus_type == 'damage':
# Нанесение урона (0..5)
damage = random.randint(0, 5)
piece.hp = max(piece.hp - damage, 0)
if piece.hp == 0:
# Удаляем фигуру, если HP равен 0
pieces.remove(piece)
if piece.name == 'king':
global winner, game_over
winner = 'black' if piece.color == 'white' else 'white'
game_over = True
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:
# Создаём новую фигуру того же типа
new_piece = Piece(piece.name, piece.color, nx, ny)
pieces.append(new_piece)
break
# Если не удалось разместить, бонус не активируется
# === МЕХАНИКА БОЯ ===
def resolve_combat(attacker, defender):
"""
Атакующая фигура (A) идёт на клетку защитника (B).
HP(A)=a, HP(B)=b.
- Если a < b: B теряет a (b := b-a), A погибает.
- Если a > b: A теряет b (a := a-b), B погибает.
- Если a == b: A побеждает, убивает B, а у A остаётся 1 HP.
Возвращаем кортеж: (attacker_alive, defender_alive)
при этом изменяем HP в самих объектах.
"""
a = attacker.hp
b = defender.hp
if a < b:
# Защитник теряет a HP
defender.hp = b - a
# Атакующая погибает
attacker.hp = 0
return (False, True)
elif a > b:
# Атакующая теряет b HP
attacker.hp = a - b
# Защитник погибает
defender.hp = 0
return (True, False)
else:
# a == b
# Атакующая побеждает, остаётся с 1 HP
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
# Инициализация 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']:
# Ограничиваем движение до 5 клеток
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, 6): # До 5 клеток
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()
initialize_bonus_cells(pieces, num_each=10)
selected_piece = None
possible_moves = []
game_over = False
winner = None
# --- Туман войны ---
cell_counters = {} # {(x,y): turns_since_last_revealed}
BONUS_GENERATION_CHANCE = 0.1 # 10% шанс появления бонуса при повторном открытии
# --- Система ходов ---
current_turn = 'white' # Начинаем с белых
def update_fog():
"""
Обновляет туман войны:
- Открытые клетки остаются открытыми.
- Закрытые клетки, которые не находятся рядом с любыми фигурами на расстоянии 2 клеток,
становятся закрытыми через 5 ходов.
- При повторном открытии клетки шанс на появление нового бонуса.
"""
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))
# Определяем только новые открытые клетки
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
# Если счетчик достигает 5, закрываем клетку
if cell_counters[pos] >= 5:
if pos in global_revealed:
global_revealed.remove(pos)
# Удаляем бонус, если клетка закрывается и бонус не является 'damage'
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 random.random() < BONUS_GENERATION_CHANCE:
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}
def draw_grid():
for x in range(0, WINDOW_WIDTH, CELL_SIZE):
pygame.draw.line(screen, COLOR_GRID, (x, 0), (x, WINDOW_HEIGHT))
for y in range(0, WINDOW_HEIGHT, CELL_SIZE):
pygame.draw.line(screen, COLOR_GRID, (0, y), (WINDOW_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 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():
"""
Отрисовка бонусов (если клетка открыта или бонус типа 'damage').
Цвета:
- 'regen': (0,255,0) зелёный
- 'hp_upgrade': (0,128,255) синий
- 'damage': (255,0,0) красный
- 'king_hp_upgrade': (255,215,0) золотой
- 'add_piece': (255,255,255) белый с плюсом или розовый
"""
for (bx, by), info in bonus_cells.items():
# Для 'damage' бонусов отображаем всегда, иначе только если клетка видна
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_pieces(pieces):
"""Рисуем фигуры, их HP и подсвечиваем ходы."""
for p in pieces:
rect = pygame.Rect(p.x*CELL_SIZE, p.y*CELL_SIZE, CELL_SIZE, CELL_SIZE)
if p.color == 'white':
pygame.draw.rect(screen, COLOR_WHITE_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}"
hp_text = font.render(hp_str, True, (0,0,0))
screen.blit(hp_text, (rect.x+2, rect.y+2))
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}"
hp_text = font.render(hp_str, True, (255,255,255))
screen.blit(hp_text, (rect.x+2, rect.y+2))
if p.selected:
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 display_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 display_turn(current_turn):
text = turn_font.render(f"Текущий ход: {current_turn.capitalize()}", True, COLOR_TURN_TEXT)
screen.blit(text, (10, WINDOW_HEIGHT - 30))
running = True
clock = pygame.time.Clock()
# Инициализируем туман войны в начале игры
update_fog()
while running:
clock.tick(30)
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()
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)]
# Проверяем, есть ли там вражеская фигура
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()
# Смена хода
current_turn = 'black' if current_turn == 'white' else 'white'
continue
else:
# Атакующая жива, перемещаем её
# Проверка прохождения через клетки (для длинных ходов)
path = []
if attacker.name in ['rook', 'bishop', 'queen']:
dx = gx - attacker.x
dy = gy - attacker.y
if dx != 0:
dx = dx // abs(dx)
if dy != 0:
dy = 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))
# Применяем урон при прохождении через клетки и удаляем 'damage' клетки
survived = True
for pos in path:
if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage':
damage = random.randint(0,5)
attacker.hp = max(attacker.hp - damage, 0)
# Удаляем 'damage' клетку как использованную
del bonus_cells[pos]
if attacker.hp == 0:
pieces.remove(attacker)
selected_piece = None
possible_moves.clear()
survived = False
break
if survived:
attacker.x = gx
attacker.y = gy
attacker.selected = False
selected_piece = None
possible_moves.clear()
# Смена хода
current_turn = 'black' if current_turn == 'white' else 'white'
else:
# Пустая клетка — просто ходим
# Проверка прохождения через клетки (для длинных ходов)
path = []
if selected_piece.name in ['rook', 'bishop', 'queen']:
dx = gx - selected_piece.x
dy = gy - selected_piece.y
if dx != 0:
dx = dx // abs(dx)
if dy != 0:
dy = 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))
# Применяем урон при прохождении через клетки и удаляем 'damage' клетки
survived = True
for pos in path:
if pos in bonus_cells and bonus_cells[pos]['type'] == 'damage':
damage = random.randint(0,5)
selected_piece.hp = max(selected_piece.hp - damage, 0)
# Удаляем 'damage' клетку как использованную
del bonus_cells[pos]
if selected_piece.hp == 0:
pieces.remove(selected_piece)
selected_piece = None
possible_moves.clear()
survived = False
break
if survived:
selected_piece.x = gx
selected_piece.y = gy
selected_piece.selected = False
selected_piece = None
possible_moves.clear()
# Смена хода
current_turn = 'black' if current_turn == 'white' else 'white'
# После успешного хода обновляем туман войны
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:
selected_piece = clicked_piece
selected_piece.selected = True
possible_moves = selected_piece.get_possible_moves(pieces)
# Отрисовка
screen.fill(COLOR_BG)
draw_grid()
draw_fog()
draw_bonus_cells()
draw_pieces(pieces)
if game_over and winner:
display_victory(winner)
else:
display_turn(current_turn)
pygame.display.flip()
pygame.quit()
sys.exit()