-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathdigger.c
More file actions
2497 lines (2123 loc) · 114 KB
/
digger.c
File metadata and controls
2497 lines (2123 loc) · 114 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "memory.h"
#include "sprites.h"
#include "sound.h"
#include "tools.h"
#include "emt.h"
#include "digger_sprites.h"
#include "digger_short_font.h"
#include "digger_levels.h"
#include "digger_music.h"
// #define DEBUG // Режим отладки включен
#define SCREEN_Y_OFFSET 25
#define FIELD_X_OFFSET 2 // Смещение игрового поля по оси X
#define FIELD_Y_OFFSET (SCREEN_Y_OFFSET + 32) // Смещение игрового поля по оси Y
#define POS_X_STEP 4 // Шаг клеток по оси X (в байтах)
#define POS_Y_STEP 16 // Шаг клеток по оси Y (в строках)
#define MOVE_X_STEP 1 // Шаг перемещения по оси X (в байтах)
#define MOVE_Y_STEP 4 // Шаг перемещения по оси Y (в строках)
#define MIN_X_POS FIELD_X_OFFSET // Минимальное положение по оси X
#define MIN_Y_POS FIELD_Y_OFFSET // Минимальное положение по оси Y
#define MAX_X_POS (FIELD_X_OFFSET + POS_X_STEP * (W_MAX - 1)) // Максимальное положение по оси X
#define MAX_Y_POS (FIELD_Y_OFFSET + POS_Y_STEP * (H_MAX - 1)) // Максимальное положение по оси Y
#define COIN_Y_OFFSET 3 // Смещение спрайта монетки в ячейке по оси Y
#define MAX_BAGS 7 // Максимальное количество мешков с деньгами на уровне
#define MAX_BUGS 5 // Максимальное количество врагов на уровне
#define MAN_START_X 7 // Начльное положение Диггера по оси X (вклетках)
#define MAN_START_Y 9 // Начльное положение Диггера по оси Y (вклетках)
#define LOOSE_WAIT 15 // Время с момента начала покачивания до момента падения мешка
#define BONUS_LIFE_SCORE 20000 // Количество очков для дополнительной жизни
#define MAX_LIVES 4 // Максимальное количество жизней
/**
* @brief Перечисление типов врагов
*/
enum bug_types : uint8_t
{
BUG_HOBBIN = 0, /**< Хоббин */
BUG_NOBBIN /**< Ноббин */
};
/**
* @brief Перечисление направлений движения
*/
enum direction : uint8_t
{
DIR_LEFT = 0, /**< Движется налево */
DIR_RIGHT, /**< Движется направо */
DIR_UP, /**< Движется вверх */
DIR_DOWN, /**< Движется вниз */
DIR_STOP /**< Стоит на месте */
};
/**
* @brief Перечисление состояний Диггера или врага
*/
enum creature_state : uint8_t
{
CREATURE_INACTIVE = 0, /**< Не активен */
CREATURE_STARTING, /**< Стратует */
CREATURE_ALIVE, /**< Жив */
CREATURE_DEAD_MONEY_BAG, /**< Убит мешком с деньгами */
CREATURE_RIP /**< Лежит дохлый */
};
enum bag_state : uint8_t
{
BAG_INACTIVE = 0, /**< Мешок неактивен */
BAG_STATIONARY, /**< Мешок стационарен (стоит на месте) */
BAG_LOOSE, /**< Мешок раскачивается */
BAG_FALLING, /**< Мешок падает */
BAG_BREAKS, /**< Мешок разбивается */
BAG_BROKEN /**< Мешок разбился */
};
/**
* @brief Перечисление состояний бонус-режима
*/
enum bonus_state : uint8_t
{
BONUS_OFF = 0, /**< Режим бонус ещё не включен */
BONUS_READY, /**< Режим бонус готов к активации (появилась вишенка) */
BONUS_ON, /**< Режим бонус включен */
BONUS_END /**< Режим борус закончился */
};
#pragma pack(push, 1)
/**
* @brief Состояние мешка с деньгами
*
* Размер дополнен до 8 байт: при индексации &bags[i] компилятор использует
* сдвиг (<<3) вместо вызова __mulhi3 на умножение на 5.
*/
struct bag_info
{
enum bag_state state; ///< Флаг активности мешка
enum direction dir; ///< Направление движения мешка
uint8_t x_graph; ///< Положение по оси X в графических координатах
uint8_t y_graph; ///< Положение по оси Y в графических координатах
uint8_t count; ///< Счётчик
uint8_t _pad[3]; ///< Выравнивание до 8 байт (см. doxygen-комментарий выше)
};
/**
* @brief Состояние врага (Хоббина/Ноббина)
*
* Размер дополнен до 16 байт по той же причине, что и bag_info.
*/
struct bug_info
{
enum creature_state state; ///< Состояние врага (жив, погиб, лежит дохлый - влияет на внешний вид)
enum bug_types type; ///< Тип врага (Ноббин или Хоббин)
enum direction dir; ///< Направление движения врага
uint8_t count; ///< Счётчик
uint8_t wait; ///< Счётчик задержки врага (при толкании мешков, изменении направления)
uint8_t image_phase; ///< Фаза анимации при выводе спрайта
int8_t image_phase_inc; ///< Направление изменения фазы анимации при выводе спрайта (+1 или -1)
uint8_t x_graph; ///< Положение по оси X в графических координатах
uint8_t y_graph; ///< Положение по оси Y в графических координатах
uint8_t _pad[7]; ///< Выравнивание до 16 байт
};
#pragma pack(pop)
/**
* @brief Единичные шаги по направлениям (DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN).
*/
static const int8_t dir_dx[4] = { -1, 1, 0, 0 };
static const int8_t dir_dy[4] = { 0, 0, -1, 1 };
/**
* @brief Поле состояний ячеек фона.
*/
uint8_t background[H_MAX][16];
/**
* @brief Поле состояний монеток. Установленный бит означает наличие монетки
*/
uint16_t coins[H_MAX];
/**
* @brief Состояние мешков с деньгами
*/
struct bag_info bags[MAX_BAGS];
/**
* @brief Состояние врагов (Хоббинов/Ноббинов)
*/
struct bug_info bugs[MAX_BUGS];
// Переменные отвечающие за состояние Диггера
uint16_t man_image_phase; /// Фаза отображения спрайта Диггера
uint16_t man_image_phase_inc; /// Инкремент(декремент) фазы отображения спрайта Диггера
uint16_t man_wait; /// Задержка перед следующим перемещением Диггера
uint16_t man_x_graph; /// Положение Диггера по оси X в графических координатах
uint16_t man_y_graph; /// Положение Диггера по оси Y в графических координатах
enum direction man_dir; /// Направление движения Диггера
enum direction man_prev_dir; /// Предыдущее направление движения Диггера
enum direction man_new_dir; /// Желаемое новое направление движения Диггера
enum creature_state man_state; /// Состояние Диггера (жив, убит, лежит дохлый)
struct bag_info *man_dead_bag; /// Указатель на мешок от котрого погиб Диггер
// Переменные отвечающие за создание врагов
uint8_t bugs_max; /// Максимальное количество врагов на уровне одновременно
uint8_t bugs_total; /// Общее количество врагов на уровне
uint8_t bugs_delay; /// Задержка перед рождением врага (Ноббина)
uint8_t bugs_delay_counter; /// Счётчик задержки перед рождением врага
uint8_t bugs_active; /// Количество активных врагов
uint8_t bugs_created; /// Общее количество сщзданных врагов
// Переменные отвечающие за мешки
uint8_t broke_max; // Время через которое исчезнет разбившийся мешок
// Переменные отвечающие за бонус-режим
enum bonus_state bonus_state; /// Состояние режима бонус
uint16_t bonus_time; /// Время активности бонус-режима
uint8_t bonus_flash; /// Время мерцания при включении/выключении Бонус-режима
uint8_t bonus_count; /// Множитель очков в Бонус-режиме (умножается на два за каждого пойманного врага)
uint32_t bonus_life_score; /// Количество очков для дополнительное жизни
// Переменные отвечающие за выстрел
uint16_t mis_x_graph; /// Положение выстрела по оси X в графических координатах
uint16_t mis_y_graph; /// Положение выстрела по оси Y в графических координатах
uint8_t mis_image_phase; ///< Фаза анимации при выводе спрайта снаряда
uint8_t mis_fire; /// Флаг выстрела
uint8_t mis_flying; /// Флаг означающий, что снаряд летит
uint8_t mis_wait; /// Задержка готовности выстрела
uint8_t mis_explode; /// Счётчик взрывающегося снаряда
enum direction mis_dir; /// Направление полёта выстрела
// Переменные отвечающие за состояние игры
uint16_t difficulty; /// Уровень сложности
uint16_t level_no; /// Текущий номер уровня
int16_t lives; /// Текущее количество жизней
uint32_t score; /// Количество очков
// Переменные отвечающие за вывод звуков.
uint16_t snd_effects = 1; /// Флаг, показывающий, что звуковые эффекты включены
struct {
uint8_t loose; /// Флаг, означающий, что звук качающегося мешка включен
uint8_t fall; /// Флаг, означающий, что звук летящего мешка включен
uint8_t break_bag; /// Флаг, означающий, что звук разбивающегося мешка включен
uint8_t money; /// Флаг, означающий, что звук съедания золота включен
uint8_t coin; /// Счётчик 0..7 - звук съедания монетки/драгоценного камня
uint8_t fire; /// Флаг, означающий, что звук выстрела включен
uint8_t explode; /// Флаг, означающий, что звук взрыва включен
uint8_t done; /// Флаг, означающий, что звук завершения уровня включен
uint8_t bug; /// Флаг, означающий, что звук съедания врага в бонус-режиме включен
uint8_t chase; /// Счётчик 0..19 - звук включения/выключения бонус-режима
uint8_t chase_flip; /// Флаг переключающий тональность звука бонус-режима
uint8_t life; /// Счётчик 0..24 - звук получения дополнительной жизни
} snd;
uint16_t loose_snd_phase; /// Фаза звука качающегося мешка
uint8_t fall_snd_phase; /// Фаза звука падающего мешка
uint16_t fall_period; /// Период звука летящего мешка
int8_t coin_snd_note; /// Номер ноты при съедании монетки (драгоценного камня)
uint8_t coin_time; /// Таймер между последовательными съедениями драгоценных камней (монеток)
uint16_t fire_snd_period; /// Период звука выстрела
#if defined(DEBUG)
/**
* @brief Отладочная процедура отображения мини-карты состояния фона
*/
void draw_bg_minimap()
{
sp_put(48, SCREEN_Y_OFFSET + MOVE_Y_STEP + 2, sizeof(background[0]), sizeof(background) / sizeof(background[0]), (uint8_t*)background, 0);
}
/**
* @brief Отладочная процедура отображения мини-карты состояния монеток (драгоценных камней)
*/
void draw_coin_minimap()
{
sp_put(45, SCREEN_Y_OFFSET + MOVE_Y_STEP + 2, sizeof(coins[0]), sizeof(coins) / sizeof(coins[0]), (uint8_t*)coins, 0);
}
#else
#define draw_bg_minimap() ;
#define draw_coin_minimap() ;
#endif
int remove_coin(uint8_t x_log, uint8_t y_log);
/**
* @brief Вывод 16-битного десятичного числа
*
* @param number - число для вывода
* @param x_graph - координата X по которой будет осуществлён вывод числа
* @param y_graph - координата Y по которой будет осуществлён вывод числа
*/
void print_dec(uint16_t number, uint16_t x_graph, uint16_t y_graph)
{
constexpr char zero = '0';
constexpr uint16_t row_w = sizeof(digit_rows[0]); // 3 байта на строку
constexpr uint16_t row_n = sizeof(digit_indices[0]) / sizeof(digit_indices[0][0]); // 12 строк на цифру
char buf[5] = { zero, zero, zero, zero, zero }; // Буфер для 5 десятичных знаков
char *ptr = &buf[sizeof(buf)];
uint_to_str(number, &ptr);
uint8_t digit_buf[row_n * row_w];
for (uint8_t i = 0; i < sizeof(buf); ++i)
{
const uint8_t *idx_row = digit_indices[buf[i] - zero];
uint8_t *dst = digit_buf;
for (uint8_t r = 0; r < row_n; ++r)
{
const uint8_t *src = digit_rows[idx_row[r]];
*dst++ = *src++;
*dst++ = *src++;
*dst++ = *src;
}
sp_put(x_graph, y_graph, row_w, row_n, digit_buf, nullptr);
x_graph += row_w;
}
}
/**
* @brief Вывод количества жизней в виде спрайтов Диггера рядом с количеством очков
*/
void print_lives()
{
uint16_t man_x_offset = sizeof(digit_rows[0]) * 5 + 1; // Смещение шириной в пять символов '0' плюс один байт (4 пикселя)
constexpr uint16_t man_y_offset = SCREEN_Y_OFFSET + 2; // Смещение спрайта Диггера по оси Y
constexpr uint16_t one_pos_width = sizeof(image_digger_right[1][0]) + 1; // Ширина спрайта Диггера плюс один байт
constexpr uint16_t height = sizeof(image_digger_right[1]) / sizeof(image_digger_right[1][0]); // Высота спрайта Диггера
int16_t width = MAX_LIVES * one_pos_width; // Общий размер места занимаемый спрайтами Диггера
uint8_t *sprite = (uint8_t *)image_digger_right[2];
for (uint16_t l = 1; width > 0; man_x_offset += one_pos_width, width -= one_pos_width)
{
if (++l > lives)
{
sp_clear_brick(man_x_offset, man_y_offset, width, height);
break;
}
sp_4_15_put(man_x_offset, man_y_offset, sprite);
}
}
/**
* @brief Добавление заданного количества очков и вывод очков в левом верхнем углу экрана
*/
void add_score(uint16_t score_add)
{
score += score_add;
print_dec(score, 0, SCREEN_Y_OFFSET + MOVE_Y_STEP);
// Если количество жизней не достигло максимального и количество очков досигло бонусного для получения жизни
if (lives < MAX_LIVES && (score >= bonus_life_score))
{
lives++; // Увеличичить количество жизней на единицу
print_lives(); // Вывесли количество жизней
bonus_life_score += BONUS_LIFE_SCORE; // Количество очков до следующего бонуса в виде жизни
snd.life = 24; // Издать звук получения жизни
}
}
/**
* @brief Добавление очков за убитого врага
*/
void add_score_250()
{
add_score(250); // 250 очков за убитого врага
}
/**
* @brief Стирает блок 16x15 пикселей (4x15 байт)
*
* @param x_graph - координата X блока
* @param y_graph - координата Y блока
*/
void erase_4_15(uint16_t x_graph, uint16_t y_graph)
{
uint8_t *ptr = (uint8_t *)MEM_VIDEO + SCREEN_BYTE_WIDTH * y_graph + x_graph;
for (uint16_t i = 0; i < 15; ++i)
{
ptr[0] = ptr[1] = ptr[2] = ptr[3] = 0;
ptr += SCREEN_BYTE_WIDTH;
}
}
/**
* @brief Проверка соприкосновения двух 4x15-спрайтов по их левым-верхним углам.
*/
int check_collision_4_15(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
return ((uint16_t)((int)x2 - (int)x1 + 3) < 7u)
&& ((uint16_t)((int)y2 - (int)y1 + 14) < 29u);
}
/**
* @brief Преобразование графической координаты X в логическую (номер клетки).
*/
static uint8_t graph_to_x_log(uint16_t x_graph)
{
return (x_graph - FIELD_X_OFFSET) / POS_X_STEP;
}
static uint8_t graph_to_y_log(uint16_t y_graph)
{
return (y_graph - FIELD_Y_OFFSET) / POS_Y_STEP;
}
/**
* @brief Вызов sp_put для спрайтов 4x15 с маской.
*/
static void sp_4_15_mask(uint16_t x, uint16_t y, const uint8_t *image, const uint8_t *outline)
{
sp_put(x, y, 4, 15, (uint8_t*)image, (uint8_t*)outline);
}
/**
* @brief Получение ячейки уровня по заданным координатам.
*/
static inline enum level_symbols getLevelSymbol(uint8_t y_log, uint8_t x_log)
{
static const uint8_t word_no_tbl[W_MAX] = { 0,0,0,0,0, 1,1,1,1,1, 2,2,2,2,2 };
static const uint8_t shift_tbl[W_MAX] = { 0,3,6,9,12, 0,3,6,9,12, 0,3,6,9,12 };
return (level[level_no][y_log][word_no_tbl[x_log]] >> shift_tbl[x_log]) & 7;
}
void bonus_indicator(uint16_t color);
/**
* @brief Инициализация переменных состояния перед старом уровня
*/
void init_level_state()
{
// Отключить бонус-режим
bonus_state = BONUS_OFF;
// Стереть вишенку
erase_4_15(FIELD_X_OFFSET + (W_MAX - 1) * POS_X_STEP, FIELD_Y_OFFSET);
// Отключение индикации бонус-режима
bonus_indicator(0);
// Деактивировать всех врагов
for (uint8_t i = 0; i < MAX_BUGS; ++i)
{
struct bug_info *bug = &bugs[i]; // Структура с информацией о враге
if (bug->state == CREATURE_INACTIVE) continue; // Пропустить неактивных врагов
erase_4_15(bug->x_graph, bug->y_graph); // Стереть врага
bug->state = CREATURE_INACTIVE; // Деактивировать врага
}
// Деактивировать нестационарные мешки
for (uint16_t i = 0; i < MAX_BAGS; ++i)
{
struct bag_info *bag = &bags[i]; // Структура с информацией о мешке
if (bag->state == BAG_INACTIVE) continue; // Пропустить неактивные мешки
if ((bag->state == BAG_STATIONARY) && (bag->dir == DIR_STOP)) continue; // Пропустить стационарные мешки
sp_4_15_mask(bag->x_graph, bag->y_graph, nullptr, outline_bag[0]); // Стереть мешок
bag->state = BAG_INACTIVE;
}
// print_dec(difficulty, 0, MAX_Y_POS + 2 * POS_Y_STEP);
if (difficulty > 6) bugs_max = 5; // На уровне сложности 7 и выше максимально 5 врагов одновременно
else if (difficulty > 0) bugs_max = 4; // На уровне сложности со 1 до 6 (включительно) до 4 врагов одновременно
else bugs_max = 3; // На первом уровне максимально три варага одновременно
// Переменные относщиеся к созданию и управлению врагами
bugs_total = difficulty + 6; // Общее количество врагов на уровне - шесть плюс уровень сложности
bugs_delay = 45 - (difficulty << 1); // Задержка появления врагов (с ростом сложности убывает)
bugs_delay_counter = bugs_delay; // Инициализация счётчика задержки врага исходным значением
bugs_active = 0; // Количество активных врагов
bugs_created = 0; // Общее количество сщзданных врагов
broke_max = 140 - difficulty * 10; // Время через которое исчезнет разбившийся мешок (с ростом сложности убывает)
// Инициализация переменных Диггера
man_dir = DIR_RIGHT;
man_prev_dir = DIR_RIGHT;
man_x_graph = FIELD_X_OFFSET + MAN_START_X * POS_X_STEP; // Исходная координата Диггера на экране по оси X
man_y_graph = FIELD_Y_OFFSET + MAN_START_Y * POS_Y_STEP; // Исходная координата Диггера на экране по оси Y
man_image_phase = 0; // Фаза анимации Диггера
man_image_phase_inc = 1; // Направление ихменения фазы анимации Диггера
man_wait = 0; // Задержка перед следующим перемещением Диггера
man_state = CREATURE_ALIVE; // Исходное состояние - Диггер жив
// Инициализация переменных снаряда
mis_fire = 0;
mis_flying = 0;
mis_wait = 0;
mis_explode = 0;
// Инициализация переменных используемых для звуковых эффектов.
clr_words(&snd, sizeof(snd) / 2);
coin_snd_note = -1;
coin_time = 0;
}
/**
* @brief Прогрызть фон в соответствии с направлением движения и текущим положением
*
* @param dir - направление движения
* @param x_graph - графическая координата по оси X
* @param y_graph - графическая координата по оси Y
*/
void gnaw(enum direction dir, uint16_t x_graph, uint16_t y_graph)
{
static const struct
{
int16_t x;
int16_t y;
uint16_t x_size;
uint16_t y_size;
uint8_t *sprite;
} gnaw_mtx[4] = {
{ -2, -1, sizeof(outline_blank_left[0]), sizeof(outline_blank_left) / sizeof(outline_blank_left[0]), (uint8_t*)outline_blank_left },
{ 4, -1, sizeof(outline_blank_right[0]), sizeof(outline_blank_right) / sizeof(outline_blank_right[0]), (uint8_t*)outline_blank_right },
{ -1, -7, sizeof(outline_blank_up[0]), sizeof(outline_blank_up) / sizeof(outline_blank_up[0]), (uint8_t*)outline_blank_up },
{ -1, 15, sizeof(outline_blank_down[0]), sizeof(outline_blank_down) / sizeof(outline_blank_down[0]), (uint8_t*)outline_blank_down }
};
sp_put(x_graph + gnaw_mtx[dir].x, y_graph + gnaw_mtx[dir].y, gnaw_mtx[dir].x_size, gnaw_mtx[dir].y_size, nullptr, gnaw_mtx[dir].sprite);
}
/**
* @brief Инициализация уровня (отрисовка фона, расстановка монеток и мешков, отрисовка прогрызенных проходов)
*/
void init_level()
{
clr_words(bags, sizeof(bags) / 2); // Деактивировать все мешки
clr_words(bugs, sizeof(bugs) / 2); // Деактивировать всех врагов
constexpr uint16_t bg_block_width = sizeof(image_background[0][0]); // Ширина блока фона
constexpr uint16_t bg_block_height = sizeof(image_background[0]) / sizeof(image_background[0][0]); // Высота блока фона
constexpr uint16_t x_size = 13; // Ширина поля фона в блоках
constexpr uint16_t y_size = POS_Y_STEP * H_MAX / bg_block_height + MOVE_Y_STEP + 2; // Высота поля фона в блоках
const uint8_t *back_image = (uint8_t *)image_background[level_no]; // Указатель на образец фона для текущего уровня
// Отрисовка фона
for (uint16_t y_graph = 0; y_graph < y_size * bg_block_height; y_graph += bg_block_height)
{
for (uint16_t x_graph = 0; x_graph < x_size * bg_block_width; x_graph += bg_block_width)
{
sp_put(x_graph - 1, y_graph + FIELD_Y_OFFSET - MOVE_Y_STEP * 3, bg_block_width, bg_block_height, back_image, nullptr);
}
}
// Инициализация поля фона, поля моненток, состояний мешков
uint16_t bag_num = 0;
uint16_t x_graph = FIELD_X_OFFSET;
uint16_t y_graph = FIELD_Y_OFFSET;
for (uint16_t y_log = 0; y_log < H_MAX; ++y_log)
{
coins[y_log] = 0; // Сброситть все биты монеток для данной строки
for (uint16_t x_log = 0; x_log < W_MAX; ++x_log)
{
uint8_t *bg = &background[y_log][x_log]; // Структура с информацией о клетке фона
*bg = 0; // Сбросить все биты состояния фона (вся клетка фона цела)
enum level_symbols ls = getLevelSymbol(y_log, x_log);
if (ls == LEV_C)
{
coins[y_log] |= 1 << x_log; // Установить бит соответствующий монетке на карте уровня
// Нарисовать монетку (драгоценный камень)
sp_put(x_graph, y_graph + COIN_Y_OFFSET, sizeof(image_coin[0]), sizeof(image_coin) / sizeof(image_coin[0]), (uint8_t *)image_coin, (uint8_t *)outline_coin);
}
else if (ls == LEV_B)
{
struct bag_info *bag = &bags[bag_num++]; // Структура с информацией об очередном мешке
bag->state = BAG_STATIONARY; // Мешок стоит на месте
bag->count = 0; // Счётчик сброшен
bag->x_graph = x_graph; // Координата мешка по оси X
bag->y_graph = y_graph; // Координата мешка по оси Y
bag->dir = DIR_STOP; // Мешок стоит на месте
// Нарисовать мешок с золотом
sp_4_15_mask(bag->x_graph, bag->y_graph, image_bag[0], outline_bag[0]);
}
else if (ls == LEV_H || ls == LEV_S)
{
*bg |= 0x0F; // устанавливаем все биты состояния фона для горизонтальных проходов
for (uint16_t i = 4; i > 0; --i)
{
gnaw(DIR_RIGHT, x_graph - i, y_graph);
}
gnaw(DIR_LEFT, x_graph + 1, y_graph);
}
if (ls == LEV_V || ls == LEV_S)
{
*bg |= 0xF0; // устанавливаем все биты состояния фона для вертикальных проходов
for (uint16_t i = 15; i > 0; i -= 3)
{
gnaw(DIR_DOWN,x_graph, y_graph - i);
}
gnaw(DIR_UP, x_graph, y_graph + 3);
}
x_graph += POS_X_STEP;
}
x_graph = FIELD_X_OFFSET;
y_graph += POS_Y_STEP;
}
init_level_state(); // Инициализировать состояние уровня
}
/**
* @brief Определяет по состоянию байта клетки, что клетка полностью проедена
*
* @param byte - байт с состоянием клетки
*
* @return - 1 - клетка полностью проедена, 0 - клетка не проедена полностью
*/
uint16_t full_bite(uint8_t byte)
{
if (((byte & 0xF0) == 0xF0) || ((byte & 0xF) == 0xF)) return 1;
return 0;
// int bits_count;
// for (bits_count = 0; byte; bits_count++)
// {
// byte &= byte - 1;
// }
//
// return bits_count > 1;
}
/**
* @brief Определяет возможность движения в заданном направлении
*
* @param dir - направлкние движения
* @param x_graph - графическая координата по оси X
* @param y_graph - графическая координата по оси Y
*
* @return - 1 - движение в заданном направлении возможно, 0 - движение в заданном направлении невозможно
*/
uint8_t check_path(enum direction dir, uint8_t x_graph, uint8_t y_graph)
{
uint8_t x_log = graph_to_x_log(x_graph);
uint8_t y_log = graph_to_y_log(y_graph);
const uint8_t current_cell = background[y_log][x_log]; // Состояние текущей клетки
static const struct
{
int8_t x;
int8_t y;
uint8_t mask;
uint8_t cur_mask;
} dir_matrix[4] = {
{ -1, 0, 0x08, 0x01 }, // Влево
{ 1, 0, 0x01, 0x08 }, // Вправо
{ 0, -1, 0x80, 0x10 }, // Вверх
{ 0, 1, 0x10, 0x80 } // Вниз
} ;
x_log += dir_matrix[dir].x;
y_log += dir_matrix[dir].y;
if ((x_log >= W_MAX) || (y_log >= H_MAX)) return 0;
const uint8_t neighbor_cell = background[y_log][x_log]; // Состояние соседней клетки
if (full_bite(neighbor_cell) && ((neighbor_cell & dir_matrix[dir].mask) || (current_cell & dir_matrix[dir].cur_mask))) return 1;
return 0;
}
/**
* @brief Очистить биты состоянияфона по заданным координатам в указанном направлении
*
* @param x_graph - графическая координата по оси X
* @param y_graph - графическая координата по оси Y
* @param dir - направлкние движения
*/
void set_background_bits(uint16_t x_graph, uint16_t y_graph, enum direction dir)
{
const uint16_t abs_x_pos = x_graph - FIELD_X_OFFSET;
const uint16_t abs_y_pos = y_graph - FIELD_Y_OFFSET;
uint16_t x_log = abs_x_pos / POS_X_STEP;
uint16_t y_log = abs_y_pos / POS_Y_STEP;
int16_t x_rem = abs_x_pos % POS_X_STEP;
int16_t y_rem = (abs_y_pos % POS_Y_STEP) >> 2;
switch (dir)
{
case DIR_LEFT:
{
if (--x_rem < 0)
{
x_rem += 4;
x_log--;
}
break;
}
case DIR_RIGHT:
{
// if (++x_rem >= 4)
// {
// x_rem -= 4;
// x_log++;
// }
x_log++;
break;
}
case DIR_UP:
{
if (--y_rem < 0)
{
y_rem += 4;
y_log--;
}
break;
}
case DIR_DOWN:
{
// if (++y_rem >= 4)
// {
// y_rem -= 4;
// y_log++;
// }
y_log++;
break;
}
}
// Проверка на выход за пределы игрового поля
if (x_log >= W_MAX || y_log >= H_MAX) return;
uint8_t *cell = &background[y_log][x_log]; // Указатель на текущую ячейку состояния фона
switch (dir)
{
case DIR_LEFT:
case DIR_RIGHT:
{
*cell |= 1 << x_rem; // Установить соответсвующий бит матрицы фона
break;
}
case DIR_UP:
case DIR_DOWN:
{
*cell |= 1 << (y_rem + 4); // Установить соответсвующий бит матрицы фона
break;
}
}
}
/**
* @brief Проверка на выход за пределы игрового поля
*/
int check_out_of_range(enum direction dir, uint16_t x_graph, uint16_t y_graph)
{
return (
(dir == DIR_RIGHT && x_graph >= MAX_X_POS) ||
(dir == DIR_LEFT && x_graph <= MIN_X_POS) ||
(dir == DIR_DOWN && y_graph >= MAX_Y_POS) ||
(dir == DIR_UP && y_graph <= MIN_Y_POS)
);
}
/**
* @brief Проверка на то, что перемещение по оси X происходит на заданный объект
*
* @param dir - направление перемещения
* @param x_graph - координата X перемещаемого объекта
* @param object_x_graph - координата X объекта на который возможно перемещение
*/
int move_to_object(enum direction dir, uint16_t x_graph, uint16_t object_x_graph)
{
// Если направление перемещения вправо и объект находится правее
// или направление перемещения влево и объект находтся левее
return (((dir == DIR_RIGHT) && (object_x_graph > x_graph)) ||
((dir == DIR_LEFT) && (x_graph > object_x_graph)));
}
/**
* @brief Переместить определённый мешок.
* Если будут затронуты другие мешки, перемещение будет отменено и возвращено значение 0.
*
* @param bag - указатель на структуру с информацией о мешке
* @param dir - направление перемещения мешка
* @return 0 - мешок был перемещён, 1 - мешок не был перемещён
*/
uint8_t move_bag(struct bag_info *bag, enum direction dir)
{
uint8_t rv = 0;
uint8_t bag_x_graph = bag->x_graph;
uint8_t bag_y_graph = bag->y_graph;
// Проверить пытается ли переместиться мешок за пределы экрана
if (check_out_of_range(dir, bag_x_graph, bag_y_graph))
{
return 1; // Если мешок пытается переместиться за пределы экрана, отменить перемещение
}
else
{
// Если раскачивающийся мешок двигают встороны, то он перестаёт раскачиваться
if (bag->state == BAG_LOOSE)
{
bag->state = BAG_STATIONARY;
bag->count = 0;
}
switch (dir)
{
case DIR_RIGHT:
{
bag_x_graph += MOVE_X_STEP; // Перемещение мешка на шаг вправо
break;
}
case DIR_LEFT:
{
bag_x_graph -= MOVE_X_STEP; // Перемещение мешка на шаг влево
break;
}
}
// Проверить перемещается ли мешок на Диггера
if (check_collision_4_15(bag_x_graph, bag_y_graph, man_x_graph, man_y_graph))
{
if (move_to_object(dir, bag_x_graph, man_x_graph))
{
rv = 1; // Если да, отменить перемещение
}
}
// Проверить перемещается ли мешок на врага
for (uint8_t i = 0; i < bugs_max; ++i)
{
struct bug_info *bug = &bugs[i]; // Структура с информацией о враге
if (!bugs_active) continue; // Пропустить неактивных врагов
if (bug->state != CREATURE_ALIVE) continue; // Пропустить неживых врагов
// Проверить, что мешок перемещается на врага
if (check_collision_4_15(bag_x_graph, bag_y_graph, bug->x_graph, bug->y_graph))
{
if (move_to_object(dir, bag_x_graph, bug->x_graph))
{
rv = 1; // Если да, отменить перемещение
break;
}
}
}
// Проверка соприкосновение с другими мешками
for (uint8_t i = 0; i < MAX_BAGS; ++i)
{
struct bag_info *another_bag = &bags[i]; // Структура с информацией о мешке
if (another_bag == bag) continue; // Пропустить мешок, процедуру обработки которого вызвали
// Пропустить не стационарные и не качающиеся мешки
if ((another_bag->state != BAG_STATIONARY) && (another_bag->state != BAG_LOOSE)) continue;
// Проверить, что мешок соприкоснулся с другим мешком
if (check_collision_4_15(bag_x_graph, bag_y_graph, another_bag->x_graph, another_bag->y_graph))
{
// Если направление перемещения вправо и другой мешок находится правее обрабатываемого мешка
// или направление перемещения влево и другой мешок находтся левее обрабатываемого мешка
if (move_to_object(dir, bag_x_graph, another_bag->x_graph))
{
// Попробовать взывать перемещение мешка с которым обнаружена коллизия
if (move_bag(another_bag, dir))
{
// Если другой мешок не смог переместиться
rv = 1; // Отменить перемещение и этого мешка
break;
}
}
}
}
}
if (!rv)
{
// Стирание мешка по старым координатам
sp_put(bag->x_graph, bag->y_graph, 4, 15, nullptr, (uint8_t *)outline_bag);
// Отрисовка спрайта передвигаемого мешка
sp_put(bag_x_graph, bag_y_graph, 4, 15, (uint8_t *)image_bag, (uint8_t *)outline_bag);
set_background_bits(bag_x_graph, bag_y_graph, dir); // Сбросить биты матрицы фона
// Удалить монеты уничтоженные мешком
remove_coin(graph_to_x_log(bag_x_graph), graph_to_y_log(bag_y_graph));
// Установить новые координаты мешка
bag->dir = dir;
bag->x_graph = bag_x_graph;
bag->y_graph = bag_y_graph;
}
return rv;
}
/**
* @brief Стереть след за объектом размером 15x4
*
* @param dir - направление движения объекта
* @param x_graph - графическая координата по оси X
* @param y_graph - графическая координата по оси Y
*/
void erase_trail(enum direction dir, uint16_t x_graph, uint16_t y_graph)
{
static const int8_t trail_dx[4] = { 4, -MOVE_X_STEP, 0, 0 };
static const int8_t trail_dy[4] = { 0, 0, 15, -MOVE_Y_STEP };
static const uint8_t trail_w[4] = { MOVE_X_STEP, MOVE_X_STEP, 4, 4 };
static const uint8_t trail_h[4] = { 15, 15, MOVE_Y_STEP, MOVE_Y_STEP };
sp_clear_brick(x_graph + trail_dx[dir], y_graph + trail_dy[dir], trail_w[dir], trail_h[dir]);
}
/**
* @brief Обработка перемещения Ноббина/Хоббина
*
* @param bug - указатель на структуру с информацией о враге
*/
void move_bug(struct bug_info *bug)
{
enum direction dir_1, dir_2, dir_3, dir_4;
const uint8_t bug_x_graph = bug->x_graph;
const uint8_t bug_y_graph = bug->y_graph;
const uint8_t bug_abs_x_pos = bug_x_graph - FIELD_X_OFFSET;
const uint8_t bug_abs_y_pos = bug_y_graph - FIELD_Y_OFFSET;
const uint8_t bug_x_rem = bug_abs_x_pos % POS_X_STEP;
const uint8_t bug_y_rem = (bug_abs_y_pos % POS_Y_STEP) >> 2;
// Проверка возможности изменения направления движения при нахождении на ровной границе клетки
if (!bug_x_rem && !bug_y_rem)
{
// Если Хоббин застрял на время более заданного, то превратить его в Ноббина
if ((bug->type == BUG_HOBBIN) && (bug->count > (32 + difficulty * 2)))
{
bug->count = 0; // Очистить время застревания
bug->type = BUG_NOBBIN; // Превратить врага в Ноббина
}
// Поиск порядка наилучших направлений движения
dir_1 = (man_x_graph < bug_x_graph) ? DIR_LEFT : DIR_RIGHT;
dir_2 = (man_y_graph < bug_y_graph) ? DIR_UP : DIR_DOWN;
// Если расстояние по горизонтали превышает расстояние по вертикали, то отдать приоритет оси X
if (abs16(man_y_graph - bug_y_graph) > abs16(man_x_graph - bug_x_graph))
{
// Если расстояние по вертикали превышает расстояние по горизонтали, то отдать приоритет оси Y
dir_1 ^= dir_2;
dir_2 ^= dir_1;
dir_1 ^= dir_2;
}
dir_3 = dir_2 ^ 1;
dir_4 = dir_1 ^ 1;
// Если включён режим Бонус, то поменять порядок направлений чтобы враги разбегались
if (bonus_state == BONUS_ON)
{
// Наиболее приоритетное направление поменять с наименее приоритетным
dir_1 ^= dir_4;
dir_4 ^= dir_1;
dir_1 ^= dir_4;
// Более приоритетное поменять с менее приоритетным
dir_2 ^= dir_3;
dir_3 ^= dir_2;
dir_2 ^= dir_3;
}
// Сделать движение назад последним выбором при определении направления
enum direction dir = (bug->dir) ^ 1; // Инвертировать направление движения врага
if (dir == dir_1) // Если движение назад наболее приоритетно
{
// Сдвинуть все направления, вместо последнего использовать движение назад
dir_1 = dir_2;
dir_2 = dir_3;
dir_3 = dir_4;
dir_4 = dir;
}
if (dir == dir_2) // Если движение назад более приоритетное
{
// Сдвинуть все направления, кроме наиболее приоритетного,
// вместо последнего использовать движение назад
dir_2 = dir_3;
dir_3 = dir_4;
dir_4 = dir;
}
if (dir == dir_3) // Если движение назад менее приоритетное
{
// Вместо менее приоритетного направления использовать наименее приоритетное,
// вместо последнего использовать движение назад
dir_3 = dir_4;
dir_4 = dir;
}
// В уровнях сложности до шестого использовать элемент случайности в выборе направления
if ((difficulty < 5) && ((rand() & 0xF) > (difficulty + 10)))
{
// В одном из (5 + difficulty) случаев поменять наиболее
// приоритетное направление с менее приоритетным
dir_1 ^= dir_3;
dir_3 ^= dir_1;
dir_1 ^= dir_3;
}
if (bug->type == BUG_NOBBIN)
{
// Для Ноббинов нужно выбрать наилучшее направление по которому свободен путь
if (check_path(dir_1, bug_x_graph, bug_y_graph)) { dir = dir_1; }
else if (check_path(dir_2, bug_x_graph, bug_y_graph)) { dir = dir_2; }
else if (check_path(dir_3, bug_x_graph, bug_y_graph)) { dir = dir_3; }
else if (check_path(dir_4, bug_x_graph, bug_y_graph)) { dir = dir_4; }
}
else