Уроки моддинга
Основной раздел
Search…
⌃K
Links

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

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