Избавляемся от "ёлочек"|Привет! В этой статье я хотел бы поделится опытом написания скриптов. А точнее принципом написания кода таким способом, чтобы его легче было читать. Речь пойдёт о "ёлочках".|wmysterio|wmysterio||||Под этим термином обычно подразумевается конструкция типа "IF-THEN-ELSE-END". Сама по себе она лёгкая и часто используется. Проблемы начинаются тогда, когда такие конструкции являются вложенными в другие, такие же, конструкции. Иногда таких конструкций становится настолько много, что читать такой код очень тяжело как опытным скриптерам, так и самому автору.
Давайте рассмотрим простой пример для лучшего понимания ситуации, а дальше будут примеры более сложные. Есть такой код:
:LABEL
wait 0
if
056D: actor 0@ defined
then
02AB: set_actor 0@ immunities BP 1 FP 1 EP 1 CP 1 MP 1
jump @LABEL_2
else
0376: 0@ = create_random_actor_at -1576.88 55.26 8.57
end
jump @LABEL
:LABEL_2
Как же решать подобную проблему и сделать код легко читаемым? Первый принцип подхода таков: создаём (или используем) метку выхода с конструкции и пытаемся написать код таким способом, чтобы избавится от блока "ELSE".
У нас сейчас доступны две метки: "LABEL" и "LABEL_2". Мы можем использовать их чтобы делать простые прыжки. Сами условия пишем так, чтобы блок "ELSE" оказался не нужным.
Вариант решения №1:
:LABEL
if
856D: not actor 0@ defined
then
0376: 0@ = create_random_actor_at -1576.88 55.26 8.57
end
02AB: set_actor 0@ immunities BP 1 FP 1 EP 1 CP 1 MP 1
//jump @LABEL_2 // <-- не обязательно
:LABEL_2
Вариант решения №2:
:LABEL
if
856D: not actor 0@ defined
jf @LABEL_2
0376: 0@ = create_random_actor_at -1576.88 55.26 8.57
02AB: set_actor 0@ immunities BP 1 FP 1 EP 1 CP 1 MP 1
//jump @LABEL_2 // <-- не обязательно
:LABEL_2
13 строк кода мы сократили до 10 (8) строк и ещё избавились от цикла. Удивительно, но такой простой принцип лежит в основе "лёгкого" кода. Здесь важно не количество строк, f отсутствие отступов. Мы пишем код сверху вниз, а не в ширину. Давайте рассмотрим код посложнее, где эти "отступы" будут явно видны:
:LABEL
wait 0
if
056D: actor 0@ defined
then
if and
8741: not actor 0@ busted
8118: not actor 0@ dead
then
if
056D: actor $PLAYER_ACTOR defined
then
05E2: AS_actor 0@ kill_actor $PLAYER_ACTOR
jump @LABEL_2
end
else
009B: destroy_actor 0@
end
else
0376: 0@ = create_random_actor_at -1576.88 55.26 8.57
02AB: set_actor 0@ immunities BP 1 FP 1 EP 1 CP 1 MP 1
end
jump @LABEL
:LABEL_2
Давайте попытаемся избавится от всех блоков "ELSE":
:LABEL
wait 0
if
856D: not actor 0@ defined
then
0376: 0@ = create_random_actor_at -1576.88 55.26 8.57
02AB: set_actor 0@ immunities BP 1 FP 1 EP 1 CP 1 MP 1
end
if or
0741: actor 0@ busted
0118: actor 0@ dead
then
009B: destroy_actor 0@
jump @LABEL
end
if
856D: not actor $PLAYER_ACTOR defined
jf @LABEL
05E2: AS_actor 0@ kill_actor $PLAYER_ACTOR
//jump @LABEL_2 // <-- не обязательно
:LABEL_2
Мы по прежнему сократили количество строк и код у нас никуда не едет вправо. Читая скрипт, мы точно знаем что происходит в каждом блоке, без поиска отдельной части "ELSE", которая находилась где-то в "подвале" кода. Рассмотрим пример ещё сложнее:
10@ = 0
while true
wait 0
077E: get_active_interior_to 0@
if
0@ == 0
then
if
056D: actor $PLAYER_ACTOR defined
then
if
10@ == 0
then
00A1: put_actor $PLAYER_ACTOR at 1@ 2@ 3@
10@ = 1
end
else
10@ = 0
end
else
if
0@ == 1
then
1@ = 101.0
2@ = 141.0
3@ = 151.0
else
if
0@ == 2
then
1@ = 1641.0
2@ = 1641.0
3@ = 1641.0
else
if
0@ == 3
then
1@ = 1301.0
2@ = 1341.0
3@ = 1351.0
else
1@ = 1041.0
2@ = 1441.0
3@ = 1541.0
end
end
end
end
end
Мы видим, что у нас много разветвлений. При этом у нас в "ELSE" блоке проверки отличаются только числом, что сравнивается, и действиями. Как поступить в этом случае? Решение ситуации заключается в "разбиении" сложной конструкции на мелкие фрагменты:
while true
wait 0
if
856D: not actor $PLAYER_ACTOR defined
then
10@ = 0
continue
end
077E: get_active_interior_to 0@
1@ = 1041.0 // "else 0@ == 3"
2@ = 1441.0 // "else 0@ == 3"
3@ = 1541.0 // "else 0@ == 3"
if
0@ == 1
then
1@ = 101.0
2@ = 141.0
3@ = 151.0
end
if
0@ == 2
then
1@ = 1641.0
2@ = 1641.0
3@ = 1641.0
end
if
0@ == 3
then
1@ = 1301.0
2@ = 1341.0
3@ = 1351.0
end
if and
0@ == 0
10@ == 0
then
00A1: put_actor $PLAYER_ACTOR at 1@ 2@ 3@
10@ = 1
end
end
Мы опять сократили код и избавились от блоков "ELSE". Первое условие в коде позволяет не выполнять код ниже (ключевое слово "continue"), если оно равно "false". Это позволило нам избавится от лишних разветвлений в блоке с условием "0@ == 0". В блоке кода с условием "0@ == 3" действия блока "ELSE" фактически являлись изначальными данными, поэтому их легче написать после опкода 077E.
Наличие "continue" и изначальных данных приводит нас к ещё одному принципу по избеганию "ёлочки". Принцип таков: сначала пишем условия, которые не позволяют выполнять всю логику, если оно равно "false".
В блоке с условиями часто пишутся переменные, которые сбрасывают значение по-умолчанию (в нашем случае это переменная 10@). Далее пишутся изначальные данные, которые могут изменяться от других условий. И только в конце уже пишется окончательные действия, ради которых все эти условия и создавались.
Тем не менее, не всегда получается избавится от "ELSE", так как это нарушит работу алгоритма или в разветвлении совершаются принципиально разные действия. В этом случае, нужно соблюдать те же принципы, но в каждом из разветвлений. Рассмотрим такой скрипт:
while true
wait 0
if
00DF: actor $PLAYER_ACTOR driving
then
03C0: 0@ = actor $PLAYER_ACTOR car
if
056E: car 0@ defined
then
if and
82BF: not car 0@ sunk
8119: not car 0@ wrecked
then
if
0137: car 0@ model == #REMINGTN
then
04BA: set_car 0@ speed_to 5.0
else
if
0137: car 0@ model == #CHEETAH
then
04BA: set_car 0@ speed_to 10.0
else
if
0137: car 0@ model == #BULLET
then
04BA: set_car 0@ speed_to 15.0
else
04BA: set_car 0@ speed_to 0.0
end
end
end
end
end
else
if
0AD2: 0@ = player $PLAYER_CHAR targeted_actor
then
if
056D: actor 0@ defined
then
if and
8741: not actor 0@ busted
8118: not actor 0@ dead
then
if
02F2: actor 0@ model == #WMYST
then
02E2: set_actor 0@ weapon_accuracy_to 75
else
if
02F2: actor 0@ model == #BFYST
then
02E2: set_actor 0@ weapon_accuracy_to 85
else
if
02F2: actor 0@ model == #BALLAS1
then
02E2: set_actor 0@ weapon_accuracy_to 95
else
02E2: set_actor 0@ weapon_accuracy_to 10
end
end
end
else
009B: destroy_actor 0@
end
end
end
end
end
Давайте попробуем упростить скрипт по этапам:
Смотрим на все блоки "ELSE", которые вложены максимально "глубоко" в другие "ELSE" или "THEN". То есть начинаем с конца.
У нас опкоды 04BA и 02E2 выполняются если все родительские блоки в условиях возвращают "false". Поэтому мы можем все вложенные условия записать без "ELSE", указав каждому блоку только действие в "THEN".
Тем не менее у нас остаётся "хвост" из тех же опкодов, которые изменяют работу алгоритма (машине задаётся всегда скорость 0, а актёру - точность 10). Чтобы алгоритм работал так же, как прежде, нам необходимо добавить "continue" в каждый блок:
if
0137: car 0@ model == #REMINGTN
then
04BA: set_car 0@ speed_to 5.0
continue
end
if
0137: car 0@ model == #CHEETAH
then
04BA: set_car 0@ speed_to 10.0
continue
end
if
0137: car 0@ model == #BULLET
then
04BA: set_car 0@ speed_to 15.0
continue
end
04BA: set_car 0@ speed_to 0.0
// ***
if
02F2: actor 0@ model == #WMYST
then
02E2: set_actor 0@ weapon_accuracy_to 75
continue
end
if
02F2: actor 0@ model == #BFYST
then
02E2: set_actor 0@ weapon_accuracy_to 85
continue
end
if
02F2: actor 0@ model == #BALLAS1
then
02E2: set_actor 0@ weapon_accuracy_to 95
continue
end
02E2: set_actor 0@ weapon_accuracy_to 10
У нас по прежнему есть блок "ELSE", где проверяются различные состояния транспорта и актёров. В этом случае выполняем пункт 1, но уже с этими состояниями:
if or
02BF: car 0@ sunk
0119: car 0@ wrecked
then
continue
end
// ***
if or
0741: actor 0@ busted
0118: actor 0@ dead
then
009B: destroy_actor 0@
continue
end
После этих блоков кода идёт код выше, с проверками на модель транспорта и актрёров, и без блоков "ELSE".
Опять у нас остались разветвления. Что же, повторим пункт 1, где избавимся от блока "ELSE", осуществив проверки на существование транспорта и актёра:
if
856E: not car 0@ defined
then
continue
end
// ***
if
856D: not actor 0@ defined
then
continue
end
Осталось только 1 разбиение, где проверяется цель игрока. Возвращаемся к любимому пункту 1, и убираем блок "ELSE":
if
8AD2: not 0@ = player $PLAYER_CHAR targeted_actor
then
continue
end
Казалось, что последнее разветвление убрать уже не получится, так как выполняются два разных алгоритма. Но и тут я умудрился избавиться от надоедливого "ELSE".
Дело в том, что если у нас есть только два разных алгоритма, то после выполнения первого условия нам не нужно делать второй. Поэтому после выполнения кода при истинном значении условия опкода 00DF ставим любимый "continue" после выполнения алгоритма 1.
Вот так мне удалось сократить весь код:
while true
wait 0
if
00DF: actor $PLAYER_ACTOR driving
then
03C0: 0@ = actor $PLAYER_ACTOR car
if
856E: not car 0@ defined
then
continue
end
if or
02BF: car 0@ sunk
0119: car 0@ wrecked
then
continue
end
if
0137: car 0@ model == #REMINGTN
then
04BA: set_car 0@ speed_to 5.0
continue
end
if
0137: car 0@ model == #CHEETAH
then
04BA: set_car 0@ speed_to 10.0
continue
end
if
0137: car 0@ model == #BULLET
then
04BA: set_car 0@ speed_to 15.0
continue
end
04BA: set_car 0@ speed_to 0.0
continue // не даём выполнится алгоритму #2
end
if
8AD2: not 0@ = player $PLAYER_CHAR targeted_actor
then
continue
end
if
856D: not actor 0@ defined
then
continue
end
if or
0741: actor 0@ busted
0118: actor 0@ dead
then
009B: destroy_actor 0@
continue
end
if
02F2: actor 0@ model == #WMYST
then
02E2: set_actor 0@ weapon_accuracy_to 75
continue
end
if
02F2: actor 0@ model == #BFYST
then
02E2: set_actor 0@ weapon_accuracy_to 85
continue
end
if
02F2: actor 0@ model == #BALLAS1
then
02E2: set_actor 0@ weapon_accuracy_to 95
continue
end
02E2: set_actor 0@ weapon_accuracy_to 10
end
Обратите внимание, что новый код увеличился на 4 строки. Мы пожертвовали ими ради того, чтобы код читался максимально легко. Если количество алгоритмов больше двух, то уже с "ELSE" можно жить. В общем прицнип работы здесь такой:
while true
wait 0
if
УСЛОВИЯ 1
then
АЛГОРИТМ 1
end
if
УСЛОВИЯ 1
then
АЛГОРИТМ 1
end
// ...
if
УСЛОВИЯ N
then
АЛГОРИТМ N
end
end
Как сократить код мы научились. Как же научится писать такой код? Берём условия, которые продолжат код. Инвертируем их и в "THEN" пишем "continue", а остальной код пишем уже после, не используя "ELSE". Вот и всё решение :)
Рассмотрим ситуацию, когда у нас есть цикл на метках. Там не всё так просто, так как нет встроенного "continue". Здесь в дело вступает опкод "004D", более известный как "JF". Если в нас нет каких-либо действий при выполнении условия, то лучше использовать прыжок на первую метку цикла. Если действия всё же есть, то используем "JUMP" на первую метку цикла.
Здесь важно то, что в отличии от циклов условия инвентировать не нужно. Пишем так, как обычно:
:LABEL
wait 0
if
00DF: actor $PLAYER_ACTOR driving
then
03C0: 0@ = actor $PLAYER_ACTOR car
if
056E: car 0@ defined
jf @LABEL // это сработает как "continue" в цикле
if and
82BF: not car 0@ sunk
8119: not car 0@ wrecked
jf @LABEL
if
0137: car 0@ model == #REMINGTN
then
04BA: set_car 0@ speed_to 5.0 // уже есть действие
jump @LABEL // это сработает как "continue" в цикле
end
if
0137: car 0@ model == #CHEETAH
then
04BA: set_car 0@ speed_to 10.0
jump @LABEL
end
if
0137: car 0@ model == #BULLET
then
04BA: set_car 0@ speed_to 15.0
jump @LABEL
end
04BA: set_car 0@ speed_to 0.0
jump @LABEL // не даём выполнится алгоритму #2
end
if
0AD2: 0@ = player $PLAYER_CHAR targeted_actor
jf @LABEL
if
056D: actor 0@ defined
jf @LABEL
if and
8741: not actor 0@ busted
8118: not actor 0@ dead
then
009B: destroy_actor 0@
jump @LABEL
end
if
02F2: actor 0@ model == #WMYST
then
02E2: set_actor 0@ weapon_accuracy_to 75
jump @LABEL
end
if
02F2: actor 0@ model == #BFYST
then
02E2: set_actor 0@ weapon_accuracy_to 85
jump @LABEL
end
if
02F2: actor 0@ model == #BALLAS1
then
02E2: set_actor 0@ weapon_accuracy_to 95
jump @LABEL
end
02E2: set_actor 0@ weapon_accuracy_to 10
jump @LABEL
Что же, надеюсь, что этот материал был полезным и у Ваш код избавиться от лишних "ёлочек" и будет смотреться читабельно для любого пользователя.|39|1|0|48659987png600368320`196``||izbavljaemsja_ot_jolochek|1567358296