Делаем игру для ТРЦ Redshift

EXAPUNKS — новейшая программная игра от Zachtronics. Читайте журналы, пишите вирусы, взламывайте все подряд. Жизнь не становится лучше. Если вы еще не играли, то прекратите читать это прямо сейчас, идите купить и вернитесь через пару дней, когда освоитесь.

Для тех из вас, кто не заинтересован в игре (что с вами не так?!), но все же хочет дочитать до конца эту статью, вот небольшой контекст. В EXAPUNKS вы программируете EXA. EXA расшифровывается как EXecution Agent — небольшая программа, которая может перемещаться по сети и выходить из нее и выполнять код. Каждый EXA имеет регистры и код, может содержать один файл и может взаимодействовать с другими EXA внутри сети через глобальный регистр. Вы программируете EXA на языке ассемблера с очень ограниченным набором инструкций. EXA можно загружать практически в любую сеть, независимо от того, какое аппаратное обеспечение делает их чрезвычайно универсальными. В этой статье я опишу, как запрограммировать EXA для создания видеоигры для внутриигровой консоли под названием TEC Redshift.

Если вы читаете дальше, есть несколько легких спойлеров.

Хорошо, теперь приступим к взлому. Итак, Гаст дал нам портативную игровую консоль под названием Redshift (очень похожую на Gameboy в этой вселенной) и второй номер своего подпольного компьютерного журнала Trash World News. Мы должны выяснить, как запрограммировать игру для этого. Я хотел сделать что-то простое для своей первой игры Redshift, поэтому решил сделать аркадный шутер в стиле Space Invaders/Galaga. В моей игре используются три EXA: один управляет игроком, один управляет врагами или захватчиками, а третий управляет пулями игрока.

EXA 1 — Игрок

У игрока есть спрайт, он может двигаться в четырех направлениях и стрелять пулями. Чтобы нарисовать спрайт, мы сначала создаем файл, содержащий данные, описывающие, какие пиксели следует включать и выключать. Мы используем инструкцию DATA, за которой следует ряд целых чисел, разделенных пробелами, для создания файла. Каждое целое число представляет собой трехзначное число: разряд сотен показывает, включен или выключен пиксель, разряд десятков представляет позицию x пикселя, а разряд единиц представляет позицию y пикселя. Спрайт имеет размер 10x10 пикселей.

DATA 000 010 020 130 140
DATA 150 160 070 080 090
DATA 001 011 121 131 141
DATA 151 161 171 081 091
DATA 002 012 122 132 142
DATA 152 162 172 082 092
DATA 003 013 023 033 143
DATA 153 063 073 083 093
DATA 004 014 024 034 144
DATA 154 064 074 084 094
DATA 005 015 025 135 145
DATA 155 165 075 085 095
DATA 006 116 126 136 146
DATA 156 166 176 186 096
DATA 107 117 027 037 147
DATA 157 067 077 187 197
DATA 108 018 028 138 048
DATA 058 168 078 088 198
DATA 109 019 029 139 049
DATA 059 169 079 089 199

Затем мы инициализируем выходной регистр столкновения EXA, чтобы другие EXA могли идентифицировать его при столкновении. Мы также размещаем спрайт в центре нижней части экрана Redshift и ищем начало удерживаемого файла EXA.

MARK INIT
COPY 1 CO
COPY 55 GX
COPY 90 GY
SEEK -9999

Затем мы читаем каждое последовательное значение из файла в регистр GP EXA, чтобы нарисовать спрайт. После каждой копии мы проверяем, достигли ли мы конца файла, и если нет, возвращаемся к метке SPRITE.

MARK SPRITE 
COPY F GP 
TEST EOF
FJMP SPRITE

Основной цикл

Как только спрайт нарисован, мы готовы запустить основной цикл. Сначала мы проверяем пользовательский ввод, читая из аппаратных регистров Redshift. Если обнаружен ввод, мы переходим к соответствующей метке, чтобы обработать либо движение, либо стрельбу. Также проверяем на столкновение с врагом. Я опишу, как мы справляемся со столкновением позже. Для движения мы просто увеличиваем или уменьшаем позицию спрайта, записывая в регистр GX или GY. Вертикальное движение довольно бесполезно для этой конкретной игры, но я все же включил его. Чтобы выстрелить пулей, мы копируем позиции x и y спрайта в глобальный регистр M. Другой EXA прислушивается к значениям этого регистра и обрабатывает запуск пули. Я подробно опишу это в одном из следующих разделов. Мы копируем положение спрайта, чтобы EXA, который обрабатывает пулю, знал, откуда стрелять пулей. После обработки всех возможных пользовательских данных, коллизий и обновления позиции мы используем инструкцию WAIT, чтобы приостановить выполнение до следующей синхронизации кадров (30 раз в секунду) и, наконец, перейти к метке INPUT, продолжая игровой цикл.

MARK INPUT
LINK 800
NOTE SHOOTING
SWIZ #PADB 1 X
TEST X = 1
TJMP SHOOT
NOTE COLLISION
TEST CI = 2
TJMP GAMEOVER
NOTE HORIZONTAL
TEST #PADX = -1
TJMP MOVL
TEST #PADX = 1
TJMP MOVR
NOTE VERTICAL
TEST #PADY = -1
TJMP MOVU
TEST #PADY = 1
TJMP MOVD
JUMP SYNC
MARK MOVL
SUBI GX 1 GX
JUMP SYNC
MARK MOVR
ADDI GX 1 GX
JUMP SYNC
MARK MOVU
SUBI GY 1 GY
JUMP SYNC
MARK MOVD
ADDI GY 1 GY
JUMP SYNC
MARK SHOOT
COPY GX M
COPY GY M
JUMP SYNC
MARK SYNC
LINK -1
WAIT
JUMP INPUT

Столкновение

В случае столкновения с захватчиком мы переходим на метку GAMEOVER. Этот код перемещает спрайт игрока за пределы экрана и пишет текст «GAME OVER». Для этого мы повторяем EXA 8 раз, по одному для каждой буквы в «GAME OVER». Инструкция REPL создает копию EXA и переходит к указанной метке в репликанте EXA. Каждый EXA отображает свою букву, а затем зацикливается, ничего не делая, пока пользователь не нажмет кнопку X, чтобы перезапустить игру. Исходный EXA переходит к метке RESET, ожидая, когда пользователь нажмет кнопку X, чтобы перезапустить игру. Когда пользователь нажимает X, EXA записывает значение в глобальный регистр M, чтобы уведомить EXA-захватчика о перезапуске игры.

MARK GAMEOVER
COPY 35 GY
REPL G
REPL A
REPL M
REPL E1
REPL O
REPL V
REPL E2 
REPL R
COPY -99 GY
JUMP RESET
MARK G
COPY 10 GX
COPY 307 GP
JUMP END
MARK A
COPY 20 GX
COPY 301 GP
JUMP END
MARK M
COPY 30 GX
COPY 313 GP
JUMP END
MARK E1
COPY 40 GX
COPY 305 GP
JUMP END
MARK O
COPY 60 GX
COPY 327 GP
JUMP END
MARK V
COPY 70 GX
COPY 322 GP 
JUMP END
MARK E2
COPY 80 GX
COPY 305 GP
JUMP END
MARK R
COPY 90 GX
COPY 318 GP
MARK END
TEST #PADB = 0
TJMP END
HALT
MARK RESET
TEST #PADB = 0
FJMP NEWGAME
JUMP RESET
MARK NEWGAME
COPY 1 M
LINK -1
JUMP INIT

Вот и все для игрока EXA. Вот полный код, прежде чем мы перейдем к коду для захватчиков.

DATA 000 010 020 130 140
DATA 150 160 070 080 090
DATA 001 011 121 131 141
DATA 151 161 171 081 091
DATA 002 012 122 132 142
DATA 152 162 172 082 092
DATA 003 013 023 033 143
DATA 153 063 073 083 093
DATA 004 014 024 034 144
DATA 154 064 074 084 094
DATA 005 015 025 135 145
DATA 155 165 075 085 095
DATA 006 116 126 136 146
DATA 156 166 176 186 096
DATA 107 117 027 037 147
DATA 157 067 077 187 197
DATA 108 018 028 138 048
DATA 058 168 078 088 198
DATA 109 019 029 139 049
DATA 059 169 079 089 199
MARK INIT
COPY 1 CO
COPY 55 GX
COPY 90 GY
SEEK -9999
MARK SPRITE 
COPY F GP 
TEST EOF
FJMP SPRITE
MARK INPUT
LINK 800
NOTE SHOOT
SWIZ #PADB 1 X
TEST X = 1
TJMP SHOOT
NOTE COLLISION
TEST CI = 2
TJMP GAMEOVER
NOTE HORIZONTAL
TEST #PADX = -1
TJMP MOVL
TEST #PADX = 1
TJMP MOVR
NOTE VERTICAL
TEST #PADY = -1
TJMP MOVU
TEST #PADY = 1
TJMP MOVD
JUMP SYNC
MARK MOVL
SUBI GX 1 GX
JUMP SYNC
MARK MOVR
ADDI GX 1 GX
JUMP SYNC
MARK MOVU
SUBI GY 1 GY
JUMP SYNC
MARK MOVD
ADDI GY 1 GY
JUMP SYNC
MARK SHOOT
COPY GX M
COPY GY M
JUMP SYNC
MARK SYNC
LINK -1
WAIT
JUMP INPUT
MARK GAMEOVER
COPY 35 GY
REPL G
REPL A
REPL M
REPL E1
REPL O
REPL V
REPL E2 
REPL R
COPY -99 GY
JUMP RESET
MARK G
COPY 10 GX
COPY 307 GP
JUMP END
MARK A
COPY 20 GX
COPY 301 GP
JUMP END
MARK M
COPY 30 GX
COPY 313 GP
JUMP END
MARK E1
COPY 40 GX
COPY 305 GP
JUMP END
MARK O
COPY 60 GX
COPY 327 GP
JUMP END
MARK V
COPY 70 GX
COPY 322 GP 
JUMP END
MARK E2
COPY 80 GX
COPY 305 GP
JUMP END
MARK R
COPY 90 GX
COPY 318 GP
MARK END
TEST #PADB = 0
TJMP END
HALT
MARK RESET
TEST #PADB = 0
FJMP NEWGAME
JUMP RESET
MARK NEWGAME
COPY 1 M
LINK -1
JUMP INIT

EXA 2 — Захватчики

Захватчики имеют спрайт, похожий на НЛО, и бесконечно появляются в верхней части экрана, ныряя к игроку. Как и игрок EXA, вражеский EXA сначала создает файл, который определяет спрайт, инициализирует регистр вывода коллизий и циклически проходит через файл, записывая значения в регистр GP, чтобы нарисовать спрайт.

DATA 000 010 020 130 140
DATA 150 160 070 080 090
DATA 001 011 121 131 141
DATA 151 161 171 081 091
DATA 002 012 122 132 142
DATA 152 162 172 082 092
DATA 003 013 023 133 143
DATA 153 163 073 083 093
NOTE INIT
COPY 2 CO
LINK 800
MARK SPRITE 
COPY F GP 
TEST EOF
FJMP SPRITE

Основной цикл

Как только спрайт нарисован, мы начинаем вторжение. На самом деле есть два цикла, которые контролируют поведение захватчика. Внешний цикл порождает нового захватчика, а внутренний цикл перемещает захватчика и проверяет наличие столкновений. Во внешнем цикле мы используем инструкцию RAND для генерации случайного значения от 0 до 110 (экран Redshift имеет размер 120x100 пикселей) и записываем это значение в регистр GX, чтобы создать захватчика в случайной позиции x в верхней части экрана. . Во внутреннем цикле мы перемещаем захватчика вниз по экрану, увеличивая значение регистра GY. Затем мы тестируем столкновения с игроком или с пулей. Если происходит столкновение с игроком, мы переходим к метке KILLPLAYER, чтобы обработать этот случай. Если происходит столкновение с пулей, мы просто переходим к метке INVADE, чтобы создать нового захватчика. Наконец, мы проверяем, находится ли захватчик за кадром. Если это так, мы переходим к метке INVADE, чтобы создать нового захватчика, в противном случае мы переходим к метке MOVE, чтобы продолжить внутренний цикл.

MARK INVADE
RAND 0 110 GX
COPY 0 GY
MARK MOVE
ADDI GY 1 GY
NOTE TEST COLLISION
TEST CI = 1
TJMP KILLPLAYER
TEST CI = 3
TJMP INVADE
NOTE TEST OFFSCREEN
TEST GY > 99
TJMP INVADE
WAIT
JUMP MOVE

Столкновение игроков

В случае столкновения с игроком мы скрываем спрайт захватчика, записывая закадровое значение в регистр GX, а затем слушаем в глобальном регистре M сообщение от игрока EXA, сигнализирующее о перезапуске игры. Мы используем оператор TEST MRD, чтобы проверить, может ли EXA читать из глобального регистра M без паузы/блокировки. Если в регистре M есть значение, мы отбрасываем это значение, чтобы отправитель мог продолжить выполнение своего кода и перейти к инструкции INVADE, чтобы перезапустить основной цикл.

MARK KILLPLAYER
NOOP
NOOP
NOOP
NOOP
NOOP
COPY -77 GX
TEST MRD
TJMP RESET
JUMP KILLPLAYER
MARK RESET
VOID M 
JUMP INVADE

*ПРИМЕЧАНИЕ. Инструкции NOOP предназначены для работы в условиях гонки. Когда происходит столкновение с игроком и захватчиком, захватчик получает сообщение первым. Инструкции NOOP приостанавливают захватчика, чтобы у игрока было время наверстать упущенное и получить сообщение о столкновении до того, как захватчик окажется за пределами экрана. Это может быть немного хакерски, но здесь мы EXAPUNKING, хакерство по определению.

EXA 3 — Пули

EXA, управляющий пулями, следует той же схеме инициализации, что и два других EXA. Он рисует квадратный спрайт 2x2 и определяет выходное значение столкновения как 3.

DATA 140 150 141 151
COPY 3 CO
MARK SPRITE 
COPY F GP 
TEST EOF
FJMP SPRITE

Как и у захватчика EXA, у пули EXA также есть два цикла: внешний цикл ожидает сообщения от игрока о выстреле пули, а внутренний цикл перемещает пулю и проверяет на столкновение с захватчиком. Мы также проверяем, пытается ли игрок выстрелить более одной пули за раз, и в этом случае мы просто игнорируем сообщение о выстреле, отбрасывая значение из регистра M и возвращаясь к метке MOVE.

MARK WAIT4SHOOT
COPY -99 GX
COPY -99 GY
COPY M GX
COPY M GY
MARK MOVE
NOTE IGNORE MORE THAN 1
TEST MRD
TJMP IGNORE
SUBI GY 1 GY
NOTE TEST COLLISION
TEST CI = 2
TJMP WAIT4SHOOT
NOTE TEST OFFSCREEN
TEST GY < 1 
TJMP WAIT4SHOOT
WAIT 
JUMP MOVE
MARK IGNORE
VOID M
JUMP MOVE

GG

И это, друзья мои, то, как вы правильно EXAPUNK.

Спасибо за чтение! Надеюсь, вам было интересно или полезно. Если вы играете в EXAPUNKS, найдите меня в Steam (bitwitch), и мы сможем сразиться в хакерской битве!