000179. Избавляемся от "ёлочек"
Избавляемся от "ёлочек"|Привет! В этой статье я хотел бы поделится опытом написания скриптов. А точнее принципом написания кода таким способом, чтобы его легче было читать. Речь пойдёт о "ёлочках".|wmysterio|wmysterio||||Под этим термином обычно подразумевается конструкция типа "IF-THEN-ELSE-END". Сама по себе она лёгкая и часто используется. Проблемы начинаются тогда, когда такие конструкции являются вложенными в другие, такие же, конструкции. Иногда таких конструкций становится настолько много, что читать такой код очень тяжело как опытным скриптерам, так и самому автору.
Давайте рассмотрим простой пример для лучшего понимания ситуации, а дальше будут примеры более сложные. Есть такой код:
1
:LABEL
2
wait 0
3
if
4
056D: actor [email protected] defined
5
then
6
02AB: set_actor [email protected] immunities BP 1 FP 1 EP 1 CP 1 MP 1
7
jump @LABEL_2
8
else
9
0376: [email protected] = create_random_actor_at -1576.88 55.26 8.57
10
end
11
jump @LABEL
12
13
:LABEL_2
Copied!
Как же решать подобную проблему и сделать код легко читаемым? Первый принцип подхода таков: создаём (или используем) метку выхода с конструкции и пытаемся написать код таким способом, чтобы избавится от блока "ELSE".
У нас сейчас доступны две метки: "LABEL" и "LABEL_2". Мы можем использовать их чтобы делать простые прыжки. Сами условия пишем так, чтобы блок "ELSE" оказался не нужным.
Вариант решения №1:
1
:LABEL
2
if
3
856D: not actor [email protected] defined
4
then
5
0376: [email protected] = create_random_actor_at -1576.88 55.26 8.57
6
end
7
02AB: set_actor [email protected] immunities BP 1 FP 1 EP 1 CP 1 MP 1
8
//jump @LABEL_2 // <-- не обязательно
9
10
:LABEL_2
Copied!
Вариант решения №2:
1
:LABEL
2
if
3
856D: not actor [email protected] defined
4
jf @LABEL_2
5
0376: [email protected] = create_random_actor_at -1576.88 55.26 8.57
6
02AB: set_actor [email protected] immunities BP 1 FP 1 EP 1 CP 1 MP 1
7
//jump @LABEL_2 // <-- не обязательно
8
9
:LABEL_2
Copied!
13 строк кода мы сократили до 10 (8) строк и ещё избавились от цикла. Удивительно, но такой простой принцип лежит в основе "лёгкого" кода. Здесь важно не количество строк, f отсутствие отступов. Мы пишем код сверху вниз, а не в ширину. Давайте рассмотрим код посложнее, где эти "отступы" будут явно видны:
1
:LABEL
2
wait 0
3
if
4
056D: actor [email protected] defined
5
then
6
if and
7
8741: not actor [email protected] busted
8
8118: not actor [email protected] dead
9
then
10
if
11
056D: actor $PLAYER_ACTOR defined
12
then
13
05E2: AS_actor [email protected] kill_actor $PLAYER_ACTOR
14
jump @LABEL_2
15
end
16
else
17
009B: destroy_actor [email protected]
18
end
19
else
20
0376: [email protected] = create_random_actor_at -1576.88 55.26 8.57
21
02AB: set_actor [email protected] immunities BP 1 FP 1 EP 1 CP 1 MP 1
22
end
23
jump @LABEL
24
25
:LABEL_2
Copied!
Давайте попытаемся избавится от всех блоков "ELSE":
1
:LABEL
2
wait 0
3
if
4
856D: not actor [email protected] defined
5
then
6
0376: [email protected] = create_random_actor_at -1576.88 55.26 8.57
7
02AB: set_actor [email protected] immunities BP 1 FP 1 EP 1 CP 1 MP 1
8
end
9
if or
10
0741: actor [email protected] busted
11
0118: actor [email protected] dead
12
then
13
009B: destroy_actor [email protected]
14
jump @LABEL
15
end
16
if
17
856D: not actor $PLAYER_ACTOR defined
18
jf @LABEL
19
05E2: AS_actor [email protected] kill_actor $PLAYER_ACTOR
20
//jump @LABEL_2 // <-- не обязательно
21
22
:LABEL_2
Copied!
Мы по прежнему сократили количество строк и код у нас никуда не едет вправо. Читая скрипт, мы точно знаем что происходит в каждом блоке, без поиска отдельной части "ELSE", которая находилась где-то в "подвале" кода. Рассмотрим пример ещё сложнее:
2
3
while true
4
wait 0
5
077E: get_active_interior_to [email protected]
6
if
8
then
9
if
10
056D: actor $PLAYER_ACTOR defined
11
then
12
if
14
then
15
00A1: put_actor $PLAYER_ACTOR at [email protected] [email protected] [email protected]
17
end
18
else
20
end
21
else
22
if
24
then
28
else
29
if
31
then
32
33
34
35
else
36
if
38
then
39
40
41
42
else
43
44
45
46
end
47
end
48
end
49
end
50
end
Copied!
Мы видим, что у нас много разветвлений. При этом у нас в "ELSE" блоке проверки отличаются только числом, что сравнивается, и действиями. Как поступить в этом случае? Решение ситуации заключается в "разбиении" сложной конструкции на мелкие фрагменты:
1
while true
2
wait 0
3
if
4
856D: not actor $PLAYER_ACTOR defined
5
then
7
continue
8
end
9
077E: get_active_interior_to [email protected]
10
[email protected] = 1041.0 // "else [email protected] == 3"
11
[email protected] = 1441.0 // "else [email protected] == 3"
12
[email protected] = 1541.0 // "else [email protected] == 3"
13
if
15
then
19
end
20
if
22
then
23
24
25
26
end
27
if
29
then
30
31
32
33
end
34
if and
37
then
38
00A1: put_actor $PLAYER_ACTOR at [email protected] [email protected] [email protected]
40
end
41
end
Copied!
Мы опять сократили код и избавились от блоков "ELSE". Первое условие в коде позволяет не выполнять код ниже (ключевое слово "continue"), если оно равно "false". Это позволило нам избавится от лишних разветвлений в блоке с условием "[email protected] == 0". В блоке кода с условием "[email protected] == 3" действия блока "ELSE" фактически являлись изначальными данными, поэтому их легче написать после опкода 077E.
Наличие "continue" и изначальных данных приводит нас к ещё одному принципу по избеганию "ёлочки". Принцип таков: сначала пишем условия, которые не позволяют выполнять всю логику, если оно равно "false".
В блоке с условиями часто пишутся переменные, которые сбрасывают значение по-умолчанию (в нашем случае это переменная [email protected]). Далее пишутся изначальные данные, которые могут изменяться от других условий. И только в конце уже пишется окончательные действия, ради которых все эти условия и создавались.
Тем не менее, не всегда получается избавится от "ELSE", так как это нарушит работу алгоритма или в разветвлении совершаются принципиально разные действия. В этом случае, нужно соблюдать те же принципы, но в каждом из разветвлений. Рассмотрим такой скрипт:
1
while true
2
wait 0
3
if
4
00DF: actor $PLAYER_ACTOR driving
5
then
6
03C0: [email protected] = actor $PLAYER_ACTOR car
7
if
8
056E: car [email protected] defined
9
then
10
if and
11
82BF: not car [email protected] sunk
12
8119: not car [email protected] wrecked
13
then
14
if
15
0137: car [email protected] model == #REMINGTN
16
then
17
04BA: set_car [email protected] speed_to 5.0
18
else
19
if
20
0137: car [email protected] model == #CHEETAH
21
then
22
04BA: set_car [email protected] speed_to 10.0
23
else
24
if
25
0137: car [email protected] model == #BULLET
26
then
27
04BA: set_car [email protected] speed_to 15.0
28
else
29
04BA: set_car [email protected] speed_to 0.0
30
end
31
end
32
end
33
end
34
end
35
else
36
if
37
0AD2: [email protected] = player $PLAYER_CHAR targeted_actor
38
then
39
if
40
056D: actor [email protected] defined
41
then
42
if and
43
8741: not actor [email protected] busted
44
8118: not actor [email protected] dead
45
then
46
if
47
02F2: actor [email protected] model == #WMYST
48
then
49
02E2: set_actor [email protected] weapon_accuracy_to 75
50
else
51
if
52
02F2: actor [email protected] model == #BFYST
53
then
54
02E2: set_actor [email protected] weapon_accuracy_to 85
55
else
56
if
57
02F2: actor [email protected] model == #BALLAS1
58
then
59
02E2: set_actor [email protected] weapon_accuracy_to 95
60
else
61
02E2: set_actor [email protected] weapon_accuracy_to 10
62
end
63
end
64
end
65
else
66
009B: destroy_actor [email protected]
67
end
68
end
69
end
70
end
71
end
Copied!
Давайте попробуем упростить скрипт по этапам:
    1. 1.
      Смотрим на все блоки "ELSE", которые вложены максимально "глубоко" в другие "ELSE" или "THEN". То есть начинаем с конца.
У нас опкоды 04BA и 02E2 выполняются если все родительские блоки в условиях возвращают "false". Поэтому мы можем все вложенные условия записать без "ELSE", указав каждому блоку только действие в "THEN".
Тем не менее у нас остаётся "хвост" из тех же опкодов, которые изменяют работу алгоритма (машине задаётся всегда скорость 0, а актёру - точность 10). Чтобы алгоритм работал так же, как прежде, нам необходимо добавить "continue" в каждый блок:
1
if
2
0137: car [email protected] model == #REMINGTN
3
then
4
04BA: set_car [email protected] speed_to 5.0
5
continue
6
end
7
if
8
0137: car [email protected] model == #CHEETAH
9
then
10
04BA: set_car [email protected] speed_to 10.0
11
continue
12
end
13
if
14
0137: car [email protected] model == #BULLET
15
then
16
04BA: set_car [email protected] speed_to 15.0
17
continue
18
end
19
04BA: set_car [email protected] speed_to 0.0
20
21
// ***
22
23
if
24
02F2: actor [email protected] model == #WMYST
25
then
26
02E2: set_actor [email protected] weapon_accuracy_to 75
27
continue
28
end
29
if
30
02F2: actor [email protected] model == #BFYST
31
then
32
02E2: set_actor [email protected] weapon_accuracy_to 85
33
continue
34
end
35
if
36
02F2: actor [email protected] model == #BALLAS1
37
then
38
02E2: set_actor [email protected] weapon_accuracy_to 95
39
continue
40
end
41
02E2: set_actor [email protected] weapon_accuracy_to 10
Copied!
    1. 1.
      У нас по прежнему есть блок "ELSE", где проверяются различные состояния транспорта и актёров. В этом случае выполняем пункт 1, но уже с этими состояниями:
1
if or
2
02BF: car [email protected] sunk
3
0119: car [email protected] wrecked
4
then
5
continue
6
end
7
8
// ***
9
10
if or
11
0741: actor [email protected] busted
12
0118: actor [email protected] dead
13
then
14
009B: destroy_actor [email protected]
15
continue
16
end
Copied!
После этих блоков кода идёт код выше, с проверками на модель транспорта и актрёров, и без блоков "ELSE".
    1. 1.
      Опять у нас остались разветвления. Что же, повторим пункт 1, где избавимся от блока "ELSE", осуществив проверки на существование транспорта и актёра:
1
if
2
856E: not car [email protected] defined
3
then
4
continue
5
end
6
7
// ***
8
9
if
10
856D: not actor [email protected] defined
11
then
12
continue
13
end
Copied!
    1. 1.
      Осталось только 1 разбиение, где проверяется цель игрока. Возвращаемся к любимому пункту 1, и убираем блок "ELSE":
1
if
2
8AD2: not [email protected] = player $PLAYER_CHAR targeted_actor
3
then
4
continue
5
end
Copied!
    1. 1.
      Казалось, что последнее разветвление убрать уже не получится, так как выполняются два разных алгоритма. Но и тут я умудрился избавиться от надоедливого "ELSE".
Дело в том, что если у нас есть только два разных алгоритма, то после выполнения первого условия нам не нужно делать второй. Поэтому после выполнения кода при истинном значении условия опкода 00DF ставим любимый "continue" после выполнения алгоритма 1.
Вот так мне удалось сократить весь код:
1
while true
2
wait 0
3
if
4
00DF: actor $PLAYER_ACTOR driving
5
then
6
03C0: [email protected] = actor $PLAYER_ACTOR car
7
if
8
856E: not car [email protected] defined
9
then
10
continue
11
end
12
if or
13
02BF: car [email protected] sunk
14
0119: car [email protected] wrecked
15
then
16
continue
17
end
18
if
19
0137: car [email protected] model == #REMINGTN
20
then
21
04BA: set_car [email protected] speed_to 5.0
22
continue
23
end
24
if
25
0137: car [email protected] model == #CHEETAH
26
then
27
04BA: set_car [email protected] speed_to 10.0
28
continue
29
end
30
if
31
0137: car [email protected] model == #BULLET
32
then
33
04BA: set_car [email protected] speed_to 15.0
34
continue
35
end
36
04BA: set_car [email protected] speed_to 0.0
37
continue // не даём выполнится алгоритму #2
38
end
39
if
40
8AD2: not [email protected] = player $PLAYER_CHAR targeted_actor
41
then
42
continue
43
end
44
if
45
856D: not actor [email protected] defined
46
then
47
continue
48
end
49
if or
50
0741: actor [email protected] busted
51
0118: actor [email protected] dead
52
then
53
009B: destroy_actor [email protected]
54
continue
55
end
56
if
57
02F2: actor [email protected] model == #WMYST
58
then
59
02E2: set_actor [email protected] weapon_accuracy_to 75
60
continue
61
end
62
if
63
02F2: actor [email protected] model == #BFYST
64
then
65
02E2: set_actor [email protected] weapon_accuracy_to 85
66
continue
67
end
68
if
69
02F2: actor [email protected] model == #BALLAS1
70
then
71
02E2: set_actor [email protected] weapon_accuracy_to 95
72
continue
73
end
74
02E2: set_actor [email protected] weapon_accuracy_to 10
75
end
Copied!
Обратите внимание, что новый код увеличился на 4 строки. Мы пожертвовали ими ради того, чтобы код читался максимально легко. Если количество алгоритмов больше двух, то уже с "ELSE" можно жить. В общем прицнип работы здесь такой:
1
while true
2
wait 0
3
if
4
УСЛОВИЯ 1
5
then
6
АЛГОРИТМ 1
7
end
8
if
9
УСЛОВИЯ 1
10
then
11
АЛГОРИТМ 1
12
end
13
// ...
14
if
15
УСЛОВИЯ N
16
then
17
АЛГОРИТМ N
18
end
19
end
Copied!
Как сократить код мы научились. Как же научится писать такой код? Берём условия, которые продолжат код. Инвертируем их и в "THEN" пишем "continue", а остальной код пишем уже после, не используя "ELSE". Вот и всё решение :)
Рассмотрим ситуацию, когда у нас есть цикл на метках. Там не всё так просто, так как нет встроенного "continue". Здесь в дело вступает опкод "004D", более известный как "JF". Если в нас нет каких-либо действий при выполнении условия, то лучше использовать прыжок на первую метку цикла. Если действия всё же есть, то используем "JUMP" на первую метку цикла.
Здесь важно то, что в отличии от циклов условия инвентировать не нужно. Пишем так, как обычно:
1
:LABEL
2
wait 0
3
if
4
00DF: actor $PLAYER_ACTOR driving
5
then
6
03C0: [email protected] = actor $PLAYER_ACTOR car
7
if
8
056E: car [email protected] defined
9
jf @LABEL // это сработает как "continue" в цикле
10
if and
11
82BF: not car [email protected] sunk
12
8119: not car [email protected] wrecked
13
jf @LABEL
14
if
15
0137: car [email protected] model == #REMINGTN
16
then
17
04BA: set_car [email protected] speed_to 5.0 // уже есть действие
18
jump @LABEL // это сработает как "continue" в цикле
19
end
20
if
21
0137: car [email protected] model == #CHEETAH
22
then
23
04BA: set_car [email protected] speed_to 10.0
24
jump @LABEL
25
end
26
if
27
0137: car [email protected] model == #BULLET
28
then
29
04BA: set_car [email protected] speed_to 15.0
30
jump @LABEL
31
end
32
04BA: set_car [email protected] speed_to 0.0
33
jump @LABEL // не даём выполнится алгоритму #2
34
end
35
if
36
0AD2: [email protected] = player $PLAYER_CHAR targeted_actor
37
jf @LABEL
38
if
39
056D: actor [email protected] defined
40
jf @LABEL
41
if and
42
8741: not actor [email protected] busted
43
8118: not actor [email protected] dead
44
then
45
009B: destroy_actor [email protected]
46
jump @LABEL
47
end
48
if
49
02F2: actor [email protected] model == #WMYST
50
then
51
02E2: set_actor [email protected] weapon_accuracy_to 75
52
jump @LABEL
53
end
54
if
55
02F2: actor [email protected] model == #BFYST
56
then
57
02E2: set_actor [email protected] weapon_accuracy_to 85
58
jump @LABEL
59
end
60
if
61
02F2: actor [email protected] model == #BALLAS1
62
then
63
02E2: set_actor [email protected] weapon_accuracy_to 95
64
jump @LABEL
65
end
66
02E2: set_actor [email protected] weapon_accuracy_to 10
67
jump @LABEL
Copied!
Что же, надеюсь, что этот материал был полезным и у Ваш код избавиться от лишних "ёлочек" и будет смотреться читабельно для любого пользователя.|39|1|0|48659987png600368320`196``||izbavljaemsja_ot_jolochek|1567358296
Copy link
Edit on GitHub