Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- Битва Битардов Director's Cut Edition Remastered v1.2
- Для работы программы потребуются:
- - Python 3.x версии (python.org);
- - Библиотека Pillow для Python 3.x. (pypi.org -> в строке поиска пишем «pillow»; или установить через pip3).
- Подробнее как это всё устанавливать для вашей ОС можно загуглить так:
- « Как установить Python 3 на %моя_операционная_система_нейм% »
- « Как установить Pillow для %моя_операционная_система_нейм% »
- Внимание! Функции сохранения и загрузки используют модуль pickle. Никогда не загружайте файлы сохранений, которые
- записал кто-то другой, например, скачанные из интернета. Это может привести к непредсказуемым последствиям, так как
- в .bbsav файлах хранятся объекты Python как есть.
- Злоумышленники могут добавить в них вредоносный код и он выполнится при загрузке сохранения.
- Кнопка «ОБНОВИТЬ КАРТУ» создаёт или перезаписывает изображение "bitva-bitardov.png" в каталог "output".
- После каждого обновления карты лог событий сбрасывается.
- Рекомендуется обновлять карту не реже 20 применённых действий.
- ====== НАЧАЛО ШАПКИ ТРЕДА =====
- [b]Битва Битардов[/b]
- Тебе предстоит выступить в качестве генерала на стороне одной из враждующих фракций — Мочи или Говна.
- Ты можешь послать свою армию защищать город твоей фракции, либо атаковать город врага.
- Нападать можно только на те города, которые находятся на соседних территориях, на карте возможности для атаки показаны стрелочками. В случае успешного захвата города, остатки победившей армии остаются в этом городе в качестве защитников.
- Защищать можно любой город своей фракции. Армия, посланная на защиту города, остаётся в городе пока не будет уничтожена противником. Сила обороняющихся армий в одном и том же городе суммируется.
- [b]В посте нужно указывать:[/b]
- 1. Принадлежность фракции (обязательно)
- 2. Имя командира (не обязательно)
- 3. Приказ твоей армии — какой город защищать или на какой город напасть.
- [b]Рероллить ссылкой на пост нельзя. В каждом посте нужно описывать всё по правилам, иначе такой пост не учитывается.[/b]
- Владение городами даёт определённые бонусы фракциям. Например, начальные города Говна дают бонус по +1 к силе армии при обороне города, а города Мочи дают +1 при атаке. Если фракция Мочи захватит один из городов Говна, она также получит бонус +1 при защите. Столицы дают особые бонусы. Информацию о бонусах можно посмотреть на карте.
- [b]Как определяется сила армии:[/b]
- Всё что ниже дабла = ничего (не учитывается);
- Дабл = цифра в дабле умноженная на себя + 10;
- Трипл = цифра в трипле умноженная на себя два раза + 30;
- Квадрипл = цифра квадрипла умноженная на себя три раза + 40;
- и так далее...
- [b]Как рассчитывается итог сражения.[/b]
- Армия нападает на город:
- [сила_атакующей_армии] - ([сила_гарнизона] + [бонус_защиты_от_городов]) x [бонус_защиты_от_столицы] = [остатки_атакующих]
- Если [остатки_атакующих] больше 0, считается, что город захвачен;
- При этом, [остатки_атакующих] добавляются к силе гарнизона города, теперь уже принадлежащего другой фракции.
- Независимо от того, был ли захвачен город после атаки, рассчитывается урон по гарнизону города:
- [сила_гарнизона] - ([сила_атакующей_армии] + [бонус_атаки_от_городов]) x [бонус_атаки_от_столицы] = [остатки_гарнизона]
- При этом, даже если [остатки_гарнизона] = 0 или отрицательное число, город считается захваченным только когда [остатки_атакующих] больше нуля. То есть когда погибает и вся атакующая армия, и весь гарнизон, город захваченным не считается.
- Армия защищает город:
- [сила_гарнизона_было] + [сила_защищающей_армии] = [сила_гарнизона_стало]
- То есть сила армии просто просто добавляется к силе гарнизона.
- В случаях, когда армия приходит атаковать город, который уже захвачен союзником, считается, что она защищает этот город.
- Когда армия приходит защищать город, который уже захвачен врагом, считается, что она атакует этот город.
- Чтобы ОП успевал обрабатывать поступающую информацию и оперативно обновлять карту, [b]после поста ОП'а «СТОП» битва приостанавливается. Любые посты после этого не учитываются. Битва продолжается и посты снова учитываются толко после того как ОП запостит обновлённую карту.[/b]
- Условия победы:
- Захватить все города на карте, либо к 500-ому посту в треде собрать в своих городах более сильное войско (суммарно), без учёта бонусов.
- Да начнётся Битва Битардов!
- ====== КОНЕЦ ШАПКИ ТРЕДА =====
- """
- import tkinter
- import tkinter.messagebox
- import tkinter.filedialog
- import os
- import os.path as path
- import pickle
- from PIL import Image, ImageDraw, ImageFont
- class Options:
- def __init__(self):
- self.FONT_GUI = "FreeSansBold 10"
- self.FG_COLOR = "#FF6600" # Цвет текста GUI
- self.BG_COLOR = "#EEEEEE" # Цвет фона GUI
- self.BACKGROUND = "background_default.png" # Фон игровой карты (файл должен находиться в папке "resources")
- self.DEFAULT_GENERAL_NAME = "Аноним" # Имя генерала по умолчанию
- self.POST_NUMBER_LENGTH = 9 # Длина номера поста, должна быть ≥ 2
- self.MAX_POST_COUNT = 500 # Пост после которого битва завершается (пользователь останавливает битву вручную)
- self.working_dir = path.split(path.realpath(__file__))[0]
- # *Meta — неизменные данные о фракциях, городах и мире.
- class FractionMeta:
- def __init__(self, human_name, label_color, accusative=None):
- self.human_name = human_name
- self.accusative = accusative or human_name
- self.label_color = label_color
- class CityMeta:
- def __init__(self, human_name, bonus, routes, starting_fraction, label_pos, radiobutton_order):
- self.human_name = human_name
- self.bonus = bonus
- self.routes = routes
- self.starting_fraction = starting_fraction
- self.label_pos = label_pos
- self.radiobutton_order = radiobutton_order
- class WorldMeta:
- def __init__(self):
- self.fractions = {
- 'mocha': FractionMeta(human_name="Моча", accusative="Мочу", label_color="#F5F325"),
- 'govno': FractionMeta(human_name="Говно", label_color="#F58500")
- }
- self.cities = {
- 'portmocha': CityMeta(
- human_name="Порт-Моча", bonus='attackX',
- routes=('huegrad', 'huisk'),
- starting_fraction='mocha', label_pos=(267, 422), radiobutton_order=0),
- 'huegrad': CityMeta(
- human_name="Хуеград", bonus='attack+',
- routes=('portmocha', 'huisk', 'huihrustalniy', 'velikiessaki'),
- starting_fraction='mocha', label_pos=(477, 343), radiobutton_order=2),
- 'velikiessaki': CityMeta(
- human_name="Великие Ссаки", bonus='attack+',
- routes=('huegrad', 'huihrustalniy'),
- starting_fraction='mocha', label_pos=(604, 256), radiobutton_order=1),
- 'huisk': CityMeta(
- human_name="Хуйск", bonus='attack+',
- routes=('portmocha', 'huegrad', 'huihrustalniy', 'forthui'),
- starting_fraction='mocha', label_pos=(588, 423), radiobutton_order=3),
- 'huihrustalniy': CityMeta(
- human_name="Хуй Хрустальный", bonus='attack+',
- routes=('velikiessaki', 'huegrad', 'huisk', 'forthui'),
- starting_fraction='mocha', label_pos=(732, 355), radiobutton_order=4),
- 'forthui': CityMeta(
- human_name="Форт-Хуй", bonus='attack+',
- routes=('huisk', 'huihrustalniy', 'severnayazalupa', 'yujnayazalupa'),
- starting_fraction='mocha', label_pos=(822, 432), radiobutton_order=5),
- 'severnayazalupa': CityMeta(
- human_name="Северная Залупа", bonus='attack+',
- routes=('forthui', 'yujnayazalupa', 'battholl', 'nordess'),
- starting_fraction='mocha', label_pos=(1025, 417), radiobutton_order=6),
- 'yujnayazalupa': CityMeta(
- human_name="Южная Залупа", bonus='attack+',
- routes=('forthui', 'severnayazalupa', 'battholl', 'sauzess'),
- starting_fraction='mocha', label_pos=(907, 499), radiobutton_order=7),
- 'govndor': CityMeta(
- human_name="Говндор", bonus='defenseX',
- routes=('analdip', 'essfinger', 'kalhill', 'dristland'),
- starting_fraction='govno', label_pos=(1649, 563), radiobutton_order=8),
- 'analdip': CityMeta(
- human_name="Анал-Дип", bonus='defense+',
- routes=('govndor', 'essfinger', 'nordess'),
- starting_fraction='govno', label_pos=(1592, 394), radiobutton_order=9),
- 'essfinger': CityMeta(
- human_name="Эссфингер", bonus='defense+',
- routes=('govndor', 'analdip', 'nordess', 'battholl', 'kalhill'),
- starting_fraction='govno', label_pos=(1459, 485), radiobutton_order=10),
- 'kalhill': CityMeta(
- human_name="Кал-Хилл", bonus='defense+',
- routes=('govndor', 'dristland', 'sauzess', 'battholl', 'essfinger'),
- starting_fraction='govno', label_pos=(1367, 599), radiobutton_order=11),
- 'dristland': CityMeta(
- human_name="Дристланд", bonus='defense+',
- routes=('govndor', 'kalhill', 'sauzess'),
- starting_fraction='govno', label_pos=(1210, 775), radiobutton_order=12),
- 'nordess': CityMeta(
- human_name="Норд-Эсс", bonus='defense+',
- routes=('analdip', 'essfinger', 'battholl', 'severnayazalupa'),
- starting_fraction='govno', label_pos=(1269, 341), radiobutton_order=13),
- 'battholl': CityMeta(
- human_name="Батт-Холл", bonus='defense+',
- routes=('nordess', 'essfinger', 'kalhill', 'sauzess', 'yujnayazalupa', 'severnayazalupa'),
- starting_fraction='govno', label_pos=(1219, 495), radiobutton_order=14),
- 'sauzess': CityMeta(
- human_name="Сауз-Эсс", bonus='defense+',
- routes=('battholl', 'kalhill', 'dristland', 'yujnayazalupa'),
- starting_fraction='govno', label_pos=(1029, 653), radiobutton_order=15),
- }
- # *State — изменяющиеся данные о фракциях, городах и мире.
- class CityState:
- def __init__(self, id, world):
- self.id = id
- self.world = world
- self.fraction_id = None
- self.forces = 0
- meta = property(lambda self: self.world.meta.cities[self.id])
- fraction = property(lambda self: self.world.fractions[self.fraction_id])
- def transfer_to(self, fraction):
- self.fraction_id = fraction.id
- class FractionState:
- def __init__(self, id, world):
- self.id = id
- self.world = world
- meta = property(lambda self: self.world.meta.fractions[self.id])
- def cities(self):
- return (city for city in self.world.cities.values() if city.fraction_id == self.id)
- def total_forces(self): return sum(city.forces for city in self.cities())
- def total_cities(self): return sum(1 for city in self.cities())
- def attack_plus(self): return sum(1 for city in self.cities() if city.meta.bonus == 'attack+')
- def attack_x(self): return 1 + sum(1 for city in self.cities() if city.meta.bonus == 'attackX')
- def defense_plus(self): return sum(1 for city in self.cities() if city.meta.bonus == 'defense+')
- def defense_x(self): return 1 + sum(1 for city in self.cities() if city.meta.bonus == 'defenseX')
- class WorldState:
- def __init__(self, meta):
- self.meta = meta
- self.fractions = { fraction_id: FractionState(fraction_id, self) for fraction_id, fraction_meta in meta.fractions.items() }
- self.cities = { city_id: CityState(city_id, self) for city_id in meta.cities }
- self.win = False # Уже победили?
- self.combat_log = list() # Лог сражений за один раунд (до следующего обновления карты)
- for city in self.cities.values():
- city.transfer_to(self.fractions[city.meta.starting_fraction])
- def check_route(self, fraction, city):
- """check_route(fraction, city) -> True или False
- Проверяет доступность города для действий одной из фракций.
- fraction - FractionState, для какой из фракций город должен быть доступен?
- city - CityState, город, который проверяется на доступность"""
- return fraction == city.fraction or any(self.cities[adjacent].fraction == fraction for adjacent in city.meta.routes)
- def __getstate__(self):
- return {k: v for k, v in self.__dict__.items() if k not in ('meta')} # meta жирная, выставляется после загрузки вручную
- class WorldChanges:
- class Change:
- def __init__(self, op, *args):
- self.op = op
- self.args = args
- def __init__(self, world):
- self.cs, self.world = list(), world
- def fraction_total_cities(self, fraction):
- cities_count = fraction.total_cities()
- for c in self.cs:
- if c.op == 'transfer_city':
- city, to_fraction = c.args
- if city.fraction == fraction: cities_count -= 1
- if to_fraction == fraction: cities_count += 1
- return cities_count
- def post_transfer_city(self, city, fraction): self.cs.append(self.Change('transfer_city', city, fraction))
- def post_set_city_forces(self, city, forces): self.cs.append(self.Change('set_city_forces', city, forces))
- def apply(self):
- for c in self.cs:
- getattr(self, '_apply_' + c.op)(*c.args)
- def _apply_transfer_city(self, city, fraction): city.transfer_to(fraction)
- def _apply_set_city_forces(self, city, forces): city.forces = forces
- # Функции основных рассчётов
- def CheckPostNumber(post_number, post_number_length):
- """CheckPostNumber(post_number, post_number_length) -> True или False
- Проверяет правильность номера поста.
- post_number - строка, должна быть длиной в post_number_length символов и состоять только из цифр,
- иначе функция вернёт Flase;
- post_number_length - целое число, допустимая длина номера поста."""
- return len(post_number) == post_number_length and all('0' <= c <= '9' for c in post_number)
- def ArmyForces(post_number):
- """ArmyForces(post_number) -> army_forces
- Возвращает целое число, обозначающее силу армии.
- post_number - строка, состоящая только из цифр и обозначающая номер поста."""
- digit_repeats = 0
- last_digit = int(post_number[-1])
- for digit in post_number[-2::-1]:
- if int(digit) == last_digit: digit_repeats += 1
- else: break
- army_forces = 0 if digit_repeats == 0 else (last_digit ** (digit_repeats + 1)) + (digit_repeats * 10)
- if digit_repeats > 1: army_forces += 10
- return army_forces
- def Attack(name, fraction, army_forces, target):
- """Attack(name, fraction, army_forces, target) -> <сообщение>, changes
- Рассчитывает результаты атаки на город.
- Возвращает строку, описывающую итог сражения, и WorldChanges — вызванные им изменения в мире, которые нужно применить через .apply().
- name - строка, имя генерала, совершающего атаку на город;
- fraction - FractionState, фракция, которая совершает атаку на город;
- army_forces - целое число, сила атакующей армии (см. описание функции ArmyForces);
- target - CityState, город, на который совершается нападение."""
- message = name + " (" + fraction.meta.human_name + ")" + " атакует " + target.meta.human_name + " с армией " + str(army_forces) + ": "
- changes = WorldChanges(target.world)
- if fraction == target.fraction: # Город уже захвачен союзниками
- changes.post_set_city_forces(target, target.forces + army_forces)
- return message + "город уже захвачен союзниками, армия присоединяется к гарнизону.", changes
- army_forces_after = max(0, army_forces - (target.forces + target.fraction.defense_plus()) * target.fraction.defense_x())
- army_losses = army_forces - army_forces_after
- garrison_before = target.forces
- garrison_after = max(0, garrison_before - (army_forces + fraction.attack_plus()) * fraction.attack_x())
- if army_forces_after > 0: # Город захвачен
- changes.post_transfer_city(target, fraction)
- changes.post_set_city_forces(target, army_forces_after)
- message += "город взят, потери атакующих " + str(army_losses) + "."
- else: # Город не захвачен
- changes.post_set_city_forces(target, garrison_after)
- garrison_losses = garrison_before - garrison_after
- message += "потери атакующих " + str(army_losses) + ", потери гарнизона " + str(garrison_losses) + "."
- return message, changes
- def Defense(name, fraction, army_forces, target):
- """Defense(name, fraction, army_forces, target, world) -> <сообщение>, changes
- Рассчитывает результаты защиты на города.
- Возвращает строку, описывающую результат действия, и WorldChanges — вызванные им изменения в мире, которые нужно применить через .apply().
- name - строка, имя генерала, совершающего защищающего город;
- fraction - FractionState, фракция, которая защищает город;
- army_forces - целое число, сила защищающей армии (см. описание функции ArmyForces);
- target - CityState, защищаемый город."""
- if fraction != target.fraction: # Город уже захвачен врагом
- message = name + " защищает " + target.meta.human_name + ": город уже захвачен врагами.\n"
- post_message, changes = Attack(name, fraction, army_forces, target)
- return message + post_message, changes
- else:
- changes = WorldChanges(target.world)
- changes.post_set_city_forces(target, target.forces + army_forces)
- message = (name + " (" + target.fraction.meta.human_name + ")" + " защищает " + target.meta.human_name + ": + "
- + str(army_forces) + " к силе гарнизона.")
- return message, changes
- def ActionProcessing(post_number, post_number_length, fraction, name, action, target, options):
- """ActionProcessing(post_number, post_number_length, fraction, name, action, target, options)
- -> check, message
- Рассчитывает результат действия игрока ("защита" или "нападение").
- Возвращает булевый флаг (нет ли ошибок входных данных?), строку, описывающую ошибку или результат действия,
- изменения в мире, которые нужно применить через .apply().
- post_number - строка, представляющая номер поста игрока,
- допустимость значений проверяется (см. функцию CheckPostNumber);
- post_number_length - целое число, максимальная длина номера поста;
- fraction - FractionState, фракция игрока;
- name - строка, обозначающя имя игрока, совершающего действие, если пустая, будет использоваться стандартное имя
- (см. глобальную перемунную DEFAULT_GENERAL_NAME);
- action - строка, обозначающая действие ('attack' или 'defense');
- target - CityState, город над которым производится действие
- проверяется есть ли у фракции игрока доступ к этому городу (см. функцию check_route);
- options - Options, опции."""
- if not CheckPostNumber(post_number, post_number_length):
- return False, "Ошибка: недопустимый номер поста", None
- if not target.world.check_route(fraction, target):
- return False, "Ошибка: у фракции " + fraction.meta.human_name + " нет подхода к городу " + target.meta.human_name + ".", None
- name = name.strip()
- if not name:
- name = options.DEFAULT_GENERAL_NAME
- army_forces = ArmyForces(post_number)
- if not army_forces:
- return False, "Сила армии 0. Ничего не происходит.", None
- if action == 'attack':
- return True, *Attack(name, fraction, army_forces, target)
- elif action == 'defense':
- return True, *Defense(name, fraction, army_forces, target)
- # Функция отрисовки карты
- def MapRender(world, options):
- """MapRender(world, options)
- Рисует карту, соответствующую текущему состоянию игрового мира с логом событий и сохраняет в файл.
- world - WorldState, мир;
- options - Options, опции."""
- folder = path.join(options.working_dir, "resources")
- with Image.new("RGB", (1920, 1080), color=("#AAAAAA")) as game_map:
- # Отрисовка фона
- with Image.open(path.join(folder, options.BACKGROUND)) as background_image:
- game_map.paste(background_image)
- # Отрисовка территорий, фортов и меток
- for part_id in ('territory', 'fort', 'text'):
- for city in world.cities.values():
- with Image.open(path.join(folder, city.id + "_" + part_id + "_" + city.fraction_id + ".png")) as part_image:
- game_map.paste(part_image, mask=part_image)
- game_map_draw = ImageDraw.Draw(game_map)
- # Отрисовка значений силы гарнизона
- font = ImageFont.truetype(path.join(folder, "FreeSansBoldOblique.ttf"), size=21)
- for city in world.cities.values():
- text = str(city.forces)
- game_map_draw.text(city.meta.label_pos, text, font=font, fill=city.fraction.meta.label_color)
- # Отрисовка лога событий
- font = ImageFont.truetype(path.join(folder, "FreeSansBold.ttf"), size=22)
- text = "\n".join(world.combat_log).strip()
- log_height = 1076 - font.getsize(text)[1] * (1 + text.count("\n"))
- game_map_draw.text((4, log_height), text, font=font, fill="#FEFEFE")
- # Записываем отрисованную карту в файл
- game_map.save(path.join(options.working_dir, "output", "bitva-bitardov.png"))
- class GUI:
- def __init__(self, world, options):
- self.world = world
- self.options = options
- # Инициализация GUI
- self.main_window = main_window = tkinter.Tk()
- main_window.config(bg=options.BG_COLOR)
- main_window.title("Битва Битардов Director's Cut Edition Remastered v1.2")
- def add_label(text):
- tkinter.Label(text=text, bg=options.BG_COLOR, fg=options.FG_COLOR, font=options.FONT_GUI).pack()
- def add_radiobutton(text, value, variable):
- tkinter.Radiobutton(text=text, variable=variable, value=value, fg=options.FG_COLOR, bg=options.BG_COLOR, font=options.FONT_GUI).pack()
- def add_button(text, command):
- tkinter.Button(main_window, text=text, fg=options.FG_COLOR, bg=options.BG_COLOR, font=options.FONT_GUI, command=command).pack()
- # Ввод номера поста
- add_label("НОМЕР ПОСТА (" + str(options.POST_NUMBER_LENGTH) + " ЦИФР): ")
- self.post_number_input = tkinter.StringVar()
- post_number_entry = tkinter.Entry(textvariable=self.post_number_input, width=options.POST_NUMBER_LENGTH, bd=2, font=options.FONT_GUI)
- post_number_entry.pack()
- # Выбор фракции
- add_label("ФРАКЦИЯ: ")
- self.fraction_input = tkinter.StringVar()
- for index, (fraction_id, fraction_meta) in enumerate(world.meta.fractions.items()):
- if index == 0: self.fraction_input.set(fraction_id)
- add_radiobutton(text=fraction_meta.human_name, variable=self.fraction_input, value=fraction_id)
- # Ввод имени генерала
- add_label("ИМЯ ГЕНЕРАЛА: ")
- self.name_input = tkinter.StringVar()
- name_input_entry = tkinter.Entry(textvariable=self.name_input, width=22, bd=2, font=options.FONT_GUI)
- name_input_entry.pack()
- # Выбор действия атака или защита
- add_label("ДЕЙСТВИЕ: ")
- self.action_input = tkinter.StringVar()
- self.action_input.set('defense')
- add_radiobutton(text="Атака", variable=self.action_input, value='attack')
- add_radiobutton(text="Защита", variable=self.action_input, value='defense')
- # Выбор города
- add_label("ГОРОД: ")
- self.target_input = tkinter.StringVar()
- for index, (city_id, city_meta) in enumerate(sorted(world.meta.cities.items(), key=lambda kv: kv[1].radiobutton_order)):
- if index == 0: self.target_input.set(city_id)
- add_radiobutton(text=city_meta.human_name, variable=self.target_input, value=city_id)
- # GUI для пользовательских команд
- # Рассчёт результата действия игрока (можно посмотреть и не применять изменения)
- add_button(text="РАСЧЁТ РЕЗУЛЬТАТА", command=self.GUIActionResult)
- # Обновляет карту и перезаписывает файл "bitva-bitardov.png" в каталоге "output"
- add_button(text="ОБНОВИТЬ КАРТУ", command=self.GUIUpdateMap)
- # Остановить битву после лимита постов (см. MAX_POST_COUNT)
- add_button(text=str(options.MAX_POST_COUNT) + "-Й ПОСТ! ОСТАНОВИТЬ БИТВУ!", command=self.GUIStopBattle)
- # Сохранить текущее состояние игрового мира
- add_button(text="СОХРАНИТЬ ИГРУ", command=self.GUISave)
- # Загрузить сохранённое состояние игрового мира
- add_button(text="ЗАГРУЗИТЬ ИГРУ", command=self.GUILoad)
- # Функции для обработки команд пользователя, принимаемых через GUI
- def GUIActionResult(self):
- """GUIActionResult()
- Показывает окно с результатами действия игрока (см. функцию ActionProcessing).
- Пользователь может принять изменения или отменить.
- В первом случае изменения применяются к миру, во втором случае он не меняется.
- Возвращает None если функция была вызвана после того как битва уже закончилась (world.win == True)."""
- if self.world.win: # Проверка победы
- tkinter.messagebox.showwarning("Битва закончилась", "Эта Битва Битардов уже завершилась.")
- return None
- check, message, changes = ActionProcessing(self.post_number_input.get(), self.options.POST_NUMBER_LENGTH,
- self.world.fractions[self.fraction_input.get()], self.name_input.get(),
- self.action_input.get(), self.world.cities[self.target_input.get()],
- self.options)
- if not check:
- tkinter.messagebox.showerror("Ошибка", message)
- else:
- # Проверка на победу одной из фракций
- winner = None
- loser = next((fraction for fraction in self.world.fractions.values() if changes.fraction_total_cities(fraction) == 0), None)
- if loser:
- winner = next(fraction for fraction in self.world.fractions.values() if fraction != loser)
- message += ("\nФракции " + winner.meta.human_name + " теперь принадлежат все города."
- + "\n" + winner.meta.human_name + " побеждает " + loser.meta.accusative + "!")
- if tkinter.messagebox.askyesno("Принять изменения карты?", message + "\n\nПринять изменения карты?"):
- changes.apply()
- self.world.combat_log.append(message)
- if winner is not None: self.world.win = True
- def GUIStopBattle(self):
- """GUIStopBattle()
- Показывает окно с итогами битвы после лимита постов. Пользователь может принять изменения или отменить.
- Если пользователь принимает изменения, world.win и world.combat_log обновляются.
- Фракция с наибольшим значением общей силы "total_forces" побеждает,
- либо объявляется ничья, если силы обеих фракций равны.
- Возвращает None если функция была вызвана после того как битва уже закончилась (world.win == True)."""
- if self.world.win: # Проверка победы
- tkinter.messagebox.showwarning("Битва закончилась", "Эта Битва Битардов уже завершилась.")
- return None
- def describe_fraction_forces(fraction):
- return "Общая сила фракции " + fraction.meta.human_name + ": " + str(fraction.total_forces()) + "."
- message = ("Битва останавливается после " + str(self.options.MAX_POST_COUNT) + "-ого поста.\n"
- + " ".join(describe_fraction_forces(fraction) for fraction in self.world.fractions.values()))
- winners, losers, best_forces = [], [], None
- for fraction in self.world.fractions.values():
- cur_forces = fraction.total_forces()
- if best_forces == None or best_forces > cur_forces:
- losers.extend(winners)
- winners = []
- best_forces = cur_forces
- (winners if best_forces == cur_forces else losers).append(fraction)
- if winners and losers:
- def join(fractions, meta_attr):
- return ", ".join(getattr(fraction.meta, meta_attr) for fraction in fractions)
- message += "\n" + join(winners, 'human_name') + " побеждает " + join(losers, 'accusative') + "!"
- else:
- message += "\nПобедила дружба!"
- if tkinter.messagebox.askyesno("Остановить Битву Битардов?", message + "\n\nОстановить битву и применить изменения?"):
- self.world.win = True
- self.world.combat_log.append(message)
- def GUIUpdateMap(self):
- """Обновляет игровую карту и записывает в файл "bitva-bitardov.png" в каталоге "output".
- Если файл уже существует, он перезаписывается.
- В случае ошибок при работе с файлами показывает сообщение об ошибке. При этом лог событий не очищается."""
- try:
- MapRender(self.world, self.options)
- except Exception as e:
- tkinter.messagebox.showerror("Ошибка", e)
- else:
- self.world.combat_log.clear()
- def GUISave(self):
- """Записывает состояние мира в файл, выбранный пользователем.
- По умолчанию предлагается сохранять в каталог "saves"."""
- try:
- save_file = tkinter.filedialog.asksaveasfile(mode="wb",
- title="Сохранение", initialdir=path.join(self.options.working_dir, "saves"),
- defaultextension=".bbsav",
- filetypes=(("Дамп состояния игрового мира", "*.bbsav"),))
- if not save_file: return
- with save_file:
- pickle.dump(self.world, save_file)
- except Exception as error:
- tkinter.messagebox.showerror("Ошибка", error)
- else:
- tkinter.messagebox.showinfo("Сохранение", "Игра сохранена")
- def GUILoad(self):
- """Загружает состояние мира из файла который выберет пользователь.
- По умолчанию предлагается загружать сохранения из каталога "saves"."""
- try:
- load_file = tkinter.filedialog.askopenfile(mode="rb",
- title="Загрузка", initialdir=path.join(self.options.working_dir, "saves"),
- filetypes=(("Дамп состояния игрового мира", "*.bbsav"),))
- if not load_file: return
- with load_file:
- new_world = pickle.load(load_file)
- new_world.meta = self.world.meta
- except Exception as error:
- tkinter.messagebox.showerror("Ошибка", error)
- else:
- self.world = new_world
- tkinter.messagebox.showinfo("Загрузка", "Игра загружена")
- def main():
- options = Options()
- world = WorldState(WorldMeta())
- GUI(world, options).main_window.mainloop()
- if __name__ == "__main__":
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement