000179. Избавляемся от "ёлочек"

Избавляемся от "ёлочек"|Привет! В этой статье я хотел бы поделится опытом написания скриптов. А точнее принципом написания кода таким способом, чтобы его легче было читать. Речь пойдёт о "ёлочках".|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

Давайте попробуем упростить скрипт по этапам:

    1. Смотрим на все блоки "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
    1. У нас по прежнему есть блок "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. Опять у нас остались разветвления. Что же, повторим пункт 1, где избавимся от блока "ELSE", осуществив проверки на существование транспорта и актёра:

if
856E: not car 0@ defined
then
continue
end

// ***

if
856D: not actor 0@ defined 
then 
continue
end
    1. Осталось только 1 разбиение, где проверяется цель игрока. Возвращаемся к любимому пункту 1, и убираем блок "ELSE":

if
8AD2: not 0@ = player $PLAYER_CHAR targeted_actor
then 
continue
end
    1. Казалось, что последнее разветвление убрать уже не получится, так как выполняются два разных алгоритма. Но и тут я умудрился избавиться от надоедливого "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

Last updated