Dicas para jogar golfe no QBasic


13

Que dicas gerais você tem para jogar golfe no QBasic? Estou procurando idéias que possam ser aplicadas aos problemas de código de golfe em geral que sejam pelo menos um pouco específicos para o QBasic (por exemplo, "remover comentários" não é uma resposta).

Dicas sobre o emulador QB64 também são bem-vindas. Possui alguns recursos extras que não estão no Microsoft QBasic.


Estou curioso sobre sua motivação. Eu não uso o QBASIC desde a minha aula de programação da 10ª série. Incrível como eu salvei diretamente em 1,44 disquetes sem qualquer forma de controle de versão e (geralmente) evitei falhas catastróficas.
Andrew Brēza 11/11

5
@ Motivação AndrewBrēza? O mesmo que minha motivação para jogar golfe em qualquer idioma: por diversão! Gosto de escrever pequenos programas no QBasic (embora não queira usá-lo para nada sério). Há também o bônus adicional de que ele possui som e gráficos (texto e pixel) incorporados, que minha linguagem "real" preferida, Python, não possui.
DLosc

É muito mais fácil escrever jogos gráficos no QBasic do que no python.
Anush

Se alguém quiser experimentar aplicativos gráficos QBasic diretamente no navegador, poderá usá-lo: github.com/nfriend/origins-host
mbomb007

Respostas:


10

Conheça suas construções em loop

QBasic tem várias construções de loop: FOR ... NEXT, WHILE ... WEND, e DO ... LOOP. Você também pode usar GOTOou (em algumas situações) RUNfazer um loop.

  • FOR ... NEXTé muito bom no que faz. Ao contrário do Python, é quase sempre menor que o equivalente WHILEou GOTOloop, mesmo quando fica um pouco mais sofisticado:

    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
    

    Observe que você não precisa repetir o nome da variável depois NEXTe pode eliminar o espaço entre os números e a maioria das palavras-chave a seguir.

  • WHILE ... WENDé bom para quando você tem um loop que pode precisar executar 0 vezes. Mas se você souber que o loop será executado pelo menos uma vez, GOTOpode ser um byte mais curto:

    WHILE n>1:n=n\2:WEND
    1n=n\2:IF n>1GOTO 1
    
  • Eu uso apenas DO ... LOOPpara loops infinitos (exceto onde RUNpode ser usado). Embora custe o mesmo número de caracteres que um incondicional GOTO, é um pouco mais intuitivo de ler. (Note-se que "loop infinito" pode incluir loops que você sair de usar um GOTO.) A DO WHILE/ DO UNTIL/ LOOP WHILE/ LOOP UNTILsintaxe é muito detalhado; é melhor usar WHILEou GOTOconforme apropriado.
  • GOTOé, como mencionado acima, a maneira geral mais curta de escrever um loop do / while. Use números de linha de um dígito em vez de etiquetas. Observe que quando a GOTOé a única coisa na THENparte de uma IFinstrução, existem duas sintaxes de atalho igualmente concisas disponíveis:

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

    GOTOtambém pode ser usado para criar fluxos de controle mais complicados . Os pessimistas se referem a isso como "código de espaguete", mas isso é código de golfe: a ilegibilidade é quase uma virtude! GOTOorgulho!

  • RUNé útil quando você precisa pular para um local fixo no programa e não precisa manter nenhum dos valores das variáveis. RUNpor si só irá reiniciar o programa a partir do topo; com um rótulo ou número de linha, ele será reiniciado nessa linha. Eu o usei principalmente para criar loops infinitos sem estado .

5

Use atalhos para PRINTeREM

Você pode usar em ?vez de PRINTe 'em REM(comentário).

'também pode ser útil ao poliglota com idiomas que suportam 'como parte da sintaxe char ou string.


5

Teste de divisibilidade

Em programas que exigem que você teste se um número inteiro é divisível por outro, a maneira óbvia é usar MOD:

x MOD 3=0

Mas uma maneira mais curta é usar a divisão inteira:

x\3=x/3

Ou seja, xint-div 3é igual a xfloat-div 3.

Observe que essas duas abordagens retornarão 0para falsey e trash -1, portanto, você pode precisar negar o resultado ou subtraí-lo em vez de adicioná-lo.


Se você precisar da condição oposta (ou xseja, não é divisível por 3), a abordagem óbvia é usar o operador not-equal:

x\3<>x/3

Mas se xé garantido que não é negativo, podemos salvar um byte. A divisão inteira trunca o resultado, portanto sempre será menor ou igual à divisão flutuante. Portanto, podemos escrever a condição como:

x\3<x/3

Da mesma forma, se xé garantido que é negativo, o truncamento aumenta o resultado e podemos escrever x\3>x/3. Se você não souber o sinal x, terá que seguir <>.


5

Abuso de scanner

Como em muitos idiomas, é importante saber quais caracteres podem ou não ser removidos.

  • Qualquer espaço ao lado de um símbolo pode ser removido: IF""=a$THEN?0
  • O espaço pode geralmente ser removido entre um dígito e uma letra que ocorre por esta ordem : FOR i=1TO 10STEP 2. Existem algumas diferenças entre o QBasic 1.1 (disponível em archive.org ) e o QB64 :
    • O QBasic 1.1 permite a remoção de espaço entre qualquer dígito e uma letra a seguir. Além disso, nas declarações impressas, ele inferirá um ponto e vírgula entre valores consecutivos: ?123xtorna - se PRINT 123; x. As exceções ao exposto acima são sequências como 1e2e 1d+3, que são tratadas como notação científica e expandidas para 100!e 1000#(precisão única e dupla, respectivamente).
    • Qb64 é geralmente o mesmo, mas não dígitos pode ser seguida por d, eou fa menos que eles fazem parte de um literal notação científica bem formado. (Por exemplo, você não pode omitir o espaço após o número da linha 1 FORou 9 END, como no QBasic propriamente.) Ele deduz o ponto-e-vírgula nas instruções de impressão se uma das expressões for uma string: ?123"abc"funciona, mas não ?TAB(5)123ou ?123x.
  • Falando em ponto e vírgula, o QBasic 1.1 adiciona um ponto e vírgula à direita em uma PRINTinstrução que termina com uma chamada para TABou SPC. (QB64 não.)
  • 0pode ser omitido antes ou depois do ponto decimal ( .1ou 1.), mas não ambos ( .).
  • ENDIFé equivalente a END IF.
  • A citação dupla de fechamento de uma string pode ser omitida no final de uma linha.

endifrealmente funciona em Qb64, consulte esta resposta
Wastl

@wastl É o que faz. Quando o testei pela primeira vez no QB64, eu estava usando uma versão mais antiga na qual havia um erro de sintaxe. Obrigado por mencionar!
21818 DLosc

4

Combinar Nextdeclarações

Next:Next:Next

Pode ser condensado até

Next k,j,i

onde os iteradores para os Forloops são i, je k- nessa ordem.

Por exemplo, o abaixo (69 bytes)

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

Pode ser condensado em até 65 bytes

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

E, na medida em que isso afeta a formatação e o recuo, acho que a melhor abordagem para lidar com isso é alinhar a próxima declaração pela mais externa. Por exemplo.

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

4

Conheça seus métodos de entrada

QBasic tem várias maneiras de obter a entrada do teclado do usuário: INPUT, LINE INPUT, INPUT$, e INKEY$.

  • INPUTé a sua declaração de entrada multiuso padrão. O programa para o que está fazendo, exibe um cursor e permite que o usuário digite alguma entrada, terminada por Enter. INPUTpode ler números ou seqüências de caracteres e pode ler vários valores separados por vírgula. Você pode especificar uma string como um prompt, pode usar o prompt de ponto de interrogação padrão e pode até (acabei de aprender isso hoje à noite) suprimir completamente o prompt. Algumas invocações de amostra:
    • INPUT x$,y
      Usa o ? prompt padrão e lê uma string e um número, separados por vírgula.
    • INPUT"Name";n$
      Solicita Name? e lê uma string.
    • INPUT"x=",x
      Solicita com x=(sem ponto de interrogação! Observe a vírgula na sintaxe) e lê um número.
    • INPUT;"",s$
      Suprime o prompt (usando a sintaxe de vírgula acima com uma string de prompt vazia), lê uma string e não passa para a próxima linha quando o usuário pressiona enter (é o que o ponto-e-vírgula depois INPUTfaz). Por exemplo, se você PRINT s$imediatamente após isso, sua tela será semelhante User_inputUser_input.
  • Uma desvantagem INPUTé que você não pode ler uma string com uma vírgula, pois ela INPUTusa como separador de campos. Para ler uma única linha de caracteres arbitrários (imprimíveis em ASCII), use LINE INPUT. Ele tem as mesmas opções de sintaxe que INPUT, exceto que é preciso exatamente uma variável que deve ser uma variável de string. A outra diferença é que LINE INPUTnão exibe um prompt por padrão; se você quiser um, precisará especificá-lo explicitamente.
  • INPUT$(n)não exibe prompt ou cursor, mas simplesmente espera até que o usuário insira ncaracteres e, em seguida, retorna uma string contendo esses caracteres. Ao contrário de INPUTou LINE INPUT, o usuário não precisa pressionar Enterdepois e, de fato, Enterpode ser um dos caracteres (ele fornecerá o caractere ASCII 13, conhecido como idiomas C \r).

    Na maioria das vezes, isso é útil INPUT$(1), geralmente em um loop. INPUT$é bom em programas interativos em que pressionamentos de tecla únicos fazem coisas . Infelizmente, ele funciona apenas com chaves que possuem códigos ASCII; isso inclui coisas como Esce Backspace, mas não as teclas de seta, Inserte Delete, e outras.

  • É aí que INKEY$entra. É semelhante ao INPUT$(1)fato de retornar os resultados de um único pressionamento de tecla 1 , mas diferente no seguinte:

    • INKEY$ não leva nenhum argumento.
    • Enquanto INPUT$(n)interrompe a execução até que o usuário insira ncaracteres, INKEY$não interrompe a execução. Se o usuário estiver pressionando uma tecla no momento, INKEY$retornará uma sequência que representa essa tecla; caso contrário, ele retorna "". Isso significa que, se você deseja usar INKEY$para obter o próximo pressionamento de tecla, precisará envolvê-lo em um loop de espera ocupada : 2

      k$=""
      WHILE""=k$
      k$=INKEY$
      WEND
      
    • Ambos INPUT$e INKEY$retornam caracteres ASCII para chaves que correspondem a caracteres ASCII (incluindo caracteres de controle como escape, tab e backspace). No entanto, INKEY$também pode manipular algumas chaves que não possuem códigos ASCII. Para isso (diz o arquivo de ajuda), "INKEY $ retorna uma string de 2 bytes composta pelo caractere nulo (ASCII 0) e pelo código de verificação do teclado".

      Claro como lama? Aqui estão alguns exemplos. Se você usar o INKEY$loop acima para capturar um pressionamento de tecla da tecla de seta esquerda, k$ele conterá a sequência "␀K"(com o Kcódigo de varredura representando 75). Para a seta direita, é "␀M"(77). A página para baixo é "␀Q"(81). F5 é "␀?"(63).

      Ainda limpo como lama? Sim. Não é a coisa mais intuitiva do mundo. O arquivo de ajuda possui uma tabela de códigos de verificação, mas eu sempre escrevo um pequeno programa para imprimir os resultados INKEY$e pressiono um monte de teclas para descobrir quais são os valores corretos. Depois de saber quais caracteres correspondem a quais teclas, você pode usar RIGHT$(k$,1)e LEN(k$)distinguir entre todos os casos diferentes que pode encontrar.

    Bottom line? INKEY$é estranho, mas é o único caminho a percorrer se o seu programa exigir entrada sem bloqueio ou precisar usar as teclas de seta .


1 Não inclui Shift, Ctrl, Alt, PrntScr, Caps Lock, e similar. Aqueles não contam. : ^ P

2 O WHILE ... WENDidioma aqui é o que aprendi nos meus livros QBasic. Para fins de golfe, no entanto, um GOTOloop é mais curto .


3

LOCATE pode ser realmente poderoso

A LOCATEinstrução permite que você coloque o cursor em qualquer lugar da tela (dentro dos limites usuais de espaço de 80x40 caracteres) e imprima algo nesse local. Esta resposta a um desafio realmente mostra isso (e também é combinada com muitas outras dicas deste tópico).

O desafio pede que produzamos todos os caracteres que um usuário pressionou em uma grade de 16x6. Com LOCATEisso, é simplesmente uma questão de div e mod sobre o código ASCII ( aneste código):

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

E depois imprimindo o caractere:

?CHR$(a)

3

No QBasic, é habitual usar a DIMinstrução para criar variáveis, dando-lhes um nome e um tipo. No entanto, isso não é obrigatório, o QBasic também pode derivar um tipo pelo sufixo do nome da variável. Como você não pode declarar e inicializar uma variável ao mesmo tempo, geralmente é aconselhável pular o DIMno codegolf. Dois trechos que são funcionalmente idênticos *:

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

* Observe que isso cria dois nomes de variáveis ​​diferentes.

Podemos especificar o tipo da variável adicionando $ao final de um nome de variável para strings, !números de precisão únicos e %duplos. Simples são assumidos quando nenhum tipo é especificado.

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

Observe que isso também vale para matrizes. Geralmente, uma matriz é definida como:

DIM a(20) AS STRING

Mas matrizes também não precisam ser DIMmed:

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

a$agora é uma matriz para seqüências de caracteres com 11 slots: do índice 0 ao e inclusive o índice 10. Isso é feito porque o QBasic tem uma opção que permite a indexação com base em 0 e em 1 para matrizes. Um tipo padrão de matriz suporta ambos dessa maneira.

Lembra da matriz de vinte slots que DIMmedimos acima? Na verdade, ele possui 21 slots, porque o mesmo princípio se aplica a matrizes esmaecidas e não esmaecidas.


Eu nunca percebi que isso se aplicava a matrizes também. Interessante.
Trichoplax

3

Encurtando IFdeclarações

IF as instruções são bastante caras, e reduzi-las pode economizar muitos bytes.

Considere o seguinte (adaptado de uma resposta de Erik, o Outgolfer):

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

A primeira coisa que podemos fazer é salvar ENDIFusando uma IFinstrução de linha única :

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

Isso funciona desde que você não tente colocá-lo na mesma linha que qualquer outra coisa. Em particular, se você tiver IFinstruções aninhadas , apenas a mais interna poderá ser de uma linha.

Mas, neste caso, podemos eliminar IFtotalmente a matemática. Considere o que realmente queremos:

  • Se RND<.5for true ( -1), queremos:
    • x diminuir em 1
    • y permanecer o mesmo
    • a(i) tornar-se 1
  • Caso contrário, se RND<.5for false ( 0), queremos:
    • x permanecer o mesmo
    • y diminuir em 1
    • a(i) tornar-se 0

Agora, se nós salvar o resultado da condicional em uma variável ( r=RND<.5), podemos calcular os novos valores de x, ye a(i):

  • Quando ré -1, x=x-1; quando ré 0, x=x+0.
  • Quando ré -1, y=y+0; quando ré 0, y=y-1.
  • Quando ré -1, a(i)=1; quando ré 0, a(i)=0.

Portanto, nosso código final se parece com:

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

economizando 20 bytes (40%) sobre a versão original.


A abordagem matemática pode ser aplicada surpreendentemente com frequência, mas quando houver uma diferença na lógica entre os dois casos (por exemplo, quando você precisar inserir algo em um caso, mas não no outro), você ainda precisará usá-lo IF.


3

Às vezes, você deve evitar matrizes

Matrizes no QBasic, quando instanciadas sem DIMter apenas 11 slots. Se um desafio exigir mais de 11 slots (ou N slots, onde N pode ser maior que 11), você deverá fazer DIMa matriz. Além disso, vamos supor que queremos preencher essa matriz com dados:

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

Mesmo jogando golfe, isso pode ocupar muito espaço. Nessas ocasiões, pode ser mais barato em bytes fazer o seguinte:

a$ = "value 1value 2"

Aqui, colocamos tudo em uma sequência concatenada. Mais tarde, acessamos da seguinte forma:

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

Para esta abordagem, é importante que todos os valores tenham o mesmo comprimento. Pegue o valor mais longo e elimine todos os outros:

a$="one  two  threefour "

Você não precisa preencher o último valor e pode até pular as aspas finais! Se o desafio especificar que o espaço em branco não é permitido na resposta, use RTRIM$()para corrigir isso.

Você pode ver esta técnica em ação aqui .


3

PRINT( ?) tem algumas peculiaridades

Os números são impressos com um espaço à esquerda e à direita.

A impressão adiciona uma quebra de linha. Esse comportamento pode ser alterado adicionando uma vírgula no final da instrução para inserir uma guia ou um ponto e vírgula para evitar inserções:

Não é necessário usar &ou ;entre operações distintas ao imprimir, por exemplo. ?1"x"s$deve imprimir o número 1, com espaços de cada lado, a letra xe o conteúdo das$

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

Saídas

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

A impressão de uma quebra de linha pode ser feita com apenas ?


Especificamente nos números de impressão: um espaço é impresso antes do número, se não for negativo; caso contrário, um sinal de menos -é impresso lá. Um espaço também é impresso após o número. A melhor maneira que eu descobri para me livrar desses espaços é - não PRINT USINGsei se você deseja adicionar isso a esta resposta ou se deve ser uma resposta separada.
21417 DLosc

2

WRITE pode ser útil no lugar de PRINT

PRINTgeralmente é da maneira que você deseja produzir, já que é bastante flexível e possui o ?atalho. No entanto, o WRITEcomando pode salvar bytes em situações específicas:

  • Ao emitir uma string, WRITEcoloque-a entre aspas duplas ( "). Se você precisar de saída com aspas duplas, WRITE s$é muito menor que ?CHR$(34);s$;CHR$(34). Veja, por exemplo, a solução QBasic mais curta conhecida .
  • Ao emitir um número, WRITEnão adiciona espaços antes e depois como ele PRINTfaz. WRITE né muito menor que ?MID$(STR$(n),2). Veja, por exemplo, o FizzBuzz no QB64 .
  • Ao emitir vários valores, os WRITEsepara com vírgulas: WRITE 123,"abc"saídas 123,"abc". Não consigo pensar em um cenário em que isso seria útil, mas isso não significa que não exista.

Limitações de WRITE:

  • Não há como gerar vários valores sem um separador como com PRINT a;b.
  • Não há como suprimir a nova linha no final da saída. (Você pode contornar isso LOCATE, mas isso custa muitos bytes.)

1

Às vezes, o QBasic gerencia entradas para funções. Abuse isso!

Existem algumas funções que funcionam com caracteres em vez de cadeias, mas não há um chartipo de dados no QBasic, existe apenas o string ($)tipo. Tomemos, por exemplo, a ASC()função, que retorna o código de chave ASCII para um caractere. Se entrarmos

PRINT ASC("lala")

somente o primeiro lseria considerado pelo QBasic. Dessa forma, não precisamos nos preocupar em cortar uma corda no comprimento 1.

Outro exemplo vem dessa pergunta em que a STRING$()função é usada em uma das respostas.

A função STRING $ recebe dois argumentos, um número n e uma sequência s $, e constrói uma sequência que consiste em n cópias do primeiro caractere de s $

@DLosc, aqui

Observe que o QBasic, quando oferecido uma sequência de vários caracteres e precisando de apenas um caractere, pega automaticamente o primeiro caractere e ignora o restante.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.