Wskazówki dotyczące gry w golfa w QBasic

13

Jakie masz ogólne wskazówki na temat gry w golfa w QBasic? Szukam pomysłów, które można by zastosować do ogólnych problemów z golfem, które są przynajmniej nieco specyficzne dla QBasic (np. „Usuń komentarze” nie jest odpowiedzią).

Mile widziane są również wskazówki dotyczące emulatora QB64 . Ma kilka dodatkowych funkcji, których nie ma w Microsoft QBasic.

DLosc
źródło
Jestem ciekawa twojej motywacji. Nie korzystałem z QBASIC od czasu mojej 10 klasy programowania. Zadziwiające, jak zapisałem bezpośrednio na dyskietkach 1,44 bez żadnej kontroli wersji i (zwykle) uniknąłem katastrofalnych awarii.
Andrew Brēza,
5
@ AndrewBrēza Motivation? Tak samo jak moja motywacja do gry w golfa w dowolnym języku: dla zabawy! Lubię pisać małe programy w QBasic (choć nie chciałbym używać go do niczego poważnego). Dodatkową zaletą jest to, że ma wbudowany dźwięk i grafikę (zarówno tekstową, jak i pikselową), czego mój ulubiony „prawdziwy” język, Python, nie ma.
DLosc
O wiele łatwiej jest pisać gry graficzne w QBasic niż w Pythonie.
Anush
Jeśli ktoś chce wypróbować graficzne aplikacje QBasic bezpośrednio w przeglądarce, może skorzystać z tego: github.com/nfriend/origins-host
mbomb007

Odpowiedzi:

10

Poznaj swoje konstrukcje zapętlające

QBasic ma kilka konstruktów zapętlenie: FOR ... NEXT, WHILE ... WEND, i DO ... LOOP. Możesz także użyć GOTOlub (w niektórych sytuacjach) RUNdo zapętlenia.

  • FOR ... NEXTjest całkiem dobry w tym, co robi. W przeciwieństwie do Pythona jest prawie zawsze krótszy niż odpowiednik WHILElub GOTOpętla, nawet jeśli robi się trochę bardziej fanatyczny:

    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
    

    Pamiętaj, że nie musisz powtarzać nazwy zmiennej po NEXT, i możesz wyeliminować spację między liczbami a większością następujących słów kluczowych.

  • WHILE ... WENDjest dobre, gdy masz pętlę, która może wymagać wykonania 0 razy. Ale jeśli wiesz, że pętla zostanie wykonana co najmniej raz, GOTOmoże być o jeden bajt krótszy:

    WHILE n>1:n=n\2:WEND
    1n=n\2:IF n>1GOTO 1
    
  • Używam tylko DO ... LOOPdo nieskończonych pętli (z wyjątkiem tego, gdzie RUNzamiast tego można użyć). Chociaż kosztuje tyle samo znaków co bezwarunkowy GOTO, jest nieco bardziej intuicyjny w czytaniu. (Zauważ, że „nieskończona pętla” może obejmować pętle, które można zerwać za pomocą a GOTO.) Składnia DO WHILE/ DO UNTIL/ LOOP WHILE/ LOOP UNTILjest zbyt szczegółowa; lepiej użyć WHILElub GOTOodpowiednio.
  • GOTOjest, jak wspomniano powyżej, najkrótszym ogólnym sposobem na napisanie pętli do / while. Użyj jednocyfrowych numerów linii zamiast etykiet. Zauważ, że gdy a GOTOjest jedyną THENczęścią IFinstrukcji, dostępne są dwie równie zwięzłe składnie skrótów:

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

    GOTOmoże być również wykorzystywany do tworzenia bardziej skomplikowanych przepływów sterowania . Naysayers nazywają to „kodem spaghetti”, ale to jest kod golfowy: nieczytelność jest prawie cnotą! GOTOduma!

  • RUNjest przydatny, gdy trzeba przeskoczyć do ustalonego miejsca w programie i nie trzeba zachowywać żadnej z wartości zmiennych. RUNsam zrestartuje program od góry; z etykietą lub numerem linii, uruchomi się ponownie w tym wierszu. Użyłem go głównie do tworzenia bezstanowych nieskończonych pętli .
DLosc
źródło
5

Użyj skrótów dla PRINTiREM

Możesz użyć ?zamiast PRINTi 'zamiast REM(komentarz).

'może się również przydać przy poliglocie z językami, które obsługują 'jako część składni char lub string.

Uriel
źródło
5

Badanie podzielności

W programach wymagających przetestowania, czy jedna liczba całkowita jest podzielna przez inną, oczywistym sposobem jest użycie MOD:

x MOD 3=0

Ale krótszym sposobem jest użycie podziału na liczby całkowite:

x\3=x/3

Oznacza to, że xint-div 3równa się xfloat-div 3.

Zauważ, że oba te podejścia powrócą 0dla falsey i -1dla prawdy, więc może być konieczne zanegowanie wyniku lub odjęcie go zamiast dodawania.


Jeśli potrzebujesz przeciwnego warunku (tzn. Niex jest on podzielny przez 3), oczywistym podejściem jest użycie operatora nierówności:

x\3<>x/3

Ale jeśli xgwarantujemy, że będzie to nieujemne, możemy zapisać bajt. Podział liczb całkowitych obcina wynik, więc zawsze będzie mniejszy lub równy podziałowi zmiennoprzecinkowemu. Dlatego możemy napisać warunek jako:

x\3<x/3

Podobnie, jeśli xgwarantuje się , że jest ujemna, obcięcie zwiększa wynik i możemy pisać x\3>x/3. Jeśli nie znasz znaku x, musisz się trzymać <>.

DLosc
źródło
5

Nadużycie skanera

Podobnie jak w wielu językach, ważne jest, które znaki można, a których nie można usunąć.

  • Każde miejsce obok symbolu można usunąć: IF""=a$THEN?0
  • Przestrzeń może być zwykle usunięte pomiędzy cyfrą i literą występującą w tej kolejności : FOR i=1TO 10STEP 2. Istnieją pewne różnice między QBasic 1.1 (dostępnym na archive.org ) a QB64 :
    • QBasic 1.1 pozwala na usunięcie spacji między dowolną cyfrą a kolejną literą. Co więcej, w instrukcjach print będzie wstawiał średnik między kolejnymi wartościami: ?123xstaje się PRINT 123; x. Wyjątkami od powyższego są sekwencje podobne do 1e2i 1d+3, które są traktowane jako notacja naukowa i rozszerzone do 100!i 1000#(odpowiednio pojedynczej i podwójnej precyzji).
    • Qb64 jest zazwyczaj taka sama, ale cyfry nie może nastąpić d, elub fw ogóle, chyba że są one częścią dobrze uformowanej notacji naukowej dosłownym. (Na przykład nie można pominąć spacji po numerze wiersza w 1 FORlub 9 END, podobnie jak w QBasic.). Odmienia średniki w instrukcjach print tylko wtedy, gdy jedno z wyrażeń jest ciągiem: ?123"abc"działa, ale nie ?TAB(5)123lub ?123x.
  • Mówiąc o średnikach, QBasic 1.1 dodaje średnik końcowy do PRINTinstrukcji kończącej się wywołaniem TABlub SPC. (QB64 nie.)
  • 0można pominąć przed lub po przecinku ( .1lub 1.), ale nie jednocześnie ( .).
  • ENDIFjest równoważne z END IF.
  • Zamykający podwójny cudzysłów łańcucha można pominąć na końcu wiersza.
DLosc
źródło
endiffaktycznie działa w QB64, zobacz tę odpowiedź
wastl
@wastl Tak się dzieje. Kiedy po raz pierwszy przetestowałem to w QB64, używałem starszej wersji, w której był to błąd składniowy. Dzięki za wzmiankę!
DLosc
4

Połącz Nextwyciągi

Next:Next:Next

Może być skondensowany do

Next k,j,i

gdzie iteratory dla Forpętli są i, ji k- w tej kolejności.

Na przykład poniżej (69 bajtów)

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

Może być skondensowany do 65 bajtów

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

I jeśli chodzi o to, jak wpływa to na formatowanie i wcięcia, myślę, że najlepszym podejściem do rozwiązania tego problemu jest wyrównanie następnej instrukcji z najbardziej zewnętrzną dla instrukcji. Na przykład.

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
źródło
4

Poznaj swoje metody wprowadzania

QBasic ma kilka sposobów, aby uzyskać dane z klawiatury użytkownik: INPUT, LINE INPUT, INPUT$, i INKEY$.

  • INPUTjest standardową instrukcją wejścia wielofunkcyjnego. Program zatrzymuje to, co robi, wyświetla kursor i pozwala użytkownikowi wpisać dane wejściowe, zakończone przez Enter. INPUTpotrafi odczytywać liczby lub ciągi i odczytywać wiele wartości oddzielonych przecinkami. Możesz podać ciąg jako podpowiedź, możesz przejść do domyślnego podpowiedzi ze znakiem zapytania, a nawet (właśnie dowiedziałem się tego dzisiejszego wieczora) całkowicie wyłączyć monit. Niektóre przykładowe wywołania:
    • INPUT x$,y
      Używa domyślnego ? monitu i odczytuje ciąg i liczbę, oddzielone przecinkami.
    • INPUT"Name";n$
      Monituje Name? i odczytuje ciąg.
    • INPUT"x=",x
      Monituje z x=(bez znaku zapytania! Zanotuj przecinek w składni) i odczytuje liczbę.
    • INPUT;"",s$
      Pomija monit (używając powyższej składni przecinka z pustym ciągiem zachęty), odczytuje ciąg i nie przechodzi do następnego wiersza, gdy użytkownik naciśnie klawisz Enter (to INPUTrobi potem średnik ). Na przykład, jeśli PRINT s$natychmiast po tym, twój ekran będzie wyglądał User_inputUser_input.
  • Wadą INPUTjest to, że nie można odczytać ciągu z przecinkiem, ponieważ INPUTużywa on przecinka jako separatora pól. Aby odczytać pojedynczy wiersz dowolnych (drukowalnych ASCII) znaków, użyj LINE INPUT. Ma takie same opcje składni jak INPUT, z tym że pobiera dokładnie jedną zmienną, która musi być zmienną łańcuchową. Inną różnicą jest to, że LINE INPUTdomyślnie nie wyświetla monitu; jeśli chcesz, musisz to wyraźnie określić.
  • INPUT$(n)nie wyświetla monitu ani kursora, ale po prostu czeka, aż użytkownik wprowadzi nznaki, a następnie zwróci ciąg zawierający te znaki. W przeciwieństwie do INPUTlub LINE INPUT, użytkownik nie musi naciskać Enterpóźniej, i w rzeczywistości Entermoże być jednym ze znaków (da znak ASCII 13, znany jako języki podobne do C \r).

    Najczęściej jest to przydatne INPUT$(1), zwykle w pętli. INPUT$sprawdza się w interaktywnych programach, w których działają pojedyncze naciśnięcia klawiszy . Niestety działa tylko z kluczami, które mają kody ASCII; obejmuje to rzeczy takie jak Esci Backspace, ale nie klawisze strzałek, Inserti Delete, i inne.

  • Który jest tam, gdzie INKEY$przychodzi. Jest podobny do INPUT$(1)tego, że zwraca wyniki pojedynczego naciśnięcia klawisza 1 , ale różni się tym, że:

    • INKEY$ nie przyjmuje żadnych argumentów.
    • Podczas gdy INPUT$(n)zatrzymuje wykonywanie, dopóki użytkownik nie wprowadzi nznaków, INKEY$nie wstrzymuje wykonywania. Jeśli użytkownik aktualnie naciska klawisz, INKEY$zwraca ciąg znaków reprezentujący ten klawisz; jeśli nie, zwraca "". Oznacza to, że jeśli chcesz użyć INKEY$następnego naciśnięcia klawisza, musisz zawinąć go w pętlę zajętości : 2

      k$=""
      WHILE""=k$
      k$=INKEY$
      WEND
      
    • Zarówno INPUT$i INKEY$znaków ASCII powrót do kluczy, które odpowiadają znaków ASCII (w tym znaków sterujących, takich jak ucieczka, zakładki, i Backspace). Jednak INKEY$może również obsługiwać niektóre klucze, które nie mają kodów ASCII. Dla nich (mówi plik pomocy) „INKEY $ zwraca 2-bajtowy ciąg złożony ze znaku null (ASCII 0) i kodu skanowania klawiatury”.

      Czyste jak błoto? Oto kilka przykładów. Jeśli użyjesz INKEY$powyższej pętli do przechwycenia naciśnięcia klawisza lewej strzałki, k$będzie ona zawierać ciąg znaków "␀K"(z Kreprezentującym kodem skanowania 75). Dla prawej strzałki to "␀M"(77). Strona w dół to "␀Q"(81). F5 to "␀?"(63).

      Nadal czysty jak błoto? Tak. To nie jest najbardziej intuicyjna rzecz na świecie. Plik pomocy zawiera tabelę kodów skanowania, ale zawsze po prostu piszę mały program do drukowania wyników INKEY$i naciskam kilka klawiszy, aby dowiedzieć się, jakie są właściwe wartości. Kiedy już wiesz, które znaki odpowiadają danym klawiszom, możesz używać RIGHT$(k$,1)i LEN(k$)rozróżniać wszystkie różne przypadki, które możesz napotkać.

    Dolna linia? INKEY$jest dziwne, ale jest to jedyna droga, jeśli Twój program wymaga nieblokujących danych wejściowych lub używa klawiszy strzałek .


1 nie w tym Shift, Ctrl, Alt, PrntScr, Caps Lock, i podobne. Te się nie liczą. : ^ P

2 Ten WHILE ... WENDidiom jest tym, czego nauczyłem się w moich książkach QBasic. Dla celów golfa jednak pętla jest krótszy .GOTO

DLosc
źródło
3

LOCATE może być naprawdę potężny

LOCATEZestawienie pozwala umieszczać nigdzie kursora na ekranie (w zwykłym 80x40 ograniczeń przestrzennych znaków) i wydrukować coś w tym miejscu. Ta odpowiedź na wyzwanie naprawdę to pokazuje (i jest również połączona z wieloma innymi wskazówkami z tego tematu).

Wyzwanie wymaga od nas wypisania każdej postaci, którą użytkownik nacisnął w siatce 16 x 6. Ze LOCATEjest to po prostu kwestia div i mod na kodzie ASCII ( aw tym kodzie):

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

A następnie wydrukowanie znaku:

?CHR$(a)
Steenbergh
źródło
3

W QBasic zwyczajowo używa się DIMinstrukcji do tworzenia zmiennych, nadając im nazwę i typ. Jednak nie jest to obowiązkowe, QBasic może również wyprowadzić typ poprzez sufiks nazwy zmiennej. Ponieważ nie można jednocześnie deklarować i inicjalizować zmiennej, często rozsądnie jest pominąć DIMkodek. Dwa fragmenty, które są funkcjonalnie identyczne *:

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

* Uwaga: powoduje to utworzenie dwóch różnych nazw zmiennych.

Możemy określić typ zmiennej, dodając $na końcu nazwy zmiennej dla ciągów, !liczb pojedynczej precyzji i %podwójnych. Zakłada się single, gdy nie określono żadnego typu.

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

Zauważ, że dotyczy to również tablic. Zwykle tablica jest definiowana jako:

DIM a(20) AS STRING

Ale tablice również nie muszą być DIMmed:

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

a$jest teraz tablicą dla łańcuchów z 11 miejscami: od indeksu 0 do włącznie z indeksem 10. Jest to zrobione, ponieważ QBasic ma opcję, która umożliwia indeksowanie tablic zarówno w oparciu o 0, jak i w oparciu o 1. Domyślna tablica obsługuje oba te sposoby.

Pamiętasz tablicę dwudziestu gniazd, którą DIMwymieniliśmy powyżej? To faktycznie ma 21 gniazd, ponieważ ta sama zasada dotyczy zarówno przyciemnionych, jak i nieściemnionych tablic.

Steenbergh
źródło
Nigdy nie zdawałem sobie sprawy, że dotyczy to również tablic. Ciekawy.
trichoplax
3

IFOświadczenia skracające

IF wyciągi są dość drogie, a ich gra w golfa pozwala zaoszczędzić wiele bajtów.

Zastanów się, co następuje (na podstawie odpowiedzi Erika the Outgolfer):

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

Pierwszą rzeczą, którą możemy zrobić, to zapisać ENDIFza pomocą IFinstrukcji jednowierszowej :

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

Działa to tak długo, jak nie próbujesz umieścić go w tym samym wierszu, co cokolwiek innego. W szczególności, jeśli masz zagnieżdżone IFinstrukcje, tylko najbardziej wewnętrzna instrukcja może być jednowierszowa.

Ale w tym przypadku możemy IFcałkowicie wyeliminować matematykę. Zastanów się, czego tak naprawdę chcemy:

  • Jeśli RND<.5jest to prawda ( -1), chcemy:
    • x zmniejszyć o 1
    • y pozostać niezmienionym
    • a(i) zostać 1
  • W przeciwnym razie, jeśli RND<.5jest to false ( 0), chcemy:
    • x pozostać niezmienionym
    • y zmniejszyć o 1
    • a(i) stać się 0

Teraz, jeśli mamy zachować wynik warunkowego w zmiennej ( r=RND<.5), możemy obliczyć nowe wartości x, yoraz a(i):

  • Kiedy rto -1, x=x-1; kiedy rto 0, x=x+0.
  • Kiedy rto -1, y=y+0; kiedy rto 0, y=y-1.
  • Kiedy rto -1, a(i)=1; kiedy rto 0, a(i)=0.

Nasz końcowy kod wygląda następująco:

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

oszczędność aż 20 bajtów (40%) w stosunku do oryginalnej wersji.


Metodę matematyczną można zaskakująco często stosować, ale gdy istnieje różnica w logice między tymi dwoma przypadkami (np. Gdy musisz wprowadzić coś w jednym przypadku, ale nie w drugim), nadal będziesz musiał użyć IF.

DLosc
źródło
3

Czasami należy unikać tablic

Tablice w QBasic, gdy są tworzone bez, DIMmają tylko 11 miejsc. Jeśli wyzwanie wymaga więcej niż 11 miejsc (lub N miejsc, gdzie N może być większy niż 11), powinieneś mieć DIMtablicę. Załóżmy również, że chcemy zapełnić tę tablicę danymi:

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

Nawet w golfa może to zająć dużo miejsca. W takich przypadkach bajty mogą być tańsze:

a$ = "value 1value 2"

Tutaj umieszczamy wszystko w 1 połączonym ciągu. Później uzyskujemy do niego dostęp w następujący sposób:

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

W tym podejściu ważne jest, aby wszystkie wartości były jednakowej długości. Wybierz najdłuższą wartość i wypisz wszystkie pozostałe:

a$="one  two  threefour "

Nie musisz wpisywać ostatniej wartości, a nawet możesz pominąć cudzysłowy zamykające! Jeśli wyzwanie określa, że ​​białe znaki nie są dozwolone w odpowiedzi, użyj, RTRIM$()aby to naprawić.

Można zobaczyć tę technikę w akcji tutaj .

Steenbergh
źródło
3

PRINT( ?) ma pewne dziwactwa

Liczby są drukowane z wiodącą i końcową spacją.

Drukowanie dodaje podział linii. To zachowanie można zmienić, dodając przecinek na końcu instrukcji, aby zamiast tego wstawić tabulator, lub średnik, aby uniknąć wstawiania:

Podczas drukowania nie jest konieczne używanie &ani ;wykonywanie różnych operacji, np. ?1"x"s$wypisze numer 1ze spacjami po każdej stronie, literę xi treśćs$

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

Wyjścia

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

Drukowanie podziału linii można wykonać za pomocą just ?

Steenbergh
źródło
W szczególności przy drukowaniu liczb: spacja jest drukowana przed liczbą, jeśli jest nieujemna; w przeciwnym razie -drukowany jest tam znak minus . Spacja jest również drukowana po numerze. Najlepszym sposobem, jaki odkryłem, aby pozbyć się tych przestrzeni jest - nie PRINT USINGwiem, jeśli chcesz dodać to do tej odpowiedzi lub powinna to być osobna odpowiedź.
DLosc
2

WRITE mogą być przydatne zamiast PRINT

PRINTjest zwykle sposobem, w jaki chcesz robić dane wyjściowe, ponieważ jest dość elastyczny i ma ?skrót. Jednak WRITEpolecenie może zaoszczędzić bajty w określonych sytuacjach:

  • Podczas wyprowadzania łańcucha, WRITEzawija go w podwójne cudzysłowy ( "). Jeśli potrzebujesz wyniku z podwójnymi cudzysłowami, WRITE s$jest znacznie krótszy niż ?CHR$(34);s$;CHR$(34). Zobacz na przykład najkrótszą znaną quinę QBasic .
  • Podczas wypisywania liczby WRITEnie dodaje spacji przed i po niej, jak to PRINTrobi. WRITE njest znacznie krótszy niż ?MID$(STR$(n),2). Zobacz na przykład FizzBuzz w QB64 .
  • Podczas wyprowadzania wielu wartości, WRITEoddziel je przecinkami: WRITE 123,"abc"wyjścia 123,"abc". Nie mogę wymyślić scenariusza, w którym byłoby to przydatne, ale to nie znaczy, że nie ma takiego.

Ograniczenia WRITE:

  • Nie ma sposobu na wyprowadzenie wielu wartości bez separatora takiego jak z PRINT a;b.
  • Nie ma sposobu, aby ukryć znak nowej linii na końcu wyniku. (Być może możesz to obejść LOCATE, ale to kosztuje dużo bajtów.)
DLosc
źródło
1

Czasami QBasic zmienia dane wejściowe do funkcji. Nadużywaj tego!

Istnieje kilka funkcji, które działają na znakach zamiast ciągów, ale charw QBasic nie ma string ($)typu danych, istnieje tylko typ. Weźmy na przykład ASC()funkcję, która zwraca kod ASCII dla znaku. Jeśli wejdziemy

PRINT ASC("lala")

tylko pierwszy lbyłby brany pod uwagę przez QBasic. W ten sposób nie musimy zawracać sobie głowy przycinaniem sznurka do długości 1.

Kolejny przykład pochodzi z tego pytania, w którym STRING$()funkcja jest używana w jednej z odpowiedzi.

Funkcja STRING $ pobiera dwa argumenty, liczbę n oraz ciąg s $ i konstruuje ciąg składający się z n kopii pierwszego znaku s $

@DLosc tutaj

Zauważ, że QBasic, gdy oferowany jest ciąg zawierający wiele znaków i potrzebuje tylko jednego znaku, automatycznie przyjmuje pierwszy znak i ignoruje resztę.

Steenbergh
źródło