Какие общие советы у вас есть для игры в гольф в QBasic? Я ищу идеи, которые могут быть применены к проблемам с гольф-кодом в целом, которые, по крайней мере, несколько специфичны для QBasic (например, «удалить комментарии» - это не ответ).
Также приветствуются советы, относящиеся к эмулятору QB64 . У него есть некоторые дополнительные функции, которых нет в Microsoft QBasic.
Ответы:
Знай свои циклические конструкции
QBasic имеет несколько конструкций циклов:
FOR ... NEXT
,WHILE ... WEND
иDO ... LOOP
. Вы также можете использоватьGOTO
или (в некоторых ситуациях)RUN
для цикла.FOR ... NEXT
довольно хорош в том, что делает. В отличие от Python, он почти всегда короче, чем эквивалентWHILE
илиGOTO
цикл, даже когда он становится немного интереснее:Обратите внимание, что вам не нужно повторять имя переменной после
NEXT
, и вы можете устранить пробел между числами и большинством следующих ключевых слов.WHILE ... WEND
хорошо, когда у вас есть цикл, который может потребоваться выполнить 0 раз. Но если вы знаете, что цикл будет выполнен хотя бы один раз,GOTO
возможно, он будет на один байт короче:DO ... LOOP
для бесконечных циклов (кроме случаев, когдаRUN
можно использовать вместо). Хотя он стоит столько же символов, сколько и безусловныйGOTO
, он немного более интуитивно понятен. (Обратите внимание, что «бесконечный цикл» может включать в себя циклы, которые вы не используетеGOTO
.) СинтаксисDO WHILE
/DO UNTIL
/LOOP WHILE
/LOOP UNTIL
слишком многословен; вам лучше использоватьWHILE
илиGOTO
по необходимости.GOTO
как упоминалось выше, самый короткий общий способ написать цикл do / while. Используйте однозначные номера строк вместо меток. Обратите внимание, что, когда aGOTO
является единственнойTHEN
частью вIF
выражении, доступны два одинаково кратких синтаксиса:GOTO
также может быть использован для создания более сложных потоков управления . Скептики называют это «кодом спагетти», но это - гольф-код: нечитаемость - это почти добродетель!GOTO
гордость!RUN
полезно, когда вам нужно перейти на фиксированное место в программе, и вам не нужно сохранять значения переменных.RUN
сам по себе перезапустит программу сверху; с меткой или номером строки, он будет перезапущен с этой строки. Я в основном использовал его для создания бесконечных циклов без сохранения состояния .источник
Используйте ярлыки для
PRINT
иREM
Вы можете использовать
?
вместоPRINT
, а'
вместоREM
(комментарий).'
может также пригодиться при полиглотировании с языками, которые поддерживают'
как часть синтаксиса char или string.источник
Проверка делимости
В программах, которые требуют от вас проверить, делится ли одно целое число на другое, очевидным способом является использование
MOD
:Но более короткий путь - использовать целочисленное деление:
То есть
x
int-div3
равенx
float-div3
.Обратите внимание, что оба этих подхода вернутся
0
как-1
к ложному, так и к правдивому, поэтому вам может потребоваться отменить результат или вычесть его вместо добавления.Если вам нужно условие противоположного (т.е.
x
это не делится на3
), очевидный подход заключается в использовании не-равно оператор:Но если
x
гарантированно будет неотрицательным, мы можем сохранить байт. Целочисленное деление усекает результат, поэтому оно всегда будет меньше или равно делению с плавающей запятой. Поэтому мы можем написать условие как:Точно так же, если
x
гарантированно отрицательный результат, усечение увеличивает результат, и мы можем записатьx\3>x/3
. Если вы не знаете признаковx
, вам придется придерживаться<>
.источник
Злоупотребление сканером
Как и во многих языках, важно знать, какие символы можно и нельзя удалить.
IF""=a$THEN?0
FOR i=1TO 10STEP 2
. Есть некоторые различия между QBasic 1.1 (доступно на archive.org ) и QB64 :?123x
становитсяPRINT 123; x
. Исключениями из вышеперечисленного являются последовательности, подобные1e2
и1d+3
, которые рассматриваются как научные обозначения и расширяются до100!
и1000#
(одинарная и двойная точность соответственно).d
,e
илиf
вообще , если они не являются частью хорошо сформированной научной нотации литерала. (Например, вы не можете опустить пробел после номера строки в1 FOR
или9 END
, как вы можете в собственном QBasic.) Он выводит точки с запятой в выражениях print, только если одно из выражений является строкой:?123"abc"
работает, но не?TAB(5)123
или?123x
.PRINT
оператор, который заканчивается вызовомTAB
илиSPC
. (QB64 нет.)0
может быть опущен до или после десятичной точки (.1
или1.
), но не оба (.
).ENDIF
эквивалентноEND IF
.источник
endif
на самом деле работает в QB64, см. этот ответОбъединить
Next
заявленияМожет быть сжат до
где итераторы для
For
петельi
,j
иk
- в таком порядке.Например, ниже (69 байт)
Может быть сжат до 65 байт
А что касается того, как это влияет на форматирование и отступ, я думаю, что лучший подход к обработке этого вопроса - это выравнивание следующего оператора с внешним большинством для оператора. Например.
источник
Знай свои методы ввода
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Оба
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
2
WHILE ... WEND
идиома вот что я узнал в моих книгах QBasic. Для целей игры в гольф, однако, цикл короче .GOTO
источник
НАЙТИ может быть действительно мощным
LOCATE
Заявление позволяет поместить курсор в любом месте на экране ( в пределах обычных 80x40 символов пространственных ограничений) и напечатать что - то в этом месте. Этот ответ на вызов действительно демонстрирует это (и также сочетается с множеством других советов из этой темы).Задача состоит в том, чтобы вывести каждого персонажа, которого пользователь нажал, в сетке 16x6. С
LOCATE
это просто вопрос DIV и мода над кодом ASCII (a
в этом коде):И затем печать персонажа:
источник
В QBasic принято использовать
DIM
оператор для создания переменных, давая им имя и тип. Однако это не является обязательным, QBasic также может выводить тип по суффиксу имени переменной. Поскольку вы не можете объявлять и инициализировать переменную одновременно, часто целесообразно пропуститьDIM
кодогольф. Два фрагмента, которые функционально идентичны *:* Обратите внимание, что это создает два разных имени переменных.
Мы можем указать тип переменной, добавив
$
в конец имени переменной для строк,!
для чисел одинарной точности и%
для двойных. Синглы предполагаются, когда тип не указан.Обратите внимание, что это также относится к массивам. Обычно массив определяется как:
Но массивы также не должны быть
DIM
медленными:a$
теперь является массивом для строк с 11 слотами: от индекса 0 до индекса 10. Включено, потому что в QBasic есть опция, которая позволяет индексировать как массивы на основе 0, так и на основе 1. Тип массива по умолчанию поддерживает оба способа.Помните массив из двадцати слотов, который мы
DIM
описали выше? Это на самом деле имеет 21 слот, потому что один и тот же принцип применяется как для неярких, так и для нерегулируемых массивов.источник
Сокращенные
IF
заявленияIF
операторы довольно дороги, и игра в гольф может сэкономить много байтов.Рассмотрим следующее (адаптировано из ответа Эрика Outgolfer):
Первое, что мы можем сделать, это сохранить
ENDIF
с помощью однострочногоIF
оператора:Это работает до тех пор, пока вы не попытаетесь поместить его в одну строку с чем-либо еще. В частности, если у вас есть вложенные
IF
операторы, только самые внутренние могут быть однорядными.Но в этом случае мы можем
IF
полностью исключить использование математики. Посмотрим, что мы на самом деле хотим:RND<.5
true (-1
), мы хотим:x
уменьшить на 1y
оставаться прежнимa(i)
стать 1RND<.5
false (0
), мы хотим:x
оставаться прежнимy
уменьшить на 1a(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
.Итак, наш окончательный код выглядит так:
сохранение колоссальных 20 байтов (40%) по сравнению с оригинальной версией.
Математический подход может применяться на удивление часто, но когда между этими двумя случаями есть различие в логике (например, когда вам нужно что-то ввести в одном случае, а не в другом), вам все равно придется использовать
IF
.источник
Иногда следует избегать массивов
Массивы в QBasic, когда создаются без него,
DIM
имеют только 11 слотов. Если для вызова требуется более 11 слотов (или N слотов, где N может быть больше 11), вам следуетDIM
использовать массив. Также предположим, что мы хотим заполнить этот массив данными:Даже в гольфе это может занять много места. В таких случаях это может быть дешевле в байтах, чтобы сделать это:
Здесь мы помещаем все в 1 каскадную строку. Позже мы получим к нему доступ так:
Для этого подхода важно, чтобы все значения имели одинаковую длину. Возьмите самое длинное значение и добавьте все остальные:
Вам не нужно дополнять последнее значение, и вы даже можете пропустить заключительные кавычки! Если в запросе указано, что в ответе не допускается пробел, используйте его
RTRIM$()
для исправления.Вы можете увидеть эту технику в действии здесь .
источник
PRINT
(?
) имеет некоторые причудыНомера печатаются с пробелом в начале и в конце.
Печать добавляет разрыв строки. Это поведение можно изменить, добавив запятую в конце оператора для вставки вместо табуляции или точку с запятой, чтобы избежать вставок:
Нет необходимости использовать
&
или;
между различными операциями при печати, например.?1"x"s$
должен распечатать номер1
, с пробелами на каждой стороне, письмоx
и содержаниеs$
Выходы
Распечатка переноса строки может быть сделана просто
?
источник
-
печатается там. Пробел также печатается после номера. Лучший способ, который я обнаружил, чтобы избавиться от этих пробелов - этоPRINT USING
--dunno, если вы хотите добавить это к этому ответу или если это должен быть отдельный ответ.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
, но это стоит много байтов.)источник
Иногда QBasic изменяет входные данные для функций. Злоупотреблять этим!
Есть пара функций, которые работают с символами вместо строк, но
char
в QBasic нетstring ($)
типа данных, есть только тип. Возьмем, к примеру,ASC()
функцию, которая возвращает код клавиши ASCII для символа. Если бы мы вошлитолько первое
l
будет рассмотрено QBasic. Таким образом, нам не нужно беспокоиться о сокращении строки до длины 1.Другой пример исходит из этого вопроса, где
STRING$()
функция используется в одном из ответов.Обратите внимание, что QBasic, когда ему предлагается строка из нескольких символов и требуется только один символ, автоматически берет первый символ и игнорирует остальные.
источник