Помогите разделить функцию на несколько классов
Написал большую функцию, которая представляет собой текстовую игру, где нужно ходить по локациям на время и сражаться с врагами. Нужно было поделить на разные классы. Кое-как формально поделил на классы, но этого недостаточно.
Нужно разделить таким образом:
В Локацию поместить чтение внешнего файла, хранение текущей локации, смену текущей локации ( + парсинг данных с регуляркой)
В Герое учитывать состояние героя, его опыт, оставшееся время, проверять жив ли он(кончилось ли время)
Во Враге хранить опыт+время, состояние (жив/мертв, можно будет вместо удаления из списка использовать, или проверять и удалять мертвых)
Игра - общий класс, регулирующий взаимодействие всех остальных. В нем выбор пользователя и запуск нужных методов и всё остальное что нужно.
И при попытке всё это разделить код ломается, у меня в одних методах получаются данные для разных классов. Можете подсказать, как можно проще всего поделить код(хотя-бы частично), чтобы не перепечатывать всё заново
import re
import json
from csv import writer
from decimal import Decimal
from datetime import datetime
remaining_time = '123456.0987654321'
# если изначально не писать число в виде строки - теряется точность!
field_names = ['current_location', 'current_experience', 'current_date']
class Enemy:
enemy_initialization = r'(Mob|Boss)_exp(\d+)_tm(\d+)'
def initialize(self, enemy):
finding_enemy = re.findall(self.enemy_initialization, enemy)
health = int(finding_enemy[0][1])
kill_time = Decimal(finding_enemy[0][2])
return health, kill_time
class Location:
location_initialization = r'(Location_\w+|Hatch)_tm([\d+|\d/.]+)'
def initialize(self, location):
finding_location = re.findall(self.location_initialization, location)
name_location = finding_location[0][0]
throw_time = Decimal(finding_location[0][1])
return name_location, throw_time
class Game:
def __init__(self, remining_time, csv_names):
self.enemy = Enemy()
self.location_class = Location()
self.current_experience = 0
self.elapsed_time = 0
self.remaining_time = Decimal(remining_time)
with open('dungeon.csv', 'a', newline='', encoding='utf8') as csv_file:
csv_writer = writer(csv_file, )
csv_writer.writerow(csv_names)
def turning(self, locations_branching, killed_enemies, enemies):
locations = []
for current_location, environment_in in locations_branching.items():
if environment_in == "You are winner":
return 'You are winner'
for item in environment_in:
if isinstance(item, dict):
locations.append(item)
elif isinstance(item, str):
if not killed_enemies:
enemies.append(item)
return current_location, enemies, locations
def current_state(self, current_location, enemies, locations):
with open('dungeon.csv', 'a', newline='', encoding='utf8') as csv_file:
csv_writer = writer(csv_file, )
available_actions = []
if enemies:
available_actions.append("Уничтожить врага")
if locations:
available_actions.append("Сходить в другую локацию")
available_actions.append("Выиграть игру")
csv_writer.writerow([current_location, self.current_experience, datetime.now()])
print(f" Вы сейчас в {current_location} и у вас {self.current_experience} опыта. ")
print(f"До наводнения {self.remaining_time} секунд.")
print(f"{self.elapsed_time} времени уже прошло.")
showing_enemies = list(map(lambda enm: '- Враг: ' + enm, enemies))
showing_locations = list(map(lambda loc: '- Вход в локацию: ' + list(loc.keys())[0], locations))
print("Перед вами:")
print(*showing_enemies, sep='\n')
print(*showing_locations, sep='\n')
print(f"Пришло время выбора:")
available_actions = list(map(lambda act:
str(available_actions.index(act) + 1)
+ '.'
+ act,
available_actions))
print(*available_actions, sep='\n')
return available_actions
def choising(self, action_length):
available_choices = [str(i + 1) for i in range(action_length)]
while True:
option = input('Какое действие выберете? : ')
if option in available_choices:
break
return option
def killing(self, enemies):
print('Доступные для уничтожения враги:')
attacking_enemies = []
for i in range(len(enemies)):
attacking_enemies.append(str(i + 1) + '.' + enemies[i])
print(*attacking_enemies, sep='\n')
choose = self.choising(len(enemies))
exp, tm = self.enemy.initialize(enemies[int(choose) - 1])
self.current_experience += exp
self.elapsed_time += tm
self.remaining_time -= tm
return choose
def step_into_location(self, locations):
print('Доступные для входа локации:')
locations_for_action = \
list(map(lambda x:
str(locations.index(x) + 1)
+ '.Пройти в локацию: '
+ list(x.keys())[0],
locations))
print(*locations_for_action, sep='\n')
choose = self.choising(len(locations))
curr_location, tm = self.location_class.initialize(locations_for_action[int(choose) - 1])
self.remaining_time -= tm
self.elapsed_time += tm
return choose
def process(self):
enemies_scope = []
killed_enemies = False
with open('rpg.json', 'r', encoding='utf8') as rpg:
locations_branching = json.load(rpg)
while True:
turn = self.turning(locations_branching, killed_enemies, enemies_scope)
if turn == 'You are winner':
if self.current_experience >= 280:
print(turn)
return "win"
else:
print('Вы слишком быстро добрались до выхода и чтобы доказать своё превосходство решаете '
'вернуться к началу и еще раз всех уничтожить')
return
else:
current_location, enemies, locations = turn
if not locations or self.remaining_time <= 0:
print('Вы слишком быстро добрались до выхода и чтобы доказать своё превосходство решаете '
'вернуться к началу и еще раз всех уничтожить')
return
todo_choice = self.current_state(current_location, enemies, locations)
action = self.choising(len(todo_choice))
action = todo_choice[int(action) - 1][2:]
if action == 'Уничтожить врага':
dead_enemy = int(self.killing(enemies_scope)) - 1
enemies_scope.remove(enemies_scope[dead_enemy])
killed_enemies = True
elif action == 'Сходить в другую локацию':
location_choice = int(self.step_into_location(locations)) - 1
next_location = locations[location_choice]
locations_branching = next_location
killed_enemies = False
enemies_scope = []
elif action == 'Выиграть игру':
print('Вы решили подождать наводнения и волной вас вынесло к выходу.')
return 'exit'
def go(self):
while True:
win = self.process()
if win in ['win', 'exit']:
print('Прекрасная игра! Вы выходите победителем. Времени в запасе у вас осталось: ', self.remaining_time)
break
game = Game(remining_time=remaining_time, csv_names=field_names)
game.go()
Текст ниже расположен в файле rpg.json, который сама игра берет для анализа
{
"Location_0_tm0": [
"Mob_exp10_tm0",
{
"Location_1_tm1040": [
"Mob_exp20_tm200",
"Mob_exp20_tm200",
{
"Location_3_tm33000": [
{
"Location_7_tm33300": [
{
"Location_10_tm55100": [
"Mob_exp25_tm1",
"Mob_exp30_tm1",
"Mob_exp20_tm1",
"Mob_exp24_tm1",
{
"Location_12_tm0.0987654320": [
"Boss100_exp100_tm10",
"Boss200_exp30_tm10"
]
}
]
}
]
}
]
}
]
},
{
"Location_2_tm33300": [
"Mob_exp20_tm1677",
{
"Location_4_tm44000": [
"Mob_exp10_tm40",
"Mob_exp15_tm40",
"Mob_exp20_tm40"
]
},
{
"Location_5_tm55100": [
{
"Location_8_tm30000": [
"Mob_exp20_tm820",
"Mob_exp25_tm825",
"Mob_exp30_tm830",
"Mob_exp35_tm835",
"Mob_exp40_tm840"
]
},
{
"Location_9_tm26000": [
"Mob_exp30_tm30",
{
"Location_11_tm4000": [
"Boss_exp100_tm1040",
{
"Location_B2_tm2000": [
"Mob_exp40_tm50",
"Mob_exp40_tm50",
"Mob_exp40_tm50",
{
"Hatch_tm159.098765432": "You are winner"
}
]
}
]
}
]
}
]
},
{
"Location_6_tm30000": [
"Boss_exp280_tm10400000",
{
"Location_B1_tm0.098765432": [
"Mob_exp10_tm0",
"Mob_exp10_tm0",
"Mob_exp10_tm0",
"Mob_exp10_tm0",
"Mob_exp10_tm0"
]
}
]
}
]
}
]
}
Ответы (1 шт):
Честно говоря, я бы не сильно переживал написать всё это заново. В конце концов именно так и учаться писать код. К сожалению не до конца зная логику не просто сделать рефакторинг, многие вещи я бы писал изначально иначе (но у меня не было цели писать все с нуля) Я понял то что есть герой и в зависимости от убийства врага и смены локации он теряет время и приобретает опыт. Код еще нуждается в улучшении, но я понял что многое еще не реализовано.
Основная логика для такого рефакторинга.
- Согласно функционалу классов вынести поля и методы в нужные классы
- Преобразовать старые вызовы в новые вызовы из других классов
- Уменьшать число передаваемых параметров в методы (убирать лишнее)
- Разбивать функции на более мелкие (в том числе для повторного использования)
- Стремится к тому чтобы в основном потоке исполнения использовались только классы и их методы
- Желательно почитать про ООП, инверсию зависимостей, чистый код, дзен
надеюсь я не зря потратил время (давно не писал на питоне) и это будет полезно =)
import os
import re
import json
from csv import writer
from decimal import Decimal
from datetime import datetime
remaining_time = Decimal('123456.0987654321')
field_names = ['current_location', 'current_experience', 'current_date']
# функция очистки экрана (linux # win?)
clear = lambda: os.system('clear') # clear = lambda: os.system('cls')
class Enemy:
'''
Во Враге хранить опыт + время,
состояние (жив/мертв, можно будет вместо удаления из списка использовать,
или проверять и удалять мертвых)
'''
def __init__(self, enemy):
enemy_initialization = r'(Mob|Boss)_exp(\d+)_tm(\d+)'
finding_enemy = re.findall(enemy_initialization, enemy)
self.name = enemy
self.health = int(finding_enemy[0][1])
self.kill_time = Decimal(finding_enemy[0][2])
def kill_self(self):
self.health = 0
class Hero(object):
'''
В Герое учитывать состояние героя, его опыт,
оставшееся время, проверять жив ли он(кончилось ли время)
'''
def __init__(self, remaining_time):
self.state = 0
self.current_experience = 0
self.elapsed_time = 0
self.remaining_time = remaining_time;
def spent_time(self, time):
self.elapsed_time += time
self.remaining_time -= time
def kill_enemy(self, enemy):
self.current_experience += enemy.health
self.spent_time(enemy.kill_time);
enemy.kill_self()
class Location:
'''
В Локацию поместить чтение внешнего файла,
хранение текущей локации, смену текущей локации ( + парсинг данных с регуляркой)
'''
def __init__(self):
self.file_data = None
self.current_location = None
self.locations = []
self.read_JSON()
def change(self, location):
location_initialization = r'(Location_\w+|Hatch)_tm([\d+|\d/.]+)'
finding_location = re.findall(location_initialization, location)
name_location = finding_location[0][0]
throw_time = Decimal(finding_location[0][1])
self.current_location = name_location
return name_location, throw_time
def go_to_location(self, index):
self.branching = self.locations[index]
def read_JSON(self):
with open('rpg.json', 'r', encoding='utf8') as rpg:
self.branching = json.load(rpg)
def save_csv(self, csv_names):
with open('dungeon.csv', 'a', newline='', encoding='utf8') as csv_file:
csv_writer = writer(csv_file, )
csv_writer.writerow(csv_names)
'''
Игра - общий класс, регулирующий взаимодействие всех остальных.
В нем выбор пользователя и запуск нужных методов и всё остальное что нужно.
'''
class Game:
def __init__(self, remaining_time, csv_names):
self.enemies = []
self.hero = Hero(remaining_time);
self.location_class = Location()
self.location_class.save_csv(csv_names)
def choising(self, action_length):
available_choices = [str(i + 1) for i in range(action_length)]
while True:
option = input('Какое действие выберете? : ')
if option in available_choices:
break
return int(option)
def list_enemies(self):
print('Доступные для уничтожения враги:')
attacking_enemies = []
for i in range(len(self.enemies)):
attacking_enemies.append(str(i + 1) + '.' + self.enemies[i].name)
print(*attacking_enemies, sep='\n')
def kill_enemy(self):
self.list_enemies()
choose = self.choising(len(self.enemies))
self.hero.kill_enemy(self.enemies[choose - 1])
def show_state_info(self):
clear()
print(f" Вы сейчас в {self.location_class.current_location} и у вас {self.hero.current_experience} опыта. ")
print(f"До наводнения {self.hero.remaining_time} секунд.")
print(f"{self.hero.elapsed_time} времени уже прошло.")
showing_enemies = list(map(lambda enm: '- Враг: ' + enm.name, self.enemies))
showing_locations = list(map(lambda loc: '- Вход в локацию: ' + list(loc.keys())[0], self.location_class.locations))
print("Перед вами:")
print(*showing_enemies, sep='\n')
print(*showing_locations, sep='\n')
########
def turning(self, killed_enemies):
self.location_class.locations = []
self.enemies = []
for current_location, environment_in in self.location_class.branching.items():
if environment_in == "You are winner":
return 'You are winner'
for item in environment_in:
if isinstance(item, dict):
self.location_class.locations.append(item)
elif isinstance(item, str) and not killed_enemies:
self.enemies.append(Enemy(item))
self.location_class.current_location = current_location
def current_state(self):
with open('dungeon.csv', 'a', newline='', encoding='utf8') as csv_file:
csv_writer = writer(csv_file, )
available_actions = []
if self.enemies:
available_actions.append("Уничтожить врага")
if self.location_class.locations:
available_actions.append("Сходить в другую локацию")
available_actions.append("Выиграть игру")
csv_writer.writerow([self.location_class.current_location , self.hero.current_experience, datetime.now()])
self.show_state_info()
print(f"Пришло время выбора:")
available_actions = list(map(lambda act:
str(available_actions.index(act) + 1)
+ '.'
+ act,
available_actions))
print(*available_actions, sep='\n')
return available_actions
def get_locations_for_action (self):
return \
list(map(lambda x:
str(self.location_class.locations.index(x) + 1)
+ '.Пройти в локацию: '
+ list(x.keys())[0],
self.location_class.locations))
def step_into_location(self, locations):
locations_for_action = self.get_locations_for_action()
print('Доступные для входа локации:')
print(*locations_for_action, sep='\n')
choose = self.choising(len(self.location_class.locations))
curr_location, tm = self.location_class.change(locations_for_action[int(choose) - 1])
self.hero.spent_time(tm)
return choose
def process(self):
killed_enemies = False
self.location_class.read_JSON()
while True:
turn = self.turning(killed_enemies)
if turn == 'You are winner':
if self.hero.current_experience >= 280:
print(turn)
return "win"
else:
print('Вы слишком быстро добрались до выхода и чтобы доказать своё превосходство решаете '
'вернуться к началу и еще раз всех уничтожить')
return
else:
if not self.location_class.locations or self.hero.remaining_time <= 0:
print('Вы слишком быстро добрались до выхода и чтобы доказать своё превосходство решаете '
'вернуться к началу и еще раз всех уничтожить')
return
todo_choice = self.current_state()
action = self.choising(len(todo_choice))
action = todo_choice[int(action) - 1][2:]
if action == 'Уничтожить врага':
self.kill_enemy()
killed_enemies = True
elif action == 'Сходить в другую локацию':
location_choice = int(self.step_into_location(self.location_class.locations)) - 1
self.location_class.go_to_location(location_choice)
killed_enemies = False
self.enemies = []
elif action == 'Выиграть игру':
print('Вы решили подождать наводнения и волной вас вынесло к выходу.')
return 'exit'
def go(self):
while True:
win = self.process()
if win in ['win', 'exit']:
print('Прекрасная игра! Вы выходите победителем. Времени в запасе у вас осталось: ', self.hero.remaining_time)
break
game = Game(remaining_time=remaining_time, csv_names=field_names)
game.go()