ASM Graphics & Mouse
A Tutorial by Charles Johnson

Hello everyone, as promised here is another tutorial written by me, about stuff you might want to know to help you write cool games.

Before I go on... allow me to explicate for a little while.

First of all, I am no writer, I'm a systems engineer, if you're looking for good writing and clear instructions, don't look here.

Also, I don't see any reason to hide source code from others. Few of us has actually completed a game, so there's no reason to hide your secrets from anybody else. These are DOS games we're writing here - nobody writes DOS code anymore. When ID released the source for Wolfenstein, it spoke to me, it was like "Hey! there aren't any secrets anymore!". You see, everything we write here has been documented in some book on how to write games at Barnes&Noble, and is probably also on my bookshelf. So I see no need to hide source like some others of us do.
And for the code I've written here, if any of you saw my last tutorial (highly doubtful) you'd see that I write with VB-DOS, I've changed over recently to QB45, partially for the executable size, but mainly because I like to write stuff for other people to see, and most of you guys use Quick BASIC, and I happen to own it, so I decided to switch over.
A word on assemblers - I do a lot of work in assembly language, my assembler of choice is A86, by Eric Isaacson - search for it on your search engine of choice. I like to work in assembly, and I think all programmers, especially BASIC programmers should learn assembly language, it's the best place to start.
Don't let my prodigous use of assembly language scare you. In this package I've included object libraries for those of you who would rather not mess with the assembly code and just link in the routines you want.

Here is a list of files included in this package:
  • mouse.8 - contains all my mouse routines
  • myget.8 - a fast QBASIC compatible get routine
  • myput.8 - two QBASIC compatible put routines, transparent and solid.
  • mylib.bi - declarations for all my assembler routines.
  • mylib.lib - The object code libraries containing all the assembly routines
  • pcx.bas - a PCX loader written by Jonathan Leger
  • pcxget.mak - MAK files allow QB to load multiple files
  • test.pcx - a test pcx file to test this program with.
  • pcxget.bas - my program for getting and saving graphics for sprites etc...

    I'll go over the files one by one in order. I won't go too much into detail on the assembly programs, because the main focus of this article is the program pcxget.bas, so I'll spend the most time on it.

    mouse.8 - Contained herein are my mouse routines, I'll run 'em down one by one.
    Mouseinit - if any of you have used any mouse code in the past, you'll recognise this one. It simply initializes the mouse driver. I don't check driver status, so watch out if you don't hav a mouse driver loaded.
    MouseCross - I don't use this routine in this program, but this routine turns the mouse cursor into a little cross-hair for accurate clicking.
    ShowMouse - turns the mouse cursor on.
    HideMouse - this may be the most valuable mouse routine available. In Windows programming, the mouse driver is notified when you draw on the screen and turns itself off so it doesn't ruin your drawings. However, in DOS, the driver doesn't know when your drawing so you have to turn it off or it will ruin your drawings.
    lButton, rButton - these functions tell you the status of the mouse buttons.
    MouseX, MouseY - these functions tell you the X and Y location of the mouse cursor - in pixels, so if you're in text mode, divide by 8 (or 16).
    XorLine - this routine gets some heavy use here, it draws a line from the top of the screen to the bottom and across the screen, showing the cursor location. This makes selecting graphics very easy. The lines are drawn using xor so they can be erased without saving any background by xoring a second time.
    XorBox - this routine draws a box from one location to another using xor, so the lines can be erased without saving the background.

    myget.8 - This file contains a single routine.
    MyGet - this routine grabs a region of the screen into an array similar to a QB get, the differences are: A segment can be passed for use with an offscreen buffer. You pass in the Width and height instead of x2 and y2. and so the code doesn't have to deal with array descriptors, you have to pass the first element of the array instead of the array itself:
    MyGet &hA000, 10, 10, 30, 30, Array(0)    'pass the first element, not the array
    
    The resulting array is exactly the same as created by the QB Get statement.

    myput.8 - Here's how you put the graphics back onto the screen.
    MyPut - My put uses an array as created with the QB get or myget, and puts it on the screen, with one major difference...all pixels that are color #0 are not put to the screen, they become transparent.
    Every good game needs a transparent put routine, be it using a screen mask or not putting a particular color, whatever it is, every game needs one.
    SolidPut - This routine is the same as the QB put using the PSET option. The reason I use my own routines for getting and solid putting is that I often need to change the video segment for use with an off screen buffer for flicker free graphics.

    mylib.bi - declarations for all my assembler routines.
    Declare Sub MouseInit()
    Declare Sub MouseCross()
    Declare Sub ShowMouse()
    Declare Sub HideMouse()
    Declare Function lButton%()
    Declare Function rButton%()
    Declare Function MouseX%()
    Declare Function MouseY%()
    Declare Sub XorLine(BYval X%, BYval Y%)
    Declare Sub XorBox(Byval X1%, Byval Y1%, Byval X2%, Byval Y2%)
    
    Declare Sub MyGet(Byval Segment%, Byval X%, Byval Y%, Byval W%, Byval H%, SEG Array)
    Declare Sub MyPut(Byval Segment%, Byval X%, Byval Y%, SEG Array)
    Declare Sub SolidPut(Byval Segment%, Byval X%, Byval Y%, SEG Array)
    
    mylib.lib - The object code library containing all the assembly routines.
    Contained herein are all the object files for all the .8 files included in this packet. Using these you can create quick libraries, and link them to your programs after compilation.

    pcx.bas - a very good pcx loader written by Jonathan Leger.
    I got this one from an ABC packet. For those of you who don't know what ABC is, search the web for "All BASIC Code", you'll be glad you did. My own PCX loader was written in C and I didn't want to throw a third language at you at this point.

    pcxget.mak - MAK files allow QB to load multiple files
    For those of you who use QB45 and have never used the load file and create file choices on you menu bar...A MAK file is simply a text file that has a list of other files within it. If you load a MAK file into QB instead of a BAS file, it will load all the files that are listed within. When you choose to create an EXE file within the IDE, QB will compile and link all the files together. That way you can segregate your code into self contained modules.

    Test.pcx - It's just a PCX file I included so you could test the program.

    pcxget.bas - This file is the main focus of this article, I'll run down the code to give you some idea of the crazy things I do.
    'Always include this:
    DEFINT A-Z
    
    'Include my external routines:
    '$INCLUDE: 'mylib.bi'
    DECLARE SUB ShowPCX (file$)
    
    
    DIM OldX, OldY
    DIM NewX, NewY
    
    '$DYNAMIC
    DIM img(0) AS INTEGER
    DIM Bak(0) AS INTEGER
    
    CLS
    COLOR 15
    
    'Just printing some instructions for the hapless user
    PRINT
    PRINT
    PRINT "PCXGet allows you to load a Screen 13 PCX file and select a "
    PRINT "region to be saved in a QuickBASIC get format."
    PRINT
    PRINT "L = Load pcx file"
    PRINT "S = Save Gotten image as QuickBASIC get"
    PRINT "Q = Quit"
    PRINT
    
    SLEEP
    DO: LOOP WHILE LEN(INKEY$)
    
    SCREEN 13
    
    'Initialize the mouse
    MouseInit
    
    'Display the PCX file on the command line
    IF LEN(COMMAND$) THEN ShowPCX COMMAND$
    
    'Since the first thing the main loop does is to clear
    'the mouse lines before calculating the new postion,
    'we need to give it something to clear.
    XorLine OldX, OldY
    
    DO
    
      'Retrieve the new mouse coordinates
      NewX = MouseX%
      NewY = MouseY%
    
      'If it has moved
      IF OldX <> NewX OR OldY <> NewY THEN
    
        'Clear the old lines
        'And draw the new
        XorLine OldX, OldY
        XorLine NewX, NewY
    
        'Then store the new as old
        OldX = NewX
        OldY = NewY
    
      END IF
    
      'If the left button goes down
      IF lButton% THEN
         
        'Clear away the mouse lines
        XorLine OldX, OldY
           
        'Save the starting position
        StartX = NewX
        StartY = NewY
           
        'Draw the first box so we have something to clear
        XorBox StartX, StartY, NewX, NewY
    
        'Draw boxes while the mouse button is down
        DO WHILE lButton%
             
          'Get the new coords
          NewX = MouseX%
          NewY = MouseY%
    
          'If it has moved...
          IF OldX <> NewX OR OldY <> NewY THEN
            'Clear the old box
            'And Draw the new
            XorBox StartX, StartY, OldX, OldY
            XorBox StartX, StartY, NewX, NewY
               
            'Save those coords
            OldX = NewX
            OldY = NewY
          END IF
    
        LOOP
    
        'Clear the last box drawn
        XorBox StartX, StartY, OldX, OldY
    
        'Recalc the coords for boxes drawn
        'from the top up or from right to left
        'xorbox takes this into account, but get doesn't
        IF StartX > OldX THEN SWAP StartX, OldX
        IF StartY > OldY THEN SWAP StartY, OldY
    
        'Calculate the array size
        W = OldX - StartX + 1: H = OldY - StartY + 1
        max = (W * H + 5) / 2
        'One for the image
        REDIM img(0 TO max)
        'One for the saved background
        REDIM Bak(0 TO max)
    
        'Get the image
        MyGet &HA000, StartX, StartY, W, H, img(0)
    
        'Save the coords
        OldX = NewX
        OldY = NewY
    
        'Get the background
        MyGet &HA000, NewX, NewY, W, H, Bak(0)
        'Display the gotten image with transparency
        MyPut &HA000, NewX, NewY, img(0)
      
        'Loop until a button press
        DO UNTIL lButton OR rButton
    
          'Get the new coords
          NewX = MouseX%
          NewY = MouseY%
    
          'If they've changed
          IF OldX <> NewX OR OldY <> NewY THEN
    
    
            'Here's a trick:
            'Wait until the screen is not being drawn before you
            'display your graphics, that'll minimize the flicker
            DO: LOOP UNTIL INP(&H3DA) AND 8
    
            'Put the background
            SolidPut &HA000, OldX, OldY, Bak(0)
            'Get the background
            MyGet &HA000, NewX, NewY, W, H, Bak(0)
           
            'Draw the gotten image at the new coords
            MyPut &HA000, NewX, NewY, img(0)
              
            'save the coords
            OldX = NewX
            OldY = NewY
          END IF
             
    
        LOOP
    
        'Loop until the buttons have been released
        DO WHILE lButton OR rButton: LOOP
    
        'Replace the background
        SolidPut &HA000, OldX, OldY, Bak(0)
    
        'Draw the lines so the can be erased again.
        XorLine NewX, NewY
      END IF
    
      'Now deal with any button presses.
      a$ = INKEY$
      IF LEN(a$) THEN
        a$ = UCASE$(a$)
        SELECT CASE ASC(a$)
        CASE 27, 81: EXIT DO
        CASE 76
          REDIM Bak(0 TO 2561)
          'Load a PCX file.
          MyGet &HA000, 0, 0, 320, 16, Bak(0)
    
          LOCATE 1, 1, 1
          INPUT "Filename"; a$
          IF LEN(a$) THEN
            ShowPCX a$
            XorLine NewX, NewY
          ELSE
            SolidPut &HA000, 0, 0, Bak(0)
          END IF
    
        CASE 83
          'Save the gotten array.
          REDIM Bak(0 TO 2561)
          MyGet &HA000, 0, 0, 320, 16, Bak(0)
    
          LOCATE 1, 1, 1
          INPUT "Filename"; a$
          IF LEN(a$) THEN
            OPEN a$ FOR BINARY AS 1
            FOR t = 0 TO UBOUND(img)
              PUT #1, , img(t)
            NEXT t
            CLOSE
          END IF
       
          SolidPut &HA000, 0, 0, Bak(0)
        END SELECT
      END IF
    
    LOOP
    
    SCREEN 0
    
    


    Charles Johnson can be contacted at charles@cts.com.