Советы по игре в гольф в QBasic

13

Какие общие советы у вас есть для игры в гольф в QBasic? Я ищу идеи, которые могут быть применены к проблемам с гольф-кодом в целом, которые, по крайней мере, несколько специфичны для QBasic (например, «удалить комментарии» - это не ответ).

Также приветствуются советы, относящиеся к эмулятору QB64 . У него есть некоторые дополнительные функции, которых нет в Microsoft QBasic.

DLosc
источник
Меня интересует ваша мотивация. Я не использовал 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, он немного более интуитивно понятен. (Обратите внимание, что «бесконечный цикл» может включать в себя циклы, которые вы не используете 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сам по себе перезапустит программу сверху; с меткой или номером строки, он будет перезапущен с этой строки. Я в основном использовал его для создания бесконечных циклов без сохранения состояния .
DLosc
источник
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, вам придется придерживаться <>.

DLosc
источник
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.) Он выводит точки с запятой в выражениях print, только если одно из выражений является строкой: ?123"abc"работает, но не ?TAB(5)123или ?123x.
  • Говоря о точках с запятой, QBasic 1.1 добавляет конечную точку с запятой в PRINTоператор, который заканчивается вызовом TABили SPC. (QB64 нет.)
  • 0может быть опущен до или после десятичной точки ( .1или 1.), но не оба ( .).
  • ENDIFэквивалентно END IF.
  • Закрывающая двойная кавычка строки может быть опущена в конце строки.
DLosc
источник
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 (включая управляющие символы, такие как escape, tab и backspace). Однако 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и тому подобные. Это не считается. : ^ P

2WHILE ... WEND идиома вот что я узнал в моих книгах QBasic. Для целей игры в гольф, однако, цикл короче .GOTO

DLosc
источник
3

НАЙТИ может быть действительно мощным

LOCATEЗаявление позволяет поместить курсор в любом месте на экране ( в пределах обычных 80x40 символов пространственных ограничений) и напечатать что - то в этом месте. Этот ответ на вызов действительно демонстрирует это (и также сочетается с множеством других советов из этой темы).

Задача состоит в том, чтобы вывести каждого персонажа, которого пользователь нажал, в сетке 16x6. С LOCATEэто просто вопрос DIV и мода над кодом ASCII ( aв этом коде):

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

И затем печать персонажа:

?CHR$(a)
steenbergh
источник
3

В QBasic принято использовать DIMоператор для создания переменных, давая им имя и тип. Однако это не является обязательным, QBasic также может выводить тип по суффиксу имени переменной. Поскольку вы не можете объявлять и инициализировать переменную одновременно, часто целесообразно пропустить DIMкодогольф. Два фрагмента, которые функционально идентичны *:

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

* Обратите внимание, что это создает два разных имени переменных.

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

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

Обратите внимание, что это также относится к массивам. Обычно массив определяется как:

DIM a(20) AS STRING

Но массивы также не должны быть DIMмедленными:

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

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

Помните массив из двадцати слотов, который мы DIMописали выше? Это на самом деле имеет 21 слот, потому что один и тот же принцип применяется как для неярких, так и для нерегулируемых массивов.

steenbergh
источник
Я никогда не осознавал, что это относится и к массивам. Интересный.
trichoplax
3

Сокращенные IFзаявления

IF операторы довольно дороги, и игра в гольф может сэкономить много байтов.

Рассмотрим следующее (адаптировано из ответа Эрика Outgolfer):

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<.5true ( -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.

DLosc
источник
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$()для исправления.

Вы можете увидеть эту технику в действии здесь .

steenbergh
источник
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

Распечатка переноса строки может быть сделана просто ?

steenbergh
источник
В частности, при печати чисел: пробел печатается перед номером, если он неотрицательный; в противном случае знак минус -печатается там. Пробел также печатается после номера. Лучший способ, который я обнаружил, чтобы избавиться от этих пробелов - это PRINT USING--dunno, если вы хотите добавить это к этому ответу или если это должен быть отдельный ответ.
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, но это стоит много байтов.)
DLosc
источник
1

Иногда QBasic изменяет входные данные для функций. Злоупотреблять этим!

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

PRINT ASC("lala")

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

Другой пример исходит из этого вопроса, где STRING$()функция используется в одном из ответов.

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

@DLosc, здесь

Обратите внимание, что QBasic, когда ему предлагается строка из нескольких символов и требуется только один символ, автоматически берет первый символ и игнорирует остальные.

steenbergh
источник