💾 Archived View for gemini.spam.works › mirrors › textfiles › programming › qbasgdc1.001 captured on 2022-04-29 at 00:32:59.

View Raw

More Information

⬅️ Previous capture (2020-10-31)

-=-=-=-=-=-=-

?????????????????
? Week 1, Day 1 ?
?????????????????

Today you will learn how to make a PACMAN symbol move across the screen.

Start QBasic and type in the following program:

CLS
PRINT "Below is PACMAN himself!"
PRINT "C"

Now RUN the program by pressing ALT-R and then S.
Is the graphics a little unconvincing?  Well, we have to start somewhere!

The command CLS clears the screen and we'll use it often.

Lets put PACMAN in the middle of the screen.
The command you need is LOCATE.
Type (or edit) and RUN the following:

CLS
LOCATE 12,40
PRINT "C"

Easy as pie!  
The first value between the brackets indicates the row, ranging from 1 at the
top to 23 at the bottom on the screen.
The second value is the column and ranges from 1 at the left to 80 at the right
on the screen.

Now to make PACMAN move!
You could do it this way:

LOCATE 12,1
PRINT "C"
LOCATE 12,1
PRINT " "        :REM delete the old picture
LOCATE 12,2
PRINT "C"        :REM draw a new picture to the right

(REM statements are ignored by the computer and you dont have to type )
(them in.                                                             )
(I will use REM statements to clarify and comment upon my programming.)

This method will take forever to program so lets start using variables.

A variable is described has a name and contains a value.
This illustrates the difference between a numeric- and a string variable:

contents ???->   ? 10 ?   ? "twenty" ?
                 ??????   ????????????
    name ???->   apples      pears$
                (numeric)   (string)

The "$" in "pears$" is pronounced "string" and identifies it as a string
variable.
When you PRINT a variable its contents is shown, not its name.
Type (or edit) and RUN:

CLS
apples = 10
pears$ = "twenty"
PRINT "Amount of apples are "; apples
PRINT "Amount of pears are "; pears$

Notice how the text between the double quotes is displayed literally while
the variable name is replaced by its contents.
The "=" operator assigns a value to a variable and does it by working from 
right to left, e.g. in "apples = 10" the "10" goes into "apples".
The ";" causes the computer to remember where it last PRINTed and the following
PRINT will continue at that position.
PRINT "ABC" will give the same output as PRINT "A"; "B"; "C"

While PACMAN is warming up for his jog, lets make things even easier on
ourselves.
You might have figured that I want to use variables in the following way:

column = 1
LOCATE 12,column          :REM This translates to LOCATE 12,1
PRINT "C"
column = column + 1       :REM Lets calculate the value:
                          :REM column + 1 gives you 1 + 1, in other words 2
LOCATE 12,column          :REM This translates to LOCATE 12,2
PRINT "C"

Still lots of repetition!
An easier way is to make the computer go in a "loop", increasing the value of
"column" and printing PACMAN.
Type (or edit) and RUN:

CLS
FOR column = 1 TO 80
LOCATE 12,column
PRINT "C"
NEXT column

Hope I haven't put you off programming altogether with that one!
You've implemented as so-called "FOR..NEXT loop" and here's the low-down on it:
The "FOR" line tells the computer that a variable called column will start off
at 1 and then increase in value until it reaches 80.
The "NEXT" line increases column's value with one and makes the program jump
back to the "LOCATE" line. 
Everything between the "FOR" and the "NEXT" is thus the actual loop and gets
repeated 80 times.

If you still dont understand the concept of the "FOR..NEXT loop" then type and
RUN:

CLS
FOR values = 1 TO 10
PRINT values
NEXT values

Hope you've found the experience enlightening!

Clearly also is that everything is happening too fast.
PACMAN completes his trip in the blink of an eye or, depending on how fast
your computer is, even less!
The solution is to use the SLEEP command which waits a number of seconds.
Type (or edit) and RUN:

CLS
FOR column = 1 TO 80
LOCATE 12,column
PRINT "C"
SLEEP 1
NEXT column

Too slow now!
Another way is to use an empty FOR..NEXT loop that does nothing but kill time.
That way you can change the value of the loop untill it provides an acceptable
delay for your computer.  

Another problem was that the PACMAN isn't deleted in its old position and you 
are left with a whole row of "CCCCCC"s.  We'll solve that now.
Here's the final code for the PACMAN 100m sprint:
Type (or edit) and RUN!

CLS
FOR column = 1 TO 79
LOCATE 12,column
PRINT " C"
FOR nothing = 1 TO 100          :REM  <??? This just delays the computer
NEXT nothing                    :REM   ??
NEXT column

If you didnt see a thing the program is probably still too fast for your
computer, so try changing the limit of the "nothing" loop to 300 or more.

The space before PACMAN cleverly deletes its old position.
How?  Two characters are displayed (a " " and a "C") but the position only
shift one to the right.
When the two characters are displayed at their new position, the " " overlaps
with the "C" previously displayed and effectively deletes it.
That's all for today!

-------------------------------------------------------------------------------
?????????????????
? Week 1, Day 2 ?
?????????????????

Watching PACMAN move is fun, but being in control opens up endless 
possibilities..
Enough said!   
Remember the FOR..NEXT loop?
Lets have a look at a different kind of loop.
Type (or edit) and RUN:

CLS                             
value = 1                  
DO WHILE value < 11
 PRINT value                     :REM  <??? This is the loop
 value = value + 1               :REM   ??
LOOP

This displayed numbers 1 through 10.
The program loops while "value" is less than 11.
We will now use the DO..WHILE loop to read the keyboard and display the 
characters that you press. 
However, there's one snag.  The program will loop "forever" unless you stop it!
Pressing CTL and BREAK at the same time will do this. 
Type (or edit) and RUN:

CLS
DO                                  :REM there's no WHILE, so DO forever..
 keyed$ = INKEY$                    :REM make keyed$ = the key pressed
 IF keyed$ <> "" THEN PRINT keyed$  :REM if keyed$ isnt empty then display it
LOOP

I bet INKEY$ has you confused!
INKEY$ is a special string variable.
The computer always sets the value of INKEY$ to the key that you press.
While you're not pressing any keys it will contain nothing (nothing is "").
The IF command is very straight forward.
IF something is true THEN do something..  got it?
So IF keyed$ is not equal to nothing THEN its value gets PRINTed.
If I didnt include the IF clause then keyed$ would always be PRINTed, whether 
it contained a value or not.
Try leaving out the IF..THEN part and you'll see what I mean!
Lots and lots of "nothings" fill the screen!

Armed with our new commands we can finally control PACMAN with the keyboard.
Type (or edit) and RUN:

CLS
row = 12
column = 40
DO 
 DO                           :REM <?? this loop waits for a key to be pressed
  keyed$ = INKEY$             :REM   ?
 LOOP UNTIL keyed$ <> ""      :REM  ??
 LOCATE row, column
 PRINT " "                               :REM this erases the "C"       
 IF keyed$ = "q" THEN row = row - 1       
 IF keyed$ = "a" THEN row = row + 1
 IF keyed$ = "o" THEN column = column - 1
 IF keyed$ = "p" THEN column = column + 1
 LOCATE row, column
 PRINT "C"                              :REM shows the "C" at the new position 
LOOP

Note that you can use your own choice of keys by replacing "q","a","o" and "p".
Using special keys like the cursor keys is another cup of tea.

If you move PACMAN outside the screen boundaries you'll get an error message.
To prevent this you must add checks to the program.
Just replace the IFs with the following lines and RUN:

IF (keyed$ = "q") AND (row > 1) THEN row = row - 1
IF (keyed$ = "a") AND (row < 23) THEN row = row + 1
IF (keyed$ = "o") AND (column > 1) THEN column = column - 1
IF (keyed$ = "p") AND (column < 80) THEN column = column + 1

This should do for today.
Hope you enjoyed this as much as I did!

-------------------------------------------------------------------------------
?????????????????
? Week 1, Day 3 ?
?????????????????

A playing area is essential to any game so lets design a maze for our PACMAN.

First I'll have to tell you about arrays.
Take a look at this example:

DIM values(3)
values(1) = 5
values(2) = 92
values(3) = 45

Arrays are variables containing multiple elements.
Each element is named after the array followed by a number to specify the 
position of the element.
Each element has its own value.
Type (or edit) and RUN the following to see how arrays can save you from a lot
of repetitive programming:

CLS
DIM values(10)
FOR count = 1 TO 10
 values(count) = count
NEXT count
FOR count = 1 to 10
 PRINT values(count)  
NEXT count
  
Remember that "count" ranges from 1 to 10 so the line "values(count) = count"
translates to "values(1) = 1, values(2) = 2, values(3) = 3" etc.  

Now lets define our maze. Type (or edit) and RUN:

CLS
DIM maze$(6)
maze$(1) = "#########"
maze$(2) = "#   #   #"
maze$(3) = "# # # # #"
maze$(4) = "# #   # #"
maze$(5) = "#   #   #"
maze$(6) = "#########"
FOR count = 1 to 6
 PRINT maze$(count)
NEXT count

Now lets put PACMAN in the maze.
Wait - there's nothing that will keep him from moving over the walls.
To solve that problem I'll show you how to inspect the maze (the contents of
maze$).

The command MID$ allows you to look at parts of a string variable.
It has the following format: MID$(name of string, starting position, number of
letters). 
Lets look at an example:
alphabet$ = "ABCDE"
PRINT MID$(alphabet$, 1, 2)         :REM displays "AB"
PRINT MID$(alphabet$, 3, 1)         :REM displays "C"

Every time PACMAN moves we'll use his position (row and column) to see if he
overlaps a wall in the maze.
Type (or edit) and RUN: (you'll have to press CTRL-BREAK to stop the program)

CLS
DIM maze$(6)
maze$(1) = "#########"
maze$(2) = "#   #   #"
maze$(3) = "# # # # #"
maze$(4) = "# #   # #"
maze$(5) = "#   #   #"
maze$(6) = "#########"
row = 5
column = 3
DO
 LOCATE 1, 1
 FOR count = 1 to 6
  PRINT maze$(count)                  :REM PRINT the maze
 NEXT count 
 LOCATE row, column
 PRINT "C"                            :REM PRINT PACMAN
 DO
  keyed$ = INKEY$
 LOOP UNTIL keyed$ <> ""
 oldRow = row                         :REM remember old position of PACMAN 
 oldColumn = column
 IF keyed$ = "q" THEN row = row - 1
 IF keyed$ = "a" THEN row = row + 1
 IF keyed$ = "o" THEN column = column - 1
 IF keyed$ = "p" THEN column = column + 1
 IF MID$(maze$(row), column, 1) = "#" THEN
  row = oldRow                                 :REM <?? move PACMAN back to his
  column = oldColumn                           :REM  ?? old position
 END IF
LOOP 

All that needs mentioning is that I used the "IF..END IF" structure to allow
for more than one action.

If you type in the examples you can save yourself some time by saving the
PACMAN game to disk (press ALT-F then S), because we will re-use the game
during the rest of the week.

Tomorrow we'll add dots and a ghost to the maze!

-------------------------------------------------------------------------------
?????????????????
? Week 1, Day 4 ?
?????????????????

Add the dots to the maze by changing the following lines:

maze$(1) = "#########"
maze$(2) = "#...#...#"
maze$(3) = "#.#.#.#.#"
maze$(4) = "#.#...#.#"
maze$(5) = "#...#...#"
maze$(6) = "#########"

As you might have guessed we'll inspect the maze each time PACMAN moves and
keep count of the number of dots he eat.
A dot has to be removed when eaten. 
When our count reaches 21 (count them!) dots, the game ends.

MID$ can be used to change a string variable as well as inspecting it.
Look at the following example:

alphabet$="ABCDE"
MID$(alphabet$, 2, 3) = "XYZ"
PRINT alphabet$                    :REM displays "AXYZE"  

We'll use MID$ to replace the dots with blanks when PACMAN eats them.
Type (or edit) and RUN: (changes in the program are marked)

CLS
DIM maze$(6)
maze$(1) = "#########"
maze$(2) = "#...#...#"
maze$(3) = "#.#.#.#.#"
maze$(4) = "#.#...#.#"
maze$(5) = "#...#...#"
maze$(6) = "#########"
row = 5
column = 3
dots = 21               :REM  <??- new
DO
 LOCATE 1, 1
 FOR count = 1 to 6
  PRINT maze$(count)                 
 NEXT count 
 LOCATE row, column
 PRINT "C"                    
                                                :REM  <??? new
 IF dots = 0 THEN                                                 
  LOCATE 8, 1                                   :REM    ?
  PRINT "You have won!"                         :REM    ?
  END                                           :REM    ?
 END IF                                         :REM   ??
 DO
  keyed$ = INKEY$
 LOOP UNTIL keyed$ <> ""
 oldRow = row                
 oldColumn = column
 IF keyed$ = "q" THEN row = row - 1
 IF keyed$ = "a" THEN row = row + 1
 IF keyed$ = "o" THEN column = column - 1
 IF keyed$ = "p" THEN column = column + 1
 IF MID$(maze$(row), column, 1) = "#" THEN
  row = oldRow         
  column = oldColumn   
 END IF
                                                :REM  <??? new
 IF MID$(maze$(row), column, 1) = "." THEN   
  MID$(maze$(row), column, 1) = " "             :REM    ?
  dots = dots - 1                               :REM    ?
 END IF                                         :REM   ??
LOOP

(QBasic does not allow me to place a ":REM" next to a IF..THEN line. )
(So if you suspect a REM is missing, see if its next tot an IF..THEN.)

The reason why we dont do the "IF dots = 0" test directly after inspecting the
maze is because we want to draw PACMAN in his new position before ending the 
game.

Here is a better (or clearer) way of inspecting keyed$ and maze$:

REM examine the keys
SELECT CASE keyed$
       CASE IS = "q"
        row = row - 1
       CASE IS = "a"
        row = row + 1
       CASE IS = "o"
        column = column - 1
       CASE IS = "p"
        column = column + 1
END SELECT            

REM examine the maze 
SELECT CASE MID$(maze$(row), column, 1)
       CASE IS = "#"
        row = oldRow
        column = oldColumn
       CASE IS = "."
        MID$(maze$(row), column, 1) = " "
        dots = dots - 1
END SELECT              

SELECT makes it a lot easier to read the program.
Also notice the way I put spaces before (indent) some words.
Another good idea is to use blank lines to seperate the various parts of your
program.

Lets place a ghost in the maze.
We'll just display the ghost for now and detect whether PACMAN collides with 
it. Tomorrow we can make it move.

Detecting whether there's a collision is a simple matter of comparing PACMAN's
and the ghost's positions (row and column).
Type (or edit) and RUN:

CLS

DIM maze$(6)
maze$(1) = "#########"
maze$(2) = "#...#...#"
maze$(3) = "#.#.#.#.#"
maze$(4) = "#.#...#.#"
maze$(5) = "#...#...#"
maze$(6) = "#########"

row = 5
column = 3
ghostRow = 2                     :REM  <??? new
ghostColumn = 7                  :REM   ??
dots = 21                             

DO

 LOCATE 1, 1
 FOR count = 1 to 6
  PRINT maze$(count)                 
 NEXT count 

 LOCATE row, column
 PRINT "C"                    

 LOCATE ghostRow, ghostColumn       :REM  <??? new
 PRINT "G"                          :REM   ??

 IF dots = 0 THEN                                           
  LOCATE 8, 1                                        
  PRINT "You have won!"                               
  END                                               
 END IF                                             
                                                           :REM  <??? new
 IF ((row = ghostRow) AND (column = ghostColumn)) THEN   
  LOCATE 8, 1                                              :REM    ?
  PRINT "You've been caught!"                              :REM    ?
  END                                                      :REM    ?
 END IF                                                    :REM   ??

 DO
  keyed$ = INKEY$
 LOOP UNTIL keyed$ <> ""

 oldRow = row                
 oldColumn = column

 REM examine the keys
 SELECT CASE keyed$
       CASE IS = "q"
        row = row - 1
       CASE IS = "a"
        row = row + 1
       CASE IS = "o"
        column = column - 1
       CASE IS = "p"
        column = column + 1
 END SELECT            

 REM examine the maze 
 SELECT CASE MID$(maze$(row), column, 1)
       CASE IS = "#"
        row = oldRow
        column = oldColumn
       CASE IS = "."
        MID$(maze$(row), column, 1) = " "
        dots = dots - 1
 END SELECT              

LOOP

Phew!  I need a rest after that!
Remember to save the program for tomorrow.
 
-------------------------------------------------------------------------------
?????????????????
? Week 1, Day 5 ?
?????????????????

Lets give the ghost some "artificial intelligence".

As you might have suspected we will compare his positions with PACMAN's and 
move him one position nearer.
Allowing the ghost to move diagonally will give it an unfair advantage so
ghostRow and ghostColumn will not change at the same time.
Of course the ghost will also have to be blocked by the walls of the maze.
We dont have to check for dots because the ghost dont eat them.

So what are we waiting for? Type (or edit) and RUN!

CLS

DIM maze$(6)
maze$(1) = "#########"
maze$(2) = "#...#...#"
maze$(3) = "#.#.#.#.#"
maze$(4) = "#.#...#.#"
maze$(5) = "#...#...#"
maze$(6) = "#########"

row = 5
column = 3 
ghostRow = 2                                   
ghostColumn = 7                      
dots = 21                             

DO

 LOCATE 1, 1
 FOR count = 1 to 6
  PRINT maze$(count)                 
 NEXT count 

 LOCATE row, column
 PRINT "C"                    

 LOCATE ghostRow, ghostColumn                    
 PRINT "G"                               

 IF dots = 0 THEN                                           
  LOCATE 8, 1                                        
  PRINT "You have won!"                               
  END                                               
 END IF                                             

 IF ((row = ghostRow) AND (column = ghostColumn)) THEN                
  LOCATE 8, 1                                                   
  PRINT "You've been caught!"                                   
  END                                                            
 END IF                                                          

 DO
  keyed$ = INKEY$
 LOOP UNTIL keyed$ <> ""

 oldRow = row                
 oldColumn = column

 REM examine the keys
 SELECT CASE keyed$
       CASE IS = "q"
        row = row - 1
       CASE IS = "a"
        row = row + 1
       CASE IS = "o"
        column = column - 1
       CASE IS = "p"
        column = column + 1
 END SELECT            

 REM examine the maze 
 SELECT CASE MID$(maze$(row), column, 1)
       CASE IS = "#"
        row = oldRow
        column = oldColumn
       CASE IS = "."
        MID$(maze$(row), column, 1) = " "
        dots = dots - 1
 END SELECT              

 REM move ghost closer to PACMAN            :REM <?? everything from this point
                                            :REM   ? onwards is new
 oldRow = ghostRow                                            
 SELECT CASE ghostRow 
        CASE < row
         ghostRow = ghostRow + 1
        CASE > row
         ghostRow = ghostRow - 1
 END SELECT
 IF MID$(maze$(ghostRow), ghostColumn, 1) = "#" THEN ghostRow = oldRow

 IF ghostRow = oldRow THEN
  oldColumn = ghostColumn
  SELECT CASE ghostColumn
         CASE < column
          ghostColumn = ghostColumn + 1
         CASE > column
          ghostColumn = ghostColumn - 1
  END SELECT            
  IF MID$(maze$(ghostRow), ghostColumn, 1) = "#" THEN ghostColumn = oldColumn
 END IF         
LOOP

Notice that I test whether ghostRow has stayed the same ( = oldRow) before
testing for ghostColumn.
This prevents diagonal movement for the ghost.
Another problem is that the ghost waits for you to make a move before moving
himself.
The loop that waits for a key to be pressed is the culprit.
Now we will have to put a delay somewhere in the program because the ghost will
move at blinding speed!
Replace the following lines in the program and RUN it:

DO
 keyed$ = INKEY$
LOOP UNTIL keyed$ <> ""

with:
 
keyed$ = INKEY$
REM: kill time
FOR nothing = 1 TO 500
NEXT nothing 

If the game is still too fast then change the limit of "nothing" to 1000 or 
more.

The game is still too difficult because the ghost is too intelligent - it 
follows you at every possible opportunity.
To add some randomness to its pattern we will use the command RND.
The value of RND is never the same (for all practical purposes) but is always
a value between 0 and 1.
The condition "IF RND < 0.1 " is true roughly 10% of the time.
Here is the entire listing with the changes added: (Type (or edit) and RUN)

CLS

DIM maze$(6)
maze$(1) = "#########"
maze$(2) = "#...#...#"
maze$(3) = "#.#.#.#.#"
maze$(4) = "#.#...#.#"
maze$(5) = "#...#...#"
maze$(6) = "#########"

row = 5
column = 3 
ghostRow = 2                                   
ghostColumn = 7                      
dots = 21                             

DO

 LOCATE 1, 1
 FOR count = 1 to 6
  PRINT maze$(count)                 
 NEXT count 

 LOCATE row, column
 PRINT "C"                    

 LOCATE ghostRow, ghostColumn                    
 PRINT "G"                               

 IF dots = 0 THEN                                           
  LOCATE 8, 1                                        
  PRINT "You have won!"                               
  END                                               
 END IF                                             

 IF ((row = ghostRow) AND (column = ghostColumn)) THEN                
  LOCATE 8, 1                                                   
  PRINT "You've been caught!"                                   
  END                                                            
 END IF                                                          

 keyed$ = INKEY$

 FOR nothing = 1 TO 500                  :REM change if your computer is faster
 NEXT nothing
 
 oldRow = row                
 oldColumn = column

 REM examine the keys
 SELECT CASE keyed$
       CASE IS = "q"
        row = row - 1
       CASE IS = "a"
        row = row + 1
       CASE IS = "o"
        column = column - 1
       CASE IS = "p"
        column = column + 1
 END SELECT            

 REM examine the maze 
 SELECT CASE MID$(maze$(row), column, 1)
       CASE IS = "#"
        row = oldRow
        column = oldColumn
       CASE IS = "."
        MID$(maze$(row), column, 1) = " "
        dots = dots - 1
 END SELECT              
                                          :REM   <??? new
 IF RND < 0.1 THEN
                                          :REM    ??
  REM move ghost closer to PACMAN
  oldRow = ghostRow  
  SELECT CASE ghostRow 
         CASE < row
          ghostRow = ghostRow + 1
         CASE > row
          ghostRow = ghostRow - 1
  END SELECT
  IF MID$(maze$(ghostRow), ghostColumn, 1) = "#" THEN ghostRow = oldRow

  IF ghostRow = oldRow THEN
   oldColumn = ghostColumn
   SELECT CASE ghostColumn
          CASE < column
           ghostColumn = ghostColumn + 1
          CASE > column
           ghostColumn = ghostColumn - 1
   END SELECT            
   IF MID$(maze$(ghostRow), ghostColumn, 1) = "#" THEN ghostColumn = oldColumn
  END IF         
 END IF                      :REM  <??? new
LOOP

Another way to slow down the game is to use a variable "wait" and add 1 to it
every time the program loops.
Only when "wait" = 20 the ghost is allowed to move.
Remember to reset "wait" to 0 after moving, or its value will be 21, 22, etc.
and never again 20!
If you use this method you must remove the FOR..NEXT loop that kills time.

Next week we'll do an RPG!

-------------------------------------------------------------------------------