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’s needed to make a game?

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

 

Screen

The game screen will display the game board and allow the two human players to compete against each other.

 

Tiles

The main tiles needed will be a Nought and a Cross.

 

Source Code

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.

 

 

Getting Started

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.


 

The Game 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

Creating the Tiles

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.

Creating the Nought

  1. 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.

  1. 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.

  2. Now select the filled circle tool and turn the character and pixel grids on.

  3. 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.

  4. Once at the corner adjust the mouse position until you get the image similar to this one.

 

  1. 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.

 

  1. You just completed creating a Nought!

 

  1. Press Ctrl + S to save your work or click the save icon on the toolbar.

 


 

Creating the Cross

  1. Enter the name Cross into the Name text box.

  2. Click the add button

  3. Select the blue ink color from the palette

  4. Select the Free hand tool

  5. Select the line tool

  6. Position the mouse of pixel 0, 0 (hint: look in the status bar at the P: x, y status slot)

  7. Press and hold the left mouse button and drag it to pixel 3, 0 and release the mouse.

  8. Repeat the line drawing steps for the following lines

    1. 0, 0 to 0, 3

    2. 27, 0 to 30, 0

    3. 30, 0 to 30, 3

    4. 0, 27 to 0, 30

    5. 0, 30 to 3, 30

    6. 27, 30 to 30, 30

    7. 30, 30 to 30, 27

  9. That should create 4 ends of a cross

 

  1.  Now join the ends by completing the next set of lines

    1. 0, 2 to 27, 29

    2. 30, 28 to 2, 0

    3. 0, 28 to 28, 0

    4. 30, 2 to 2, 30

  2. That should create the links between all the ends

  3.  Now select the Fill tool and click in side each of the empty areas to flood fill the holes.

  4. You should now have a completed Cross.

  5.  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.

 

 


 

Creating the Screens

Select the Screens tab and change the Format combo box from Monochrome to ZX Spectrum.

Creating the Game Board Screen

  1. Enter a name of GameBoard (no spaces) into the Name text box, and click the Add button

  2. Select the Text tool

  3. Select White Ink, Black Paper, Bright On and Flash Off.

  4. Position the mouse at pixel position 23, 17 and click the left mouse button

  5. Change the Font to Terminal, Bold and Size 14

  6. Enter the text “NOUGHTS and CROSSES” into the text box and click Ok

  7. Position the mouse at pixel position 2, 95 and click the left mouse button

  8. Enter the text “WINS” into the text box and click Ok

  9. Position the mouse at pixel position 210, 95 and click the left mouse button

  10. Enter the text “WINS” into the text box and click Ok

  11. Position the mouse at pixel position 2, 111 and click the left mouse button

  12. Enter the text “DRAW” into the text box and click Ok

  13. Position the mouse at pixel position 210, 111 and click the left mouse button

  14. Enter the text “DRAW” into the text box and click Ok

  15. Select the dropper tool

  16. Select Black Paper and White Ink, Bright On and Flash Off.

  17. Draw a line down from character position 13, 7 to 13, 22

  18. Draw a line down from character position 18, 7 to 18, 22

  19. Draw a line across from character position 8,12 to 23, 12

  20. Draw a line across from character position 8, 17 to 23, 17

  21. Change the Color to White Ink, Blue Paper, Bright On and Flash Off

  22. Fill in a Rectangle using the dropper tool from character position 2, 1 to 29, 4

  23. Change to the Tiles Table

  24. Click on the Selection tool.

  25. Select the Nought image

  26. Hold Shift and selection from character 0, 0 to character 3,3

  27. Press Ctrl + C to copy the selection

  28. Change to the Screen tab

  29. There is a paste bug in the Image editor, so in the mean-time click the Map editor and then back to Image editor.

  30. Select the GameBoard image

  31. Press Ctrl + V to paste the tile onto the screen

  32. Hold Shift while positioning the selection and place it at character position 1, 7

  33. Repeat the copying procedure for the Cross and place it at character position 27, 6

  1. This should give you a screen similar to this one  

  2. One last adjustment to the screen.

  3. Select Black Paper, Black Ink, Bright Off and Flash Off.

  4. Select the color dropper tool

  5. 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.

  6. You get a screen similar to this one.

  7. Save your work, Ctrl + S before continuing.

 

Ok, that’s the game second screen completed. Now let’s CODE!!!

 

 

 

 


 

The Game 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.

Adding some Files to Edit

First we will add 2 files to place out code into and the image data into.

  1. Enter the filename “game.asm” into the Source Code name text box

  2. Click on the Add button

  3. The file will appear in the Project File List

  4. Enter the filename “images.inc” into the Source Code name text box

  5. Click on the Add button

  6. 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”’

 

Setting up the Assembler and Emulator

  1. Add the Assembler and Emulator like you were shown in tutorial 1.

  2. Enter the Assembler parameters of

-v --tapbas --err game.asm game.tap game.map

  1. 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


 

Generating the image data

  1. Double click on the image.inc file in the project file list. This will display the file in the text editor.

  2. Click the Insert Resource button  on the toolbar or press Ctrl + I. This will bring the Insert Resource dialog.

  3. Select ZX Spectrum Images from the Parser combo box. This means we want to parse images with the ZX Spectrum palette.

  4. Select Tile from the Type combo box. This means we are going to create tile data.

  5. The Resource list will now be filled with all available images of type Tile.

  6. Either check both the Nought and Cross image check boxes manually or Press Ctrl + A to select all at once.

  7. 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.

  8. Press the Insert button.

  9. 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.

  10. Press Ctrl + I to get the Insert Resources dialog up again.

  11. Select ZX Spectrum Images for the parser.

  12. Select Screen as the type.

  13. Press Ctrl + A to select all the list items.

  14. Clear the Place image size at start, convert width and convert height checkboxes from the Options panel.

  15. Press the Insert button.

  16. The editor now contains all entire screen images. Press Ctrl + End to jump to the end of the file to see the screen data.

  17. You have just produced 330 odd lines of pure assembly code. Easy!


 

Designing the Game Logic

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.

The Rules

  1. A player is chosen at random to take the first turn when playing against the computer.

  2. Player one is assigned the Noughts.

  3. Player two is assigned the Crosses.

  4. Each player gets to place a single Nought or Cross into an empty square on a 3x3 grid.

  5. No player can place a marker on top of another player’s marker.

  6. 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.

  7. 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.

 


 

Pseudo Code?

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.

 

 


 

Writing the Game in Z80 assembler

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

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.


 

Game Z80 Source Code

;---------------------------------------------------------------;

;                                                               ;

; 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 Game

The Keys

 

The Rules

 

Things you can try to add to the game

  1. A computer player. So that the player can play the computer.

  2. You could also add a new start screen to allow the player to choose Player vs. Computer, Player vs. Player or Quit.

  3. A simple score tally. Keep track of the number of each Noughts and Crosses win.

  4. Improve the graphics. Create better and more interesting graphics.

  5. When the Music editor is shipped, add a tune to listen to while playing.

    1. For extra bonus points, make the music play from an interrupt service routine.

 

 

Conclusion

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.