Троллейбусно-Маршруточные войны (серия игр)
В этой статье повествуются события, связанные с Троллейбусно-Маршруточными Войнами в Новосибирске Осторожно, двери закрываются...
|
Троллейбусно-Маршруточные Войны в Новосибирске — ряд крупнейших войн в истории Новосибирска, России и Вселенной в целом, описанные в лучшей и единственной правдивой энциклопедии в интернете.
Стоп!
Вся информация уже подробно изложена в этом цикле статей. Перед запуском компьютерных игр следует изучить эту тему.
Задача этой статьи — визуализировать эти события.
Собственно миссии по хронологии:
Оборона Ул. Школьной по улице Пьяного Хмеля[править]
Первая игра этой серии, написанная на питоне спустя 20 лет после тех событий. Так как у разрабов не было компьютера, а был только калькулятор, игра не может похвастаться 3D графикой, но в ней реализован бесконечный мир (т.е. вы не дойдёте до конца карты).
Геймплей[править]
Вы — командир троллейбуса ЗиУ-620501 № 1239 из легендарной 22-й дивизии. Ваша задача — совместно с троллейбусами 9-й дивизии (подсвечены голубым) и вашей 22-й дивизии (светло-зелёный цвет, более тусклый, чем у вашего) уничтожать маршрутки, со всех сторон едущие захватывать улицу (чуть темнее, чем остальные). Вы можете стрелять по ним, но не всё так просто, ведь с 50 хода они начнут стрелять по вам не выхлопными газами из труб, а боевыми пулями из автоматов, которые за 2-3 попадания отправят ваш троллейбус на тот свет. Однако не всё так безнадёжно, ведь при гибели 10 союзников командование решит, что это бесполезная трата сил — удерживать улицу и отступит, спася ваши жизни.
Управление[править]
Здесь всё просто: WASD — движение, ← → ↑ ↓ — стрельба (по диагонали стрелять нельзя).
Код игры (бесплатно!)[править]
import pygame
import sys
import random
# Константы
CELL_SIZE = 50
GRID_SIZE = 10
WINDOW_SIZE = CELL_SIZE * GRID_SIZE
BLOCK_SIZE = 8
FOREST_BLOCK = 30
BIG_FOREST_PROBABILITY = 10
PARK_BLOCK = 50
PARK_PROBABILITY = 10
BULLET_SPEED = 10
FIRE_LIFETIME = 13
# Параметры главной улицы
MAIN_STREET_X_START = -25
MAIN_STREET_X_END = 24
MAIN_STREET_Y = (0, 1)
CROSS_STREETS_X = [-15, 0, 15]
CROSS_STREETS_Y_RANGE = (-10, 10)
# Параметры врагов (маршруток)
ENEMY_HP = 25
ENEMY_DAMAGE = 3
ENEMY_BULLET_SPEED = 3
BULLET_DAMAGE = 10
ENEMY_SPAWN_BLOCK_SIZE = 3
ENEMY_SPAWN_MIN_DIST = 4
# Параметры союзников (троллейбусов)
ALLY_HP = 50
ALLY_DAMAGE = 10
ALLY_SHOOT_INTERVAL = 1 # стреляют каждый ход игрока
ALLY_GROUP_A_COUNT = 25
ALLY_GROUP_B_COUNT = 24
# Цвета
COLOR_ROAD = (200, 200, 200)
COLOR_MAIN_ROAD = (160, 160, 160)
COLOR_GRID = (50, 50, 50)
COLOR_TROLLEY_FALLBACK = (0, 0, 255)
COLOR_ENEMY_BG = (139, 0, 0)
COLOR_ALLY_A_BG = (173, 216, 230) # тускло-голубой
COLOR_ALLY_B_BG = (152, 251, 152) # тускло-салатовый
# Кэши и состояние
small_forest_cache = {}
big_forest_cache = {}
park_cache = {}
fire_cells = {}
burned_cells = set()
bullets = []
enemy_map = {}
enemy_bullets = []
explosions = {}
generated_enemy_blocks = set()
ally_map = {} # (x,y) -> Ally
# Глобальные переменные для новой механики
turn_count = 0
enemy_damage_increased = False
allied_deaths = 0
retreat_phase = False
retreat_timer = 0
notification_text = None
notification_timer = 0
# -------------------------------------------
# Хеш и генерация кварталов (без изменений)
# -------------------------------------------
def hash_coords(bx: int, by: int) -> int:
seed = (bx * 374761393 + by * 668265263) & 0xFFFFFFFF
seed = (seed ^ (seed >> 13)) * 1274126177
seed = seed ^ (seed >> 16)
return seed
def get_quarter_info(x: int, y: int):
bx = x // BLOCK_SIZE
by = y // BLOCK_SIZE
hsh = hash_coords(bx, by)
w = 5 + (hsh % 3)
h = 5 + ((hsh // 4) % 3)
max_dx = BLOCK_SIZE - w
max_dy = BLOCK_SIZE - h
dx = (hsh // 16) % (max_dx + 1) if max_dx >= 0 else 0
dy = (hsh // 64) % (max_dy + 1) if max_dy >= 0 else 0
x0 = bx * BLOCK_SIZE + dx
y0 = by * BLOCK_SIZE + dy
if x0 <= x < x0 + w and y0 <= y < y0 + h:
return (x0, y0, w, h)
return None
def distance_to_main_street(x: int, y: int) -> int:
# Расстояние по X до диапазона главной улицы
if x < MAIN_STREET_X_START:
dx = MAIN_STREET_X_START - x
elif x > MAIN_STREET_X_END:
dx = x - MAIN_STREET_X_END
else:
dx = 0
# Расстояние по Y до ближайшей из строк 0 или 1
if y <= 0:
dy = -y # для y=0 даёт 0, для y<0 – положительное
else: # y >= 1
dy = y - 1 # для y=1 даёт 0, для y>1 – положительное
return dx + dy
# -------------------------------------------
# Главная улица и пересечения
# -------------------------------------------
def is_main_street(x: int, y: int) -> bool:
return (MAIN_STREET_X_START <= x <= MAIN_STREET_X_END and y in MAIN_STREET_Y)
def is_cross_street(x: int, y: int) -> bool:
return (x in CROSS_STREETS_X and CROSS_STREETS_Y_RANGE[0] <= y <= CROSS_STREETS_Y_RANGE[1])
def is_building_row(x: int, y: int) -> bool:
if not (MAIN_STREET_X_START <= x <= MAIN_STREET_X_END):
return False
if y not in (-1, 2):
return False
if x in CROSS_STREETS_X:
return False
return True
# -------------------------------------------
# Леса (маленькие и большие) – без изменений
# -------------------------------------------
def generate_small_forests(fx: int, fy: int):
key = (fx, fy)
if key in small_forest_cache:
return
hsh = hash_coords(fx, fy)
rng = random.Random(hsh)
count = 1 + (hsh % 6)
all_trees = set()
for _ in range(count):
length = 1 + (rng.randint(0, 9))
start = None
for _ in range(100):
tx = fx * FOREST_BLOCK + rng.randint(0, FOREST_BLOCK - 1)
ty = fy * FOREST_BLOCK + rng.randint(0, FOREST_BLOCK - 1)
if get_quarter_info(tx, ty) is None:
start = (tx, ty)
break
if start is None:
continue
forest = set()
forest.add(start)
x, y = start
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for _ in range(length - 1):
rng.shuffle(directions)
moved = False
for dx, dy in directions:
nx, ny = x + dx, y + dy
if (fx * FOREST_BLOCK <= nx < (fx + 1) * FOREST_BLOCK and
fy * FOREST_BLOCK <= ny < (fy + 1) * FOREST_BLOCK and
get_quarter_info(nx, ny) is None):
forest.add((nx, ny))
x, y = nx, ny
moved = True
break
if not moved:
break
all_trees.update(forest)
small_forest_cache[key] = all_trees
def generate_big_forest(fx: int, fy: int):
key = (fx, fy)
if key in big_forest_cache:
return
hsh = hash_coords(fx, fy)
if (hsh % 100) >= BIG_FOREST_PROBABILITY:
big_forest_cache[key] = set()
return
rng = random.Random(hsh)
length = 50 + (hsh % 407)
start = None
for _ in range(200):
tx = fx * FOREST_BLOCK + rng.randint(0, FOREST_BLOCK - 1)
ty = fy * FOREST_BLOCK + rng.randint(0, FOREST_BLOCK - 1)
if get_quarter_info(tx, ty) is None:
start = (tx, ty)
break
if start is None:
big_forest_cache[key] = set()
return
forest = set()
forest.add(start)
x, y = start
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for _ in range(length - 1):
rng.shuffle(directions)
moved = False
for dx, dy in directions:
nx, ny = x + dx, y + dy
if (fx * FOREST_BLOCK <= nx < (fx + 1) * FOREST_BLOCK and
fy * FOREST_BLOCK <= ny < (fy + 1) * FOREST_BLOCK and
get_quarter_info(nx, ny) is None):
forest.add((nx, ny))
x, y = nx, ny
moved = True
break
if not moved:
break
big_forest_cache[key] = forest
def is_big_forest(x: int, y: int) -> bool:
fx = x // FOREST_BLOCK
fy = y // FOREST_BLOCK
key = (fx, fy)
if key not in big_forest_cache:
generate_big_forest(fx, fy)
return (x, y) in big_forest_cache.get(key, set())
def is_forest(x: int, y: int) -> bool:
fx = x // FOREST_BLOCK
fy = y // FOREST_BLOCK
key = (fx, fy)
if key not in big_forest_cache:
generate_big_forest(fx, fy)
if (x, y) in big_forest_cache.get(key, set()):
return True
if key not in small_forest_cache:
generate_small_forests(fx, fy)
return (x, y) in small_forest_cache.get(key, set())
# -------------------------------------------
# Парки развлечений
# -------------------------------------------
def generate_park(bx: int, by: int):
key = (bx, by)
if key in park_cache:
return
hsh = hash_coords(bx, by)
if (hsh % 100) >= PARK_PROBABILITY:
park_cache[key] = set()
return
rng = random.Random(hsh)
area = 2 + rng.randint(0, 13)
attractions = ['🎪', '🎢', '🎡', '⛲', '🎠']
start = None
for _ in range(100):
tx = bx * PARK_BLOCK + rng.randint(0, PARK_BLOCK - 1)
ty = by * PARK_BLOCK + rng.randint(0, PARK_BLOCK - 1)
if get_quarter_info(tx, ty) is None and not is_forest(tx, ty):
start = (tx, ty)
break
if start is None:
park_cache[key] = set()
return
park = set()
park.add(start)
x, y = start
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for _ in range(area - 1):
rng.shuffle(directions)
moved = False
for dx, dy in directions:
nx, ny = x + dx, y + dy
if (bx * PARK_BLOCK <= nx < (bx + 1) * PARK_BLOCK and
by * PARK_BLOCK <= ny < (by + 1) * PARK_BLOCK and
get_quarter_info(nx, ny) is None and
not is_forest(nx, ny) and
(nx, ny) not in park):
park.add((nx, ny))
x, y = nx, ny
moved = True
break
if not moved:
break
park_cache[key] = park
def is_park(x: int, y: int) -> bool:
bx = x // PARK_BLOCK
by = y // PARK_BLOCK
key = (bx, by)
if key not in park_cache:
generate_park(bx, by)
return (x, y) in park_cache.get(key, set())
def park_emoji(x: int, y: int) -> str:
hsh = hash_coords(x, y)
attractions = ['🎪', '🎢', '🎡', '⛲', '🎠']
return attractions[hsh % len(attractions)]
# -------------------------------------------
# Проходимость и препятствия (с учётом союзников)
# -------------------------------------------
def is_burned(x: int, y: int) -> bool:
return (x, y) in burned_cells
def is_on_fire(x: int, y: int) -> bool:
return (x, y) in fire_cells
def is_walkable(x: int, y: int) -> bool:
# Если клетка занята союзником - непроходима
if (x, y) in ally_map:
return False
if is_on_fire(x, y):
return False
if is_burned(x, y):
return True
if is_forest(x, y) or is_park(x, y):
return False
if is_main_street(x, y) or is_cross_street(x, y):
return True
if is_building_row(x, y):
return False
info = get_quarter_info(x, y)
if info is None:
return True
x0, y0, w, h = info
on_edge = (x == x0 or x == x0 + w - 1 or y == y0 or y == y0 + h - 1)
if not on_edge:
return True
if (x == x0 and y == y0 + h // 2) or \
(x == x0 + w - 1 and y == y0 + h // 2) or \
(y == y0 and x == x0 + w // 2) or \
(y == y0 + h - 1 and x == x0 + w // 2):
return True
return False
def is_solid_obstacle(x: int, y: int) -> bool:
# Союзники - твёрдые препятствия для пуль врагов и игрока?
# Для пуль игрока и союзников они не препятствие (пролетают)
# Для пуль врагов - препятствие (останавливаются)
# Но мы будем проверять в соответствующих классах, поэтому здесь оставим только стены/лес/здания
if is_on_fire(x, y):
return False
if is_burned(x, y):
return False
if is_forest(x, y) or is_park(x, y):
return True
if is_main_street(x, y) or is_cross_street(x, y):
return False
if is_building_row(x, y):
return True
info = get_quarter_info(x, y)
if info is None:
return False
x0, y0, w, h = info
on_edge = (x == x0 or x == x0 + w - 1 or y == y0 or y == y0 + h - 1)
if not on_edge:
return False
if (x == x0 and y == y0 + h // 2) or \
(x == x0 + w - 1 and y == y0 + h // 2) or \
(y == y0 and x == x0 + w // 2) or \
(y == y0 + h - 1 and x == x0 + w // 2):
return False
return True
# -------------------------------------------
# Класс пули игрока (не взаимодействует с союзниками)
# -------------------------------------------
class Bullet:
def __init__(self, x, y, dx, dy):
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.speed = BULLET_SPEED
self.alive = True
def update(self):
global enemy_map, explosions
if not self.alive:
return
for _ in range(self.speed):
new_x = self.x + self.dx
new_y = self.y + self.dy
# Попадание во врага
enemy = enemy_map.get((new_x, new_y))
if enemy:
enemy.hp -= BULLET_DAMAGE
if enemy.hp <= 0:
explosions[(new_x, new_y)] = 1
del enemy_map[(new_x, new_y)]
self.alive = False
break
# Препятствие (стена, лес, здание)
if is_solid_obstacle(new_x, new_y):
if not is_on_fire(new_x, new_y):
fire_cells[(new_x, new_y)] = FIRE_LIFETIME
self.alive = False
break
self.x = new_x
self.y = new_y
# -------------------------------------------
# Класс союзника (троллейбус)
# -------------------------------------------
class Ally:
def __init__(self, x, y, group='A'):
self.x = x
self.y = y
self.hp = ALLY_HP
self.group = group # 'A' или 'B'
self.direction = 1 # для группы A: 1 - вправо, -1 - влево
self.color = COLOR_ALLY_A_BG if group == 'A' else COLOR_ALLY_B_BG
def update(self):
"""Движение и стрельба (вызывается каждый ход игрока)"""
# Движение только для группы А
if self.group == 'A':
new_x = self.x + self.direction
# Проверяем, что клетка свободна и в пределах главной улицы
if (MAIN_STREET_X_START <= new_x <= MAIN_STREET_X_END and
self.y in MAIN_STREET_Y and
(new_x, self.y) not in ally_map and
(new_x, self.y) not in enemy_map and
is_walkable(new_x, self.y)):
# Перемещаем
del ally_map[(self.x, self.y)]
self.x = new_x
ally_map[(self.x, self.y)] = self
else:
# Разворачиваемся
self.direction *= -1
# Стрельба
self.shoot()
def shoot(self):
"""Стреляет по ближайшему врагу в прямой видимости."""
global enemy_map, enemy_bullets
if not enemy_map:
return
# Находим ближайшего врага по манхэттенскому расстоянию (или евклидову)
target = None
min_dist = float('inf')
for (ex, ey), enemy in enemy_map.items():
# Проверяем видимость по горизонтали или вертикали
if self.x == ex or self.y == ey:
# Проверяем, нет ли препятствий на линии
if self.x == ex:
step = 1 if ey > self.y else -1
y_check = self.y + step
blocked = False
while y_check != ey:
if is_solid_obstacle(self.x, y_check) or (self.x, y_check) in ally_map:
blocked = True
break
y_check += step
if not blocked:
dist = abs(ey - self.y)
if dist < min_dist:
min_dist = dist
target = (ex, ey)
elif self.y == ey:
step = 1 if ex > self.x else -1
x_check = self.x + step
blocked = False
while x_check != ex:
if is_solid_obstacle(x_check, self.y) or (x_check, self.y) in ally_map:
blocked = True
break
x_check += step
if not blocked:
dist = abs(ex - self.x)
if dist < min_dist:
min_dist = dist
target = (ex, ey)
if target:
tx, ty = target
dx = 0 if tx == self.x else (1 if tx > self.x else -1)
dy = 0 if ty == self.y else (1 if ty > self.y else -1)
# Стреляем только если по одной оси
if dx == 0 or dy == 0:
enemy_bullets.append(EnemyBullet(self.x, self.y, dx, dy, is_enemy=False))
# -------------------------------------------
# Класс пули врага (может поражать игрока и союзников)
# -------------------------------------------
class EnemyBullet:
def __init__(self, x, y, dx, dy, is_enemy=True):
self.x = x
self.y = y
self.dx = dx
self.dy = dy
self.speed = ENEMY_BULLET_SPEED if is_enemy else BULLET_SPEED
self.alive = True
self.is_enemy = is_enemy # True - пуля врага, False - пуля союзника
def update(self, trolley):
global ally_map, enemy_map, explosions, allied_deaths, fire_cells
if not self.alive:
return
for _ in range(self.speed):
new_x = self.x + self.dx
new_y = self.y + self.dy
if self.is_enemy:
# Пуля врага: поражает игрока или союзника
if new_x == trolley.x and new_y == trolley.y:
trolley.hp -= ENEMY_DAMAGE
self.alive = False
break
ally = ally_map.get((new_x, new_y))
if ally:
ally.hp -= ENEMY_DAMAGE
if ally.hp <= 0:
explosions[(new_x, new_y)] = 1
del ally_map[(new_x, new_y)]
allied_deaths += 1
self.alive = False
break
# Проверка на твёрдое препятствие (стены, лес, здания)
if is_solid_obstacle(new_x, new_y):
if not is_on_fire(new_x, new_y):
fire_cells[(new_x, new_y)] = FIRE_LIFETIME
self.alive = False
break
else:
# Пуля союзника: поражает врагов
enemy = enemy_map.get((new_x, new_y))
if enemy:
enemy.hp -= ALLY_DAMAGE
if enemy.hp <= 0:
explosions[(new_x, new_y)] = 1
del enemy_map[(new_x, new_y)]
self.alive = False
break
# Столкновение с твёрдым препятствием (стены, лес, здания)
if is_solid_obstacle(new_x, new_y):
if not is_on_fire(new_x, new_y):
fire_cells[(new_x, new_y)] = FIRE_LIFETIME
self.alive = False
break
self.x = new_x
self.y = new_y
# -------------------------------------------
# Класс игрока (троллейбус) – без изменений, кроме проверки на союзников в движении
# -------------------------------------------
class Trolleybus:
def __init__(self):
self.x, self.y = 0, 0
self.hp = 50
while not is_walkable(self.x, self.y) or is_on_fire(self.x, self.y) or is_burned(self.x, self.y):
self.x += 1
def move(self, dx: int, dy: int) -> bool:
global enemy_map, ally_map
new_x = self.x + dx
new_y = self.y + dy
# Проверяем проходимость (учитывая союзников)
if not is_walkable(new_x, new_y) and not is_on_fire(new_x, new_y):
return False
if is_on_fire(new_x, new_y):
if self.hp >= 10:
self.hp -= 10
self.x, self.y = new_x, new_y
self.check_enemy_collision()
return True
else:
return False
self.x, self.y = new_x, new_y
self.check_enemy_collision()
return True
def check_enemy_collision(self):
enemy = enemy_map.get((self.x, self.y))
if enemy:
self.hp -= ENEMY_DAMAGE
# -------------------------------------------
# Генерация врагов (без изменений)
# -------------------------------------------
def generate_enemy_for_block(bx: int, by: int):
key = (bx, by)
if key in generated_enemy_blocks:
return
generated_enemy_blocks.add(key)
hsh = hash_coords(bx, by)
idx = hsh % 25
cell_x = bx * ENEMY_SPAWN_BLOCK_SIZE + (idx % ENEMY_SPAWN_BLOCK_SIZE)
cell_y = by * ENEMY_SPAWN_BLOCK_SIZE + (idx // ENEMY_SPAWN_BLOCK_SIZE)
if cell_y >= 0:
dist = cell_y - 1 if cell_y >= 2 else 0
else:
dist = -cell_y
if dist < ENEMY_SPAWN_MIN_DIST:
return
if (is_walkable(cell_x, cell_y) and
not is_on_fire(cell_x, cell_y) and
not is_burned(cell_x, cell_y) and
(cell_x, cell_y) not in enemy_map and
(cell_x, cell_y) not in ally_map):
enemy = Enemy(cell_x, cell_y)
enemy_map[(cell_x, cell_y)] = enemy
# -------------------------------------------
# Генерация союзников (добавлено)
# -------------------------------------------
def spawn_allies():
"""Детерминированный спавн 49 союзников."""
global ally_map
ally_map.clear()
hsh = hash_coords(0, 0) # базовый хеш для последовательности
rng = random.Random(hsh)
# Группа A: 25 на главной улице
main_cells = []
for x in range(MAIN_STREET_X_START, MAIN_STREET_X_END + 1):
for y in MAIN_STREET_Y:
if is_walkable(x, y) and (x, y) not in ally_map and (x, y) not in enemy_map:
main_cells.append((x, y))
# Перемешиваем детерминированно
rng.shuffle(main_cells)
count_a = min(ALLY_GROUP_A_COUNT, len(main_cells))
for i in range(count_a):
x, y = main_cells[i]
ally = Ally(x, y, 'A')
ally_map[(x, y)] = ally
# Группа B: 24 на перекрёстках и концах, не далее 7 клеток от главной (по вертикали)
# Допустимые x: перекрёстки и концы
allowed_x = set(CROSS_STREETS_X)
allowed_x.add(MAIN_STREET_X_START)
allowed_x.add(MAIN_STREET_X_END)
# y от -7 до 7, исключая главную (0,1)
allowed_y = list(range(-7, 8))
allowed_y = [y for y in allowed_y if y not in MAIN_STREET_Y]
cells_b = []
for x in allowed_x:
for y in allowed_y:
if is_walkable(x, y) and (x, y) not in ally_map and (x, y) not in enemy_map:
cells_b.append((x, y))
rng.shuffle(cells_b)
count_b = min(ALLY_GROUP_B_COUNT, len(cells_b))
for i in range(count_b):
x, y = cells_b[i]
ally = Ally(x, y, 'B')
ally_map[(x, y)] = ally
# -------------------------------------------
# Класс врага (изменён ИИ)
# -------------------------------------------
class Enemy:
def __init__(self, x, y):
self.x = x
self.y = y
self.hp = ENEMY_HP
def move_towards_main_street(self):
"""
Выбирает направление, уменьшающее расстояние до главной улицы.
Если клетка в этом направлении является твёрдым препятствием – стреляет по ней (поджигает) и не двигается.
Если клетка проходима – двигается.
В остальных случаях (огонь, союзник, враг) – стоит на месте.
"""
global enemy_bullets
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
best_dir = None
best_dist = None
# Находим направление, которое сильнее всего уменьшает расстояние до главной улицы
for dx, dy in directions:
nx, ny = self.x + dx, self.y + dy
dist = distance_to_main_street(nx, ny)
if best_dist is None or dist < best_dist:
best_dist = dist
best_dir = (dx, dy)
if best_dir is None:
return self.x, self.y
dx, dy = best_dir
nx, ny = self.x + dx, self.y + dy
# Проверяем, проходима ли клетка
if is_walkable(nx, ny):
# Если проходима и не занята – двигаемся
if (nx, ny) not in enemy_map and (nx, ny) not in ally_map:
return nx, ny
else:
# занята другим врагом или союзником – стоим
return self.x, self.y
else:
# Не проходима – проверяем, является ли твёрдым препятствием
if is_solid_obstacle(nx, ny):
# Стреляем по препятствию (создаём пулю врага)
enemy_bullets.append(EnemyBullet(self.x, self.y, dx, dy, is_enemy=True))
# Не двигаемся
return self.x, self.y
else:
# Например, горит – просто стоим
return self.x, self.y
def shoot(self, trolley):
"""Стреляет по ближайшему троллейбусу (игрок или союзник) в прямой видимости."""
global ally_map
# Собираем все цели: игрок + союзники
targets = [(trolley.x, trolley.y)]
for (ax, ay) in ally_map.keys():
targets.append((ax, ay))
# Находим ближайшую цель по прямой видимости
best_target = None
min_dist = float('inf')
for (tx, ty) in targets:
if self.x == tx or self.y == ty:
# Проверка видимости
if self.x == tx:
step = 1 if ty > self.y else -1
y_check = self.y + step
blocked = False
while y_check != ty:
if is_solid_obstacle(self.x, y_check) or (self.x, y_check) in ally_map:
blocked = True
break
y_check += step
if not blocked:
dist = abs(ty - self.y)
if dist < min_dist:
min_dist = dist
best_target = (tx, ty)
elif self.y == ty:
step = 1 if tx > self.x else -1
x_check = self.x + step
blocked = False
while x_check != tx:
if is_solid_obstacle(x_check, self.y) or (x_check, self.y) in ally_map:
blocked = True
break
x_check += step
if not blocked:
dist = abs(tx - self.x)
if dist < min_dist:
min_dist = dist
best_target = (tx, ty)
if best_target:
tx, ty = best_target
dx = 0 if tx == self.x else (1 if tx > self.x else -1)
dy = 0 if ty == self.y else (1 if ty > self.y else -1)
if dx == 0 or dy == 0: # только по одной оси
enemy_bullets.append(EnemyBullet(self.x, self.y, dx, dy, is_enemy=True))
# -------------------------------------------
# Основной цикл
# -------------------------------------------
def main():
global bullets, fire_cells, burned_cells, enemy_bullets, explosions, enemy_map, ally_map
global turn_count, enemy_damage_increased, allied_deaths, retreat_phase, retreat_timer, notification_text, notification_timer, ENEMY_DAMAGE
pygame.init()
screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
pygame.display.set_caption("Троллейбус с союзниками")
clock = pygame.time.Clock()
trolley = Trolleybus()
spawn_allies() # создаём 49 союзников
game_over = False
game_over_message = ""
try:
font = pygame.font.SysFont("Segoe UI Emoji", CELL_SIZE - 6)
except:
font = pygame.font.SysFont("Arial", CELL_SIZE - 6)
running = True
while running:
player_acted = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN and not game_over and not retreat_phase:
if event.key == pygame.K_w:
trolley.move(0, -1)
player_acted = True
elif event.key == pygame.K_s:
trolley.move(0, 1)
player_acted = True
elif event.key == pygame.K_a:
trolley.move(-1, 0)
player_acted = True
elif event.key == pygame.K_d:
trolley.move(1, 0)
player_acted = True
elif event.key == pygame.K_UP:
bullets.append(Bullet(trolley.x, trolley.y, 0, -1))
player_acted = True
elif event.key == pygame.K_DOWN:
bullets.append(Bullet(trolley.x, trolley.y, 0, 1))
player_acted = True
elif event.key == pygame.K_LEFT:
bullets.append(Bullet(trolley.x, trolley.y, -1, 0))
player_acted = True
elif event.key == pygame.K_RIGHT:
bullets.append(Bullet(trolley.x, trolley.y, 1, 0))
player_acted = True
if game_over:
screen.fill((0,0,0))
text = pygame.font.SysFont("Arial", 40).render(game_over_message, True, (255,0,0))
screen.blit(text, (WINDOW_SIZE//2 - text.get_width()//2, WINDOW_SIZE//2 - 20))
pygame.display.flip()
clock.tick(30)
continue
# Если наступила фаза отступления, обрабатываем только таймер и отрисовку
if retreat_phase:
# Уменьшаем таймер
if retreat_timer > 0:
retreat_timer -= 1
else:
# Время вышло, показываем финальное сообщение и завершаем
screen.fill((0,0,0))
victory_text = pygame.font.SysFont("Arial", 50).render("Миссия выполнена!", True, (0,255,0))
screen.blit(victory_text, (WINDOW_SIZE//2 - victory_text.get_width()//2, WINDOW_SIZE//2 - 25))
pygame.display.flip()
pygame.time.wait(2000)
running = False
continue
# Рисуем игровой мир (без обновления логики) - повторяем код отрисовки
screen.fill((0, 0, 0))
offset_x = trolley.x - GRID_SIZE // 2
offset_y = trolley.y - GRID_SIZE // 2
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
gx = offset_x + i
gy = offset_y + j
rect = pygame.Rect(i * CELL_SIZE, j * CELL_SIZE, CELL_SIZE, CELL_SIZE)
draw_fire = is_on_fire(gx, gy)
draw_burned = is_burned(gx, gy)
draw_enemy = (gx, gy) in enemy_map
draw_ally = (gx, gy) in ally_map
draw_forest = is_forest(gx, gy)
draw_park = is_park(gx, gy)
draw_building = not is_walkable(gx, gy) and not draw_fire and not draw_burned and not draw_ally
if draw_fire:
try:
fire_surf = font.render("🔥", True, (255, 0, 0))
screen.blit(fire_surf, fire_surf.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (255, 100, 0), rect)
elif draw_burned:
pygame.draw.rect(screen, COLOR_ROAD, rect)
elif draw_ally:
ally = ally_map[(gx, gy)]
pygame.draw.rect(screen, ally.color, rect)
elif draw_enemy:
pygame.draw.rect(screen, COLOR_ENEMY_BG, rect)
elif draw_forest:
try:
emoji = "🌳" if hash_coords(gx, gy) % 2 == 0 else "🌲"
if is_big_forest(gx, gy):
choice = hash_coords(gx, gy) % 3
emoji = ["🌳", "", "🌲"][choice]
text_surface = font.render(emoji, True, (0, 128, 0))
screen.blit(text_surface, text_surface.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (34, 139, 34), rect)
elif draw_park:
try:
emoji = park_emoji(gx, gy)
text_surface = font.render(emoji, True, (255, 20, 147))
screen.blit(text_surface, text_surface.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (255, 105, 180), rect)
elif draw_building:
try:
emoji = "🏢" if (gx * 31 + gy * 17) % 2 == 0 else "🏠"
text_surface = font.render(emoji, True, (0, 0, 0))
screen.blit(text_surface, text_surface.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (80, 80, 120), rect)
else:
color = COLOR_MAIN_ROAD if is_main_street(gx, gy) else COLOR_ROAD
pygame.draw.rect(screen, color, rect)
pygame.draw.rect(screen, COLOR_GRID, rect, 1)
# Враги
for (x, y), enemy in enemy_map.items():
sx = (x - offset_x) * CELL_SIZE
sy = (y - offset_y) * CELL_SIZE
rect = pygame.Rect(sx, sy, CELL_SIZE, CELL_SIZE)
try:
text = font.render("🚐", True, (255, 255, 255))
screen.blit(text, text.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (0, 0, 255), rect.inflate(-10, -10))
# Союзники
for (x, y), ally in ally_map.items():
sx = (x - offset_x) * CELL_SIZE
sy = (y - offset_y) * CELL_SIZE
rect = pygame.Rect(sx, sy, CELL_SIZE, CELL_SIZE)
try:
text = font.render("🚎", True, (0, 0, 0))
screen.blit(text, text.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (0, 0, 255), rect.inflate(-10, -10))
# Взрывы
for (x, y) in explosions:
sx = (x - offset_x) * CELL_SIZE
sy = (y - offset_y) * CELL_SIZE
rect = pygame.Rect(sx, sy, CELL_SIZE, CELL_SIZE)
try:
text = font.render("💥", True, (255, 0, 0))
screen.blit(text, text.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (255, 255, 0), rect)
# Пули игрока
for bullet in bullets:
sx = (bullet.x - offset_x) * CELL_SIZE + CELL_SIZE // 2
sy = (bullet.y - offset_y) * CELL_SIZE + CELL_SIZE // 2
pygame.draw.circle(screen, (255, 255, 0), (sx, sy), 4)
# Пули врагов и союзников
for b in enemy_bullets:
sx = (b.x - offset_x) * CELL_SIZE + CELL_SIZE // 2
sy = (b.y - offset_y) * CELL_SIZE + CELL_SIZE // 2
color = (255, 0, 0) if b.is_enemy else (0, 255, 0)
pygame.draw.circle(screen, color, (sx, sy), 4)
# Троллейбус игрока
player_rect = pygame.Rect(5 * CELL_SIZE, 5 * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, (0, 255, 0), player_rect)
try:
trolley_text = font.render("🚎", True, (0, 0, 255))
trolley_rect = trolley_text.get_rect(center=player_rect.center)
screen.blit(trolley_text, trolley_rect)
except:
pygame.draw.rect(screen, COLOR_TROLLEY_FALLBACK, player_rect.inflate(-10, -10))
# Информация
try:
info_font = pygame.font.SysFont("Arial", 20)
except:
info_font = font
hp_text = info_font.render(f"HP: {trolley.hp}", True, (255, 255, 255))
screen.blit(hp_text, (10, 10))
coord_text = info_font.render(f"X:{trolley.x} Y:{trolley.y}", True, (255, 255, 255))
screen.blit(coord_text, (10, 35))
allies_text = info_font.render(f"Союзников: {len(ally_map)}", True, (255, 255, 255))
screen.blit(allies_text, (10, 60))
deaths_text = info_font.render(f"Погибло союзников: {allied_deaths}", True, (255, 255, 255))
screen.blit(deaths_text, (10, 85))
# Уведомление, если есть
if notification_text and notification_timer > 0:
notif_font = pygame.font.SysFont("Arial", 30)
notif_surf = notif_font.render(notification_text, True, (255, 255, 0))
notif_rect = notif_surf.get_rect(center=(WINDOW_SIZE//2, 30))
bg_rect = notif_rect.inflate(20, 10)
pygame.draw.rect(screen, (0,0,0), bg_rect)
screen.blit(notif_surf, notif_rect)
notification_timer -= 1
pygame.display.flip()
clock.tick(30)
continue
# ----- Нормальный ход игры -----
if player_acted:
# Увеличиваем счётчик ходов
turn_count += 1
# Проверяем достижение 50-го хода для увеличения урона врагов
if turn_count >= 50 and not enemy_damage_increased:
enemy_damage_increased = True
ENEMY_DAMAGE = 25
notification_text = "Маршрутки стали использовать автоматы"
notification_timer = 60 # 2 секунды при 30 FPS
# Генерация врагов для видимых блоков
offset_x = trolley.x - GRID_SIZE // 2
offset_y = trolley.y - GRID_SIZE // 2
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
gx = offset_x + i
gy = offset_y + j
bx = gx // ENEMY_SPAWN_BLOCK_SIZE
by = gy // ENEMY_SPAWN_BLOCK_SIZE
generate_enemy_for_block(bx, by)
# 1. Движение врагов (используем новый ИИ)
for enemy in list(enemy_map.values()):
old_pos = (enemy.x, enemy.y)
new_x, new_y = enemy.move_towards_main_street()
if (new_x, new_y) != old_pos:
if is_main_street(new_x, new_y):
game_over = True
game_over_message = "Маршрутка захватила главную улицу! Вы проиграли!"
break
del enemy_map[old_pos]
enemy.x, enemy.y = new_x, new_y
enemy_map[(new_x, new_y)] = enemy
if game_over:
continue
# 2. Стрельба врагов (по троллейбусам)
for enemy in list(enemy_map.values()):
enemy.shoot(trolley)
# 3. Движение и стрельба союзников
for ally in list(ally_map.values()):
ally.update() # внутри стрельба
# 4. Обновление пуль игрока
for bullet in bullets:
bullet.update()
bullets = [b for b in bullets if b.alive]
# 5. Обновление пуль врагов и союзников (все в enemy_bullets)
for b in enemy_bullets:
b.update(trolley)
enemy_bullets = [b for b in enemy_bullets if b.alive]
# 6. Взрывы
to_remove_exp = []
for (x, y), timer in explosions.items():
if timer <= 0:
to_remove_exp.append((x, y))
else:
explosions[(x, y)] = timer - 1
for pos in to_remove_exp:
del explosions[pos]
# 7. Огонь
to_remove_fire = []
for (x, y), t in fire_cells.items():
if t <= 1:
to_remove_fire.append((x, y))
burned_cells.add((x, y))
else:
fire_cells[(x, y)] = t - 1
for pos in to_remove_fire:
del fire_cells[pos]
# Проверка смерти игрока
if trolley.hp <= 0:
game_over = True
game_over_message = "Троллейбус был уничтожен! Вы проиграли!"
# Проверка условия отступления (10 погибших союзников)
if allied_deaths >= 10 and not retreat_phase and not game_over:
retreat_phase = True
retreat_timer = 120 # 4 секунды при 30 FPS
notification_text = "Далее удерживать улицу нецелесообразно, отступаем!!!"
notification_timer = 120 # будет уменьшаться в цикле
# Отрисовка (нормальный режим)
screen.fill((0, 0, 0))
offset_x = trolley.x - GRID_SIZE // 2
offset_y = trolley.y - GRID_SIZE // 2
for i in range(GRID_SIZE):
for j in range(GRID_SIZE):
gx = offset_x + i
gy = offset_y + j
rect = pygame.Rect(i * CELL_SIZE, j * CELL_SIZE, CELL_SIZE, CELL_SIZE)
draw_fire = is_on_fire(gx, gy)
draw_burned = is_burned(gx, gy)
draw_enemy = (gx, gy) in enemy_map
draw_ally = (gx, gy) in ally_map
draw_forest = is_forest(gx, gy)
draw_park = is_park(gx, gy)
draw_building = not is_walkable(gx, gy) and not draw_fire and not draw_burned and not draw_ally
if draw_fire:
try:
fire_surf = font.render("🔥", True, (255, 0, 0))
screen.blit(fire_surf, fire_surf.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (255, 100, 0), rect)
elif draw_burned:
pygame.draw.rect(screen, COLOR_ROAD, rect)
elif draw_ally:
ally = ally_map[(gx, gy)]
pygame.draw.rect(screen, ally.color, rect)
elif draw_enemy:
pygame.draw.rect(screen, COLOR_ENEMY_BG, rect)
elif draw_forest:
try:
emoji = "🌳" if hash_coords(gx, gy) % 2 == 0 else "🌲"
if is_big_forest(gx, gy):
choice = hash_coords(gx, gy) % 3
emoji = ["🌳", "", "🌲"][choice]
text_surface = font.render(emoji, True, (0, 128, 0))
screen.blit(text_surface, text_surface.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (34, 139, 34), rect)
elif draw_park:
try:
emoji = park_emoji(gx, gy)
text_surface = font.render(emoji, True, (255, 20, 147))
screen.blit(text_surface, text_surface.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (255, 105, 180), rect)
elif draw_building:
try:
emoji = "🏢" if (gx * 31 + gy * 17) % 2 == 0 else "🏠"
text_surface = font.render(emoji, True, (0, 0, 0))
screen.blit(text_surface, text_surface.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (80, 80, 120), rect)
else:
color = COLOR_MAIN_ROAD if is_main_street(gx, gy) else COLOR_ROAD
pygame.draw.rect(screen, color, rect)
pygame.draw.rect(screen, COLOR_GRID, rect, 1)
# Враги
for (x, y), enemy in enemy_map.items():
sx = (x - offset_x) * CELL_SIZE
sy = (y - offset_y) * CELL_SIZE
rect = pygame.Rect(sx, sy, CELL_SIZE, CELL_SIZE)
try:
text = font.render("🚐", True, (255, 255, 255))
screen.blit(text, text.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (0, 0, 255), rect.inflate(-10, -10))
# Союзники
for (x, y), ally in ally_map.items():
sx = (x - offset_x) * CELL_SIZE
sy = (y - offset_y) * CELL_SIZE
rect = pygame.Rect(sx, sy, CELL_SIZE, CELL_SIZE)
try:
text = font.render("🚎", True, (0, 0, 0))
screen.blit(text, text.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (0, 0, 255), rect.inflate(-10, -10))
# Взрывы
for (x, y) in explosions:
sx = (x - offset_x) * CELL_SIZE
sy = (y - offset_y) * CELL_SIZE
rect = pygame.Rect(sx, sy, CELL_SIZE, CELL_SIZE)
try:
text = font.render("💥", True, (255, 0, 0))
screen.blit(text, text.get_rect(center=rect.center))
except:
pygame.draw.rect(screen, (255, 255, 0), rect)
# Пули игрока
for bullet in bullets:
sx = (bullet.x - offset_x) * CELL_SIZE + CELL_SIZE // 2
sy = (bullet.y - offset_y) * CELL_SIZE + CELL_SIZE // 2
pygame.draw.circle(screen, (255, 255, 0), (sx, sy), 4)
# Пули врагов и союзников
for b in enemy_bullets:
sx = (b.x - offset_x) * CELL_SIZE + CELL_SIZE // 2
sy = (b.y - offset_y) * CELL_SIZE + CELL_SIZE // 2
color = (255, 0, 0) if b.is_enemy else (0, 255, 0)
pygame.draw.circle(screen, color, (sx, sy), 4)
# Троллейбус игрока
player_rect = pygame.Rect(5 * CELL_SIZE, 5 * CELL_SIZE, CELL_SIZE, CELL_SIZE)
pygame.draw.rect(screen, (0, 255, 0), player_rect)
try:
trolley_text = font.render("🚎", True, (0, 0, 255))
trolley_rect = trolley_text.get_rect(center=player_rect.center)
screen.blit(trolley_text, trolley_rect)
except:
pygame.draw.rect(screen, COLOR_TROLLEY_FALLBACK, player_rect.inflate(-10, -10))
# Информация
try:
info_font = pygame.font.SysFont("Arial", 20)
except:
info_font = font
hp_text = info_font.render(f"HP: {trolley.hp}", True, (255, 255, 255))
screen.blit(hp_text, (10, 10))
coord_text = info_font.render(f"X:{trolley.x} Y:{trolley.y}", True, (255, 255, 255))
screen.blit(coord_text, (10, 35))
allies_text = info_font.render(f"Союзников: {len(ally_map)}", True, (255, 255, 255))
screen.blit(allies_text, (10, 60))
deaths_text = info_font.render(f"Погибло союзников: {allied_deaths}", True, (255, 255, 255))
screen.blit(deaths_text, (10, 85))
turn_text = info_font.render(f"Ход: {turn_count}", True, (255, 255, 255))
screen.blit(turn_text, (10, 110))
# Уведомление, если есть
if notification_text and notification_timer > 0:
notif_font = pygame.font.SysFont("Arial", 30)
notif_surf = notif_font.render(notification_text, True, (255, 255, 0))
notif_rect = notif_surf.get_rect(center=(WINDOW_SIZE//2, 30))
bg_rect = notif_rect.inflate(20, 10)
pygame.draw.rect(screen, (0,0,0), bg_rect)
screen.blit(notif_surf, notif_rect)
notification_timer -= 1
else:
# Сбрасываем текст, когда таймер истёк
if notification_text is not None:
notification_text = None
pygame.display.flip()
clock.tick(30)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
PS. Остальные миссии ещё не готовы, пишите пожелания к ним.
Игровая комната |
||
|---|---|---|
| Собранные игрушки | Second Hand · SimCity 2000 · QTE · Wikigacha · Весёлый работодатель · Казино «Флэш Рояль» · Маленький человечек в большой квартире: Яичница · Отшей блондинку · Пустыня · Путешествие куда-нибудь и не только · Сказка про белого бычка | |
| Разобранные игрушки | AI Adventures · Бездна · Интерактивное кино · Крестики-нолики · Самый сложный экзамен · Симулятор динозавра | |
| Игрушечный питон | Велогрибник: Заводской Дрифт · Велогрибник 2.0: Лесное Метро · Проф. Волкгонагалл: Лесная Трансфигурация (версия 37.0) | |
| Ещё чего: Портал «Игры Абсурдопедии» | Компьютерные игры | Ролевая игра Абсурдопедии | ||