Suggerimenti per giocare a golf in QBasic


13

Quali consigli generali hai per giocare a golf in QBasic? Sto cercando idee che possano essere applicate ai problemi del codice golf in generale che siano almeno in qualche modo specifiche per QBasic (ad esempio "rimuovere i commenti" non è una risposta).

Anche i suggerimenti relativi all'emulatore QB64 sono i benvenuti. Ha alcune funzionalità extra che non sono in Microsoft QBasic.


Sono curioso della tua motivazione. Non uso QBASIC dalla mia classe di programmazione di 10 ° grado. Incredibile come ho salvato direttamente su 1.44 floppy disk senza alcuna forma di controllo della versione e (di solito) ho evitato guasti catastrofici.
Andrew Brēza,

5
@ AndrewBrēza Motivation? Lo stesso della mia motivazione per giocare a golf in qualsiasi lingua: per divertimento! Mi piace scrivere piccoli programmi in QBasic (anche se non vorrei usarlo per qualcosa di serio). C'è anche il vantaggio aggiuntivo di avere audio e grafica (sia testo che pixel) integrati, cosa che il mio linguaggio "reale" preferito, Python, non ha.
DLosc

È molto più facile scrivere giochi grafici in QBasic che in Python.
Anush,

Se qualcuno vuole provare applicazioni grafiche QBasic direttamente nel browser, potrebbe usare questo: github.com/nfriend/origins-host
mbomb007

Risposte:


10

Conosci i tuoi costrutti loop

QBasic ha diversi costrutti di loop: FOR ... NEXT, WHILE ... WEND, e DO ... LOOP. Puoi anche utilizzare GOTOo (in alcune situazioni) RUNper eseguire il loop.

  • FOR ... NEXTè abbastanza bravo in quello che fa. A differenza di Python, è quasi sempre più corto dell'equivalente WHILEo del GOTOloop, anche quando diventa un po 'più elaborato:

    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
    

    Nota che non è necessario ripetere il nome della variabile dopo NEXTe puoi eliminare lo spazio tra i numeri e la maggior parte delle parole chiave seguenti.

  • WHILE ... WENDè utile quando hai un ciclo che potrebbe dover essere eseguito 0 volte. Ma se sai che il ciclo verrà eseguito almeno una volta, GOTOpotrebbe essere più corto di un byte:

    WHILE n>1:n=n\2:WEND
    1n=n\2:IF n>1GOTO 1
    
  • Uso solo DO ... LOOPper loop infiniti (tranne dove RUNpuò essere utilizzato invece). Mentre costa lo stesso numero di caratteri di un incondizionato GOTO, è un po 'più intuitivo da leggere. (Notare che "loop infinito" può includere loop che si interrompe usando a GOTO.) La sintassi DO WHILE/ DO UNTIL/ LOOP WHILE/ LOOP UNTILè troppo dettagliata; stai meglio usando WHILEo GOTOcome appropriato.
  • GOTOè, come detto sopra, il modo generale più breve per scrivere un ciclo do / while. Usa numeri di riga a una cifra anziché etichette. Si noti che quando a GOTOè l'unica cosa nella THENparte di IFun'istruzione, sono disponibili due sintassi di scorciatoia ugualmente concise:

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

    GOTOpuò anche essere usato per creare flussi di controllo più complicati . Gli oppositori si riferiscono a questo come "codice spaghetti", ma questo è il codice golf: illeggibilità è quasi una virtù! GOTOorgoglio!

  • RUNè utile quando è necessario passare a un punto fisso nel programma e non è necessario mantenere nessuno dei valori delle variabili. RUNda solo riavvierà il programma dall'alto; con un'etichetta o un numero di riga, si riavvierà su quella riga. L'ho usato principalmente per creare loop infiniti apolidi .

5

Utilizzare le scorciatoie per PRINTeREM

È possibile utilizzare ?invece di PRINTe 'anziché REM(commento).

'potrebbe anche essere utile quando si poliglotta con linguaggi che supportano 'come parte della sintassi char o string.


5

Test di divisibilità

Nei programmi che richiedono di verificare se un numero intero è divisibile per un altro, il modo più ovvio è utilizzare MOD:

x MOD 3=0

Ma un modo più breve è usare la divisione intera:

x\3=x/3

Cioè, xint-div è 3uguale a xfloat-div 3.

Nota che entrambi questi approcci torneranno 0per falsità e -1verità, quindi potresti dover annullare il risultato o sottrarlo invece di aggiungere.


Se hai bisogno della condizione opposta (cioè nonx è divisibile per ), l'approccio ovvio è usare l'operatore non uguale a:3

x\3<>x/3

Ma se xè garantito che non è negativo, possiamo salvare un byte. La divisione intera tronca il risultato, quindi sarà sempre minore o uguale alla divisione float. Pertanto, possiamo scrivere la condizione come:

x\3<x/3

Allo stesso modo, se xè garantito che sia negativo, il troncamento aumenta il risultato e possiamo scrivere x\3>x/3. Se non conosci il segno di x, dovrai attenersi a <>.


5

Abuso di scanner

Come in molte lingue, è importante sapere quali caratteri possono e non possono essere rimossi.

  • È possibile rimuovere qualsiasi spazio accanto a un simbolo: IF""=a$THEN?0
  • Spazio di solito può essere rimossa tra una cifra e una lettera che si verificano in questo ordine : FOR i=1TO 10STEP 2. Ci sono alcune differenze tra QBasic 1.1 (disponibile su archive.org ) e QB64 :
    • QBasic 1.1 consente la rimozione dello spazio tra qualsiasi cifra e una lettera successiva. Inoltre, nelle istruzioni stampate, inferirà un punto e virgola tra valori consecutivi: ?123xdiventa PRINT 123; x. Le eccezioni a quanto sopra sono sequenze simili a 1e2e 1d+3, che sono trattate come notazione scientifica e ampliate a 100!e 1000#(rispettivamente a precisione singola e doppia).
    • Qb64 è generalmente lo stesso, ma le cifre non può essere seguita da d, eo faffatto meno che non siano parte di un ben formato letterale notazione scientifica. (Ad esempio, non è possibile omettere lo spazio dopo il numero di riga in 1 FORo 9 END, come è possibile in QBasic in senso proprio.) Dà solo punti e virgola nelle istruzioni di stampa se una delle espressioni è una stringa: ?123"abc"funziona, ma non ?TAB(5)123oppure ?123x.
  • A proposito di punti e virgola, QBasic 1.1 aggiunge un punto e virgola finale a PRINTun'istruzione che termina con una chiamata a TABo SPC. (QB64 no.)
  • 0può essere omesso prima o dopo il punto decimale ( .1o 1.), ma non entrambi ( .).
  • ENDIFè equivalente a END IF.
  • La doppia virgoletta di chiusura di una stringa può essere omessa alla fine di una riga.

endiffunziona effettivamente in QB64, vedi questa risposta
wastl

@wastl Così fa. Quando l'ho provato per la prima volta in QB64, stavo usando una versione precedente in cui si trattava di un errore di sintassi. Grazie per averlo menzionato!
DLosc

4

Combina Nextdichiarazioni

Next:Next:Next

Può essere ridotto a

Next k,j,i

dove gli iteratori per i Forcicli sono i, je k- in questo ordine.

Ad esempio il seguente (69 byte)

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

Può essere ridotto a 65 byte

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

E per quanto riguarda il modo in cui ciò influisce sulla formattazione e sul rientro, penso che l'approccio migliore per gestirlo sia quello di allineare la frase successiva a quella più esterna. Per esempio.

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

4

Conosci i tuoi metodi di input

QBasic ha diversi modi per ottenere input da tastiera dell'utente: INPUT, LINE INPUT, INPUT$, e INKEY$.

  • INPUTè la tua istruzione di input multiuso standard. Il programma interrompe ciò che sta facendo, visualizza un cursore e consente all'utente di digitare un input, terminato da Enter. INPUTpuò leggere numeri o stringhe e può leggere più valori separati da virgola. Puoi specificare una stringa come prompt, puoi andare con il prompt del punto interrogativo predefinito e puoi persino (ho appena imparato questa sera) sopprimere del tutto il prompt. Alcune invocazioni di esempio:
    • INPUT x$,y
      Utilizza il ? prompt predefinito e legge una stringa e un numero, separati da virgola.
    • INPUT"Name";n$
      Richiede Name? e legge una stringa.
    • INPUT"x=",x
      Richiede x=(nessun punto interrogativo! Annotare la virgola nella sintassi) e legge un numero.
    • INPUT;"",s$
      Sopprime il prompt (utilizzando la sintassi della virgola sopra indicata con una stringa del prompt vuota), legge una stringa e non passa alla riga successiva quando l'utente preme Invio (è quello che INPUTfa dopo il punto e virgola ). Ad esempio, se lo fai PRINT s$subito dopo, lo schermo apparirà User_inputUser_input.
  • Uno svantaggio di INPUTè che non puoi leggere una stringa con una virgola, poiché INPUTusa la virgola come separatore di campo. Per leggere una singola riga di caratteri arbitrari (ASCII stampabili), utilizzare LINE INPUT. Ha le stesse opzioni di sintassi di INPUT, tranne per il fatto che richiede esattamente una variabile che deve essere una variabile stringa. L'altra differenza è che LINE INPUTnon visualizza un prompt per impostazione predefinita; se ne vuoi uno, dovrai specificarlo esplicitamente.
  • INPUT$(n)non visualizza alcun prompt o cursore, ma attende semplicemente che l'utente immetta ncaratteri, quindi restituisce una stringa contenente tali caratteri. A differenza di INPUTo LINE INPUT, l'utente non ha bisogno di premere in Enterseguito, e in effetti Enterpuò essere uno dei caratteri (fornirà il carattere ASCII 13, noto ai linguaggi di tipo C come \r).

    Molto spesso, questo è utile come INPUT$(1), in genere in un ciclo. INPUT$è buono nei programmi interattivi in ​​cui i singoli tasti premono . Sfortunatamente, funziona solo con chiavi che hanno codici ASCII; questo include cose come Esce Backspace, ma non i tasti freccia, Inserte Delete, e altri.

  • È qui che INKEY$entra in gioco. È simile al fatto INPUT$(1)che restituisce i risultati di un singolo tasto 1 , ma diverso da quello:

    • INKEY$ non prende argomento.
    • Mentre INPUT$(n)interrompe l'esecuzione fino a quando l'utente immette ncaratteri, INKEY$non interrompe l'esecuzione. Se l'utente sta attualmente premendo un tasto, INKEY$restituisce una stringa che rappresenta quel tasto; in caso contrario, ritorna "". Ciò significa che se si desidera utilizzare INKEY$per ottenere il prossimo tasto premuto, è necessario avvolgerlo in un ciclo di attesa : 2

      k$=""
      WHILE""=k$
      k$=INKEY$
      WEND
      
    • Entrambi INPUT$e INKEY$restituiscono caratteri ASCII per i tasti che corrispondono ai caratteri ASCII (compresi i caratteri di controllo come escape, tab e backspace). Tuttavia, INKEY$può anche gestire alcuni tasti che non hanno codici ASCII. Per questi (dice il file di aiuto), "INKEY $ restituisce una stringa di 2 byte composta dal carattere null (ASCII 0) e dal codice di scansione della tastiera."

      Chiaro come fango? Ecco alcuni esempi. Se si utilizza il INKEY$ciclo sopra per acquisire una pressione del tasto freccia sinistra, k$conterrà la stringa "␀K"(con il Kcodice di scansione rappresentativo 75). Per la freccia destra, è "␀M"(77). Pagina giù è "␀Q"(81). F5 è "␀?"(63).

      Ancora limpido come fango? Si. Non è la cosa più intuitiva al mondo. Il file della guida contiene una tabella di codici di scansione, ma scrivo sempre un piccolo programma per stampare i risultati INKEY$e premo un mucchio di chiavi per scoprire quali sono i valori giusti. Una volta che sai quali caratteri corrispondono a quali chiavi, puoi usare RIGHT$(k$,1)e LEN(k$)distinguere tra tutti i diversi casi che potresti incontrare.

    Linea di fondo? INKEY$è strano, ma è l'unica strada da percorrere se il programma richiede input non bloccanti o se è necessario utilizzare i tasti freccia .


1 Escluso Shift, Ctrl, Alt, PrntScr, Caps Lock, e simili. Quelli non contano. : ^ P

2 Il WHILE ... WENDlinguaggio qui è quello che ho imparato nei miei libri di QBasic. Ai fini del golf, tuttavia, un GOTOciclo è più breve .


3

LOCATE può essere davvero potente

L' LOCATEistruzione ti consente di posizionare il cursore in qualsiasi punto dello schermo (entro i consueti limiti di spazio di 80x40 caratteri) e di stampare qualcosa in quella posizione. Questa risposta a una sfida lo dimostra davvero (ed è anche combinata con molti altri suggerimenti da questo argomento).

La sfida ci chiede di produrre ogni personaggio che un utente ha premuto in una griglia 16x6. Con LOCATEquesto è semplicemente una questione di div e mod sul codice ASCII ( ain questo codice):

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

E quindi stampare il personaggio:

?CHR$(a)

3

In QBasic, è consuetudine utilizzare l' DIMistruzione per creare variabili, dando loro un nome e un tipo. Tuttavia, questo non è obbligatorio, QBasic può anche derivare un tipo dal suffisso del nome della variabile. Dal momento che non è possibile dichiarare e inizializzare una variabile allo stesso tempo, è spesso saggio saltare DIMin in codegolf. Due frammenti funzionalmente identici *:

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

* Notare che ciò crea due nomi di variabili differenti.

Possiamo specificare il tipo di variabile aggiungendo $alla fine di un nome variabile per stringhe, !per singoli numeri di precisione e %per doppi. I singoli vengono assunti quando non viene specificato alcun tipo.

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

Si noti che ciò vale anche per gli array. Di solito, un array è definito come:

DIM a(20) AS STRING

Ma anche le matrici non devono essere DIMmed:

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

a$è ora un array per stringhe con 11 slot: dall'indice 0 all'indice 10. compreso, poiché QBasic ha un'opzione che consente l'indicizzazione sia su 0 sia su 1 per array. Un tipo di array predefinito supporta entrambi in questo modo.

Ricordi l'array a venti slot di cui DIMsopra? Questo in realtà ha 21 slot, perché lo stesso principio si applica sia agli array dimmer che a quelli non dimmerati.


Non ho mai capito che questo valeva anche per gli array. Interessante.
trichoplax,

3

IFDichiarazioni di accorciamento

IF le dichiarazioni sono piuttosto costose e il loro golf down può far risparmiare parecchi byte.

Considera quanto segue (adattato da una risposta di Erik the Outgolfer):

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

La prima cosa che possiamo fare è salvare ENDIFutilizzando IFun'istruzione a riga singola :

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

Funziona finché non provi a metterlo sulla stessa linea di qualsiasi altra cosa. In particolare, se hai IFdichiarazioni nidificate , solo quella più interna può essere a riga singola.

Ma in questo caso, possiamo eliminare il IFtutto usando la matematica. Considera cosa vogliamo effettivamente:

  • Se RND<.5è vero ( -1), vogliamo:
    • x per diminuire di 1
    • y per rimanere lo stesso
    • a(i) diventare 1
  • Altrimenti, se RND<.5è false ( 0), vogliamo:
    • x per rimanere lo stesso
    • y per diminuire di 1
    • a(i) per diventare 0

Ora, se salviamo il risultato del condizionale in una variabile ( r=RND<.5), possiamo calcolare i nuovi valori di 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.

Quindi il nostro codice finale appare come:

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

risparmiando un enorme 20 byte (40%) rispetto alla versione originale.


L'approccio matematico può essere applicato sorprendentemente spesso, ma quando c'è una differenza nella logica tra i due casi (ad esempio quando è necessario inserire qualcosa in un caso ma non nell'altro), sarà comunque necessario utilizzarlo IF.


3

A volte, dovresti evitare le matrici

Matrici in QBasic, se istanziate senza DIMsolo 11 slot. Se una sfida richiede più di 11 slot (o N slot, dove N può essere maggiore di 11), è necessario DIMl'array. Inoltre, supponiamo di voler popolare questo array con i dati:

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

Anche da golf, questo può occupare molto spazio. In tali occasioni, potrebbe essere più economico in byte per fare questo:

a$ = "value 1value 2"

Qui, inseriamo tutto in 1 stringa concatenata. Successivamente, accediamo in questo modo:

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

Per questo approccio, è importante che tutti i valori abbiano la stessa lunghezza. Prendi il valore più lungo e completa tutti gli altri:

a$="one  two  threefour "

Non è necessario riempire l'ultimo valore e puoi anche saltare le virgolette di chiusura! Se la sfida specifica che lo spazio bianco non è consentito nella risposta, utilizzare RTRIM$()per risolverlo.

Puoi vedere questa tecnica in azione qui .


3

PRINT( ?) ha alcune stranezze

I numeri sono stampati con uno spazio iniziale e finale.

La stampa aggiunge un'interruzione di riga. Questo comportamento può essere modificato aggiungendo una virgola alla fine dell'istruzione per inserire invece una scheda o un punto e virgola per evitare eventuali inserimenti:

Non è necessario utilizzare &o ;tra operazioni distinte durante la stampa, ad es. ?1"x"s$stampa il numero 1, con spazi su ciascun lato, la lettera xe il contenuto dis$

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

Uscite

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

La stampa di un'interruzione di riga può essere eseguita solo con ?


In particolare sui numeri di stampa: uno spazio viene stampato prima del numero se non è negativo; in caso contrario, un segno meno -viene stampato lì. Viene anche stampato uno spazio dopo il numero. Il modo migliore che ho scoperto per sbarazzarmi di questi spazi è - non PRINT USINGso se vuoi aggiungerlo a questa risposta o se dovrebbe essere una risposta separata.
DLosc,

2

WRITE può essere utile al posto di PRINT

PRINTdi solito è il modo in cui vorrai fare l'output, poiché è piuttosto flessibile e ha il ?collegamento. Tuttavia, il WRITEcomando può salvarti byte in situazioni specifiche:

  • Quando si emette una stringa, la WRITEavvolge tra virgolette doppie ( "). Se hai bisogno di output con virgolette doppie, WRITE s$è molto più breve di ?CHR$(34);s$;CHR$(34). Vedi, ad esempio, il quine QBasic più breve conosciuto .
  • Quando si emette un numero, WRITEnon aggiunge spazi prima e dopo come PRINTfa. WRITE nè molto più breve di ?MID$(STR$(n),2). Vedi, ad esempio, FizzBuzz in QB64 .
  • Quando si emettono più valori, WRITEli separa con virgole: WRITE 123,"abc"output 123,"abc". Non riesco a pensare a uno scenario in cui questo sarebbe utile, ma ciò non significa che non ce ne sia uno.

Limitazioni di WRITE:

  • Non è possibile generare più valori senza un separatore come con PRINT a;b.
  • Non è possibile sopprimere la nuova riga alla fine dell'output. (Potresti essere in grado di aggirare questo problema con LOCATE, ma questo costa molti byte.)

1

A volte, QBasic mangia input per le funzioni. Abusa!

Esistono un paio di funzioni che funzionano sui caratteri anziché sulle stringhe, ma charin QBasic non esiste un tipo di dati, esiste solo il string ($)tipo. Prendiamo ad esempio la ASC()funzione, che restituisce il codice ASCII per un carattere. Se entrassimo

PRINT ASC("lala")

solo il primo lsarebbe considerato da QBasic. In questo modo, non dobbiamo preoccuparci di tagliare una corda fino alla lunghezza 1.

Un altro esempio viene da questa domanda in cui la STRING$()funzione viene utilizzata in una delle risposte.

La funzione STRING $ accetta due argomenti, un numero n e una stringa s $ e costruisce una stringa composta da n copie del primo carattere di s $

@DLosc, qui

Si noti che QBasic, quando viene offerta una stringa multi-carattere e richiede solo un carattere, prende automaticamente il primo carattere e ignora il resto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.