���������������ķ � 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! -------------------------------------------------------------------------------