TUTORIAL TWO
Alright then let’s get serious! In the first tutorial we learnt a little about how to navigate TommyGun and how to load and run a pre-built project. So in this tutorial we are going to create a simple game of ‘Noughts and Crosses’.
The plan is to keep it simple and usable, so there will be no sprite animation at first just a simple O’s and X’s game.
We’ll make the game for the Sinclair ZX Spectrum.
What do we need to make our game?
Well we’ll need some graphics, a game screen and some images of an O and an image of a X. Oh and some source code would help a lot too! J
The game screen will display the game board and allow the two human players to compete against each other.
The main tiles needed will be a Nought and a Cross.
We’ll need some source code to make a game of course so we’ll write some of that as well. This will be covered later. First let's draw some pictures.
Start up TommyGun, or if you have it open start a new project by selecting the File | New menu. Click no to saving any work from the last tutorial project.
On the startup screen click in the Name text box under the Create a New Project section and enter the name Noughts and Crosses. Make sure that the ZX Spectrum is selected and then click the Create Project button.
You should now have a blank project file. So let’s get started with making some graphics.
In this part of the tutorial we are going to create the graphics for our game.
We’ll be creating a screen image and 2 tile objects.
Let’s start with the tile objects
Select the tiles tab.
Change the Format combo box from Monochrome to ZX Spectrum.
If you make a mistake during any part of the image editing process, Press Ctrl + Z to undo your last operation.
Enter a name of Nought into the name text box, and click the Add button.
We will use the default 32 x 32 tile size.
A
color palette will appear that represents the ZX Spectrums screen colors.
From the palette select the Yellow Ink color, and turn the select Bright
On button. This will make the color in the editor when we draw a bright
yellow ink and back paper.
Now select the filled circle tool and turn the character and pixel grids on.
Position the mouse cursor over the center of the image and then press and hold the left mouse button and drag it to the bottom right corner. You can turn on the character grid to help locate the pixel center of the image.
Once at the corner adjust the mouse position until you get the image similar to this one.

Now place the mouse back into the center and press and hold the right mouse button this time and drag the mouse to the bottom right corner again but stop when the width and height of the pen pixels get to 5 pixels. You should get an image similar to this one.
You just completed creating a Nought!
Press Ctrl + S to save your work or click the save icon on the toolbar.
Enter the name Cross into the Name text box.
Click the add button
Select the blue ink color from the palette
Select the Free hand tool
Select the line tool
Position the mouse of pixel 0, 0 (hint: look in the status bar at the P: x, y status slot)
Press and hold the left mouse button and drag it to pixel 3, 0 and release the mouse.
Repeat the line drawing steps for the following lines
0, 0 to 0, 3
27, 0 to 30, 0
30,
0 to 30, 3
0, 27 to 0, 30
0, 30 to 3, 30
27, 30 to 30, 30
30, 30 to 30, 27
That should create 4 ends of a cross
Now join the ends by completing the next set of lines
0,
2 to 27, 29
30, 28 to 2, 0
0, 28 to 28, 0
30, 2 to 2, 30
That should create the links between all the ends
Now select the Fill tool and click in side each of the empty areas to flood fill the holes.
You should now have a completed Cross.
Press Ctrl + S to save your work

Ok we have now completed creating the tiles; next we will create the title screen and the game screen.
Select the Screens tab and change the Format combo box from Monochrome to ZX Spectrum.
Enter a name of GameBoard (no spaces) into the Name text box, and click the Add button
Select the Text tool
Select White Ink, Black Paper, Bright On and Flash Off.
Position the mouse at pixel position 23, 17 and click the left mouse button
Change the Font to Terminal, Bold and Size 14
Enter the text “NOUGHTS and CROSSES” into the text box and click Ok
Position the mouse at pixel position 2, 95 and click the left mouse button
Enter the text “WINS” into the text box and click Ok
Position the mouse at pixel position 210, 95 and click the left mouse button
Enter the text “WINS” into the text box and click Ok
Position the mouse at pixel position 2, 111 and click the left mouse button
Enter the text “DRAW” into the text box and click Ok
Position the mouse at pixel position 210, 111 and click the left mouse button
Enter the text “DRAW” into the text box and click Ok
Select the dropper tool
Select Black Paper and White Ink, Bright On and Flash Off.
Draw a line down from character position 13, 7 to 13, 22
Draw a line down from character position 18, 7 to 18, 22
Draw a line across from character position 8,12 to 23, 12
Draw a line across from character position 8, 17 to 23, 17
Change the Color to White Ink, Blue Paper, Bright On and Flash Off
Fill in a Rectangle using the dropper tool from character position 2, 1 to 29, 4
Change to the Tiles Table
Click on the Selection tool.
Select the Nought image
Hold Shift and selection from character 0, 0 to character 3,3
Press Ctrl + C to copy the selection
Change to the Screen tab
There is a paste bug in the Image editor, so in the mean-time click the Map editor and then back to Image editor.
Select the GameBoard image
Press Ctrl + V to paste the tile onto the screen
Hold Shift while positioning the selection and place it at character position 1, 7
Repeat the copying procedure for the Cross and place it at character position 27, 6
This should give you a screen similar
to this one 
One last adjustment to the screen.
Select Black Paper, Black Ink, Bright
Off and Flash Off.
Select the color dropper tool
Color the both WINS and DRAW characters in. This will make them appear erased, but they aren’t. We will be using a little ZX Spectrum attribute trick to show these when the game in either a O win, a X win or a Draw for both.
You get a screen similar to this one.
Save your work, Ctrl + S before continuing.
Ok, that’s the game second screen completed. Now let’s CODE!!!
Ok, in this section of the tutorial we are going to construct the code for our game. Firstly we will convert our images into data for the code to use. We then will write some game pseudo code (pretend code) to nut out the game logic. Finally we will turn the pseudo code into real Z80 assembly code.
Let’s start by selecting the Code editor, by clicking on the Keyboard icon in the Chooser bar.
First we will add 2 files to place out code into and the image data into.
Enter the filename “game.asm” into the Source Code name text box
Click on the Add button
The file will appear in the Project File List
Enter the filename “images.inc” into the Source Code name text box
Click on the Add button
It too will appear in the Project File List, but after the game.asm file
Ok, that’s it we have created the source code files.
The first is a .asm or assembler file, the other is a .inc or include file. We will be including the images.inc file in our game.asm file.
Double click on the games.asm file in the project file list.
Click on the text editor window and hit the enter key a 4 or 5 times
Enter the text ‘include “images.inc”’
Add the Assembler and Emulator like you were shown in tutorial 1.
Enter the Assembler parameters of
-v --tapbas --err game.asm game.tap game.map
Enter the Emulator parameters of
%Pfolder%\game.tap
This completes the setup of the assembler and emulator.
We can test the compiler by clicking on the Build All Files button on the toolbar.
Pasmo should now build own empty files successfully.
Do not run the emulator as the executable file is blank at the moment.
So now let’s add some data to the images.inc file
Double
click on the image.inc file in the project file list. This will display
the file in the text editor.
Click the Insert Resource button
on the
toolbar or press Ctrl + I. This will bring the Insert Resource dialog.
Select ZX Spectrum Images from the Parser combo box. This means we want to parse images with the ZX Spectrum palette.
Select Tile from the Type combo box. This means we are going to create tile data.
The Resource list will now be filled with all available images of type Tile.
Either check both the Nought and Cross image check boxes manually or Press Ctrl + A to select all at once.
Under the options panel to the right, make sure that place size at start is check, and that both the convert width and height checkboxes are checked.
Press the Insert button.
The editor will now contain the entire
Tile image data converted into assembler data code. You may notice the
code has some comments with funny formatting in it such as [RESOURCES
SECTION and [RESOURCE:. These are tags used to update the data when you
make changes in the Image editor. So instead of having to delete the
code and re-inserting the resource you simply press the Update Resources
buttons
on the toolbar or Press Ctrl + U and the resources will be re-generated
using the information in the tags.
Press Ctrl + I to get the Insert Resources dialog up again.
Select ZX Spectrum Images for the parser.
Select Screen as the type.
Press Ctrl + A to select all the list items.
Clear the Place image size at start, convert width and convert height checkboxes from the Options panel.
Press the Insert button.
The editor now contains all entire screen images. Press Ctrl + End to jump to the end of the file to see the screen data.
You have just produced 330 odd lines of pure assembly code. Easy!
Before you write any game or even any piece of software, you should have a clear understanding of what it is you are making. Lucky for us the rules and game design of Noughts and Crosses is fairly simply. But never the less we will write the rules and log it down so that we can convert it into computer code.
A player is chosen at random to take the first turn when playing against the computer.
Player one is assigned the Noughts.
Player two is assigned the Crosses.
Each player gets to place a single Nought or Cross into an empty square on a 3x3 grid.
No player can place a marker on top of another player’s marker.
The winner is the first player to get 3 of their markers in a row, either 3 vertically, 3 horizontally or 3 diagonally from corner to corner.
Game is a draw if all squares are filled and no player has 3 markers in a row. (see Rule 6)
That’s basically it for the rules, what we need to do now if describe them using pseudo code and to incorporate them into the game.
What is pseudo code?
Well pseudo code is not real computer code, but a way of describing the logic of a program without using any language in particular. Pseudo code can be formatted anyway you like, as there are no formal rules to it. It could be a list of things to do, or a mini program based on C syntax, or every dummy assembler states. Basically it’s whatever you want or need at the time. We are going to use a style similar to BASIC or PASCAL for this example. Later we’ll take that logic and make some Z80 assembler from it.
Noughts and Crosses Pseudo Code
Game main loop:
Display the Game Board
ML: Highlight the Player Icons
Player to position marker
Check for a Win
If no Win then
Swap Players
go back to ML
End if
If Nought wins show as winner
If Crosses wins show as winner
If Draw show game as a draw
The main loop simply allows each player to move the marker (O or X) and to place it, it also decides who wins or if the game is a draw.
Highlight Player Icons:
We use the Spectrum attributes to change only the Paper of the Player icons
If Nought is current player then
Set Nought icon to use green paper
Set Cross icon to use black paper
Endif
If Cross is current player then
Set Cross icon to use green paper
Set Nought icon to use black paper
End if
This routine will show the icons on the left and right of the screen in a green highlighted state when it is that players turn.
Player to position marker
Get key press
If key is any cursor key then
Move game cursor in direction of the cursor key
End if
If key is 0 or enter or space then
Place Marker
If marker is placed then
return to Main loop
Else
keep moving the game cursor
End if
End if
This routine will let the current player move the game cursor and position their game marker into a blank grid position.
Place Marker
If cursor is not occupied then
Place Marker
Return that marker is placed
Else
Return that marker is not placed
End if
This routine checks that the player is trying to place the game marker into a blank grid square, and if allows them to complete their move or else they have to keep going to place the marker into a blank square.
Check for a Win
Check all grid combinations for a winner
If a winner is found then
Return the Winner or Draw status
Return to the main loop
End if
This routine will check all the possible win combinations to find a winner. We only need to this after each player has placed their marker.
That’s basically the game in terms of simple pseudo code logic. Now we need to write the assembly version.
Here we will write the game using Z80 assembler. I’m going to use a simple ZX Spectrum technique to make the game cursor and the player icons appear highlighted with changing the graphics. I’m going to change the paper attribute value associated with an area of the screen. If you don’t know how the ZX Spectrum screen is organized then please read Chapter 24 of the ZX Spectrum BASIC programming manual page 121.
Changing the paper attribute of a character or area of the display will allow us to create a game cursor without actually creating any special graphics such as a tile or sprite. This will help to keep our game code as simple as possible.
The code that we’ll be writing for this game will not be complicated or use too many coding tricks. Let’s go.
The main loop is one of the most important parts of any game. It controls the flow of the game from where everything else is called from as a result of the user’s actions. Our main loop is pretty simple though let’s write it.

This is pretty similar the pseudo, with the exception of the call to the Pause su-routine. Because assembly is so fast, we need to slow down our game a little. J

This code is used to display the Winner of the game or to display a Draw result. It uses the attribute highlight trick to turn on the WIN and DRAW graphics that we colored black during the creation of the Screen image.
It also wait for the user to space when the game is complete to restart a new game again.

This is another use of the attribute trick, here we highlight the player icons (O and X) on the game board screen to show who’s turn it is. The current player has a green background color and the next player has a black background color.

These sub-routines are used to show(draw) and hide(erase) the game cursor. The IsOccupied sub-routine checks to see if the grid position the game cursor is at is occupied by a player marker. If does this by checking the current ink value of the top left hand corner of the grid position. If the ink is yellow then a Nought is there, and if its Blue a Cross is there and finally if its black then there is not marker there and the grid position is unoccupied.

This lot of code lets the currently player move the game cursor around grid and to choose a grid position to place the marker. It basically checks the keyboard for the cursor keys to move the game cursor and then enter, space and 0 to place the marker. If the grid position is not occupied then the routine draws the current player marker at the position and returns to the main loop.

This is the main loop to check for a Win or Draw. It loops through a table of all possible grid position win combinations and checks to see if a particular player has 3 of their markers in a row. If no winner is found, then it will then check to see if a draw has happened. It does this by trying to find a unoccupied grid position, if one does not exist then a draw has happened, else there was no draw and no winner yet, so the game can continue.

This sub-routine is called from the check win main loop and will check a single win combination from the WinTable.

These are a couple of utility functions to pause for a while and to wait for the user to press the space key.

This is the function that changes the attribute area for us. We use this to change the background highlight colors of the player icons, game cursor etc.

Here is the data used for the game.
CurPos is the current position of the game cursor. It is stored in X, then Y order.
CurPlayer is the player current moving the game cursor. 0 for Noughts and 1 for Crosses
WInTable is a table of all the row, column combinations that make up a Win.
The include “images.inc” statement includes the screen and tile image data we generated.
Well that’s about it really. We use a few more utility functions to draw tiles and calculate screen and attribute addresses from within the code. We won’t be going over these functions, so as not to complicate the tutorial too much.
Below your see the complete source code that you can cut and paste in your game.asm file from with TommyGun.
;---------------------------------------------------------------;
; ;
; NOUGHTS AND CROSSES ;
; ;
; Author : Tony Thompson ;
; Date : 18 March 2006 ;
; Comment: Created for the TommyGun - Getting Started Guide ;
; ;
;---------------------------------------------------------------;
SCREEN equ 16384
ATTRIBUTES equ 22528
MENU_OFF equ 64 + 7 ; White Ink, Black Paper, Bright On, Flash Off
MENU_ON equ 64 + 32 + 7 ; White Ink, Green Paper, Bright On, Flash Off
ICON_ON equ 64 + 32
ICON_OFF equ 64
NOUGHTS_WIN equ 1
CROSSES_WIN equ 2
DRAW_NO_WIN equ 3
C1 equ 9
C2 equ 14
C3 equ 19
R1 equ 8
R2 equ 13
R3 equ 18
GRID_STEP equ 5
INK_MASK equ 7
org 30000
;---------------------------------------------------------------;
; PlayGame ;
; The games main loop ;
;---------------------------------------------------------------;
PlayGame: ld hl, GameBoard ; display the title screen
call DrawScr ; draw the screen
PGLoop: call Pause ; pause between key presses
call DrawPlayers ; draw the current players
call PosCursor ; position the player marker
call CheckForWin ; check for player win
cp NOUGHTS_WIN ; Noughts WINS?
jr z, N_WIN ; if 0 then noughts won
cp CROSSES_WIN ; Crosses WINS?
jr z, C_WIN ; if 0 then crosses won
cp DRAW_NO_WIN ; was it a draw?
jr z, D_WIN ; if 0 then it was a draw
ld a, (CurPlayer) ; no winner yet
xor 1 ; so swap players
ld (CurPlayer), a ; store the next player
jr PGLoop ; keep going still the game is finished
;---------------------------------------------------------------;
; D_WIN, C_WIN, DRAW_NO_WIN, ShowWinner ;
; Show the Winner of the game ;
;---------------------------------------------------------------;
D_WIN: ld l, 0 ; x position
ld h, 14 ; y position
ld b, 2 ; height
call ShowWinner ; shade the Nought zone in flashing green
ld l, 26 ; x position
ld h, 14 ; y position
ld b, 2 ; height
call ShowWinner ; sahde the Cross zone in flashing green
call WaitForSpace ; wait for space
jr PlayGame ; back to playing the game
N_WIN: ld l, 0 ; x position
ld h, 6 ; y position
ld b, 8 ; height
call ShowWinner ; shade the nought in flashing green
call WaitForSpace ; wait for space
jr PlayGame ; back to playing the game
C_WIN: ld l, 26 ; x position
ld h, 6 ; y position
ld b, 8 ; height
call ShowWinner ; shade the cross in flashing green
call WaitForSpace ; wait for space
jr PlayGame ; back to playing the game
ShowWinner: ld c, 6 ; width
ld d, 128 + MENU_ON ; white ink, green paper, bright and flash on
ld e, INK_MASK ; keep the ink colour
call ColBlock ; highlight the required zone
ret
;---------------------------------------------------------------;
; DrawPlayers ;
; highlight the current player icon and reset the other icon ;
;---------------------------------------------------------------;
DrawPlayers: ld a, (CurPlayer) ; get the current player
ld l, 0 ; nought x position
ld h, 6 ; nought y position
and a ; is the current player the nought?
jr z, DrawCurrent ; yes, then draw as the current player
ld l, 26 ; no the cross is so change the x position
DrawCurrent: ld b, 6 ; color 6 characters high
ld c, 6 ; and 6 wide
ld d, ICON_ON ; use the green paper
ld e, INK_MASK ; and make the ink transparent
call ColBlock ; do the color changes
ld a, (CurPlayer) ; get the current player again
ld l, 0 ; nought x position
ld h, 6 ; nought y position
and a ; is the current player the nought?
jr nz, DrawNext ; no, so draw it as the next player
ld l, 26 ; yes, so draw the cross as the next player
DrawNext: ld b, 6 ; color 6 characters high
ld c, 6 ; and 6 wide
ld d, ICON_OFF ; in a black paper and bright turn on
ld e, INK_MASK ; and ink is transparent
call ColBlock ; do the color change
ret ; return back to main loop
;---------------------------------------------------------------;
; IsOccupied ;
; return ink color of a grid to indicate if its occupied ;
; zero or black ink means the grid position is empty ;
;---------------------------------------------------------------;
IsOccupied: ld bc, (CurPos) ; get the current position
call Attr ; convert it to an attr address
ld a, (hl) ; place the attr into a
and INK_MASK ; and get on the ink
ret ; square is not occupied if its ink is black
;---------------------------------------------------------------;
; DrawCursor ;
; draw the game cursor ;
; show a green background if a marker can be dropped ;
; show a red background if a marker cannot be dropped ;
;---------------------------------------------------------------;
DrawCursor: push af ; save the af registers
call IsOccupied ; is the square occupied?
ld hl, (CurPos) ; get the current cursor pos into hl
jr z, NotOccupied ; is the position occupied? if not goto NotOccupied
ld d, 64 + 16 ; Red Paper, Black Ink, Bright On
ld e, INK_MASK ; transparent ink
jr DrawCur
NotOccupied: ld d, 64 + 32 ; Green Paper, transparent Ink, Bright On
ld e, INK_MASK ; keep the current ink
DrawCur: ld b, 4 ; height
ld c, 4 ; width
call ColBlock ; highlight the sqaure
pop af ; restore af registers
ret ; return to caller
;---------------------------------------------------------------;
; EraseCursor ;
; erase the game cursor ;
;---------------------------------------------------------------;
EraseCursor: push af ; save af
ld hl, (CurPos)
ld d, 0
ld e, 64 + INK_MASK ; keep bright bit and ink
ld b, 4
ld c, 4
call ColBlock ; erase the game cursor
pop af
ret
;---------------------------------------------------------------;
; PosCursor ;
; let the player move their marker and place it into a square ;
;---------------------------------------------------------------;
PosCursor: call DrawCursor ; draw the game cursor
call MoveCursor ; move the game cursor
ret ; return to the main loop
;---------------------------------------------------------------;
; MoveCursor ;
; move the game cursor around the grid ;
;---------------------------------------------------------------;
MoveCursor: ld bc, 61438 ; check keys 0-6
in a, (c) ; get keys
bit 2, a ; test for right
jr z, MoveRight ; if pressed goto MoveRight
bit 3, a ; test for up
jr z, MoveUp ; if pressed goto MoveUp
bit 4, a ; test for down
jr z, MoveDown ; if pressed goto MoveDown
bit 0, a ; test for place marker
jr z, MovePlace ; if pressed goto MovePlace
ld bc, 63486 ; check keys 1-5
in a, (c) ; get keys
bit 4, a ; test for left
jr z, MoveLeft ; if pressed goto MoveLeft
ld bc, 49150 ; check keys enter-H
in a, (c) ; get keys
bit 0, a ; test for enter
jr z, MovePlace ; if pressed goto MovePlace
ld bc, 32766 ; check keys Space-B
in a, (c) ; get keys
bit 0, a ; test for space
jr z, MovePlace ; if pressed goto MovePlace
jr MoveCursor ; check for keys again
MoveRight: ld a, (CurPos) ; get cursor x position
cp C3 ; test for end of grid
jr z, MoveCursor ; if at end goto back to checking keys
call EraseCursor ; erase the game cursor
add a, GRID_STEP ; move it right
ld (CurPos), a ; save the cursor x position
call DrawCursor ; draw the new cursor position
call Pause ; pause a little
jr MoveCursor ; back to checking keys
MoveLeft: ld a, (CurPos) ; get cursor x position
cp C1 ; test for start of grid
jr z, MoveCursor ; if at start goto back to checking keys
call EraseCursor ; erase the game cursor
sub GRID_STEP ; move it left
ld (CurPos), a ; save the cursor x position
call DrawCursor ; draw the new cursor position
call Pause ; pause a little
jr MoveCursor ; back to checking keys
MoveUp: ld a, (CurPos+1) ; get cursor y position
cp R1 ; test for top of grid
jr z, MoveCursor ; if at top goto back to checking keys
call EraseCursor ; erase the game cursor
sub GRID_STEP ; move it up
ld (CurPos+1), a ; save the cursor x position
call DrawCursor ; draw the new cursor position
call Pause ; pause a little
jr MoveCursor ; back to checking keys
MoveDown: ld a, (CurPos+1) ; get cursor y position
cp R3 ; test for bottom of grid
jr z, MoveCursor ; if at bottom goto back to checking keys
call EraseCursor ; erase the game cursor
add a, GRID_STEP ; move it down
ld (CurPos+1), a ; save the cursor x position
call DrawCursor ; draw the new cursor position
call Pause ; pause a little
jp MoveCursor ; back to checking keys
MovePlace: call IsOccupied ; is the square occupied?
jp nz, MoveCursor ; if yes, then back to checking keys
ld a, (CurPos) ; no, draw a marker here
sla a ; x * 2
sla a ; x * 4
sla a ; x * 8
ld c, a ; store the char -> pixel x value in c
ld a, (CurPos+1) ; get the y position
sla a ; y * 2
sla a ; y * 4
sla a ; y * 8
ld b, a ; store the char -> pixel y value in b
ld a, (CurPlayer) ; get the current player
and a ; is it the nought?
jr nz, DrawCross ; if not draw the cross
ld hl, Nought ; yes, get nought tile
jr MoveDoDraw ; draw the tile
DrawCross: ld hl, Cross ; no, its the cross
MoveDoDraw: call DrwTile ; draw the player marker
ret ; return to main loop
;---------------------------------------------------------------;
; CheckForWin ;
; check for a winner ;
;---------------------------------------------------------------;
CheckForWin: ld b, 8 ; 8 entries in the wintable
ld hl, WinTable ; hl points to wintable
CFWloop: push bc ; save b counter
call CheckEntry ; check the current entry
pop bc ; restore the b counter
and a ; check the entry result
ret nz ; return to main loop if it has a result
djnz CFWloop ; check next table entry
ld b, 9 ; no winners yet, so do we have a draw
ld hl, WinTable ; back to the wintable
CheckForDraw: push bc ; save counter
ld c, (hl) ; get x position from table into c
inc hl ; move to y position
ld b, (hl) ; get y position into b
inc hl ; move to y position
push hl ; save hl
call Attr ; convert bc to a attr addr
ld a, (hl) ; get the attr
and INK_MASK ; get the ink color
pop hl ; restore hl (wintable pointer)
pop bc ; restore the table counter
and a ; is the ink black?
ret z ; yes, then the grid is not fill yet
djnz CheckForDraw ; chech the next grid position
ld a, DRAW_NO_WIN ; no empty squares, so its a draw
ret ; return to the main loop
;---------------------------------------------------------------;
; CheckEntry ;
; check the WinTable entry positions for a winner ;
;---------------------------------------------------------------;
CheckEntry: ld c, (hl) ; get x into c
inc hl ; move to y
ld b, (hl) ; get y into b
inc hl ; move to x
push hl ; save hl
call Attr ; convert bc to attr addr
ld a, (hl) ; get attr
and INK_MASK ; get the ink color
ld e, a ; save ink in e
pop hl ; restore hl
ld c, (hl) ; get x in c
inc hl ; move to y
ld b, (hl) ; get y in b
inc hl ; move to x
push hl ; save hl
call Attr ; convert bc to attr addr
ld a, (hl) ; get attr
and INK_MASK ; get the ink color
ld d, a ; save ink in e
pop hl ; restore hl
ld c, (hl) ; get x in c
inc hl ; move to y
ld b, (hl) ; get y in b
inc hl ; move to x
push hl ; save hl
call Attr ; convert bc to attr addr
ld a, (hl) ; get attr
and INK_MASK ; get the ink color
pop hl ; restore hl
add a, d ; add all the inks together
add a, e ;
jr nz, FindWinner ; are their any colors set?
CheckNoWin: xor a ; no, so return 0 - no winner yet
ret ; return to main loop
FindWinner: cp 18 ; is it 3 noughts in a row then 3*6 is 18
jr z, NoughtWins ; yes, goto NoughtWins
cp 3 ; is it 3 crosses in a row then 3*1 is 3
jr nz, CheckNoWin ; no, goto CheckNoWin
ld a, CROSSES_WIN ; yes, crosses won
ret ; return in the main loop
NoughtWins: ld a, NOUGHTS_WIN ; noughts won
ret ; return to the main loop
;---------------------------------------------------------------;
; Pause ;
; pause briefly ;
;---------------------------------------------------------------;
Pause: push bc ; save bc
push de ; save de
push hl ; hl
ld bc, 50000 ; define the pause length
ld de, 0 ; define block transfer destination
ld hl, 0 ; define block transfer source
ldir ; do a block transfer (takes a little while)
pop hl ; restore hl
pop de ; restore de
pop bc ; restore bc
ret ; pause complete
;---------------------------------------------------------------;
; WaitForSpace ;
; wait for space key to be pressed ;
;---------------------------------------------------------------;
WaitForSpace: ld bc, 32766 ; check keys from Space-B
in a, (c) ; get keys
bit 0, a ; if space pressed
jr nz, WaitForSpace ; no, then check again
ret ; yes, return to caller
;---------------------------------------------------------------;
; Incy ;
; ;
; Moves the screen address down 1 line ;
; Written by Tony Thompson ;
; Written by Nick Fleming ;
; Both versions where identical ;
; Created 1984 ;
; Last Changed 1st May 2003 ;
; ;
; Inputs ;
; hl - the address of a screen location ;
; ;
; Outputs ;
; hl - the address of the line below ;
; ;
; Regs Used ;
; af, hl ;
; ;
; Regs destoryed ;
; af ;
;---------------------------------------------------------------;
Incy: inc h ; try to move down 1 line in a character 1M 4T
ld a, h ; get h into a 1M 4T
and 7 ; test if still inside character 2M 7T
ret nz ; ret if in character square 1M 5T 3M 5T 7M 20T
ld a, l ; no, get lower byte of address 1M 4T
add a, 32 ; and move it to the next character block 2M 7T
ld l, a ; store the result 1M 4T
ret c ; return if we are still in the same segment? 1M 5T 3M 5T 13M 40T
ld a, h ; no, so need to adjust high order byte of address 1M 4T
sub 8 ; adjust screen segment 2M 7T
ld h, a ; store the correction 1M 4T
ret ; 3M 10T 18M 60T
;---------------------------------------------------------------;
; DrawScr ;
; ;
; Draws a screen object onto the display ;
; Written by Tony Thompson ;
; Created 2006 ;
; Last Changed 18 March 2006 ;
; ;
; Inputs ;
; hl - the address of a screen to draw ;
; ;
; Regs Used ;
; af, bc, de, hl ;
; ;
; Regs destoryed ;
; none ;
;---------------------------------------------------------------;
DrawScr: push af
push bc
push de
push hl ; save all the registers
push bc
push hl
ld bc, 768 ; clear the attributes to black
ld hl, ATTRIBUTES
DrwScrClr: ld (hl), 0
inc hl
dec bc
ld a, c
or b
jr nz, DrwScrClr
pop hl
pop bc
ld b, 192 ; transfer 192 lines
ld de, SCREEN ; point de at the screen address
DrwScrLp: push bc ; save bc
ld bc, 32 ; transfer 1 line of pixels
push de ; save the screen position
ldir ; do the transfer
pop de ; restore the screen position
ex de, hl ; swap screen address for off screen address
call Incy ; get next line of the physical screen
ex de, hl ; put screen address back into de
pop bc ; get the line count
djnz DrwScrLp ; decrement it and keep draw lines if not zero
ld bc, 768 ; transfer the attributes to the screen
ldir ; do the transfer
pop hl ; restore all the registers
pop de
pop bc
pop af
ret ; return to caller
;---------------------------------------------------------------;
; Pix ;
; ;
; Converts a screen pixel coord into a screen address and ;
; pixel position ;
; Written by Tony Thompson ;
; Created 1984 ;
; Last Changed 1st May 2003 ;
; ;
; Inputs ;
; b - y position in pixels ;
; c - x position in pixels ;
; ;
; Outputs ;
; hl - the attribute address for the screen location ;
; a - contains the bit position of the pixel ;
; ;
; Regs Used ;
; af, bc, hl ;
; ;
; Regs destoryed ;
; af ;
;---------------------------------------------------------------;
Pix: ld a, b
rra
scf
rra
rra
and 88
ld h, a
ld a, b
and 7
add a, h
ld h, a
ld a, c
rrca
rrca
rrca
and 31
ld l, a
ld a, b
and 56
add a, a
add a, a
or l
ld l, a
ld a, c
and 7
ret
;---------------------------------------------------------------;
; MAttr ;
; ;
; Converts a screen pixel coord into a off screen attr address;
; Written by Tony Thompson ;
; Created 1984 ;
; Last Changed 1st May 2003 ;
; ;
; Inputs ;
; b - y position in pixels ;
; c - x position in pixels ;
; ;
; Outputs ;
; hl - the attribute address for the off screen location ;
; ;
; Regs Used ;
; af, bc, de, hl ;
; ;
; Regs destoryed ;
; af, bc, hl ;
;---------------------------------------------------------------;
MAttr: push de
srl c
srl c
srl c ; div x pos by 8 into col
ld l, b ; put row into hl
ld h, 0
add hl, hl
add hl, hl
ld a, l
or c ; add c to hl
ld l, a
ld de, ATTRIBUTES
add hl, de
pop de
ret
;---------------------------------------------------------------;
; DrwTile ;
; ;
; Draws a tile object on the screen ;
; ;
; Written by Tony Thompson ;
; Created 24th April 2003 ;
; Last Changed 1st May 2003 ;
; ;
; Inputs ;
; hl - the address of the tile to draw ;
; b - the y position of the tile on the screen ;
; c - the x position of the tile on the screen ;
; ;
;Outputs ;
; none ;
; ;
; Regs Used ;
; hl, de, bc, af ;
; ;
; Regs destoryed ;
; af, bc, de, hl ;
;---------------------------------------------------------------;
DrwTile: ld e, (hl) ; read the width of the tile
inc hl ; move to height
ld d, (hl) ; read the height of the tile
inc hl ; hl now points to the tile data, and de contains the size of the tile
push bc ; save the position of the tiles
push hl ; save tile address
call Pix ; convert bc position into screen address
push de ; push size onto stack
pop bc ; and put size into bc
pop de ; put tile address into de
push hl ; save the screen address
push bc ; save the size
sla b ; convert character height to
sla b ; pixel height by muliplying the
sla b ; character height by 8
dt1: push bc ; save the size
push hl ; save the screen address
ex de, hl ; swap screen addr and tile addr (hl - tile)
ld b, 0 ; load b with zero, now bc contains width
ldir ; block transfer the tile to the screen
ex de, hl ; swap screen addr and tile addr back again (de - tile)
pop hl ; get the 1st column screen address
call Incy ; move down 1 line in the screen buffer
pop bc ; get the size
djnz dt1 ; draw all lines of the tile
pop bc ; get the size
pop hl ; get the top/left screen address
pop af ; put position into af
push bc ; save size
push af ; save position
pop bc ; put position back into bc
call MAttr ; convert tile pos to attribute address
pop bc ; put size back into bc
dt2: push bc ; save the size
push hl ; save the attr address
ld b, 0 ; make bc contain only the width
ex de, hl ; swap attr addr and tile addr
ldir ; block transfert the attr data
ex de, hl ; swap attr addr and tile addr back again
pop hl ; get 1st column attr address
ld c, 32 ; ld bc with width of screen
add hl, bc ; goto the next line down
pop bc ; put currently drawn size into bc
djnz dt2 ; do all the lines of attr's
ret ; finished drawing tile
;---------------------------------------------------------------;
; Attr ;
; ;
; Converts a screen char coord into a screen attr address ;
; Written by Tony Thompson ;
; Created 1984 ;
; Last Changed 1st May 2003 ;
; ;
; Inputs ;
; b - y position in characters ;
; c - x position in characters ;
; ;
; Outputs ;
; hl - the attribute address for the screen location ;
; ;
; Regs Used ;
; af, bc, de, hl ;
; ;
; Regs destoryed ;
; af, bc, hl ;
;---------------------------------------------------------------;
Attr: ld l, b ; get y position into l
ld h, 0 ; reset h to 0
add hl, hl ; hl * 2
add hl, hl ; hl * 4
add hl, hl ; hl * 8
add hl, hl ; hl * 16
add hl, hl ; hl * 32
ld a, c ; get the x position
or l ; merge l and c
ld l, a ; put a into l (ie. (y*32) + x position
ld bc, ATTRIBUTES ; get the attributes address into bc
add hl, bc ; make the address relative to the attrs
ret ; hl contains the attribute address of bc
;---------------------------------------------------------------;
; ColBlock ;
; ;
; Colours a block of attributes to a desired set of colours ;
; Written by Tony Thompson ;
; Created 2006 ;
; Last Changed 18 March 2006 ;
; ;
; Inputs ;
; b - y height in characters ;
; c - x width in characters ;
; d - attribute colour ;
; e - attribute mask ;
; h - y position in characters ;
; l - x position in characters ;
; ;
; Regs Used ;
; af, bc, de, hl ;
; ;
; Regs destoryed ;
; none ;
;---------------------------------------------------------------;
ColBlock: push af
push bc
push de
push hl ; all regs saved
push bc ; save bc - the size
push hl
pop bc ; put hl into bc -bc has screen pos
call Attr ; hl now has the attribute address
pop bc ; bc now has the size again
ColBlock1: push bc ; save the size
push hl ; save the attribute screen address
ld b, c ; put the width into b
ColBlock2: ld a, (hl) ; get the current attribute
and e ; mask off the attributes we don't want
or d ; and set the new attributes we do want
ld (hl), a ; store the result
inc hl
djnz ColBlock2 ; do the width of the block
ld bc, 32
pop hl
add hl, bc ; move to next attribute line
pop bc ; get the current width and height
djnz ColBlock1 ; do all the lines of the block
pop hl
pop de
pop bc
pop af ; all regs restored
ret
CurPos: defb 9, 8 ; the current position of the board cursor
CurPlayer: defb 0 ; current player is nought, next is cross
WinTable:
defb C1, R1, C2, R1, C3, R1 ; top row
defb C1, R2, C2, R2, C3, R2 ; middle row
defb C1, R3, C2, R3, C3, R3 ; bottom row
defb C1, R1, C1, R2, C1, R3 ; left column
defb C2, R1, C2, R2, C2, R3 ; middle column
defb C3, R1, C3, R2, C3, R3 ; right column
defb C1, R1, C2, R2, C3, R3 ; left to right, top to bottom
defb C3, R1, C2, R2, C1, R3 ; right to left, top to bottom
org 32768 ; place the screens and tiles from RAM address 32768
include "images.inc" ; include the screens and tiles
end 30000 ; the execution start address (pc) of the .tap file
Playing
the GameCursor keys to move the game cursor.
Either Space, Enter or 0 to set the marker at the grid position.
Space after the game is over to restart a new game.
Noughts always go first.
The green square on the left or right of the screen indicates whose turn it is.
Move the cursor to an empty grid position and place your marker, then let the other player do the same, until someone wins or the game is a draw.
A computer player. So that the player can play the computer.
You could also add a new start screen to allow the player to choose Player vs. Computer, Player vs. Player or Quit.
A simple score tally. Keep track of the number of each Noughts and Crosses win.
Improve the graphics. Create better and more interesting graphics.
When the Music editor is shipped, add a tune to listen to while playing.
For extra bonus points, make the music play from an interrupt service routine.
You should now have completed your first game! This tutorial highlighted just how simple it is to make a game. So long as you keep the end goal within your reach you can complete the tasks ahead.