[Assembler] Gra Snake (wąż) w asemblerze pod procesor 8086 (architektura x86)

Gra Snake napisana w asemblerze pod procesor 8086. Gra ta wykorzystuje tryb tekstowy 80x25 gdzie rysujemy znaki ASCII po pamięci. Gra była kompilowana TASM i linkowana TLINK. W związku z tym, że gra korzysta z trybu DOSa to należy ją odpalić pod emulatorem Dosbox http://www.dosbox.com/ Po zainstalowaniu Dosbox tworzymy sobie na dysku katalog: C:Assembler Wrzucamy do niego kod naszej gry w pliku snake.asm oraz program TASM i TLINK. TASMa i TLINK musimy sobie pobrać z Internetu.

Gra Snake napisana w asemblerze pod procesor 8086. Gra ta wykorzystuje tryb tekstowy 80x25 gdzie rysujemy znaki ASCII po pamięci.

Gra była kompilowana TASM i linkowana TLINK.

W związku z tym, że gra korzysta z trybu DOSa to należy ją odpalić pod emulatorem Dosbox http://www.dosbox.com/

Po zainstalowaniu Dosbox tworzymy sobie na dysku katalog: C:Assembler Wrzucamy do niego kod naszej gry w pliku snake.asm oraz program TASM i TLINK. TASMa i TLINK musimy sobie pobrać z Internetu.

Uruchamiamy emulator Dosbox. Musimy zamontować katalog z naszą grą wpisując poniższe polecenia: Każda nowa linia to kolejne polecenie które wpisujemy w oknie Dosbox. Ostatnie polecenie uruchamia naszego snake’a. [code=plain]mount C C:Assembler C: tasm snake tlink snake snake[/code]

Gra oferuje różne tryby szybkości poruszania się węża oraz możliwość losowania dodatkowych owoców na planszy. Standardowo na planszy cały czas są dwa czerwone owoce. Klawiszem 4 zwiększamy o jeden liczbę owoców na planszy. Klawisz 5 dodaje nam pięć owoców na planszę. Pozycja każdego kolejnego owocu jest losowana przy kolejnym ruchu węża o jedno pole. Klawisz 6 ustawia nam liczbę owoców na standardową czyli na dwa. Oznacza to, że dopiero gdy snake zje owoce znajdujące się na planszy to ponownie będą losowane kolejne owoce tak aby zawsze na planszy znajdowały się max dwa owoce.

Gra jest szczegółowo opisana komentarzami także nie powinno być problemów ze zrozumieniem kodu. Ogólnie mechanika gry opiera się na tym, że snake zapisywany jest w tablicy będącej kolejką. Każda nowa pozycja głowy węża jest dopisywana na początku kolejki zaś z jej końca pobierany jest adres ogona węża w którego miejsce wstawiany jest znak podłoża po którym porusza się wąż.

Gra kończy się w momencie gdy dotkniemy żółtej ramki planszy lub gdy wąż wjedzie na samego siebie. A także w momencie gdy zawrócimy węża o 180 stopni.

Poniżej kod gry który należy zapisać do pliku [b]snake.asm[/b] [code=plain] ;################################################################################################################### ;# Copyright (c) 2011 Webook.pl ;# The MIT License ;# Gra: Snake 2011 ;################################################################################################################### ;# Notacja w kodzie: ;# ;# pNR__NazwaProcedury ;# * p - Każda nazwa procedury zaczyna się od litery p ;# * NR - oznacza numer procedury ;# * __ - wymagane dwa podkreślenia w nazwie procedury ;# * NazwaProcedury - nazwa naszej procedury ;# ;# pNR1_petla_NR2 ;# * pNR1 - numer procedury w której występuje pętla ;# * petla - stałe oznaczenie, że to pętla ;# * NR2 - numer pętli występującej w procedurze pNR1 ;# ;# petla_Nazwa [Są to oznaczenia dla pętli umieszczonych globalnie] ;# * petla_ - oznaczenie ze to pętla ;# * Nazwa - nazwa dla pętli ;# ;# e_pNR1_NR2 ;# * e_ - oznacza, że to etykieta ;# * pNR1 - numer pętli w której występuje etykieta ;# * NR2 - numer etykiety występującej w pętli pNR1 ;# ;# e_Nazwa [Są to oznaczenie dla etykiet globalnych] ;# * e_ - oznaczenie, że to etykieta ;# * Nazwa - nazwa naszej etykiety ;# ;# z_Nazwa ;# * z_ - prefix oznaczający zmienną ;# * Nazwa - nazwa naszej zmiennej ;# ;###################################################################################################################

; Stos stos1 segment STACK dw 1024 dup(?) ; podwójne słowo, dup-duplikat ?-brak określonej wartości, pojemność 1024 szczyt label word stos1 ends

dane segment PUBLIC ; PUBLIC - segmenty o tej samej nazwie łączone są w jeden ciągły segment. Wszystkie adresy w segmencie są łączone względem jego początku.

;=== Zmienne	
; 0Dh, 0Ah - znaki nowej linii rn
z_txt_intro db "Webook.pl | 2011 Gra Snake", 0Dh, 0Ah, 0Dh, 0Ah
			db "ESC - wyjscie", 0Dh, 0Ah
			db "Strzalki - start gry, ruch wezem w dana strone", 0Dh, 0Ah, 0Dh, 0Ah				
			db " _______    _          _______    _          _______ ", 0Dh, 0Ah
			db "(  ____   ( (    /|  (  ___  )  |     /  (  ____ ", 0Dh, 0Ah
			db "| (    /  |    ( |  | (   ) |  |    / /  | (    /", 0Dh, 0Ah
			db "| (_____   |    | |  | (___) |  |  (_/ /   | (__    ", 0Dh, 0Ah
			db "(_____  )  | ( ) |  |  ___  |  |   _ (    |  __)   ", 0Dh, 0Ah
			db "      ) |  | |    |  | (   ) |  |  (     | (      ", 0Dh, 0Ah
			db "/____) |  | )    |  | )   ( |  |  /     | (____/", 0Dh, 0Ah
			db "_______)  |/    )_)  |/     |  |_/    /  (_______/", 0Dh, 0Ah, 0Dh, 0Ah
			db "1 - mala predkosc weza", 0Dh, 0Ah
			db "2 - srednia predkosc weza", 0Dh, 0Ah
			db "3 - duza predkosc weza", 0Dh, 0Ah
			db "4 - zwieksz limit owocow o jeden", 0Dh, 0Ah
			db "5 - zwieksz limit owocow o 5", 0Dh, 0Ah
			db "6 - ustaw limit owocow na 2", 0Dh, 0Ah, 0Dh, 0Ah
			db "Aby rozpoczac nacisnij dowolny klawisz...$"
	
; Długośc węża. Typ dw czyli słowo (16 bajtów ponieważ może wąż urosnąć do maksymalnie 1794 (cała plansza zarysowana. Hipotetyczna sytuacja)) 
z_dlugosc_weza dw 1  ; Domyślnie jeden ponieważ na początku taką długość ma wąż
z_odczekaj dw 0 ; Zmienna przechowujaca czas odczekania
z_kierunek db 0 ; Kierunek ruchu węża
; Kody strzałek: 75-lewo 77-prawo 72-gora 80-dol
z_lewo db 75d
z_prawo db 77d
z_gora db 72d
z_dol db 80d
z_esc db 27d
z_wynik db 0Dh, 0Ah, "KONIEC", 0Dh, 0Ah, "Zdobyles punktow: $"
z_szybkosc db 4 ; określa szybkość węża. Im mniejsza tym szybciej się wąż porusza
z_owocow_na_planszy dw 0 ; informuje ile jest owocow na planszy. Gdy będą 2 to nie będziemy dalej losować kolejnych
z_losowa1 dw 0 ; losowa liczba
z_losowa2 dw 0
z_limit_owocow dw 2 ; Ustalamy ile owocow moze byc na planszy jednoczesnie

;=== Stałe
s_kolor_ramki equ 14d ; żółty kolor dla ramki planszy
s_kolor_podloza equ 10d ; zielony jasny
s_kolor_weza equ 15d ; biały
s_kolor_owoc equ 12d ; czerwony	
s_ascii_podloze equ 176d ; kod ASCII podłoża (trawa)
s_ascii_waz equ 219d ; kod ASCII kratka zamalowana


;=== tablice
; Tablica będzie przechowywała położenie każdego z klocków będących częścią węża. 
; (80-2)*(25-2)=1796 Maksymalne zarysowanie planszy przez węża. Pomijamy ramki czyli odejmujemy dwa
; Tablice wypełniamy zerami.
; Pierwszy element tablicy to głowa węża, każdy kolejny to 
s_tab_limit equ 1794d ; Stały limit wielkosci tablicy
tab_waz dw s_tab_limit dup(0) 
; UWAGA: Opis działania
; Tablica będzie działać jak kolejka. Podczas ruchu węża w miejsce indexu 0 wstawiany będzie numer położenia głowy węża na ekranie.
; Dlasze części węża będą po prostu przesunięte w tablicy o jeden element w górę. Ostatni element będzie kasowany przy każdym kolejnym ruchu.
; Naprościej mówiąc przy każdym kolejnym ruchu węża elementy jego położenia będą się przesuwać o jeden index w górę.
; Na index 0 w tablicy wchodzi element do kolejki, a z ostatniego indexu wychodzi element z kolejki.
; W przypadku gdy wąż zje owoc to nie będzie kasowany ostatni element tablicy dzięki czemu wąż urośnie o jeden element.
; Do kontrolowania długości węża wykorzystamy zmienną pomocniczą z_dlugosc_weza
; UWAGA##### Tablica typu dw dlatego drugi element jest pod indexem 2. Będziemy skakać co dwa indexy!!

dane ends

; AT - segmenty z tym połączeniem są ładowane ; od adresu wskazanego przez wyrażenie numeryczne ; w naszym przypadku pod adresem 0B8000h znajduje ; się początek pamięci ekranu ekran segment AT 0B800h ek db ? atr db ? ekran ends

; Segment kodu code segment ; PRIVATE

assume cs:code,ds:dane,es:ekran,ss:stos1

start:
	mov	ax,seg dane ;ładujemy rejestry segmentowe DS i ES
	mov	ds,ax
	mov ax,seg ekran ;pamięć ekranu do ES
	mov	es,ax
	;mov	ax,seg code
	mov ax,stos1
	mov ss,ax
	mov sp,offset szczyt
	
	MOV	AH,0 ;wybór trybu pracy monitora
	MOV	AL,3 ;text 80x25 kolor
	INT	10H	;obsługa ekranu	
	
	
	; Ukrywamy migotający kursor
	mov ah, 1
	mov ch, 2bh
	mov cl, 0bh
	int 10h
	
	
	mov	dx,offset z_txt_intro
	mov	ah,9h ; wypisanie tekstu intro	
	int	21h	
	
	call p2__KolorujIntro ; wywołanie procedury kolorującej napis SNAKE w intro
			
	; Czeka na naciśnięcie dowolnego klawisza
	mov ah, 00h
	int 16h
	
			
	; Rysujemy plansze
	call p1__RysujPlansze
	
	
	
	; Rysujemy węża w początkowym miejscu
	mov	ek[2000],219d ; Znak ASCII blok zamalowany 
	mov atr[2000],s_kolor_weza
	
	
	call p4__LosujOwoc
	
	
	
	
	
	;### Naciśnięcie ESC spowoduje wyjście z gry
	;### Nacisnięcie strzałek powoduje start gry
	;### Jeśli naciśniemy inny klawisz to gra czeka aż naciśniemy któryś z wyżej wymienionych
	e_UruchomGre:
		mov ah,08h ; Pobiera znak z klawiatury. Będzie zapisany w AL
		int 21h ; przerwanie
		
		cmp al,z_esc ; jeśli znak to ESC to wychodzimy z programu
		; Tutaj musilismy wykonać trik z stworzeniem pomostu w skoku. Ponieważ skok "je" i inne mu podobne są krótkimi skokami
		; Powodowało to że nie można było z tego miejsca skoczyć do końca programu (relative jump out of range)
		; Dlatego wykonano pomost i użyto skoki jmp
		je e_pomost_jmp 
			jmp e_pomin_pomost
		e_pomost_jmp:
		jmp e_KoniecProgramu
		e_pomin_pomost:
		
		; Kody strzałek: 75-lewo 77-prawo 72-gora 80-dol
		cmp al,z_lewo ; strzłka w lewo	
		je e_StartWeza
		cmp al,z_prawo ; strzłka w prawo	
		je e_StartWeza
		cmp al,z_gora ; strzłka w górę	
		je e_StartWeza
		cmp al,z_dol ; strzłka w dół	
		je e_StartWeza
					
		; Jeśli doszliśmy do tego kroku to znaczy że nie przechwycono klawisza ESC ani strzałek zatem ponownie oczekujemy na naciśnięcie klawisza
		jmp e_UruchomGre 
	
	
	
	;### Wąż zaczyna ruch. Ten blok wykona się tylko przy rozpoczęciu gry
	e_StartWeza:		
		; Jeśli wskoczylismy tu z góry gdzie mamy wybór kierunku ruchu węża przy rozpoczęciu gry to znaczy że w AL mamy wartość strzałki określającej 
		; w którą stronę zaczyna ruch wąż. Zatem ładujemy do z_kierunek wartość wybranej strzałki
		mov z_kierunek,al 
		; Do pierwszego elementu tablicy ładujemy położenie początkowe naszego węża			
		mov tab_waz[0],2000d ; Jest to położenie głowy. Głowa zawsze jest pod indexem 0 w tablicy
					
	
	e_RuchWeza: ; etykieta do której skaczemy gdy wąż jest już w ruchu
		
		mov di,tab_waz[0] ; di zawiera numer pozycji w ktorej narysowana jest głowa węża
									
		mov al,z_kierunek ; ładujemy z_kierunek do AL żeby móc wykonac porównanie.
			
		cmp al,z_lewo
		jne e_pomin1 ; Jeśli nie wciśnięto lewo to skaczemy do następnego porównania
			; Ten blok się wykona jeśli wciśnieto lewo
			sub di,2 ; 
		e_pomin1:
		
		cmp al,z_prawo
		jne e_pomin2
			add di,2
		e_pomin2:
		
		cmp al,z_gora
		jne e_pomin3
			sub di,160 ; odejmujemy 160 aby przesunąc się o jeden wiersz w górę
		e_pomin3:
		
		cmp al,z_dol
		jne e_pomin4
			add di,160 ; dodajemy 160 aby przesunąc się o jeden wiersz w dół
		e_pomin4:
		
		
		push di
		push ax
		push cx
		
		; wywołujemy procedure która sprawdzi czy wąż wjechał na niedozwolony element ramke planszy
		; lub jeśli wjechał na owoc to powoduje to wydluzenie węża
		call p3__SprawdzZdarzenie
		
		; wywołujemy procedure losujaca owoc
		call p4__LosujOwoc
		
		pop cx
		pop ax
		pop di
		
		
	
	e_PobierzKlawisz:
		mov ah,01h ; Sprawdzamy czy naciśnięto klawisz
		int 16h
		jz e_NieWybranoKlawisza ; Skok jeśli liczba równa zero, czyli jeśli nie naciśnięto żadnego klawisza

		mov ah,00h
		int 16h

		cmp al,z_esc ; Porównujemy do klawisza ESC 
		je e_KoniecProgramu ; Jeśli naciśnięto ESC to skaczemy do wyjścia z gry 
		
		; Jeśli naciśnięto klawisze 1,2,3 to zmieniamy szybkość węża
		cmp al,31h
		je e_szyb_1				
		cmp al,32h
		je e_szyb_2			
		cmp al,33h
		je e_szyb_3
		
		; Jeśli naciśnięto 4 to zwiekszamy limit owocow na planszy o jeden
		; jeśli 5 to zwiekszamy limit o 5
		; jeśli naciśnięto 6 to resetujemy limit owocow na 2
		cmp al,34h ;4 - zwiekszamy limit owocow o 1
		je e_limit_owocow1				
		cmp al,35h ;5 - zwiekszamy limit owocow o 5
		je e_limit_owocow2	
		cmp al,36h ;6 - resetujemy limit owocow na 2
		je e_limit_owocow3
		
		
		mov z_kierunek,ah ; ładujemy numer naciśniętego klawisza do zmiennej informującej o kierunku ruchu
		jmp e_NieWybranoKlawisza ; skaczemy do etykiety aby pominac ustawienia szybkosci jesli nie skoczono do nich wczesniej
		
		
		; Obsługa zmiany szybkosci ruchu węża
		e_szyb_1:
			mov z_szybkosc,4 ; wolno
			jmp e_NieWybranoKlawisza
		e_szyb_2:
			mov z_szybkosc,2 ; średnia prędkość
			jmp e_NieWybranoKlawisza
		e_szyb_3:
			mov z_szybkosc,1 ; szybko
			jmp e_NieWybranoKlawisza
			
			
		; Obsługa zmiany limitu owocow na planszy
		e_limit_owocow1:
			add z_limit_owocow,1 ; dodajemy jeden
			jmp e_NieWybranoKlawisza
		e_limit_owocow2:
			add z_limit_owocow,5
			jmp e_NieWybranoKlawisza
		e_limit_owocow3:
			mov z_limit_owocow,2
			jmp e_NieWybranoKlawisza	
			
		
	e_NieWybranoKlawisza:



	; Opóźnienie. Cykl 18,2 na sekundę
	mov ah,00h
	int 1ah
	cmp dx,z_odczekaj
	jb e_PobierzKlawisz ; jb - skok gdy jest poniżej (CF=1)
	xor cx,cx
	mov cl,z_szybkosc ; Dzięki tej zmiennej będziemy mogli zmieniać sobie szybkość węża
	add dx,cx
	mov z_odczekaj,dx
	
	
	jmp e_RuchWeza ; Powtarzamy ruch węża skacząc do etykiety e_RuchWeza
	
	
	

	
	
	;mov	dx,offset tekst1
	;mov	ah,9h ; wypisanie tekstu (DOS)		
	;int	21h	; funkcja DOS
		
	;int 16h ; Wymaga naciśnięcia dowolnego klawisza aby przejść dalej
	
	e_KoniecProgramu:	
		mov	ax,4c00h;koniec programu (DOS)
		int	21h






;###################################################################################################################
;### Procedury
;### ASCII http://www.asciitable.com/

;=== Rysowanie obramowania planszy =================================================================================
p1__RysujPlansze proc
	
	;=== Ustawiamy limit ekranu, limit ile razy się pętla wykona
	mov	cx,80*25
	
	
	;=== Rysujemy tło planszy które będzie podłożem (trawa). Tło to będzie później zamalowane przez ramki planszy
	mov	si,0
	p1_petla_1:	
		mov	ek[si],s_ascii_podloze ; kod ASCII dla kropeczek, które będą udawac trawę
		mov atr[si],s_kolor_podloza ; zielony jasny
		add si,2 ; zwiększamy index o dwa
	loop p1_petla_1
	
	
	
	;=== Rysujemy górną i dolną linie obramowania planszy				
	mov	si,0 ; zaczynamy rysowanie od indexu zero
	p1_petla_2:

		; rysowanie górnej poziomej linii
		cmp si,160d
		jge e_p1_1 ; jeśli rysujemy już powyżej 80 znaku to pomijamy rysowanie (jeśli si jest większe lub równe 160 (dlatego równe bo rysujemy od 0))
			mov	ek[si],219d ; Znak ASCII blok zamalowany 				
			mov atr[si], s_kolor_ramki ; ładujemy żółty kolor
		e_p1_1:
		
		; rysowanie poziomej linii na dole ekranu
		cmp si,3840d ; 2*80*25 = 2*2000 = 4000  (od 4000 odjąc jedną linijkę tekstu 80*2 czyli 4000-160=3840)
		jnge e_p1_2 ; skok gdy nie jest większy od
			mov	ek[si],219d ; Znak ASCII blok zamalowany 
			mov atr[si],s_kolor_ramki ; ładujemy żółty kolor
		e_p1_2:
					
		add si,2 ; zwiększamy index o dwa poniważ 1-to znak ASCII a 2-to atrybut
	loop p1_petla_2
	
	
	
	;=== Rysujemy linie ponową z lewej strony
	; 23 ponieważ będziemy rysować pionową linie z pominięciem już narysowanego jednego znaku u góry i dołu ekranu
	mov	cx,23 ; limit wywołania pętli.
	mov si,160
	p1_petla_3:
		mov	ek[si],219d ; Znak ASCII blok zamalowany 
		mov atr[si],s_kolor_ramki ; ładujemy żółty kolor			
		add si,160 ; przesuwamy się o jeden cały wiersz
	loop p1_petla_3
	
	
	
	;=== Rysujemy linie ponową z prawej strony
	; 23 ponieważ będziemy rysować pionową linie z pominięciem już narysowanego jednego znaku u góry i dołu ekranu
	mov	cx,23 ; limit wywołania pętli.
	mov si,318 ; zaczynamy rysowanie od 318 czyli od drugiego wiersza i ostatniego znaku w nim. (160 + 158)
	p1_petla_4:
		mov	ek[si],219d ; Znak ASCII blok zamalowany 
		mov atr[si],s_kolor_ramki ; ładujemy żółty kolor			
		add si,160 ; przesuwamy się o jeden cały wiersz
	loop p1_petla_4
	
	

	ret ; Powrót 
p1__RysujPlansze endp





;=== Rysowanie obramowania planszy =================================================================================
p2__KolorujIntro proc

	mov	cx,80*8 ; limit wywołania pętli. Ustawiamy na 8 bo chcemy ustawic kolor dla osmiu wierszy (każdy wiersz to 80 znaków czyli 160 bo mamy atrybut)
	mov si,5*160 ; zaczynamy rysowanie od 5 wiersza bo tam zaczyna się napis SNAKE
	mov al,1d ; ładujemy kolor do AL
	p2_petla_1:
		
		; Kolor wiersza 0 jest ustawiony powyżej w AL. Pierwsza linia znaków która rysuje duzy napis SNAKE
		
		cmp si,6*160
		je e_p2_kolor_wiersza_1 ; jeśli si równe 6*160 to możemy zmienić kolor przechowywany w al
		cmp si,7*160
		je e_p2_kolor_wiersza_2
		cmp si,8*160
		je e_p2_kolor_wiersza_3
		cmp si,9*160
		je e_p2_kolor_wiersza_4
		cmp si,10*160
		je e_p2_kolor_wiersza_5
		cmp si,11*160
		je e_p2_kolor_wiersza_6
		cmp si,12*160
		je e_p2_kolor_wiersza_7
		cmp si,13*160
		je e_p2_kolor_wiersza_8
		
	
		; Przeskakujemy ustawienia kolorów. Jeśli powyżej nie nastąpił skok do zmiany koloru w rejestrze AL to pomijamy zmiane kolorów 
		; skacząc do e_p2_1 i używając koloru aktualnie przechowywanego w rejestrze AL.
		; Dzięki takiemu zabiegowi nie będziemy ustawiać rejestru AL na ten sam kolor przy każdym ponowym ustawianiu atrybutu danego znaku na ekranie
		jmp e_p2_1 
	
	
		; Po ustawieniu danego koloru w AL przeskakujemy do jego ustawienia na ekranie
		e_p2_kolor_wiersza_1:
			mov al,2d
			jmp e_p2_1 
		e_p2_kolor_wiersza_2:
			mov al,3d
			jmp e_p2_1
		e_p2_kolor_wiersza_3:
			mov al,4d
			jmp e_p2_1
		e_p2_kolor_wiersza_4:
			mov al,5d
			jmp e_p2_1
		e_p2_kolor_wiersza_5:
			mov al,6d
			jmp e_p2_1
		e_p2_kolor_wiersza_6:
			mov al,7d
			jmp e_p2_1
		e_p2_kolor_wiersza_7:
			mov al,8d
			jmp e_p2_1
		e_p2_kolor_wiersza_8:
			mov al,9d
			jmp e_p2_1
	
		
		e_p2_1:		
			mov atr[si],al ; ładujemy żółty kolor			
			add si,2 ; przesuwamy się o dwa czyli o jedną literkę
		
	loop p2_petla_1
	
	ret ; Powrót
p2__KolorujIntro endp






;=== Funkcja sprawdzająca czy dotknięto ściany lub owocu =======================================================================
; Jeśli dotknięto owocu to następuje wydłużenie węża.
; Funkcja ponadto obsługuje ruch węża i zapis jego położenia w kolejce opartej o tablicę tab_waz
p3__SprawdzZdarzenie proc

	; W di zapisany jest numer w którym postawiona będzie głowa węża. Sprawdzimy czy uderzy o ścianę
	
	cmp atr[di],s_kolor_ramki
	jne p3_pomin1 ; jeśli kolor klocka na który wstawić chcemy węża nie jest koloru ramki to przeskakujemy
		jmp p3_uderzono_w_przeszkode ; jesli wjechalismy na	ramke
	p3_pomin1:
	cmp atr[di],s_kolor_weza 
	jne p3_pomin2 ; jeśli kolor klocka na który wstawić chcemy węża nie jest koloru węża to przeskakujemy
		jmp p3_uderzono_w_przeszkode
	
	
	p3_uderzono_w_przeszkode:
		mov	dx,offset z_wynik
		mov	ah,9h ; wypisanie tekstu o końcu gry.	
		int	21h	
		
		xor ax,ax
		mov ax,z_dlugosc_weza ; ładujemy do ax dlugosc weza
		push ax ; wkładamy na stos ax
		call p5__DrukujLiczbe ; wywołujemy procedure wypisujaca wynik liczbowy
		
		
		; Czeka na naciśnięcie dowolnego klawisza
		mov ah, 00h
		int 16h
		
		jmp e_KoniecProgramu
		
		
	p3_pomin2:
	
	; Tutaj napisac sprawdzanie najechania na owoc. Ma to spowodowac ze nie wywola sie kasowanie ogona
	xor bx,bx
	; w bl będziemy przechowywac informacje czy nastąpiło zjedzenie owoca. 
	; Jeśli tak to inna będzie wywoływana pętla.
	; 0-oznacza ze nie zwiekszono dlugosci weza		
	mov bl,0 
	
	cmp atr[di],s_kolor_owoc
	jne p3_pomin3 ; jeśli nie stawiamy głowy węża na owocu to skaczemy do etykiety
		; Ten blok wykona się gdy postawiliśmy głowę węża na owocu
		; Dodajemy zwiększamy więc długość węża
		add z_dlugosc_weza,1
		mov bl,1 ; ustawiamy flagę z informacją że zwiększono długość węża bo zjedzono owoc			
		
	p3_pomin3:
		
		
		;############
		; Gdy wąż dłuższy niż 1 i nie zwiększono jego długości
		cmp z_dlugosc_weza,1
		jle e_pomin_war1_; skok gdy jest mniejszy lub rowny 1
			jmp e_pomin_war1_blok
		e_pomin_war1_: ; pomost aby wykonac dlugi skok
			jmp e_pomin_war1
			e_pomin_war1_blok:
			; Ten blok się wykona gdy wąż ma długość większą od 1
			cmp bl,0
			jne e_pomin_war2 ; skocz gdy bl nie jest rowne 0
				;###### Ten blok wykona się gdy bl=0 czyli gdy nie zjedzono owocu
				; Przenosimy elementy w tablicy o jeden index w górę, a w miejsce indexu=0 wstawimy nową głowę węża
				xor cx,cx ; czyścimy cx
				; ile razy ma się wykonać pętla. Jest to ilość operacji przeniesienia elementów w tablicy która jest naszą kolejką
				; Dlatego -1 bo nie przenosimy ostatniego elementu nigdzie dalej gdyz ma byc on nadpisany przez przedostatni element
				mov cx,z_dlugosc_weza
				sub cx,1
				
				; si będziemy zmniejszać co krok
				; -4 poniewaz ostatni element tablicy nie bedzie przenoszony poniewaz jest on kasowany jesli nie najechano na owoc. A minus 4 ponieważ tablica zaczyna się od indexu 0 i skaczemy co dwa indexy aby dostac sie do kolejnego elementu
				mov si,z_dlugosc_weza
				add si,z_dlugosc_weza ; dwukrotna dlugosc zapisujemy poniewaz idziemy po tablicy co 2 indexy
				sub si,4

				; Kasowanie ostatniego elementu z planszy
				; odbywa się przed operacją przeniesienia elementów w tablicy o jeden index w górę
				; ponieważ w czasie przeniesienia tych elementow przedostatni element przykrywa ostatni i nie mielibysmy pozniej mozliwosci odczytania jakie polozenie ma ogon weza
				push di
				xor di,di
				mov di,tab_waz[si+2] ; +2 poniewaz wyzej mielismy -4 i to bylo wybranie przedostatniego elementu a my potrzebujemy ostatni element w tablicy
				mov	ek[di],s_ascii_podloze ; Znak ASCII podłoża planszy
				mov atr[di],s_kolor_podloza															
				pop di
				
				p3_petla_1:						
											
					xor ax,ax
					mov ax,tab_waz[si] ; przedostatni element
					mov tab_waz[si+2],ax ; ostatni element przyjmuje wartosc poprzedniego
									
					sub si,2
				loop p3_petla_1
				
				
				
				
				jmp e_pomin_a ; po zakonczonej operacji skaczemy aby pominac bloki ponizsze
			e_pomin_war2:
		
		
			cmp bl,1
			jne e_pomin_a ; skocz gdy bl nie jest rowne 1
				;###### Ten blok wykona się gdy bl=1 czyli gdy zjedzono owoc
				
				sub z_owocow_na_planszy,1 ; zjedlismy owoc wiec odejmujemy z licznika
				
				; Przenosimy elementy w tablicy o jeden index w górę, a w miejsce indexu=0 wstawimy nową głowę węża
				xor cx,cx ; czyścimy cx
				; ile razy ma się wykonać pętla. Jest to ilość operacji przeniesienia elementów w tablicy która jest naszą kolejką
				; Dlatego -1 bo nie przenosimy ostatniego elementu nigdzie dalej gdyz ma byc on nadpisany przez przedostatni element
				mov cx,z_dlugosc_weza
				sub cx,1
				
				; si będziemy zmniejszać co krok
				; -4 poniewaz ostatni element tablicy nie bedzie przenoszony poniewaz jest on kasowany jesli nie najechano na owoc. A minus 4 ponieważ tablica zaczyna się od indexu 0
				mov si,z_dlugosc_weza
				add si,z_dlugosc_weza ; dwukrotna dlugosc zapisujemy poniewaz idziemy po tablicy co 2 indexy
				sub si,4 
				
				p3_petla_2:	
											
					xor ax,ax
					mov ax,tab_waz[si] ; przedostatni element
					mov tab_waz[si+2],ax ; ostatni element przyjmuje wartosc poprzedniego
					
					sub si,2
				loop p3_petla_2
				
				jmp e_pomin_a
		
		e_pomin_war1:
		
		
		
		;###############
		; Poniższy kod wykonuje się gdy wąż ma długość 1
		cmp z_dlugosc_weza,1
		jne e_pomin_a ; jeśli nie jest rowne 1
			; Ten blok wykona się gdy wąż ma długość 1
			push di ; odkladamy di na stos
			mov di,tab_waz[0] ; pobieramy numer pozycji starego polozenia glowy weza
			; wstawiamy podloze w miejsce starego polozenia glowy
			mov	ek[di],s_ascii_podloze ; Znak ASCII podłoża planszy
			mov atr[di],s_kolor_podloza
			pop di ; zdejmujemy ze stosu di, ktory nam wskazuje nowe polozenie głowy węża
			mov tab_waz[0],di ; zapisujemy glowe weza w tablicy
			jmp e_pomin_b			
		
		e_pomin_a:
			; do pierwszego elementu tablicy ładujemy nowe położenie glowy weza
			mov tab_waz[0],di
			
		e_pomin_b:
		
		; Rysujemy głowe węża
		mov	ek[di],s_ascii_waz ; Znak ASCII
		mov atr[di],s_kolor_weza
	

	ret
p3__SprawdzZdarzenie endp








;=== Funkcja losująca położenie owoca na planszy =======================================================================
; Jeśli wylosowane pole jest już zajęte to ponawiamy losowanie az trafimy 
; na puste pole czyli na podloze na ktorym bedziemy mogli postawic owoc
p4__LosujOwoc proc
	
	mov si,z_limit_owocow
	cmp z_owocow_na_planszy,si
	jge p4_pomin ; jeśli na planszy jest wiecej owocow lub tyle samo ile wynosi limit to pomijamy generowanie kolejnych owocow
	
	p4_start: ; rozpoczynamy losowanie owoca
		
		mov ah,2ch 
		int 21h ; Pobieramy czas
		; CH=godziny CL=minuty DH=sekundy DL=1/100s
		
		xor ax,ax
		mov al,dl
		; UWAGA tutaj losujemy tylko do 80 poniewaz dl moze max przyjac wartosc 100.
		; Pozniej sobie zwiekszymy dwukrotnie ta wartosc
		cmp dl,80
		jle p4_pomin2; jeśli jest mniejsze lub rowne
			; Ten blok wykona sie gdy dl ma wartosc wieksza od 80
			; Odejmiemy wtedy 20 aby miec dlugosc jednego wiersza
			sub al,20
		p4_pomin2:
		mov z_losowa1,ax ; w zmiennej zapisalismy liczbe z zakresu od 0 do 80
		add ax,z_losowa1 ; teraz ax bedzie dwukrotnoscia losowej liczby wynoszacej max 80
		mov z_losowa1,ax ; ladujemy do losowej liczby dwukrotnosc jej
		
		
		mov ah,2ch 
		int 21h ; Pobieramy czas
		
		xor ax,ax
		mov al,dl
		; UWAGA tutaj zastosowalismy odrazu 50 czyli 2x25 poniewaz dzieki temu mozemy odjac 50 od wylosowanej wiekszej liczby niz 50 i mamy pewnosc ze liczba ta jest wieksza od zera
		cmp dl,50
		jle p4_pomin3; jeśli jest mniejsze lub rowne
			; Ten blok wykona sie gdy dl ma wartosc wieksza od 50
			; Odejmiemy wtedy 50 aby miec losowy numer kolumny
			sub al,50
		p4_pomin3:
		mov z_losowa2,ax ; w zmiennej zapisalismy liczbe z zakresu od 0 do 80
		
		
		mov ax,z_losowa1
		mov bx,z_losowa2
		mul bx
		; wynik w ax
		mov si,ax
		
		cmp atr[si],s_kolor_podloza
		jne p4_start ; jeśli nie wylosowalismy podloza to skaczemy do gory. Będziemy tak długo losowac az postawimy owoc na podłożu
		
		; malujemy owoc
		mov	ek[si],219d ; Znak ASCII blok zamalowany 
		mov atr[si],s_kolor_owoc
		
		add z_owocow_na_planszy,1 ; gdy juz postawimy owoc to zwiększamy flagę o jeden
	p4_pomin:
	
	ret
p4__LosujOwoc endp




;=== Procedura wypisujaca liczbe na ekran =======================================================================
p5__DrukujLiczbe PROC
	ARG liczba:BYTE = PARAMETR
	push bp ; zapisujemy stara wartosc bp na stosie
	mov bp,sp ; do bp ladujemy sp
	xor ax,ax ; czyscimy
	xor cx,cx
	mov cl,10 ; ustawiamy na 10 ponieważ chcemy wyświetlić wynik w liczbach dziesiętnych
	mov di,0 ; 
	mov al,liczba
	p5_skok:
	div cl ; AL=(AX div cl), AH=(AX mod cl)
	xor dh,dh
	mov dl,ah
	add dl,30h ; dodajemy 30h aby były to liczby ASCII
	push dx ; dx wrzucamy na stos.
	inc di ; zwiększamy o jeden
	xor ah,ah
	cmp al,0
	jne p5_skok ; jeśli nie jest równe 0 to skaczemy do góry
	mov cx,di
	p5_skok2:
		pop dx ; zdejmujemy dx ze stosu. Będziemy go drukować
		mov ah,2 ; Funkcja 2 powoduje wypisanie wyniku na standardowe wyjście
		int 21h ; przerwanie 21h
	loop p5_skok2
	pop bp ; zdejmujemy wartosc ze stosu i ladujemy do bp
	ret PARAMETR
p5__DrukujLiczbe ENDP

code ends

end start

[/code]