Історія комп'ютерів та ігор
Історія комп'ютерів та ігор
Конкурс є міжнародним, але з 2019 року я досі залишаюсь єдиним учасником з України. Я зайняв 1-ше (SCHAU-256) та 3-тє (PUR-120) місця у 2021-му, та 2-ге місце (PUR-256) у 2022-му, продемонструвавши талант і майстерність у цій вельми специфічній галузі комп'ютерного програмування. Тепер я хочу поділитися деякими техніками створення 10-рядкових ігор. Я щиро сподіваюся, що ця стаття надихне читача на написання вашої першої десятирядкової гри на BASIC та участь у наступнорічному конкурсі.
Можливо ви великий фанат певного ретрокомп'ютера, скажімо Sinclair ZX81. Але це не означає, що він є хорошим вибором для вашої 10-рядкової гри, якщо тільки метою не є участь без перемоги. Наприклад, Microsoft BASIC у Mattel Aquarius дозволяє лише 72 символи у рядку. Це один втрачений рядок у PUR-80, бо 72×10=80×9. Було багато надходжень для цієї платформи, але жодне не виграло зі зрозумілих причин. Деякі діалекти, зокрема Бейсик Вільнюс у БК0010-01, забороняють декілька операторів у одному рядку, роблячи задачу програміста вкрай складною.
Деякі платформи мають функції, корисні для створення ігор на BASIC. Незважаючи на згадане обмеження довжини рядка, Mattel Aquarius пропонує набір символів, що містить ігрових персонажів, стіни, вибухи тощо. Меншою мірою це стосується набору символів Commodore PETSCII та деяких інших платформ. Таким чином, можна виводити таких персонажів (PRINT) або заносити їх безпосередньо у відеопам’ять (POKE), створюючи привабливу ігрову графіку.
Іноді платформа не визначає псевдографічні символи, але користувач може перепрограмувати окремі гліфи. Це додає ще більше гнучкості, проте не безкоштовно — визначення нових символів (часто в операторах DATA) займає місце у програмі. ZX Spectrum пропонує UDG (User Defined Graphics) для цієї мети. Є платформи з багатими можливостями, такі як MSX. Ви можете визначити апаратні спрайти у спосіб подібний до UDG символів, але позиціонувати їх з піксельною точністю (PUT SPRITE), і пересувати заданням нових координат без жодної потреби у відновленні фону в попередній позиції.
Останнім, але не менш важливим, є швидкодія обраної платформи. Деякі комп'ютери та діалекти BASIC є повільнішими за інші, і певні алгоритми просто не підходять для реалізації на ретроплатформі.
Нижче ми називатимемо MS BASIC інтерпретатори Microsoft (включно з MSX і PC), а інтерпретатори Sinclair — ZX BASIC. Деякі приклади наведено для обох, оскільки підходи Білла Гейтса і Стіва Вікерса радикально відрізнялись.
Деякі платформи примусово диктують категорію — якщо рядок BASIC фізично не може перевищувати 80 символів, немає розумних причин подавати гру на PUR-256. Інакше це ваше власне рішення. Деякі роботи минулих років були більш-менш спрощеними версіями класичних ігор, таких як Arkanoid, Tetris або Soco-Ban. Тож якщо у вас немає кращої ідеї, просто спробуйте переробити щось із золотої ери відеоігор — але зауважте, що рецензенти завжди порівнюватимуть вашу реалізацію з оригіналом.
Ви можете переосмислити існуючий сюжет абсолютно по-новому. Моя нещодавня гра NLAW була натхненна… Tapper (1983). Замість того, щоб обслуговувати п’яниць пивними кухлями, ви "годуєте" протитанковими ракетами кацапські танки, що наближаються.
Маючи велику фантазію, можна спробувати вигадати власний сюжет. Але не забувайте про головне правило: ваша програма має бути придатною для гри.
Завжди формулюйте максимальні, проміжні та мінімальні цілі. Наприклад, здатність ворожих танків стріляти не була реалізована у NLAW, оскільки для неї не залишилося місця в коді. Але це було необов'язково за задумом (як максимальна мета). З іншого боку, гра була випущена з досить складною графікою. Простіша графіка потребує менше місця, і це був найгірший сценарій.
Деякі діалекти, такі як Integer BASIC Стіва Возняка для Apple II, оперують виключно цілими числами. Інші, як то ранні інтерпретатори Microsoft, завжди працюють з плаваючою комою.
MSX BASIC та подібні діалекти пропонують як цілі, так і числа з плаваючою комою. Додавання суфікса % до імені змінної робить її цілочисельною, і код, що оперує цілочисельними змінними, працюватиме набагато швидше, що дуже корисно для програмування ігор.
Але в програмі з 10 рядків кожен символ має значення, і % після кожної змінної наближає нас до межі. На щастя, є рішення:
DEFINT A-Z
оголошує всі змінні цілочисельними. Ви можете оголосити підмножину, наприклад імена, що починаються з C, I, J та K:
DEFINT C,I-K
Якщо є багато рядкових змінних, той самий підхід можна застосувати, щоб усунути зайві суфікси $:
DEFSTR A-D,S
оголошує рядкові змінні, імена яких починаються на A, B, C, D і S.
Усуньте пробіли, де це можливо. Для Microsoft BASIC немає різниці між
1 FOR I=1 TO 10
та
1FORI=1TO10
У деяких діалектах ви також можете опускати нулі в операторах DATA:
DATA 1,0,2,0,0,3
дорівнює
DATA1,,2,,,3
Нумерація рядків від 0 дає вам один додатковий символ, оскільки останній рядок буде 9 замість 10. Іноді цей дешевий трюк врятує ваш проєкт.
До речі, оператори DATA дозволені скрізь. Якщо є заповнені рядки, які не досягають ліміту категорії, ви можете помістити DATA туди:
1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:DATA1,2,3,4,5
2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:DATA6,7
3 xxxxxxxxxxxxxxxxxxxxxxxxxxxx:DATA8,9,Hello,World
де xx…xx є деяким значущим кодом на BASIC.
Крім того, DATA/READ може усунути присвоєння, ще більше стиснувши ваш код на ZX BASIC:
1 LET a=16384: LET x=128: LET y=96: LET u=32: LET v=0: LET w=80
можна записати як
1 READ a,x,y,u,v,w: DATA 16384,128,96,32,0,80
Найбільш типова проблема в грі - керувати якимось об'єктом за допомогою клавіатури. Розглянемо один вимір, де x — горизонтальна координата, а дозволений діапазон — від 0 до 199. Наш BASIC не підтримує конструкцію ELSE. Клавіша [o] використовується для переміщення вліво, а [p] — вправо:
0 LET x=99
1 REM Draw sprite
2 LET k$=INKEY$ : IF k$="" THEN GOTO 2
3 IF k$="o" AND x>0 THEN LET x=x-1
4 IF k$="p" AND x<199 THEN LET x=x+1
5 GOTO 1
Нам безумовно потрібен більш компактний спосіб виразити дії, зроблені в рядках 3 і 4.
У ZX BASIC результатом логічного виразу, наприклад k$="o" AND x>0, є або 1 (істина), або 0 (хиба). Отже, ми можемо вилучити рядки 4-5 і переписати рядок 3 як:
3 LET x=x-(k$="o" AND x>0)+(k$="p" AND x<199) : GOTO 1
Як це працює?
[o] і може рухатися вліво: x = x-1+0 = x-1
[p] і може рухатися вправо: x = x-0+1 = x+1
жодне: x = x-0+0 = x
У MS BASIC результатом логічного виразу є -1 (істина) або 0 (хиба), тому нам просто потрібно змінити знаки:
3 x=x+(k$="o" AND x>0)-(k$="p" AND x<199) : GOTO 1
Щоб перемістити об’єкти з заданим кроком (наприклад, 8), просто помножте відповідний логічний вираз:
3 x=x+8*(k$="o" AND x>0)-8*(k$="p" AND x<199) : GOTO 1
Тут ми поговоримо про кон'юнкцію і диз'юнкцію. Незважаючи на назву розділу, результати цих операцій не є булевими і відрізняються між діалектами. Розглянемо тестову програму:
1 PRINT "1. "; 2>1 AND 13
2 PRINT "2. "; 2<1 AND 13
3 PRINT "3. "; 2>1 OR 13
4 PRINT "4. "; 2<1 OR 13
5 PRINT "5. "; 13 AND 2>1
6 PRINT "6. "; 13 AND 2<1
7 PRINT "7. "; 13 OR 2>1
8 PRINT "8. "; 13 OR 2<1
яка виведе такі результати:
+----+---------+
|Test| BASIC |
| +----+----+
| No.| MS | ZX |
+----+----+----+
| 1 | 13 | 1 |
| 2 | 0 | 0 |
| 3 | -1 | 1 |
| 4 | 13 | 1 |
| 5 | 13 | 13 |
| 6 | 0 | 0 |
| 7 | -1 | 1 |
| 8 | 13 | 13 |
+----+----+----+
Найкорисніший результат показаний у тестах 5 і 6. В обох діалектах,
IF e THEN LET x=v ELSE LET x=0
можна записати як
LET x=v AND e
де e — логічний вираз, а v — будь-яке ціле число. Якщо v є з плаваючою комою, трюк також працює для ZX BASIC, тоді як MS BASIC залишає лише цілу частину (оскільки логічні операції там є, по суті, побітовими).
Інші вирази залежать від платформи і менш корисні, але все ще застосовні для специфічних завдань, заощаджуючи трохи місця в програмі.
Недоліком більшості інтерпретаторів BASIC є відсутність оператора ENDIF. Конструкції THEN і ELSE (за наявності) діють до кінця рядка. Це значно ускладнює вираження ваших ідей у десяти рядках.
0 REM This is just a dream
1 INPUT x: IF x > 5 THEN PRINT "Here": ENDIF: PRINT "There"
Деякі прийоми для подолання цього обмеження вже були представлені вище. Але є спосіб написати явний IF ... ENDIF: просто використовуйте оператор FOR ... NEXT.
0 REM This is reality (ZX BASIC)
1 INPUT x: FOR i=1 TO x>5: PRINT "Here": NEXT i: PRINT "There"
Як відомо, x>5 дає 1 для істинної умови, тому цикл буде виконано один раз. Для хибної умови він перетворюється на FOR i=1 TO 0, тому тіло циклу буде пропущено.
Для MS BASIC x>5 дає -1 для істинної умови, тому ми можемо записати це як:
0 REM Is this still just a dream? (MS BASIC)
1 INPUT x: FOR i=x>5 TO -1: PRINT "Here": NEXT: PRINT "There"
На жаль, Білл Гейтс вирішив виконати тіло циклу, перш ніж перевірити умову його завершення, і трюк не працює на ранніх інтерпретаторах Microsoft, включно з MSX BASIC. Однак він працює на IBM PC.
Цікавою особливістю ZX BASIC є те, що номер рядка можна взяти зі змінної або виразу:
GO TO 7*(k$="")
є чинним оператором. Він переходить до рядка 7, якщо змінна k$ є порожньою, і до рядка 0 в іншому випадку. Зверніть увагу, що номер рядка має бути цілим. Такий самий підхід працює і з GO SUB.
У інших діалектах схожий ефект досягається за допомогою операторів ON ... GOTO та ON ... GOSUB.
Оператор DEF FN, присутній у більшості діалектів, можна використовувати для стиснення коду. Якщо один і той самий параметризований вираз використовується багато разів у вашій грі на BASIC, визначте його як функцію. Наприклад, навігаційний код клавіатури (MS BASIC) із уявної гри для двох гравців
1k$=INKEY$
2u=u+(k$="a"ANDu>28)-(k$="d"ANDu<228):v=v+(k$="w"ANDv>28)-(k$="s"ANDv<228)
3x=x+(k$="j"ANDx>28)-(k$="l"ANDx<228):y=y+(k$="i"ANDy>28)-(k$="k"ANDy<228)
можна переписати як
0DEFFNK(P,A$,B$)=P+(k$=A$ANDP>28)-(k$=B$ANDP<228)
1k$=INKEY$:u=FNK(u,"a","d"):v=FNK(v,"w","s"):x=FNK(x,"j","l"):y=FNK(y,"i","k")
зекономивши 80 символів!
Нас оточує симетрія. Люди, тварини, рослини, майже все на Землі та поза її межами є симетричним. Це рівною мірою стосується і багатьох штучних об’єктів, таких як автомобілі чи літаки.
Дзеркальне відображення — це різновид симетрії. Астронавт, повернутий ліворуч, є дзеркальною версією самого себе, повернутого праворуч. Гоночний автомобіль, що рухається по екрану (вперед), можна повернути на 180 градусів, щоб транспорт рухався в протилежному напрямку.
Ви можете використовувати цю особливість для оптимізації розміру гри. Розглянемо визначення спрайту кулі в MSX BASIC:
0 SCREEN2:FORI=1TO8:READX:A$=A$+CHR$(X):NEXT:SPRITE$(0)=A$
1 DATA&B00111100
2 DATA&B01111110
3 DATA&B11111111
4 DATA&B11111111
5 DATA&B11111111
6 DATA&B11111111
7 DATA&B01111110
8 DATA&B00111100
Використовуючи вертикальну симетрію, він перетворюється на:
0 SCREEN2:FORI=1TO4:READX:A$=CHR$(X)+A$+CHR$(X):NEXT:SPRITE$(0)=A$
1 DATA&B11111111
2 DATA&B11111111
3 DATA&B01111110
4 DATA&B00111100
Подібним чином ми можемо створити реактивний винищувач, спрямований вгору і вниз за єдиним шаблоном:
0 FORI=1TO8:READX:A$=A$+CHR$(X):B$=CHR$(X)+B$:NEXT:SPRITE$(0)=A$:SPRITE$(1)=B$
1 DATA&B00011000
2 DATA&B00011000
3 DATA&B00111100
4 DATA&B01111110
5 DATA&B11111111
6 DATA&B00011000
7 DATA&B00111100
8 DATA&B01111110
Нотація &B в операторах DATA використовується для ілюстрації, десяткові числа дають більш компактне представлення.
Як і в серіалі "Кремнієва долина", алгоритми стиснення можуть чарівним чином перетворити щось велике у маленьке. Єдина проблема полягає в тому, що декомпресор займає програмний простір, тому потрібен ідеальний баланс між складністю декомпресора та ступенем стиснення. Універсального рецепту не існує, але алгоритми RLE часто дають хороші результати.
Розглянемо спрайт танкової гармати з моєї гри NLAW:
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
255 ■■■■■■■■■■■□■■■■ 239
255 ■■■■■■■■■■■□■■■■ 239
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
Він видається ідеальним для RLE-компресії: 4×0, 2×255, 14×0, 2×239, 10×0. Ми будемо кодувати дані як:
256 * (лічильник - 1) + значення
отримавши DATA768,511,3328,495,2304. Всього 25 символів, щоб визначити спрайт 32×32.
Зрозуміло, що багато спрайтів відрізняються лише деталями, наприклад:
□□□□□□□■■■□□□□□□ □□□□□□□■■■□□□□□□
□□□□□□■■■■■□□□□□ □□□□□□■■■■■□□□□□
□□□□□□■□□□■□□□□□ □□□□□□■□□□■□□□□□
□□□□□□■□□□■□■□□□ □□□□□□■□□□■□■□□□
□□□□□■■■□■■■□□□□ □□□□□■■■□■■■□□□□
□□□□■■■■■■■■■□□□ □□□□■■■■■■■■■□□□
□□□■■■■■■■■■■■□□ □□□■■■■■■■■■■■□□
□□□■■□■■■■■■■■□□ □□□■■■■■■■■□■■□□
□□□□□□■■■■■□■■□□ □□□■■□■■■■■□□□□□
□□□□□□■□■□■□■■□□ □□□■■□■□■□■□□□□□
□□□□□□■■■■■■□□□□ □□□□□■■■■■■□□□□□
□□□□□■■■■■■■□□□□ □□□□□■■■■■■■□□□□
□□□□□■■■□■■■■□□□ □□□□■■■■□■■■□□□□
□□□□□■■■□□■■■□□□ □□□□■■■□□■■■□□□□
□□□□■■■□□□□□□□□□ □□□□□□□□□□■■■□□□
□□□□■■■□□□□□□□□□ □□□□□□□□□□■■■□□□
Якщо деякі байти збігаються з відповідними у попередньому спрайті, ми кодуємо дані як:
-(лічильник - 1)
що дає -6 для фрагмента 8×7 пікселів у верхньому лівому куті.
Завдяки вищеописаному підходу я зберіг близько двох рядків коду у відзначеній нагородами грі NLAW.
Для визначення ковбойської графіки в моїй грі на тему Дикого Заходу була використана зовсім інша техніка. Зображення описувалося лініями сканування, де два значення вказують початкові та кінцеві координати. Від’ємний початок запобігає переходу до наступної лінії сканування (використовується абсолютне значення). Нульовий кінець - це ознака зупинки.
Важко втиснути багато рівнів у десять рядків, якщо кожен рівень представляє інший лабіринт або місцевість. Часто можна запакувати рівні у бітові мапи, подібно до спрайтів (наприклад, повітря=&B00, вода=&B01, сходи=&B10, цегла=&B11). Також застосовуються методи стиснення, описані вище.
Зовсім інший підхід — процедурна генерація. Він рідко використовується з BASIC, оскільки такі алгоритми, як правило, повільні. Однак у моїй грі Escape! для Mattel Aquarius була реалізована процедурна генерація 5 рівнів.
Написання десятирядкової гри на BASIC, як-от гра в шахи або розгадування головоломок, є цікавою вправою для вашого мозку. У світі, де програмний код і дані вимірюються в мегабайтах і гігабайтах, це спосіб показати, як багато можна досягти малими засобами. Деякі роботи, подані минулими роками на конкурс, були дійсно вражаючими. У цій статті описано лише деякі хитрощі програмування, і ви за бажання зможете відкрити щось нове. То чому б не оновити свої знання BASIC (або не вивчити цю просту, але потужну мову, якщо ви новачок), а потім спробувати висловити свою ідею всього в 10 рядках?
Хмара тегів
Прямий переказ коштів на PayPal акаунт: paybox@it8bit.club або info@leocraft.com на моє ім'я Dmitry Cherepanov
Або підпишіться на Patreon та отримайте щось більше.
Цей сайт використовує cookies, як власні, так і від третіх осіб. Використовуючи цей сайт, ви даєте згоду на використання cookies
Я згоден (на)