Consejos para jugar golf en QBasic

13

¿Qué consejos generales tienes para jugar al golf en QBasic? Estoy buscando ideas que puedan aplicarse a los problemas de código de golf en general que sean al menos algo específicos de QBasic (por ejemplo, "eliminar comentarios" no es una respuesta).

Consejos sobre el emulador QB64 también son bienvenidos. Tiene algunas características adicionales que no están en Microsoft QBasic.

DLosc
fuente
Tengo curiosidad por tu motivación. No he usado QBASIC desde mi clase de programación de décimo grado. Es sorprendente cómo guardé directamente en 1.44 disquetes sin ningún tipo de control de versión y (generalmente) evité fallas catastróficas.
Andrew Brēza
55
@ AndrewBrēza ¿Motivación? Lo mismo que mi motivación para jugar golf en cualquier idioma: ¡por diversión! Disfruto escribiendo pequeños programas en QBasic (aunque no me gustaría usarlo para nada serio). También existe la ventaja adicional de que tiene sonido y gráficos (tanto texto como píxeles) integrados, que mi lenguaje "real" preferido, Python, no tiene.
DLosc
Es mucho más fácil escribir juegos gráficos en QBasic que en Python.
Anush
Si alguien quiere probar las aplicaciones gráficas de QBasic directamente en el navegador, puede usar esto: github.com/nfriend/origins-host
mbomb007

Respuestas:

10

Conozca sus construcciones en bucle

QBasic tiene varias construcciones de bucle: FOR ... NEXT, WHILE ... WEND, y DO ... LOOP. También puede usar GOTOo (en algunas situaciones) RUNpara hacer un bucle.

  • FOR ... NEXTes bastante bueno en lo que hace. A diferencia de Python, casi siempre es más corto que el equivalente WHILEo el GOTObucle, incluso cuando se vuelve un poco más elegante:

    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
    

    Tenga en cuenta que no necesita repetir el nombre de la variable después NEXT, y puede eliminar el espacio entre los números y la mayoría de las siguientes palabras clave.

  • WHILE ... WENDes bueno para cuando tienes un ciclo que podría necesitar ejecutarse 0 veces. Pero si sabe que el bucle se ejecutará al menos una vez, GOTOpodría ser un byte más corto:

    WHILE n>1:n=n\2:WEND
    1n=n\2:IF n>1GOTO 1
    
  • Solo lo uso DO ... LOOPpara bucles infinitos (excepto donde RUNse puede usar en su lugar). Si bien cuesta la misma cantidad de caracteres que un incondicional GOTO, es un poco más intuitivo de leer. (Tenga en cuenta que "bucle infinito" puede incluir bucles de los que se salga usando a GOTO.) La sintaxis DO WHILE/ DO UNTIL/ LOOP WHILE/ LOOP UNTILes demasiado detallada; es mejor usarlo WHILEo GOTOsegún corresponda.
  • GOTOes, como se mencionó anteriormente, la forma general más corta de escribir un bucle do / while. Use números de línea de un solo dígito en lugar de etiquetas. Tenga en cuenta que cuando a GOTOes lo único en la THENparte de una IFdeclaración, hay dos sintaxis de acceso directo igualmente concisas disponibles:

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

    GOTOTambién se puede utilizar para crear flujos de control más complicados . Los detractores se refieren a esto como "código de espagueti", pero esto es golf de código: ¡la imposibilidad de leer es casi una virtud! GOTO¡orgullo!

  • RUNes útil cuando necesita saltar a un lugar fijo en el programa y no necesita mantener ninguno de los valores de las variables. RUNpor sí solo reiniciará el programa desde arriba; con una etiqueta o número de línea, se reiniciará en esa línea. Lo he usado principalmente para crear bucles infinitos sin estado .
DLosc
fuente
5

Use atajos para PRINTyREM

Puede usar en ?lugar de PRINT, y en 'lugar de REM(comentario).

'También puede ser útil cuando se poliglota con lenguajes que son compatibles 'como parte de la sintaxis char o string.

Uriel
fuente
5

Prueba de divisibilidad

En los programas que requieren que pruebe si un entero es divisible por otro, la forma obvia es usar MOD:

x MOD 3=0

Pero una forma más corta es usar la división de enteros:

x\3=x/3

Es decir, xint-div 3es igual a xfloat-div 3.

Tenga en cuenta que ambos enfoques volverán 0para falsey y -1para verdadero, por lo que es posible que deba negar el resultado o restarlo en lugar de sumar.


Si necesita la condición opuesta ( xes decir, no es divisible por 3), el enfoque obvio es utilizar el operador no igual:

x\3<>x/3

Pero si xse garantiza que no es negativo, podemos guardar un byte. La división entera trunca el resultado, por lo que siempre será menor o igual que la división flotante. Por lo tanto, podemos escribir la condición como:

x\3<x/3

Del mismo modo, si xse garantiza que es negativo, el truncamiento aumenta el resultado y podemos escribir x\3>x/3. Si no conoce el signo de x, deberá atenerse <>.

DLosc
fuente
5

Abuso del escáner

Como en muchos idiomas, es importante saber qué caracteres pueden y no pueden eliminarse.

  • Cualquier espacio al lado de un símbolo se puede eliminar: IF""=a$THEN?0
  • El espacio se pueden quitar entre un dígito y una letra que ocurre en ese orden : FOR i=1TO 10STEP 2. Existen algunas diferencias entre QBasic 1.1 (disponible en archive.org ) y QB64 :
    • QBasic 1.1 permite la eliminación de espacio entre cualquier dígito y la siguiente letra. Además, en las declaraciones de impresión, inferirá un punto y coma entre valores consecutivos: se ?123xconvierte PRINT 123; x. Las excepciones a lo anterior son secuencias como 1e2y 1d+3, que se tratan como notación científica y se expanden a 100!y 1000#(precisión simple y doble, respectivamente).
    • QB64 es generalmente el mismo, pero dígitos no puede ser seguido por d, eo fen absoluto a menos que sean parte de un literal notación científica bien formada. (Por ejemplo, no puede omitir el espacio después del número de línea 1 FORo 9 END, como puede hacerlo en QBasic propiamente dicho). Solo infiere punto y coma en las declaraciones de impresión si una de las expresiones es una cadena: ?123"abc"funciona, pero no ?TAB(5)123o ?123x.
  • Hablando de punto y coma, QBasic 1.1 agrega un punto y coma final a una PRINTdeclaración que termina con una llamada a TABo SPC. (QB64 no lo hace)
  • 0puede omitirse antes o después del punto decimal ( .1o 1.), pero no ambos ( .).
  • ENDIFes equivalente a END IF.
  • La comilla doble de cierre de una cadena se puede omitir al final de una línea.
DLosc
fuente
endifen realidad funciona en QB64, mira esta respuesta
wastl
@wastl Así es. Cuando lo probé por primera vez en QB64, estaba usando una versión anterior en la que se trataba de un error de sintaxis. Gracias por mencionar!
DLosc
4

Combinar Nextdeclaraciones

Next:Next:Next

Puede condensarse hasta

Next k,j,i

donde los iteradores para los Forbucles son i, jy k- en ese orden.

Por ejemplo, el siguiente (69 Bytes)

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

Puede condensarse hasta 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

Y en cuanto a cómo esto afecta el formato y la sangría, creo que el mejor enfoque para manejar esto es alinear la siguiente declaración con la declaración más externa. P.ej.

Input n,m,o
For i=0To n
    For j=0To m
        For k=0To o
            ?i;j;k
Next k,j,i
Taylor Scott
fuente
4

Conoce tus métodos de entrada

QBasic tiene varias maneras de conseguir la entrada de teclado del usuario: INPUT, LINE INPUT, INPUT$, y INKEY$.

  • INPUTes su enunciado de entrada multipropósito estándar. El programa detiene lo que está haciendo, muestra un cursor y permite al usuario escribir alguna entrada, terminada por Enter. INPUTpuede leer números o cadenas, y puede leer múltiples valores separados por comas. Puede especificar una cadena como mensaje, puede ir con el mensaje predeterminado de signo de interrogación, e incluso puede (acabo de enterarme esta noche) suprimir el mensaje por completo. Algunas invocaciones de muestra:
    • INPUT x$,y
      Utiliza el ? indicador predeterminado y lee una cadena y un número, separados por comas.
    • INPUT"Name";n$
      Solicita Name? y lee una cadena.
    • INPUT"x=",x
      Aparece con x=(sin signo de interrogación; observe la coma en la sintaxis) y lee un número.
    • INPUT;"",s$
      Suprime la solicitud (usando la sintaxis de coma anterior con una cadena de solicitud vacía), lee una cadena y no se mueve a la siguiente línea cuando el usuario presiona enter (eso es lo que hace el punto y coma después INPUT). Por ejemplo, si PRINT s$inmediatamente después de esto, se verá su pantalla User_inputUser_input.
  • Un inconveniente INPUTes que no puede leer una cadena con una coma, ya que INPUTusa la coma como separador de campo. Para leer una sola línea de caracteres arbitrarios (ASCII imprimible), use LINE INPUT. Tiene las mismas opciones de sintaxis que INPUT, excepto que toma exactamente una variable que debe ser una variable de cadena. La otra diferencia es que LINE INPUTno muestra una solicitud por defecto; si quieres uno, tendrás que especificarlo explícitamente.
  • INPUT$(n)no muestra ningún indicador o cursor, sino que simplemente espera hasta que el usuario ingrese ncaracteres y luego devuelve una cadena que contiene esos caracteres. A diferencia de INPUTo LINE INPUT, el usuario no necesita presionar Enterdespués, y de hecho Enterpuede ser uno de los caracteres (le dará el carácter ASCII 13, conocido como lenguajes tipo C \r).

    Muy a menudo, esto es útil como INPUT$(1), típicamente en un bucle. INPUT$es bueno en programas interactivos donde las pulsaciones de teclas individuales hacen cosas . Desafortunadamente, solo funciona con teclas que tienen códigos ASCII; esto incluye cosas como Escy Backspace, pero no las teclas de flecha, Inserty Delete, y otros.

  • Que es donde INKEY$entra. Es similar a INPUT$(1)que devuelve los resultados de una sola pulsación de tecla 1 , pero diferente en eso:

    • INKEY$ No tiene argumento.
    • Mientras INPUT$(n)detiene la ejecución hasta que el usuario ingrese ncaracteres, INKEY$no detiene la ejecución. Si el usuario está presionando una tecla, INKEY$devuelve una cadena que representa esa tecla; si no, vuelve "". Esto significa que si desea utilizar INKEY$para obtener la siguiente pulsación de tecla, debe envolverlo en un bucle de espera ocupada : 2

      k$=""
      WHILE""=k$
      k$=INKEY$
      WEND
      
    • Ambos caracteres de retorno INPUT$y INKEY$ASCII para claves que corresponden a caracteres ASCII (incluidos los caracteres de control como escape, tabulación y retroceso). Sin embargo, INKEY$también puede manejar algunas claves que no tienen códigos ASCII. Para estos (dice el archivo de ayuda), "INKEY $ devuelve una cadena de 2 bytes compuesta por el carácter nulo (ASCII 0) y el código de escaneo del teclado".

      ¿Claro como el barro? Aquí hay algunos ejemplos. Si utiliza el INKEY$bucle anterior para capturar una pulsación de tecla de la tecla de flecha izquierda, k$contendrá la cadena "␀K"(con el Kcódigo de exploración que representa 75). Para la flecha derecha, es "␀M"(77). Página abajo es "␀Q"(81). F5 es "␀?"(63).

      Todavía claro como el barro? Si. No es la cosa más intuitiva del mundo. El archivo de ayuda tiene una tabla de códigos de escaneo, pero siempre escribo un pequeño programa para imprimir los resultados INKEY$y presiono un montón de teclas para averiguar cuáles son los valores correctos. Una vez que sepa qué caracteres corresponden a qué teclas, puede usar RIGHT$(k$,1)y LEN(k$)distinguir entre todos los diferentes casos que pueda encontrar.

    ¿Línea de fondo? INKEY$es extraño, pero es el único camino a seguir si su programa requiere una entrada sin bloqueo o necesita usar las teclas de flecha .


1 No incluye Shift, Ctrl, Alt, PrntScr, Caps Lock, y similar. Esos no cuentan. : ^ P

2 El WHILE ... WENDmodismo aquí es lo que aprendí en mis libros de QBasic. Para fines de golf, sin embargo, un GOTOcircuito es más corto .

DLosc
fuente
3

LOCATE puede ser realmente poderoso

La LOCATEdeclaración le permite colocar el cursor en cualquier lugar de la pantalla (dentro de los límites habituales de espacio de 80x40 caracteres) e imprimir algo en esa ubicación. Esta respuesta a un desafío realmente muestra esto (y también se combina con muchos otros consejos de este tema).

El desafío nos pide que saquemos todos los caracteres que un usuario ha presionado en una cuadrícula de 16x6. Con LOCATEesto es simplemente una cuestión de div y mod sobre el código ASCII ( aen este código):

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

Y luego imprimiendo el personaje:

?CHR$(a)
Steenbergh
fuente
3

En QBasic, es costumbre usar la DIMdeclaración para crear variables, dándoles un nombre y un tipo. Sin embargo, esto no es obligatorio, QBasic también puede derivar un tipo por el sufijo del nombre de la variable. Como no puede declarar e inicializar una variable al mismo tiempo, a menudo es aconsejable omitir el DIMcodegolf. Dos fragmentos que son funcionalmente idénticos *:

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

* Tenga en cuenta que esto crea dos nombres de variables diferentes.

Podemos especificar el tipo de variable agregando $al final de un nombre de variable para cadenas, !para números de precisión individuales y %para dobles. Se suponen solteros cuando no se especifica ningún tipo.

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

Tenga en cuenta que esto también es válido para las matrices. Por lo general, una matriz se define como:

DIM a(20) AS STRING

Pero las matrices tampoco necesitan DIMmeditar:

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

a$ahora es una matriz para cadenas con 11 ranuras: desde el índice 0 hasta el índice 10 incluido. Esto se hace porque QBasic tiene una opción que permite la indexación basada en 0 y en 1 para matrices. Un tipo de matriz predeterminado es compatible con ambos de esta manera.

¿Recuerdas la matriz de veinte ranuras que DIMmedimos arriba? Eso en realidad tiene 21 ranuras, porque el mismo principio se aplica tanto a las matrices atenuadas como a las no atenuadas.

Steenbergh
fuente
Nunca me di cuenta de que esto también se aplicaba a las matrices. Interesante.
trichoplax
3

IFDeclaraciones de acortamiento

IF las declaraciones son bastante caras, y reducirlas puede ahorrar muchos bytes.

Considere lo siguiente (adaptado de una respuesta de Erik the Outgolfer):

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

Lo primero que podemos hacer es guardar el ENDIFmediante el uso de una IFdeclaración de una sola línea :

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

Esto funciona siempre que no intentes ponerlo en la misma línea que cualquier otra cosa. En particular, si tiene IFdeclaraciones anidadas , solo la más interna puede tener una línea.

Pero en este caso, podemos eliminar IFcompletamente el uso de las matemáticas. Considere lo que realmente queremos:

  • Si RND<.5es verdadero ( -1), queremos:
    • x disminuir en 1
    • y permanecer igual
    • a(i) convertirse en 1
  • De lo contrario, si RND<.5es falso ( 0), queremos:
    • x permanecer igual
    • y disminuir en 1
    • a(i) convertirse en 0

Ahora si salvamos el resultado de la condicional en una variable ( r=RND<.5), podemos calcular los nuevos valores de x, yy a(i):

  • Cuando res -1, x=x-1; cuando res 0, x=x+0.
  • Cuando res -1, y=y+0; cuando res 0, y=y-1.
  • Cuando res -1, a(i)=1; cuando res 0, a(i)=0.

Entonces nuestro código final se ve así:

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

ahorrando la friolera de 20 bytes (40%) sobre la versión original.


El enfoque matemático se puede aplicar sorprendentemente a menudo, pero cuando hay una diferencia en la lógica entre los dos casos (por ejemplo, cuando necesita ingresar algo en un caso pero no en el otro), aún tendrá que usarlo IF.

DLosc
fuente
3

A veces, debes evitar las matrices

Las matrices en QBasic, cuando se instancian sin DIMtener solo 11 espacios. Si un desafío requiere más de 11 ranuras (o N ranuras, donde N puede ser mayor que 11), debe hacer DIMla matriz. Además, supongamos que queremos llenar esta matriz con datos:

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

Incluso jugando al golf, esto puede ocupar mucho espacio. En tales ocasiones, puede ser más barato en bytes hacer esto:

a$ = "value 1value 2"

Aquí, colocamos todo en 1 cadena concatenada. Más tarde, accedemos así:

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

Para este enfoque, es importante que todos los valores tengan la misma longitud. Tome el valor más largo y rellene todos los demás:

a$="one  two  threefour "

No necesita rellenar el último valor, ¡e incluso puede omitir las comillas de cierre! Si el desafío especifica que el espacio en blanco no está permitido en la respuesta, utilícelo RTRIM$()para solucionarlo.

Puedes ver esta técnica en acción aquí .

Steenbergh
fuente
3

PRINT( ?) tiene algunas peculiaridades

Los números se imprimen con un espacio inicial y final.

La impresión agrega un salto de línea. Este comportamiento puede modificarse agregando una coma al final de la instrucción para insertar una pestaña o un punto y coma para evitar cualquier inserción:

No es necesario usar &o ;entre operaciones distintas al imprimir, por ejemplo. ?1"x"s$imprimirá el número 1, con espacios a cada lado, la letra xy el contenido des$

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

Salidas

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

La impresión de un salto de línea se puede hacer con solo ?

Steenbergh
fuente
Específicamente en la impresión de números: se imprime un espacio antes del número si no es negativo; de lo contrario, -se imprime un signo menos allí. También se imprime un espacio después del número. La mejor manera que he descubierto para deshacerme de estos espacios es - no PRINT USINGsé si quieres agregar eso a esta respuesta o si debería ser una respuesta separada.
DLosc
2

WRITE puede ser útil en lugar de PRINT

PRINTgeneralmente es la forma en que querrá hacer la salida, ya que es bastante flexible y tiene el ?acceso directo. Sin embargo, el WRITEcomando puede guardarle bytes en situaciones específicas:

  • Al generar una cadena, la WRITEenvuelve entre comillas dobles ( "). Si necesita resultados con comillas dobles, WRITE s$es mucho más corto que ?CHR$(34);s$;CHR$(34). Ver, por ejemplo, la quina QBasic más corta conocida .
  • Al generar un número, WRITEno agrega espacios antes y después, como lo PRINThace. WRITE nes mucho más corto que ?MID$(STR$(n),2). Ver, por ejemplo, FizzBuzz en QB64 .
  • Al generar múltiples valores, WRITEsepárelos con comas: WRITE 123,"abc"salidas 123,"abc". No puedo pensar en un escenario en el que esto sea útil, pero eso no significa que no haya ninguno.

Limitaciones de WRITE:

  • No hay forma de generar múltiples valores sin un separador como con PRINT a;b.
  • No hay forma de suprimir la nueva línea al final de la salida. (Es posible que pueda solucionar esto LOCATE, pero eso cuesta muchos bytes).
DLosc
fuente
1

A veces, QBasic manipula las entradas a las funciones. Abusa de eso!

Hay un par de funciones que funcionan en caracteres en lugar de cadenas, pero no hay un chartipo de datos en QBasic, solo existe el string ($)tipo. Tomemos por ejemplo la ASC()función, que devuelve el código clave ASCII para un personaje. Si quisiéramos entrar

PRINT ASC("lala")

solo el primero lsería considerado por QBasic. De esta manera, no tenemos que molestarnos en cortar una cuerda hasta la longitud 1.

Otro ejemplo proviene de esta pregunta donde la STRING$()función se usa en una de las respuestas.

La función STRING $ toma dos argumentos, un número ny una cadena s $, y construye una cadena que consta de n copias del primer carácter de s $

@DLosc, aquí

Tenga en cuenta que QBasic, cuando se le ofrece una cadena de caracteres múltiples y necesita solo un carácter, toma automáticamente el primer carácter e ignora el resto.

Steenbergh
fuente