256 Bajtów - Lizard


      Prawdę powedziawszy, znudziły mi się już intra 256B i mam ochotę trochę pograć. Ponieważ jestem osobą towarzyską, to w ramach kontaktów interpersonalnych chętnie pograłbym z kimś innym. Mam tylko jeszcze jedno życzenie: gra musi być krótka, tak by szybko się ładowała. Najlepiej jakby jej długość nie przekraczała jedengo sektora w DD. Czy to możliwe?

      Jasne. Chyba nikt nie miał wątpliwości. :-) Gierka, której źródło tu prezentuję jest już bardzo stara. Jej tytuł - Tron - znany jest chyba wszystkim pokoleniom zapaleńców gier komputerowych (może z wyjątkiem tych najmłodszych wychowujących się na multimedialnych Qwakach nieudolnie udających rzeczywistość). Zasady gry są bardzo proste: zajechać przeciwnikowi drogę i nie być samemu zajechanym. Należy przy tym uważac, by nie wjechać w publiczność stojącą za bandą. Przegrywa ten, kto się nie wykaże refleksem i spostrzegawczością słabszą od przeciwnika, czyli wjedzie na ślad pozostawiony przez drugiego gracza bądź własny, lub spróbuje uciec poza ekran. Po skończonej rundzie program czeka na wciśnięcie jednego z dwóch klawiszy: START lub SHIFT. Ten drugi powoduje powrót do DOS'u. Tak, tak, wrócimy do systemu. Długość (a raczej krótkość) kodu nie usprawiedliwia braku możliwości wyjścia z programu w sposób elegancki, a nie przez jakiś tam reset.

      Po skończonej rundzie możemy wcisnąć START, by zagrać jeszcze raz. Jednak obaj przeciwnicy zaczynają od miejsca, w którym zakończyli wcześniejszą rozgrywkę. To samo tyczy się kierunku jazdy. Wyjątkiem jest zdeżenie z bandą, które powoduje, że dany gracz zaczyna od tego miejsca, z którego startował za pierwszym razem.

      Po tym przydługawym, aczkolwiek koniecznym wstępie przystąpmy teraz do analizy programu:

 prog   equ $8800   adres programu
 z_page equ $80     adres zmiennych

 * Zmienne, rejestry, ... systemowe

 rtclock  equ $12
 iccomz   equ $22
 icax1z   equ $2a
 icax2z   equ $2b
 rowcrs   equ $54
 colcrs   equ $55
 jstick0  equ $0278
 colpf1s  equ $02c5
 colpf2s  equ $02c6
 runad    equ $02e0

 scrvec   equ $e410

 consol   equ $d01f
 audf1    equ $d200
 audc1    equ $d201
 skstat   equ $d20f
 wsync    equ $d40a

 bit      equ $2c

Kod rozkazu BIT abs, który w zdecydowanej większości przypadków używany jest niezgodnie z przeznaczeniem. :-)
 * Zmienne na stronie zerowej

      org z_page

 y0     org *+1        ; Pozycje
 y1     org *+1        ; graczy
 x0     org *+1
 x1     org *+1
 dir0   org *+1        ; Kierunki ruchu
 dir1   org *+1
 player org *+1        ; Numer gracza

 * Poczatek programu

      org prog

        ldx #$05      Inicjacja strony
 setzp  lda zp,x      zerowej
        sta z_page,x
        dex
        bpl setzp

new     lda #$07      Graphics 7
        jsr gr_opn

Włączenie trybu graficznego połączone jest z jednoczesnym ustawieniem koloru 1 dla operacji PLOT/DRAW oraz zapaleniem pixla o współrzędnych 0,0.

Do etykiety NEW następuje skok, po kolizji i naciśnięciu klwaisza START.

        lda #$9f      Drawto 159,0
        jsr drawto
        dey           Drawto 159,95
        lda #$5f
        jsr drawto
        lda #$00      Drawto 0,95
        jsr drawto
        dey           Drawto 0,0
        tya
        jsr drawto

Rysowanie ramki. Zwracam uwagę na fakt wykorzystywania wartości zwracanych przez system! Jeśli coś pójdzie nie tak, to zamiast ramki ujrzymy pięny czarny ekran i to w jednej ramce.
        lda #$c6      Setcolor 1,12,6
        sta colpf1s
        lda #$38      Setcolor 2,3,8
        sta colpf2s

Ustawiamy kolory graczy. Przy okazji ukłon w stronę właścicieli monitorów mono - gracze różnią się nie tylko kolorem, ale równierz jasnością.
 * Petla glowna

 m_loop lda rtclock+2  Pause 2
        adc #$02
 mlw    cmp rtclock+2
        bne mlw

Wpierw czekamy 3 ramki, inaczej gra toczyć się będzie za szybko. Ponieważ znacznik C jest ustawiony, to ADC #$02 dodaje w rzeczywistości 3. Pause 2 w komentarzu nie jest pomyłką!
        ldx #$02     Player 1
        jsr chkjoy
        bne boom     Z = 0 - kolizja

Wpierw zajmujemy się pierwszym graczem (tym zielonym/ciem niejszym).
        ldx #03       Player 2
        jsr chkjoy
        beq m_loop

Następnie drugim.

      W rejestrze X przekazujemy numer koloru zawodnika, który jest jednocześnie wskaźnikiem w tablicach współrzędnych i kierunku powiększonym o dwa.

      Gdy po powrocie z CHKJOY znacznik Z jest ustawiony i w akumulatorze siedzi zero, znaczy to, że nikt nikogo ani w nic nie stuknął i można grać dalej. W przeciwnym wypadku...

 boom   lsr @         Buuum!!!
        bne sound
        ldx player
 bmlp   lda zp-2,x
        sta y0-2,x
        inx
        inx
        cpx #$08
        bcc bmlp

Sprawdzamy, czy A = 1. Jeśli tak, to zderzenie z bandą i trzeba przywrócić pierwotną pozycję gracza.
 sound  lda #$8f      Wybuch:
        sta audc1
        ldx #$01      For X=1 To 70
 slp    stx audf1       Sound 0,X,8,15
        lda rtclock+2   Pause 0
 w      cmp rtclock+2
        beq w
        inx           Next X
        cpx #$50
        bcc slp
        dey           Sound 0,0,0,0
        sty audc1
        sty audf1

Gra bez dzwięku niewiele jest warta. Zderzeniu towarzyszy odgłos wybuchu.
 w_key  lda consol  Klawiszem Start
        lsr @       ruchamiamy grę
        bcc new     jeszcze raz,
        lda skstat  a Shiftem wracamy
        and #$08    do systemu.
        bne w_key

 gr_opn sta icax2z   Graphics 0
        lda #$0c
        sta icax1z
        ldx #$00
        jsr go_scr
        bit icax2z
        bne put_it
        rts

      Ponieważ wywołanie procedury GR_OPN poprzedzone jest załadowaniem do akumulatora numeru trybu, a tym samym ustawieniem znacznika Z, to wykorzystamy to do sprawdzenia, czy mamy wyjść z gry. Włączenie trybu 0 równoważne jest z żądaniem opuszczenia programu (rozkaz BIT ICAX2Z rozwiązuje dylemat, w którą stronę idziemy).

Teraz będzie główny podprogram. Podzielony on jest na części:

  • sprawdzenie stanu wychylenia joystick'a,
  • przesunięcie gracza w odpowiednim kierunku,
  • sprawdzenie, czy nastąpiło zderzenie,
  • "narysowanie" gracza na nowej pozycji.
Do procedury tej przekazuje się w X kolor gracza (2 lub 3).
 * Procedury i funkcje

 chkjoy sei           Kierunek joy'a
        lda #$00
 dirlp  lsr jstick0-2,x
        bcc dfnd
        adc #$3f
        bcc dirlp
        dta b(bit)
 dfnd   sta dir0-2,x
        cli

      Wpierw sprawdzamy wychylenie joy'a. Robi to pętla wysuwająca pojedyńcze bity z rejestru JSTICKx. Gdy któryś jest skasowany, to nastąpiła zmiana kierunku i do zmiennej DIRx wpisywany jest nowy kierunek aktualizowany na bieżąco w akumulatorze (ADC #$3F). Jeśli wszystkie bity są zapalone, to rejestr DIRx pozostaje bez zmian. Ponieważ cała operacja przeprowadzana jest na komórce aktualizowanej przez drugą fazę VBL, to dezaktywujemy ją rozkazem SEI, a później przywracamy.
 move   lda dir0-2,x
        asl @
        bcs horiz
        bpl chu

Teraz określamy kierunek w jakim ma być poruszany gracz.
        inc y0-2,x    ...w dół
        dta b(bit)

 chu    dec y0-2,x    ...w górę
        bcc chkcol    BRA (C = 1)

 horiz  bmi chr
        dec x0-2,x    ...w lewo
        dta b(bit)

 chr    inc x0-2,x    ...w prawo

Po ruchu zawodnikiem nadszedł czas, by sprawdzić, czy przypadkiem na coś się nie wgramolił:
 chkcol lda y0-2,x     Pobranie pozycji
        sta rowcrs     zawodnika.
        lda x0-2,x
        sta colcrs
        stx player
        ldx #$04
        jsr go_scr  Sprawdzenie.
        pha          A - kolor pixala o
                     wsółrzędnych nowej
                     pozycji gracza.

Ostania część procedury: postawienie zawodnika na nowej pozycji.
        ldy player
        lda x0-2,y
        ldx y0-2,y
        jsr putpxl
        ldx player
        pla
        rts

      Pobieramy do Y kolor zawodnika, a do A i X wędrują jego wsółrzędne i następuje zapalenie pixla. Na koniec odtwarzamy stary kolor pixla, na którym właśnie postawiliśmy gracza ustawiając tym samym flagę kolizji (znacznik Z).
 drawto sta rowcrs,y  Rysowanie linii.
        lda #$11
        sta iccomz
        ldx #$0a
        bne go_scr

      To powyżej to wywołanie procedury rysującej na początku programu pole gry. Rysowanie odbywa się w taki sam sposób jak w Basicu: od bieżącej pozycji kursora do pozycji podanej w parametrach (tu w COLCRS i ROWCRS). Ponieważ jedna ze wsółrzędnych jest już ustowina, to w A podajemy brakującą składową pozycji, a w Y określamy, która to jest (rzędna/odcięta). Kod $11 oznacza DRAW, a $12 - FILL.
 putpxl sta colcrs   Zapalenie na ekra-
        stx rowcrs   nie pixla  o wsół-
 put_it ldx #$06     rzędnych  w A, X i
                     kolorze w Y.

 go_scr lda scrvec+1,x  Wykonanie  ope-
        pha             racji  na ekra-
        lda scrvec,x    nie.
        pha
        tya
        rts

      Ten kawałek powinien wszystkim wyjaśnić ładownie do rejestru X dziwnych liczb przed wywołaniami działań na ekranie. Pod adresem wskazywanym przez SCRVEC znajduje się tablica wektorów do procedur obsługi ekranu. Ponieważ wektory te zmnejszone są o jeden, to najwygodniej korzystać jest z nich poprzez odłożenie ich na stos i wykonanie RTS. Ponieważ procedury te kończą się również RTSem, to należy wywoływać je z podprogramu. Opis tablicy wektorów znajduje się na końcu tego tekstu.
 * Zmienne poczatkowe

 zp     dta b(48),b(48)
        dta b(19),b(139)
        dta b($c0),b($80)

      A to są wartości zmiennych umieszczanych docelowo na stronie 0. W pierwszej linii umieszczone są początkowe pozycje Y graczy, w drugiej - X, a w ostatniej kierunki ruchu graczy.
       org runad
       dta a(prog)
       end

      Nadszedł czas na omówienie tabeli wektorów urządzeń CIO umieszczonej pod adresem $E400. Cała tablica podzielona jest na pięć mniejszych tabel odopowiedzialnych za standardowe urządzenia zewnętrzne: E:, S:, K:, P:, C:. Ponieważ dla każdego urządzenia tablica wygląda tak samo, opiszę ją na przykładzie ekranu, który wykorzystywany jest w naszej gierce.

Offset
Nazwa
Opis
+$00
VSCOPN
Otwarcie ekranu. W ICAX1Z ($2A) tryb:
  • $04 - odczyt,
  • $08 - zapis,
  • $0C - wymiana danych
W ICAX2Z ($2B) tryb graficzny (dodać $10, gdy z oknem tekstowym, +$20 - pamięć ekranu nie będzie czyszczona)
+$02
VSCCLS
Zamknięcie ekranu. Raczej użyteczne tylko dla CIO.
+$04
VSCGTCH
Pobranie znaku (numeru koloru) z pozycji określonej w COLCRS i ROWCRS. Kod ATASCII znaku lub numer rejestru koloru zwracany jest w akumulatorze.
+$06
VSCPTCH
Wyświetlenie znaku lub zapalenie pixla na pozycji (COLCRS; ROWCRS) o kodzie ATASCII (kolorze) w A.
+$08
VSCSTAT
Pobranie statusu. W przypadku edytora, ekranu i klawiatury zwracany jest w akumulatorze kod ostatnio przesłanego znaku, a w Y numer błędu ostaniej operacji.
+$0A
VSCSPEC
Operacja specjalna. ICCOMZ - kod operacji, pozostałe pola ZIOCB ($20-$2F) zgodnie z wymogami żądanej operacji. Dla ekranu należy umieścić współrzędne "końca" operacji w COLCRS i ROWCRS.
+$0C
JPOWON
Skok do POER ON inicjującej edytor, klawiaturę i ekran. Jest to rezerwacja na przyszłość.
+$0F
---
Zero dla wyrównania długości tablicy do szesnastu bajtów.

      Jeśli chcemy wysłać jeden bajt na dane urządzenie można to zobić przez tę właśnie tablice, choć bardziej wskazane jest skorzystać z wektora, również zmniejszonego o jeden, umieszczonaego w bloku IOCB otwartego kanału ($0346+16*kanał). Będziemy mieć wtedy dostęp do ewentualnych rozszerzeń, czy ulepszeń systemu (np. Quick Ed).

      I to już koniec całej zabawy, mam nadzieję, że dotrwaliście do końca.

Lizard/BBSL

PS. Termin "Intro 256B" oznacza długość PLIKU, nie kodu. Niestety, niektórzy udają, że o tym nie wiedzą.