Поради щодо гольфу в QBasic


13

Які загальні поради щодо гольфу в QBasic? Я шукаю ідеї, які можна застосувати до коду проблем із гольфом взагалі, які принаймні дещо специфічні для QBasic (наприклад, "видалити коментарі" - це не відповідь).

Поради щодо емулятора QB64 також вітаються. Він має деякі додаткові функції, яких немає в Microsoft QBasic.


Мені цікаво ваша мотивація. Я не використовував QBASIC з мого класу з програмування 10 класу. Дивно, як я врятував прямо на 1,44 дискети без будь-якої форми контролю версій і (як правило) уникнув катастрофічних збоїв.
Андрій Бреза

5
@ AndrewBrēza Мотивація? Те саме, що моя мотивація займатися гольфом будь-якою мовою: для розваги! Мені подобається писати невеликі програми на QBasic (хоча я не хотів би використовувати її для нічого серйозного). Також є додатковий бонус, що вбудований звук та графіка (як текст, так і піксель), чого моя краща мова "справжня", Python, не має.
DLosc

Набагато простіше писати графічні ігри в QBasic, ніж в python.
Ануш

Якщо хтось хоче спробувати графічні додатки QBasic безпосередньо у браузері, він може скористатися цим: github.com/nfriend/origins-host
mbomb007

Відповіді:


10

Знайте свої петельні конструкції

QBasic має кілька конструкцій циклів: FOR ... NEXT, WHILE ... WENDі DO ... LOOP. Ви також можете використовувати GOTOабо (в деяких ситуаціях) RUNдля циклу.

  • FOR ... NEXTдосить добре в тому, що робить. На відміну від Python, він майже завжди коротший, ніж еквівалент WHILEабо GOTOцикл, навіть коли він стає трохи більш фантазійним:

    FOR i=1TO 19STEP 2:?i:NEXT
    i=1:WHILE i<20:?i:i=i+2:WEND
    i=1:9?i:i=i+2:IF i<20GOTO 9
    

    Зауважте, що вам не потрібно повторювати ім'я змінної після NEXT, і ви можете усунути пробіл між номерами та більшості наступних ключових слів.

  • WHILE ... WENDдобре, коли у вас є цикл, який може знадобитися виконати 0 разів. Але якщо ви знаєте, що цикл виконається хоча б один раз, GOTOможе бути на один байт коротше:

    WHILE n>1:n=n\2:WEND
    1n=n\2:IF n>1GOTO 1
    
  • Я використовую лише DO ... LOOPдля нескінченних циклів (за винятком випадків, коли RUNможна використовувати їх замість). Хоча це коштує стільки ж символів, що і безумовне GOTO, читати трохи інтуїтивніше. (Зверніть увагу, що "нескінченний цикл" може включати петлі, які ви вириваєте з використання a GOTO.) DO WHILE/ DO UNTIL/ LOOP WHILE/ LOOP UNTILСинтаксис занадто багатослівний; вам краще використовувати WHILEабо, GOTOяк це доречно.
  • GOTOяк зазначено вище, це найкоротший загальний спосіб написання циклу do / while. Використовуйте одноцифрові номери рядків замість міток. Зауважте, що коли a GOTO- це єдине, що є THENчастиною IFоператора, наявні два однаково коротких синтаксиси ярликів:

    IF x>y GOTO 1
    IF x>y THEN 1
    

    GOTOтакож можна використовувати для створення більш складних потоків управління . Найсеї називають це "кодом спагетті", але це код гольфу: нечитабельність - це майже чеснота! GOTOгордість!

  • RUNкорисно, коли вам потрібно перейти на певне місце в програмі, і вам не потрібно зберігати жодну зі змінних значень. RUNсам по собі перезапустить програму зверху; з міткою або номером рядка, вона буде перезапущена в цьому рядку. В основному я використовував це для створення нескінченних циклів без громадянства .

5

Використовуйте ярлики для PRINTтаREM

Ви можете використовувати ?замість PRINT, а 'замість REM(коментар).

'може бути корисним також при поліглоті з мовами, які підтримують 'як частину синтаксису char або string.


5

Тестування на роздільність

У програмах, які вимагають перевірити, чи ділиться одне ціле число на інше, очевидним способом є використання MOD:

x MOD 3=0

Але коротший спосіб - використовувати ціле ділення:

x\3=x/3

Тобто, xint-div 3дорівнює xfloat-div 3.

Зауважте, що обидва ці підходи повернуться 0як -1для фальси, так і для трити, тому вам, можливо, доведеться заперечувати результат або віднімати його, а не додавати.


Якщо вам потрібно умова протилежної (тобто xце НЕ ділиться на 3), очевидний підхід полягає у використанні не-одно оператор:

x\3<>x/3

Але якщо xгарантовано буде негативним, ми можемо зберегти байт. Ціле ділення обрізає результат, тому він завжди буде меншим або рівним поплавковому поділу. Тому ми можемо записати умову як:

x\3<x/3

Так само, якщо xгарантовано буде негативним, усічення збільшує результат, і ми можемо писати x\3>x/3. Якщо ви не знаєте ознаки x, вам доведеться дотримуватися <>.


5

Зловживання сканером

Як і в багатьох мовах, важливо знати, які символи можна, а які неможливо видалити.

  • Будь-який пробіл поруч із символом можна видалити: IF""=a$THEN?0
  • Простір зазвичай може бути видалено між цифрою і буквою , що сталася в такому порядку : FOR i=1TO 10STEP 2. Існують деякі відмінності між QBasic 1.1 (доступний на archive.org ) та QB64 :
    • QBasic 1.1 дозволяє видалити пробіл між будь-якою цифрою та наступною літерою. Крім того, у друкованих висловлюваннях він виводить крапку з комою між послідовними значеннями: ?123xстає PRINT 123; x. Винятки з вищезазначених складають такі послідовності, як 1e2і 1d+3, які трактуються як наукові позначення та розширюються до 100!та 1000#(одно- та подвійна точність відповідно).
    • Qb64 , як правило , та ж, але цифри не можуть слідувати d, eабо fвзагалі , якщо вони не є частиною добре сформованої наукової нотації литерала. (Наприклад, ви не можете опустити пробіл після номера рядка в 1 FORабо 9 END, як можна в належному QBasic.) Він виводить крапки з комою в операторах друку, якщо один з виразів є рядком: ?123"abc"працює, але не є ?TAB(5)123або ?123x.
  • Говорячи про крапки з комою, QBasic 1.1 додає крапку з комою до PRINTзаяви, яка закінчується викликом до TABабо SPC. (QB64 ні.)
  • 0можна опустити перед або після десяткової крапки ( .1або 1.), але не обидва ( .).
  • ENDIFеквівалентно END IF.
  • Закінчувальна подвійна лапка рядка може бути пропущена в кінці рядка.

endifнасправді працює в QB64, дивіться цю відповідь
wastl

@wastl Так і є. Коли я вперше тестував його в QB64, я використовував старішу версію, в якій це була помилка синтаксису. Дякуємо за згадування!
DLosc

4

Поєднайте Nextзаяви

Next:Next:Next

Може скорочуватися до

Next k,j,i

де ітератори для Forпетель i, jі k- в такому порядку.

Наприклад нижче (69 байт)

Input n,m,o
For i=0To n
For j=0To m
For k=0To o
?i;j;k
Next
Next
Next

Може скоротитися до 65 байт

Input n,m,o
For i=0To n
For j=0To m
For k=0To o
?i;j;k
Next k,j,i

Що ж стосується того, як це впливає на форматування та відступ, я думаю, що найкращим підходом до цього є залишити наступне твердження із зовнішнім самим для висловлювання. Напр.

Input n,m,o
For i=0To n
    For j=0To m
        For k=0To o
            ?i;j;k
Next k,j,i

4

Знайте свої способи введення

QBasic має кілька способів отримати для користувача введення з клавіатури: INPUT, LINE INPUT, INPUT$, і INKEY$.

  • INPUT- це ваш стандартний багатоцільовий вхідний вислів. Програма зупиняє те, що вона робить, виводить курсор і дає можливість користувачеві ввести деякий вхід, який закінчується Enter. INPUTможе читати числа або рядки, і він може читати кілька значень, розділених комами. Ви можете вказати рядок як підказку, ви можете перейти із запитом знака запитання за замовчуванням, і ви навіть можете (при цьому я це сьогодні дізнався сьогодні) загалом придушити. Деякі приклади викликів:
    • INPUT x$,y
      Використовує ? запит за замовчуванням і читає рядок і число, розділені комами.
    • INPUT"Name";n$
      Підказує Name? і читає рядок.
    • INPUT"x=",x
      Підказує x=(без знака питання! Відмітьте коду в синтаксисі) і читає число.
    • INPUT;"",s$
      Пригнічує підказку (використовуючи вищевказаний синтаксис кома з порожнім рядком запиту), читає рядок і не переходить до наступного рядка, коли користувач натискає клавішу (це те, що INPUTробить точка з комою після ). Наприклад, якщо ви PRINT s$відразу після цього буде виглядати ваш екран User_inputUser_input.
  • Одним недоліком INPUTє те, що ви не можете прочитати рядок із комою, оскільки він INPUTвикористовує кому як роздільник поля. Щоб прочитати один рядок довільних (друкованих ASCII) символів, використовуйте LINE INPUT. У нього є ті самі параметри синтаксису INPUT, що інакше, за винятком однієї змінної, яка повинна бути рядковою змінною. Інша відмінність полягає в тому, LINE INPUTщо не відображається запит за замовчуванням; якщо ви хочете його, вам доведеться чітко вказати його.
  • INPUT$(n)не відображає запит або курсор, але просто чекає, поки користувач введе nсимволи, а потім поверне рядок, що містить ці символи. На відміну від INPUTабо LINE INPUT, користувачеві не потрібно Enterпотім натискати , і насправді він Enterможе бути одним із символів (він надасть ASCII символ 13, відомий як C-подібним мовам \r).

    Найчастіше це корисно, як INPUT$(1)правило, в циклі. INPUT$добре в інтерактивних програмах, де окремі натискання клавіш роблять речі . На жаль, він працює лише з ключами, які мають коди ASCII; сюди входять такі речі, як Escі Backspace, але не клавіші зі стрілками, Insertта Deleteта інші.

  • Це місце, де INKEY$надходить. Він схожий на INPUT$(1)те, що він повертає результати одного натискання клавіші 1 , але відрізняються цим:

    • INKEY$ не бере аргументів.
    • Хоча INPUT$(n)зупиняє виконання, поки користувач не введе nсимволи, INKEY$виконання не зупиняє. Якщо користувач в даний час натискає клавішу, INKEY$повертає рядок, що представляє цю клавішу; якщо ні, то повертається "". Це означає, що якщо ви хочете скористатися INKEY$для отримання наступного натискання клавіші, вам потрібно загорнути його в цикл, який чекає на зайнятість : 2

      k$=""
      WHILE""=k$
      k$=INKEY$
      WEND
      
    • Обидва INPUT$і INKEY$повертають символи ASCII для ключів, що відповідають символам ASCII (включаючи символи управління, такі як втеча, вкладка та зворотний простір). Однак INKEY$може також обробляти деякі клавіші, які не мають ASCII-кодів. Для них (каже файл довідки) "INKEY $ повертає 2-байтну рядок, що складається з нульового символу (ASCII 0) та коду сканування клавіатури."

      Ясна, як грязь? Ось кілька прикладів. Якщо ви використовуєте INKEY$петлю вище для зйомки клавіші лівої клавіші зі стрілкою, вона k$буде містити рядок "␀K"(із Kпредставленим кодом сканування 75). Для стрілки праворуч це "␀M"(77). Сторінка вниз "␀Q"(81). F5 є "␀?"(63).

      Ще ясно, як грязь? Так. Це не найінтуїтивніша річ у світі. У файлі довідки є таблиця кодів сканування, але я завжди просто запитую невелику програму для друку результатів INKEY$і натискаю купу клавіш, щоб дізнатися, що таке правильні значення. Коли ви дізнаєтесь, які символи відповідають яким клавішам, ви можете використовувати RIGHT$(k$,1)та LEN(k$)розрізняти всі різні випадки, з якими ви можете зіткнутися.

    Нижня лінія? INKEY$дивно, але це єдиний шлях, якщо ваша програма потребує введення, що не блокує або потрібно використовувати клавіші зі стрілками .


1 Чи не в тому числі Shift, Ctrl, Alt, PrntScr, Caps Lockі тому подібні. Ті не рахуються. : ^ П

2WHILE ... WEND ідіома ось що я дізнався в моїх книгах QBasic. Для цілей гри в гольф, однак, цикл коротше .GOTO


3

LOCATE може бути справді потужним

LOCATEЗаява дозволяє помістити курсор в будь-якому місці на екрані ( в межах звичайних 80x40 символів просторових обмежень) і надрукувати що - то в цьому місці. Ця відповідь на виклик справді демонструє це (а також поєднується з безліччю інших порад з цієї теми).

Завдання просить нас вивести кожного символу, який користувач натиснув у сітці 16x6. З LOCATEцим просто питання діва та моди щодо коду ASCII ( aу цьому коді):

LOCATE a\16-1,1+2*(a MOD 16)

А потім надрукуйте символ:

?CHR$(a)

3

У QBasic прийнято використовувати DIMоператор для створення змінних, даючи їм ім'я та тип. Однак це не є обов'язковим, QBasic також може виводити тип за допомогою суфікса імені змінної. Оскільки ви не можете одночасно оголосити та ініціалізувати змінну, часто розумно пропустити DIMкод в гольф. Два фрагменти, функціонально однакові *:

DIM a AS STRING: a = "example"
a$ = "example"

* Зауважте, що це створює два різних імена змінних.

Ми можемо вказати тип змінної, додавши $до кінця ім'я змінної для рядків, !для одиничних точних чисел та %для парних. Сингли передбачаються, коли не вказано тип.

a$ = "Definitely a string"
b! = "Error!"

Зауважте, що це також стосується масивів. Зазвичай масив визначається як:

DIM a(20) AS STRING

Але масиви також не повинні бути DIMmed:

a$(2) = "QBasic 4 FUN!"

a$тепер це масив для рядків з 11 слотами: від індексу 0 до включення індексу 10. Це робиться тому, що QBasic має опцію, яка дозволяє здійснювати як індексацію масивів на основі 0, так і 1. Тип масиву за замовчуванням підтримує і цей спосіб.

Пам'ятаєте двадцятислотовий масив, який ми DIMрозмістили вище? Насправді це 21 слот, оскільки той самий принцип застосовується як до затемнених, так і до не затемнених масивів.


Я ніколи не розумів, що це стосується і масивів. Цікаво.
трихоплакс

3

Скорочення IFтверджень

IF заяви доволі дорогі, і гольф їх може врятувати багато байт.

Розглянемо наступне (адаптовано з відповіді Еріка Побіжника):

IF RND<.5THEN
x=x-1
a(i)=1
ELSE
y=y-1
a(i)=0
ENDIF

Перше, що ми можемо зробити - це зберегти ENDIF, використовуючи однорядковий IFоператор:

IF RND<.5THEN x=x-1:a(i)=1ELSE y=y-1:a(i)=0

Це працює до тих пір, поки ви не намагаєтесь поставити його на ту саму лінію, що і все інше. Зокрема, якщо у вас є вкладені IFвисловлювання, односкладними можуть бути лише найпотужніші.

Але в цьому випадку ми можемо виключити IFцілком, використовуючи математику. Розглянемо, що ми насправді хочемо:

  • Якщо RND<.5це правда ( -1), ми хочемо:
    • x зменшити на 1
    • y залишатися таким же
    • a(i) стати 1
  • В іншому випадку, якщо RND<.5false ( 0), ми хочемо:
    • x залишатися таким же
    • y зменшити на 1
    • a(i) стати 0

Тепер , якщо ми збережемо результат умовної в змінної ( r=RND<.5), ми можемо обчислити нові значення x, yі a(i):

  • Коли rце -1, x=x-1; коли rє 0, x=x+0.
  • Коли rце -1, y=y+0; коли rє 0, y=y-1.
  • Коли rце -1, a(i)=1; коли rє 0, a(i)=0.

Отже, наш підсумковий код виглядає так:

r=RND<.5
x=x+r
y=y-1-r
a(i)=-r

заощаджуючи колосальні 20 байт (40%) над початковою версією.


Математичний підхід можна застосовувати напрочуд часто, але коли між двома випадками є різниця в логіці (наприклад, коли вам потрібно ввести щось в одному випадку, а не в іншому), вам все одно доведеться використовувати IF.


3

Іноді вам слід уникати масивів

Масиви в QBasic, коли інстанціювання без, DIMмають лише 11 слотів. Якщо для виклику потрібно більше 11 слотів (або N слотів, де N може бути більшим, ніж 11), ви повинні мати DIMмасив. Також припустимо, що ми хочемо заповнити цей масив даними:

DIM a$(12)
a$(0) = "Value 1"
a$(1) = "Value 2"
...

Навіть гольф, це може зайняти багато місця. У таких випадках це може бути дешевше в байтах:

a$ = "value 1value 2"

Тут ми розміщуємо все в 1 з'єднаний рядок. Пізніше ми отримуємо доступ до нього так:

?MID$(a$,i*7,7)

Для цього підходу важливо, щоб усі значення були однакової довжини. Візьміть найдовше значення і відкладіть всі інші:

a$="one  two  threefour "

Вам не потрібно прокладати останнє значення, і ви навіть можете пропустити заключні пропозиції! Якщо у виклику вказано, що пробіл у відповіді заборонений, скористайтеся RTRIM$()для виправлення цього.

Ви можете побачити цю техніку в дії тут .


3

PRINT( ?) має деякі примхи

Номери друкуються з провідним і заднім пробілом.

Друк додає лінію рядка. Цю поведінку можна змінити, додавши кому в кінці оператора, щоб замість неї вставити вкладку, або крапку з комою, щоб уникнути будь-яких вставок:

Не потрібно використовувати &або ;між різними операціями під час друку, наприклад. ?1"x"s$повинна надрукувати номер 1із пробілами на кожній стороні, букву xта вмістs$

?"foo"
?"bar"
?10
?"foo",
?"bar"
?"foo"; 
?"bar"
?1CHR$(65)
?1" "CHR$(65)
?"A","B

Виходи

foo
bar
 10
foo           bar
foobar
 1 A
 1  A
A             B

Друк рядкового рядка можна зробити просто ?


Зокрема, про номери друку: пробіл друкується перед номером, якщо він не є негативним; інакше -там надруковано знак мінус . Після номера друкується пробіл. Найкращий спосіб, який я виявив, щоб позбутися цих просторів, - це, PRINT USINGбезперечно, якщо ви хочете додати це до цієї відповіді або якщо це має бути окрема відповідь.
DLosc

2

WRITE може бути корисним замість PRINT

PRINTзазвичай це спосіб, яким ви хочете зробити вихід, оскільки він досить гнучкий і має ?ярлик. Однак WRITEкоманда може зберегти вам байти в конкретних ситуаціях:

  • Виводячи рядок, WRITEзагортайте його у подвійні лапки ( "). Якщо вам потрібен вихід з подвійними котируваннями, WRITE s$це набагато коротше, ніж ?CHR$(34);s$;CHR$(34). Дивіться, наприклад, найкоротшу відому квітку QBasic .
  • Виводячи число, WRITEне додайте пробілів до і після того, як це PRINTробиться. WRITE nнабагато коротше, ніж ?MID$(STR$(n),2). Дивіться, наприклад, FizzBuzz у QB64 .
  • Виводячи кілька значень, WRITEвідокремлюйте їх комами: WRITE 123,"abc"виводи 123,"abc". Я не можу придумати сценарій, коли це було б корисно, але це не означає, що його немає.

Обмеження WRITE:

  • Немає можливості вивести кілька значень без роздільника, як, наприклад, з PRINT a;b.
  • Неможливо придушити новий рядок наприкінці виводу. (Ви, можливо, зможете обійтись із цим LOCATE, але це коштує чимало байтів.)

1

Іноді QBasic маніпулює входами до функцій. Зловживайте цим!

Є кілька функцій, які працюють над символами замість рядків, але charв QBasic немає типу даних, є лише string ($)тип. Візьмемо для прикладу ASC()функцію, яка повертає ASCII код ключа для символу. Якби ми увійшли

PRINT ASC("lala")

тільки перший lвважав би QBasic. Таким чином, нам не потрібно турбуватися з розрізанням струни до довжини 1.

Інший приклад випливає з цього питання, де STRING$()функція використовується в одній з відповідей.

Функція STRING $ бере два аргументи, число n і рядок s $, і будує рядок, що складається з n копій першого символу s $

@DLosc, тут

Зауважте, що QBasic, пропонуючи рядок з декількома знаками та потребуючи лише одного знака, автоматично бере перший знак, а решту ігнорує.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.