Variables

z80 » Intermediate

Say your game is a racing game. You have to know what speed the car is going at. You don't want to designate c to hold the speed because you need to use it for calculations all over the game code. This is where variables come in handy.

Temporary Trash

Values that you only need during game play. Once the game is over (player dies or exits), you don't care what happens to that data. An example of this is the coordinates of a ball bouncing on the screen. You usually need the x coordinate and the y coordinate. Those both would be stored in two separate bytes usually.

Before you ever begin to start to use game data, you need to define the address in memory that you are refering to, or else the assembler thinks you're crazy. This is similiar to the defined addresses for calls in ti86asm.inc. If you're not familiar with the equate directive, glance at the Assembler Directives Section. There are plenty of areas you can store data to in memory during program execution; check out the Memory and ROM Pages section for some of those addresses. After you know all that stuff, the next example should be clear. We're going to save the speed of our car already in a into the byte at car_speed and exit.

car_speed	=_textShadow	;address in memory ($c0f9)
save_speed:
	ld (car_speed),a	;save speed
	ret			;exit routine

You probably already understood this concept before I ever covered it. Here's another example of storing the player's current score in a Tetris type game. We're goint to store the score already in hl at memory address current_score designated in our program's memory. We need to save the money into 2 bytes (a word) this time since we know ahead of time that it's possible to get more than 255 (the highest possible value for a byte).

save_current_score:		;routine to save money
	ld (current_score),hl	;save score
	ret			;exit routine

high_money:	.db 0,0		;2 bytes to save score value
				; in (could have used .dw)

Game Saved

Let's say the player just got a high score in Galaxian by Patrick Davidson. They most definitely want to have it saved so they can come back later and try to beat it.

We need to save that high score among our program data like the last example under Temporary Trash. There's a difference though. You'll might understand this more once you've gone through the TI-OS Variables Sections and have understood how the TI-OS variables work on the calculator. When you go to run a program, the TI86 gets that program and copies it to a designated area used only for running assembly programs. The _asm_exec_ram is the address ($d748) where the program is copied to. The name of the program currently running is stored in _asapvar ($d6fc) in Variable Name Format. TI-OS will execute your program at _asm_exec_ram until finished. It will then return the the caller (either the homescreen, the TI-BASIC program that ran the assembly program, or the assembly program that called the program). What TI-OS doesn't do is copy the program back to it's original location in memory, so if you got a high score and saved it somewhere in the program, that wouldn't be saved for next time. You have to manually copy that high score data back the the original program location (or from whence it came) or it will be overwritten the next time any assembly program is run.

You have two choices of data storage:

  1. Resident - The data is stored in memory allocated to the actual program.
  2. External - The data is specific to that calculator and not be transferable to another calculator.

Resident

Resident data storage is data inside the actual program. My game Addiction is an example of this. Just before exiting, the program copies the data such as cash, drugs, ammunition, bank account, and health back to where they were in the program. This technique is called Write Back. Here's a snippet of the code in Addiction that copies the data.
_asapvar		=$d6fc
_set_abs_src_addr	=$4647
_set_abs_dest_addr	=$5285
_set_mm_num_bytes	=$464f
_mm_ldir		=$52ed
save_game:
	ld hl,_asapvar			;name of current program running
	rst 20h				;copy to OP1
	rst 10h				;_findsym
	ld a,b				;returns bde as prog data
	ld hl,data_start-_asm_exec_ram+4
					;offset in this program for start
					; of saved game data
					;2 bytes for prog length
					;2 bytes for asm identifier bytes
	add hl,de			;hl=pointer to data in original
					; prog
	adc a,$00			;in case we overlapped pages
					;if carry was set in previous
					; instruction...this will add 1 to
					; a else...it adds 0 to a
	call _set_abs_dest_addr		;set that as the absolute
					; destination address to copy to
	xor a				;no absolute addressing now
	ld hl,data_start		;*get data from here
	call _set_abs_src_addr		;set that as the absolute source
					; address to copy from
	ld hl,data_end-data_start	;*number of bytes to save
	call _set_mm_num_bytes		;set that as the number of bytes
					; to copy
	call _mm_ldir			;perform absolute address data
					; block copy
	jp _clrScrn			;clear screen and exit
data_start:				;start of data to copy
saved_game_data:	.db 0,0,0,0,0	;drugs
saved_health:		.db 5		;health (start out at 5)
saved_ammo:		.db 0		;ammo
saved_bank:		.dw 0		;bank account
saved_cash:		.dw 20		;cash on hand
data_end:

The routine locates where the actual program is in memory, adds the offset of the data to work with to the start of the absolute address of the program, and then performs an absolute address data block copy before exiting.

This routine is adaptable to any program of yours. All you need to change are the lines of code with the * in the comments to wherever the start and finish of your program data is. There one limitation to this method: you can't copy more than 255 bytes of memory.

External

External data storage is a simple concept: Store the data in a variable other than the program running under a name that TI-OS doesn't pick up in variable searches. For this section, I'll be refering to two programs in the External Zip:
  1. Create.86p - Creates the hidden variable and loads it with the data.
  2. Access.86p - Accesses the hidden variable and prints the contents in ASCII on the screen.
  3. %data.86s - Hidden string containing the characters "AB".

Hidden variables are made by placing an invalid variable name character in the variable's name, such as % or $. When TI-OS is searching for variables to display in the Memory Delete Screen, it won't detect these hidden variables because they are not valid names. TI-OS will skip right over them in all of its variable detection routines, except the Free RAM Screen. These variables take up memory just as any regular variable would. When TI-OS calculates how much Free RAM you have left, it takes the address of the last byte in RAM being used and subtracts the start of the RAM to get the length of the RAM. These hidden variables are counted in that space.

The first program to run is Create.86p. It will create a new string variable named %data.86p that is hidden because of the invalid character "%" in the name. I have commented all the code.

_createstrng		=$472f	;all our equates needed in
_ex_ahl_bde		=$45f3	; the program
_ahl_plus_2_pg3		=$4c3f
_set_abs_dest_addr	=$5285
_set_abs_src_addr	=$4647
_set_mm_num_bytes	=$464f
_mm_ldiR		=$52ed

#include "ti86asm.inc"

.org _asm_exec_ram

	ld hl,string-1		;copy anything before string for
				; type byte
	rst 20h			;call _Mov10toOP1
	rst 10h			;call _FindSym to see if variable
				; exists
	call nc,_delvar		;delete variable if it exists
				;all necessary info still preserved
	ld hl,string_end-string_start	;minus start from end of
					; string so result is length
					; of string
	call _createstrng	;$472f	create string
	call _ex_ahl_bde	;$45f3	ABS bde & ahl swapped
	call _ahl_plus_2_pg3	;$4c3f	increase ABS ahl by two
	call _set_abs_dest_addr	;$5285	set that as destination for
				; block copy
	xor a			;AKA ld a,0
	ld hl,string_start	;hl points to string_start
				;address of string is in relation
				; to 16 bit and ram page
	call _set_abs_src_addr	;$4647	set that as source for
				; block copy
	xor a			;AKA ld a,0 -it's on already swapped
				; in page (0)
	ld hl,string_end-string_start	;length of string
	call _set_mm_num_bytes	;$464f	set # of bytes to copy in
				; block copy
	jp _mm_ldir		;$52ed	ABS block copy
				;we jump instead of calling
				; and returning this way we just use
				; _mm_ldir's ret as ours
				;saves 1 byte

string:		.db 5,"%data"	;length indexed string name
string_start:	.db "AB"	;two bytes storage
string_end:

We now have a string named %data containing two characters A and B. We need to set up the program that will access that hidden variable and display the contents.

_ex_ahl_bde		=$45f3	;all our equates needed in
_ahl_plus_2_pg3		=$4c3f	; the program
_set_abs_dest_addr	=$5285
_set_abs_src_addr	=$4647
_set_mm_num_bytes	=$464f
_mm_ldiR		=$52ed
#include "ti86asm.inc"
.org _asm_exec_ram

	call _homeup		;cursor coordinates at
				; top left
	ld hl,name-1		;
	rst 20h			;copy 10 bytes from hl to op1
	rst 10h
	call _ex_ahl_bde
	call _ahl_plus_2_pg3	;hl points to start of
			;data in program

	call _set_abs_src_addr	;$5285	set that as destination
				; for block copy
	sub a			;AKA ld a,0
	ld hl,data		;hl points to data's start address
				;address of string is in
				; relation to 16 bit and ram page
	call _set_abs_dest_addr	;$4647	set that as source for
				; block copy
	xor a			;AKA ld a,0 -it's on already
				; swapped in page (0)
	ld hl,2			;length of data
	call _set_mm_num_bytes	;$464f	set # of bytes to
				; copy in block copy
	call _mm_ldir		;$52ed	ABS block copy

	ld de,data
	ld a,(de)
	call _putc		;print first character
	inc de
	ld a,(de)
	jp _putc		;print second character
				; and return

data:	.dw 0			;data copied from %data.86s
name:	.db 5,"%data"		;length indexed string name

.end

If you are going to use this routine in a game to save high scores, make sure you have a routine that checks to see if the hidden file is even there. Since the hidden file doesn't travel with the main program, there has to be a routine to create the hidden file for the first time. If the hidden file's not there, everything will go screwy!

The best way to do this is using _FindSym which will return with the carry flag set if the variable doesn't exist. Just use that condition to jump to a routine to create a new data variable with defaults.


More from z80 » Intermediate
All the Flags // Debugging // Down-Left Bug // _GetKey Codes // Logical Operators // Memory, ROM, RAM, and Safe Areas // Miscellaneous Instructions // PC and SP // Random Numbers // TI's ROM Calls // Restart Commands // Simulated 16-bit Addition // The Stack // Tables and Arrays // Text Display // Variables