Monday, September 24, 2012

Petit Computer Journal #8


Petit Computer Journal #8





The Apple Picker Game? We're going to finish it up proper! Actually, we're not going to really make it professional quality. But we'll put on some fancy dressing on it to make it really enjoyable!

Adding color:
It was later in the development cycle (fancy words for repeatedly finding and fixing numerous bugs) that I find myself squinting at the screen looking for that last apple among the many snakes. Adding colors solves this problem. I colored the apples pink because I find red is too strong among the snake green.

Adding music:
It makes a great game. Looking at the help menu, I see a list of ready made music. Use BGMPLAY N, where N is a number. BGMSTOP to stop the music. Remember that N is a number. If you do this: BGMPLAY FANCY. That means you're playing music as defined by variable FANCY, which would be zero if you haven't set it to anything.

I purposely did not add music for level playing. I found it to be too noisy. But if you want it, add this to @NEWLEVEL
BGMPLAY LEVEL+ADJ
This will launch a new background music with every new level, with ADJ as offset.

Adding Sound Effect:
Oh, this is a good one! I added scream, hit, and coin sounds immediately. Easy sound effects! It took me awhile to add steps sound, but once I did, I never want to go back! This is how you know what you did is good!

Adding walls:
This was a doozy! The program kept hanging up (a polite way to say unintended infinite loop) and I didn't know why! Obviously, I needed to work out the math on paper, and then do exhaustive analysis upon it. I did neither. I did hack-and-slash programming, so even now, I'm not sure the solution is correct.

I did test out this form:
FOR I=0 TO 0:T=RND(5)+5):I=ABS(MY-T)-1:NEXT:WX[0]=T
You probably wonder why I bothered to put the FOR loop in there. I use it as REPEAT loop, just so you know. Notice that I set the value of I everytime. When MY==T, the loop will repeat. That is, T of any value EXCEPT WY.

The walls will erase snakes. That's fine. I also have to make sure that the walls will never form an enclosed space where the player cannot enter/exit. The easy way is to create some levels with array or DATA statements. I decided to create it dynamically, which isn't the easiest in the world, but I hate putting in DATA too much and results in repeat levels, making them boring.

Adding Lives:
I added this as I realize that the later levels may feature apples surrounded by snakes. I don't want the game to end immediately. Like any good design decision, once I put it in, I don't want to take it out.

Adding Time:
This comes in last. After a while, I started to add challenges. Can I finish 10 levels in 5 minutes? 20 levels in 10 minutes? Yes, I can! You can, too. Look at the levels and time completed. Major bragging rights, there!

Adding Alternative control:
It's not hard to do button controls. I simply extended the button readouts, and that's it! Noteworthy to mention is that I didn't put fast button where pushing L or R button results in faster movement. It's not hard to do, but I can pull the feature out after I put it in. Not a good design decision.

Adding moving snake:
I decided not to. It's not that kind of game. The game evolves from taking apples (Path Finding/Travelling Salesman) problem, to maze game, to Least Cost finding. Along the way, you have to be able to control the person very well to avoid running into snakes.

Adding customized graphic:
I decided not to. The game benefits from seeing the whole level all at once. If I use same size sprites, the size of the game would have expanded radically, for little returns. If I use double size sprites, the game will look great, but play suffers. If I use smaller playfield, the game becomes boring really quickly. If I use big sprite on top screen, and normal size for bottom screen as map, then the player will constantly look at the bottom screen. In which case, why bother?

How to CHEAT:
Yes, you can cheat! You have the source code in front of you! Whenever you feel like you want some extra lives, hit BREAK, then type this:
LIVE=20:CONT
You'll continue no problem. Your live is now 20! You have to be careful to do it so the screen does not scroll, and no apple is overwritten. I recommend waiting until the last apple is on upper right corner.

You can also add more apples, by typing this:
SCORE=9:CONT
Since the level advances every 10 points, this will cause new apples to appear at the next apple taken.

Level select is no problem either. Can you guess how?
LEVEL=15:CONT
Yup. It's that easy!

Finally, you may want a puzzle game, instead of an action game. Not a problem! Do this at @GAMELOOP

From this: VSYNC 15:GOSUB @SETB2
To this: VSYNC 1:GOSUB @SETB3

And you got yourself a puzzle game!

Professional Quality?

This game is a good Hobbyist effort, but I wouldn't call it a professional quality. There are different things that I can do as a professional, but choose not to. I already mentioned the exhaustive testing of walls. Here are some other things that I need to do in order to be professionals:

1. Limit the number of lives to 30, as to not mess up the display.
2. Add option to set background music to the levels
3. Add faster speed button
4. Save/Load High scores
5. Refresh whole screen to prevent display corruption
6. Add variety of enemies. Maybe some will add apples, others fling you randomly to another part of the screen.
7. Add time limit, allowing for levels of difficulty.
8. Also adjust player speed according to level of difficulty.
9. Add Puzzle mode from Option
10. Add replay option, play as demo on splash screen.
11. Tune up the presentation, not necessarily fancy graphics, but I would experiment with different placements of elements.
12. Make sure adding apples, snakes will not hang the game.

If I want to put this out for sale, I will add these:
13. Multiplayer option. 2 players. One is using Dpad. The other, buttons. Photo Dojo style
14. Computer player, single, double. With good path finding algorithm.
15. Selectable number of player
16. Sound/Music selection. Volume adjustable individually.
17. Optional level editor. It's not that hard.
18. Optional sprite editor. It's not that hard.
19. Optional music editor. It's not that hard.
20. Optional 3D graphic. This one is hard, and not at all useful. It's great marketing tool, though.


Monday, September 17, 2012

Petit Computer Journal #7


Petit Computer Journal #7
Character and String

So far we have dealt with numbers. It's time to deal with the other data type: String. What is a string? It's a string of characters. What is a character? That's a good question. It goes back to the beginning. What are computer made of? A bunch of numbers. Therefore a character is a number. It's a special number, however. What's the difference between this character number and ordinary number? An ordinary number can go to 524287. A character number is from 0 to 255. You can see what they are by going to Help Menu#12 "Text and Display" - Character Code List.

Please note that character 0 (NULL) and character 32 (SPACE) are two different characters!

Basic Character ASC() CHR$() VAL() STR$() HEX$()

You already know how to get a character given a number. Use CHR$() as given by this program from Petit Computer Journal#5:

'ASCII TABLE
S=0
@MAINLOOP
CLS
FOR I=S TO S+15
R=I%16
LOCATE 0,R:?I;:LOCATE 5,R:?CHR$(I) :'<--Right here!
NEXT

GOSUB @SETB3

IF INBUTTON$=="U" THEN S=S+16
IF INBUTTON$=="D" THEN S=S+256-16
S=S%256
?:?"S=";S;"   ";INBUTTON$:WAIT 60:'OPTIONAL FOR DEBUGGING
GOTO @MAINLOOP

Given a number, CHR$() will show a character. What is the reverse function of it? The ASC(). Given a character, it will return a number. How about regular number? Let's say we have a number 1043, and we want a string "1043". How do we do it? We use STR$(). And the reverse? VAL(). Go to RUN Mode, and try these out:

?CHR$(31) 'Prints "!"
?CHR$(65) 'Prints "A"
?ASC("A") 'Prints 65
?ASC("a") 'Prints 97
?HEX$(80) 'Prints 50
?HEX$(80,4) 'Prints 0050 Note leading zeros.

?"ABC"+"DEF" 'Prints ABCDEF
?"ABC"*3 'Prints ABCABCABC


Let's do a couple quick functions. UCASE and LCASE, which returns Upper Case string and Lower case strings respectively.

@MAINLOOP
LINPUT "string",A$
?"Upper Case: ";:GOSUB @UCASE:?A$
?"Lower case: ";:GOSUB @LCASE:?A$
?:?
GOTO @MAINLOOP

@UCASE
T$=""
FOR I=0 TO LEN(A$)-1
C$=MID$(A$,I,1):'SINGLE CHARACTER AT LOCATION I
IF ASC(C$)>=97 AND 122>=ASC(C$) THEN C$=CHR$(ASC(C$)-32)
T$=T$+C$:'APPEND C$ TO T$
NEXT
A$=T$
RETURN


@LCASE
T$=""
FOR I=0 TO LEN(A$)-1
C$=MID$(A$,I,1):'SINGLE CHARACTER AT LOCATION I
IF ASC(C$)>=65 AND 90>=ASC(C$) THEN C$=CHR$(ASC(C$)+32)
T$=T$+C$:'APPEND C$ TO T$
NEXT
A$=T$
RETURN


Search and Replace LEN() MID$() RIGHT$() LEFT$() INSTR SUBST$

You have seen a few more string functions: LEN(A$). This is the number of character in A$. In other words, string LENgth of A$. We subtract 1 from LEN(A$) because strings are zero-based, while the LEN() is 1-based.


MID$(A$,I,1). You'll probably see this construct a lot. What it does is basically returns the character at position I (zero based). We can return more than 1 character. If we want 4 characters starting at position I, we'll do this: MID$(A$,I,4). It's that simple!

LEFT$() and RIGHT$() are just like MID$, except they don't start in the middle. They start at left and right respectively. Try this out in RUN Mode:

A$="Hello World"
?LEFT$(A$,5)  'Prints "Hello"
?RIGHT$(A$,5)  'Prints "World"

Type NEW. Start a new program. Type this program in Edit Mode and run it:

INPUT A$
CLS
@LOOP
LOCATE 0,0:?A$
WAIT 15
A$=RIGHT$(A$,LEN(A$)-1)+LEFT$(A$,1)
GOTO @LOOP

Did you see a rotating banner? Good! All this does is take the rest of A$ except the first character and add the first character. In other words, put the first character at the end. You can change the number '1' with any number to rotate that many characters.

Let's do a stack and queue real quick. Stacks puts in data (PUSH) and pull out data (POP) at the same place. We'll do it at the end. Queue in (ENQ) also at the end. but Queue Out (DEQ) is at the opposing end. We'll see it in action. Start a new program.

CLS:CLEAR
A$="A":GOSUB @PUSH
A$="B":GOSUB @PUSH
A$="C":GOSUB @PUSH
A$="D":GOSUB @PUSH
GOSUB @POP
GOSUB @POP
GOSUB @POP
A$="E":GOSUB @PUSH
A$="F":GOSUB @PUSH

CLEAR
A$="A":GOSUB @ENQ
A$="B":GOSUB @ENQ
A$="C":GOSUB @ENQ
A$="D":GOSUB @ENQ
GOSUB @DEQ
GOSUB @DEQ
GOSUB @DEQ
A$="E":GOSUB @ENQ
A$="F":GOSUB @ENQ

END

@PUSH
?"PUSH ";
S$=S$+A$
?"A$=";A$;" S$=";S$
RETURN

@POP
?"POP  ";
A$=RIGHT$(S$,1)
S$=LEFT$(S$,LEN(S$)-1) 
?"A$=";A$;" S$=";S$
RETURN

@ENQ :'Enqueue (same as PUSH!)
?"ENQ ";
S$=S$+A$
?"A$=";A$;" S$=";S$
RETURN

@DEQ :'Dequeue
?"DEQ ";
A$=RIGHT$(S$,1)
S$=LEFT$(S$,LEN(S$)-1) 
?"A$=";A$;" S$=";S$
RETURN

Compare the two. Do you see the difference between stack and queue?
Push enters data from the right side. Pops it from the right side.
Enqueue enters data from the right side. Takes it from the left side.
That's it,really. Simple. BTW, PUSHing from Left is S$=A$+S$.

I use INSTR function quite a lot. I use it as indexing function. Basically, If I want to know whether something is a vowel. I can use this:

L=INSTR("AEIOU",C$) :'Where C$ is a character to check for vowel. L=-1 means not a vowel.

SUBSTR$() is handy if you don't want to do a lot of shifting with LEFT$ AND RIGHT$. Let's say you want to replace <> with the player's name. You can do this:

CLS:CLEAR
T$="HELLO <>! TODAY IS <>. THE TIME IS <

INPUT "NAME";N$
S$=T$ :'COPIES T$ TO S$
L=INSTR(S$,"<>"):LLEN=LEN("<>")
S$=SUBST$(S$,L,LLEN,N$)
L=INSTR(S$,"<>"):LLEN=LEN("<>")
S$=SUBST$(S$,L,LLEN,DATE$)
L=INSTR(S$,"<
S$=SUBST$(S$,L,LLEN,TIME$)

?:?S$

Now you have the skeleton program for MadLibs. Add this to the program to see how you can replace the same string multiple times. This is also the way Mail Merge program works, BTW. Simple Template applications.

T$="HELLO <>! GOODBYE <>!"
S$=T$ :'COPIES T$ TO S$
L=INSTR(S$,"<>"):LLEN=LEN("<>")
S$=SUBST$(S$,L,LLEN,N$)
?:?S$ :'SUB THE FIRST <>
L=INSTR(S$,"<>"):LLEN=LEN("<>")
S$=SUBST$(S$,L,LLEN,N$)
?:?S$ :'SUB THE SECOND <>

And that's all there is to it! Wow, we have written a lot of programs today, and we're still not done! I know I'm bucking the convention here, but I like to just go through the steps all at once. It's not that hard to go through the different commands. It's hard to understand the purpose of such commands, which is why I'm giving you all the different examples to use. You probably don't know why I choose these kind of programs. Trust me when I say that these programs are the basic building blocks of computer programming.

You may take a break here if you're tired. Come back later and we'll do one final program: a game that uses our newfound knowledge, and something that is doable in one hour, and fun, and something worthwhile that you can show off to other people. Wow, is that a tall order or what?

Haha, joking aside, I do have such program. And, no, it's not Mad Libs. That one is 15 minutes! Try to guess what it is before you proceed. who knows? Maybe you have a better suggestion than mine!




A Simple Game: Hangman

Let's do a Hangman program real quick. What is it? Show and Tell! what is Hangman? It's a game where you have to guess the secret sentence. Each wrong guess nets you a mark. Once 10 marks or whatever number decided is reach, you lose! It's simple enough to be done in one hour.

What do we need?
1. T$: The secret sentence.
2. G$: Guessed letters
3. W$: Wrong guesses
4. K$: Available characters to guess.

What are the steps?
1. Display all 4 strings. If T$ char not in G$ then show Square
2. Ask for input (set to UPPERCASE)
3. If the character is in K$, then proceed, else GOTO step 1.
4. if the character is in T$, then add to G$, else add to W$.
5. If LEN(w$)>=10 then LOSE
6. If all char in T$ is in G$ then WIN
7. GOTO 1


CLS:CLEAR

@INIT
T$="":G$=" ":W$=" "
K$="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
C$="abcdefghijklmnopqrstuvwxyz"

T$="THIS IS THE SECRET WORD"

@MAIN
CLS:?"HANGMAN"
?:?
FOR I=0 TO LEN(T$):A$=MID$(T$,I,1)
IF INSTR(G$,A$)< 0 THEN ?"#"; ELSE ?A$;
NEXT
?:?:?"GUESSED LETTERS"
?G$
?:?:?"WRONG LETTERS"
?W$
?:?:?"AVAILABLE LETTERS"
FOR I=0 TO LEN(K$):A$=MID$(K$,I,1)
IF INSTR(G$,A$)< 0 AND INSTR(W$,A$)< 0 THEN ?A$;
NEXT
?:?:?:LINPUT "LETTER?";S$

First stage: 18 minutes. Now to complete the rest of the program.

CLS:CLEAR

@INIT
T$="":G$=" ":W$=" "
K$="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
C$="abcdefghijklmnopqrstuvwxyz"

T$="THIS IS THE SECRET WORD"

@MAIN
CLS:?"HANGMAN"
?:?
FOR I=0 TO LEN(T$):A$=MID$(T$,I,1)
IF INSTR(G$,A$)< 0 THEN ?"#"; ELSE ?A$;
NEXT
?:?:?"GUESSED LETTERS"
?G$
?:?:?"WRONG LETTERS"
?W$
?:?:?"AVAILABLE LETTERS"
FOR I=0 TO LEN(K$):A$=MID$(K$,I,1)
IF INSTR(G$,A$)< 0 AND INSTR(W$,A$)< 0 THEN ?A$;
NEXT

@STEP2
?:?:?:LINPUT "LETTER?";S$
S$=LEFT$(S$,1):IF S$=="" GOTO @MAIN
IF INSTR(C$,S$)>=0 THEN S$=CHR$(ASC(S$)-32)
?"YOUR GUESS IS: ";S$

@STEP3
IF INSTR(G$,S$)>=0 GOTO @MAIN
IF INSTR(W$,S$)>=0 GOTO @MAIN

@STEP4
IF INSTR(T$,S$)>=0 THEN G$=G$+S$ ELSE W$=W$+S$

@STEP5
IF LEN(W$)>9 GOTO @LOSE

@STEP6
WIN=1
FOR I=0 TO LEN(T$)
C1=INSTR(K$,MID$(T$,I,1))
C2=INSTR(G$,MID$(T$,I,1))
IF C1< 0 GOTO @STEP6A
IF C2< 0 THEN WIN=0
@STEP6A
NEXT
IF WIN GOTO @WIN
GOTO @MAIN

@LOSE
?"YOU LOSE!"
GOTO @ANYKEY

@WIN
?"YOU WIN!"
GOTO @ANYKEY

@ANYKEY
WAIT 60
FOR I=0 TO 1:I=TCHST OR BUTTON(3):NEXT
GOTO @INIT


Second stage:  18 minutes



We have a complete program in about 36 minutes. That's typing it from scratch using stylus on the device. We'll add one more thing: READ and DATA

CLS:CLEAR

@INIT
T$="":G$=" ":W$=" "
K$="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
C$="abcdefghijklmnopqrstuvwxyz"

'T$="THIS IS THE SECRET WORD"
I=1:RESTORE @TEXTDATA
@SETT
READ D$
IF D$=="" GOTO @MAIN
IF !RND(I) THEN T$=D$
I=I+1
GOTO @SETT

@MAIN
CLS:?"HANGMAN"
?:?
FOR I=0 TO LEN(T$):A$=MID$(T$,I,1)
IF INSTR(G$,A$)< 0 THEN ?"#"; ELSE ?A$;
NEXT
?:?:?"GUESSED LETTERS"
?G$
?:?:?"WRONG LETTERS"
?W$
?:?:?"AVAILABLE LETTERS"
FOR I=0 TO LEN(K$):A$=MID$(K$,I,1)
IF INSTR(G$,A$)< 0 AND INSTR(W$,A$)< 0 THEN ?A$;
NEXT

@STEP2
?:?:?:LINPUT "LETTER?";S$
S$=LEFT$(S$,1):IF S$=="" GOTO @MAIN
IF INSTR(C$,S$)>=0 THEN S$=CHR$(ASC(S$)-32)
?"YOUR GUESS IS: ";S$

@STEP3
IF INSTR(G$,S$)>=0 GOTO @MAIN
IF INSTR(W$,S$)>=0 GOTO @MAIN

@STEP4
IF INSTR(T$,S$)>=0 THEN G$=G$+S$ ELSE W$=W$+S$

@STEP5
IF LEN(W$)>9 GOTO @LOSE

@STEP6
WIN=1
FOR I=0 TO LEN(T$)
C1=INSTR(K$,MID$(T$,I,1))
C2=INSTR(G$,MID$(T$,I,1))
IF C1< 0 GOTO @STEP6A
IF C2< 0 THEN WIN=0
@STEP6A
NEXT
IF WIN GOTO @WIN
GOTO @MAIN

@LOSE
?"YOU LOSE!"
GOTO @ANYKEY

@WIN
?"YOU WIN!"
GOTO @ANYKEY

@ANYKEY
WAIT 60
FOR I=0 TO 1:I=TCHST OR BUTTON(3):NEXT
GOTO @INIT

@TEXTDATA
DATA "THIS IS THE SECRET WORD"
DATA "THIS IS A VERY LONG PHRASE THAT WILL WRAP AROUND"
DATA "I WOULD TYPE A LITTLE FASTER"
DATA "TO BOLDLY GO WHERE NO MAN HAS   GONE BEFORE"
DATA "THE QUICK BROWN FOX JUMPS OVER  THE LAZY DOG"
DATA ""

And that's the complete program. Obviously, if you have more time, you can add more data. That's the beauty of it. You can add as many as you like! Be careful about punctuation marks, since the program cannot handle them. Or put some punctuation marks on G$!

The program we have now is just a simple quick and dirty program. We don't draw the Hangman character, for one. Also, it would be nice to show the completed solution. But that's enough for now. Not bad for one hour computer programming using stylus pecking!

Monday, September 10, 2012

Petit Computer Journal #6


Petit Computer Journal #6

I don't know about you, but I'm itching for a game. I know I'm bucking the convention here, but I want a real game, none of this Hi-Lo stuff that some people call game. I actually have 2 choices: A word guessing game (Like Wheel of Fortune) or a Snake game. Let's do Snake game first. It's probably a good idea to set the difficulty in linear manner, but I think we can handle otherwise.

We'll lower the difficulty by making The Apple Picker Game. What kind of game is that? It's an Apple Eating Snake game without the Snake. Not that implementing a snake is difficult, but I want to explain how strings work before I do that. The reasonable approach is to do the other game first. We'll do this game first because, hey, sometimes it's good to live dangerously!

Haha, joking aside, I think we have all that we need in order to build it. Show-and-Tell time! What makes an Apple Picker game?

1. A Character who picks apples
2. Apples
3. Area
4. Obstacles (optional)
5. Time Limit (optional)
6. Score (optional)

The gameplay area is the console 32x24 character map. We'll use character for graphics. The movement will be once per second (VSYNC 60). A character will keep moving forward until it runs into an obstacle (wall). Do we need time limit? How about ending the game after all the apples are gone? Do we need score?

Let's keep things simple for now. We do a character, put some apples, and build wall along the perimeter. We'll split the program into 3 sections: Init, GameLoop, and Ending

Init:
1. Clear the screen CLS
2. Put character on location 10,20
3. Draw Wall along perimeter
4. Put 10 apples randomly

GameLoop:
1. Wait 1 second VSYNC 60
2. Read BUTTON(0) via SETB2
3. Depending on UDLR, we check the next space
3a. Space - move character there
3b. Apple - Increase score by 1, and move character there
3c. Wall  - Set character movement to STOP.
4. If score is 10 then GOTO Ending

Ending:
1. Display "GAME OVER"
2. Wait 2 seconds
3. Wait for button press SETB3
4. End Program.

That's not too bad. It's not even one page. How hard can it be? Notice how we reuse our old BUTTON reading code. That makes thing simple.

@INIT :'Program Initialization
'1. Clear the screen 
CLS
'2. Put character on location 10,20
LOCATE 10,20:?CHR$(244):'MAN
'3. Draw Wall along perimeter
LOCATE 0,1:?CHR$(3)*32
LOCATE 0,21:?CHR$(3)*32
FOR Y=1 TO 21
LOCATE 0,Y:?CHR$(3)
LOCATE 31,Y:?CHR$(3)
NEXT
'4. Put 10 apples randomly
GOSUB @PUTAPPLE


@ENDING :'Program Ending
'1. Display "GAME OVER"
LOCATE 10,22:?"GAME OVER";
'2. Wait 2 seconds
WAIT 120
'3. Wait for button press SETB3
GOSUB @SETB3
'4. End Program.
END

We need to work on @GameLoop. We also need to work on @PUTAPPLE. Otherwise, the program is half done. Here is PUTAPPLE:

@PUTAPPLE :'CHR$(233) IS APPLE
FOR I=1 TO 10
X=RND(31-1)+1:Y=RND(20-2)+2:C=CHKCHR(X,Y)
IF C==0 THEN LOCATE X,Y:?CHR$(233) ELSE I=I-1
NEXT
RETURN

The thing we want to watch out for is that we only put apple on bare ground. We also make sure that there are EXACTLY 10 apples. Ah, that was easy. We run it, and see if it works. Save it as APPLE.

Now for the main game loop. Wait 1 second. Read Input. Move pieces. Repeat until done. What can be the problem? Oh, wait. We forgot to store our man's location. Put this on Init: "MX=10:MY=20". We also forgot the score: "SCORE=0"


@GameLoop
'1. Wait 1 second 
'2. Read BUTTON(0) via SETB2
VSYNC 60:GOSUB @SETB2

'3. Depending on UDLR, we check the next space
GOSUB @NEXTSPACE:'SETS NX,NY
GOSUB @DOMOVE

'4. If score is 10 then GOTO Ending
IF SCORE==10 GOTO @ENDING
GOTO @GAMELOOP


'3a. Space - move character there
'3b. Apple - Increase score by 1, and move character there
'3c. Wall  - Set character movement to STOP.
@DOMOVE
C=CHKCHR(NX,NY)
IF C==0 THEN GOSUB @MANMOVE:RETURN
IF C==233 THEN GOSUB @EATAPPLE:GOSUB @MANMOVE:RETURN
IF C==3 THEN GOSUB @HITWALL:RETURN
RETURN

@MANMOVE
LOCATE MX,MY:?CHR$(0);
LOCATE NX,NY:?CHR$(244);
MX=NX:MY=NY
RETURN

@EATAPPLE
BEEP 7:'COIN
SCORE=SCORE+1
LOCATE 0,22:?"SCORE ";SCORE;
RETURN

@HITWALL
BEEP 11:'DAMAGE
RETURN

@NEXTSPACE
IF INBUTTON$=="U" THEN NX=MX:NY=MY-1:RETURN
IF INBUTTON$=="D" THEN NX=MX:NY=MY+1:RETURN
IF INBUTTON$=="L" THEN NX=MX-1:NY=MY:RETURN
IF INBUTTON$=="R" THEN NX=MX+1:NY=MY:RETURN
IF INBUTTON$=="" THEN NX=MX:NY=MY:RETURN
RETURN

If you look at @NEXTSPACE structure, you may wonder why I put in so many RETURNs in there. Actually, I'm using it as ELSE-IF ladder. Since SmileBASIC doesn't allow multi-line ELSE-IF ladder, I'm using Subroutine as substitute. It works fine. The many RETURNs can be skipped, at the cost of performance. Since this structure can be difficult to discover on your own, I figure I'll highlight it here.

It took me about one hour to do everything. Not bad at all. Save the game and pat yourself on the back. You have successfully completed a whole new game.

How about some improvements? Once per second is too slow. 3 per second seems about right. Change to VSYNC 20.

How about changing the movement so that once you move, you cannot stop?

How about putting in some other obstacle? Snakes? Who says we can't have snakes in there? Have the program ends when you hit a snake. Otherwise, keep going.

@PUTSNAKE :'CHR$(27) IS SNAKE
LEVEL=LEVEL+1
FOR I=1 TO LEVEL
X=RND(31-1)+1:Y=RND(20-2)+2:C=CHKCHR(X,Y)
IF C==0 THEN LOCATE X,Y:?CHR$(27) ELSE I=I-1
NEXT
LOCATE 21,22:?"LEVEL ";LEVEL;
RETURN



What Have We Learned?
1. We know multiplying a character repeats that character.
2. CHKCHR(X,Y) returns the value of the character on that console location.
3. CLS fills the console with CHR$(0).
4. BEEP makes easy sound effects.
5. Computer Programming is easy. Good Design is hard!

Wednesday, September 5, 2012

The Art of Review


I do read game reviews on the Web. Sometimes, I even buy the games based on reviews. Sometimes, though, there is a clearly biased review, when the reviewer is applying his prejudices to a product. Almost without exception, the reviewer provides excuses as to how his review can stand without change. I hope that by writing this essay, I can persuade them otherwise. Not hopeful, as biased reviewers don't really care what other people think, but perhaps there are those future writers who do care about their readers. This will give you a change to see the other side.

A Bad Hammer

There was a salesman who provided a craftsman a tool for review. The craftsman stated "This is a terrible hammer! The handle is too short. The metal is too slender. The weight is unbalanced. It is too light to hammer effectively. You will get tired too easily. I just can't see how anybody can be stupid enough to buy this hammer!"

Having thus voiced his professional opinion, the craftsman gave the tool back to the salesman.

The salesman said, "It's a screwdriver."


We can all laugh at the story. Who in the world cannot tell a screwdriver from a hammer? Nobody, that's who. However, in the world of computer programs, that is not so easy. In fact, it happened with rather alarming regularity.

The spectrum of Racing: Mario Kart - Forza Motorsport

Let's take a very successful game: Mario Kart. I don't need to tell you that this driving game is extremely fun and energetic. It's very popular with a lot of people, and rightly so. Very easy to pick up, and caters to a wide variety of people.

Here's another game that is just as well done, but without too much fan base: Forza Motorsport. It's the ultimate driving game. The simulation is highly detailed, and I was impressed by the accuracy of it. But it's not as exciting, and the courses are rather plain. Coming from Mario Kart, the feel is rather boring. Does that mean Forza Motorsport is a bad product?

Of course not! They are two different products. How can that be? Aren't they both driving games? Well, yes. How can they be different? Isn't Mario Kart with its hugely successful sales numbers clearly a "better" product? Of course not, and here's why:

It's a question of a driving GAME, and a DRIVING game. That is where the emphasis lies. Is it about a GAME? Or is it about DRIVING? Mario Kart is a great GAME, but bad driving. Forza Motorsport is a great DRIVING, but a bad game. Neither is "better" than the other. They are both great examples at what they want to achieve.

2 Different Games

It is a great mistake to treat them as the same game. Let me put it this way. Mario Kart is a game where you go from one place to another, in a frentic manner, fending off all kind of obstacles and enemies. Forza Motorsport, OTOH, is about finding that ideal line in which to make the turns, speeding up and slowing down as required, making the turn JUST RIGHT as to preserve as much speed as possible.

Read the descriptions again. Now tell me, am I wrong to say that those descriptions describe Super Monkey Ball(1) and Downhill Snow Racing? Will you claim that Super Monkey Ball and Downhill Snow Racing both represent the same game? Of course not, don't be silly! Likewise, Mario Kart and Forza Motorsport aren't the same game. They're the same GENRE, but they're not the same game!

(1) I actually haven't played Super Monkey Ball, so if it isn't accurate, substitute it with something else. Follow the reasoning anyway. Or how about this? Take Doom/Quake Deathmatch levels where you go from start spawn point to end level. The fastest player get shot in the back! That sums up my feeling about Mario Kart. If that's your game, then you'll like Mario Kart.


The Shifting Expectation

So, now there's another racing game. Let's take Ridge Racer. We can see that although the game isn't a hyper realistic driving simulation, it still leans toward the DRIVING aspect of it. Take another game, such as OutRun. It clearly leans toward the GAME aspect of it.

It is folly to review Ridge Racer while applying the standards of Mario Kart: No bombs. No holes. No beach. No underwater course. No missiles. Gosh, how boring!

It is folly to review OutRun while applying the standards of Forza Motorsport. Roads don't look like that. Speed of cars are off. How about some reasonable damage behavior here?

And yet, game reviewers would review the games per their preferences. If they like games, then they will think Ridge Racer is a bad game, citing that Mario Kart is "better". If a game review like RPG, they will rate Mario Kart as insignificant toys. In either cases, the game gets low marks for "not living up to the expectation."

I argue that as a good, impartial game reviewer, you need to be able to handle different expectations. You can't just say that you don't like the game, therefore the game is bad. Having an opinion is fine, but back it up with facts, and details about the game so that the reader can make their own mind about the game.

What Should We Do?

It's not easy to write enough details to satisfy everybody. It's even harder in print format where space is at premium and that there's only room for so many words. I tend to discount reviewers who would use their alloted words to bring unrelated scenes just to make a point. It may read better, but if it's done at the expense of missing details, then I get upset.

There's not so much excuse when it comes to On-line review, though. A whole new page is only a couple of kilo bytes. 15 minutes if you type fast. There's just no excuse of not doing your homework when it comes to the web.

There's opinion and there's fact. I do not want to catch you saying that a particular game does not feature "drifting" when I drift in that particular game in mid level! That just smacks of either stupidity (and nobody is THAT stupid) or laziness (he didn't bothered playing other than easy level? If the reviewer complained that the AI is so easy to beat, then yeah, I'd say so!)

Then it becomes his reputation that is tarnished, because I find in 5 minutes that he was wrong! I'm not saying that he only spent 5 minutes reviewing the game, but it sure looks like it! This has happened many times over the years, with many games, by many reviewers.

One of the victim was Chris Crawford (Yes, THAT Chris Crawford) who detailed the incident in his book (on Game Design), about how a review is riddled with so many factual errors that Chris Crawford wasn't sure that the reviewer got past the title screen. I am sorry to say, that the tradition of lazy reviewer continues to this day.


Is There a Bad Review?

I'm not saying reviewing a game properly is easy. It's not. It's hard. I would have said that Tetris game is boring. Then, again, I would have been very, very wrong. But at least I would describe the game properly and tried to find a demographic for it. The game may be niche, but if it's well done, and it's great for that niche, then I will give a favorable review, EVEN IF I PERSONALLY DON'T LIKE IT. I'd say something like, "Not for me, but for these [demographic] people, it's a great product." I clearly state my opinion, and yet, I give a fair review so that people other than me can still enjoy the products. I think that's important.

It's very hard to critize something after "walking a mile in their shoes." Yet it must be done that way. It's very easy to critize something you are ignorant about. Yet, it is clearly wrong. So, walk a mile in their shoes. Try to find a positive thing or two, and always give detailed factual reviews, while keeping your opinions to a minimum.

I am not saying you can never roast a product. If the designer of the game is clearly ignorant, such as putting the levels in reverse order of difficulty (Yes, it happened!), then by all means, say so. If the program is so buggy as to be unplayable, it is a disservice to your reader for you to hold out that information. But do research the issue, spending time with it, and make sure to show that you did.

This message is intended for all reviewers out there, but especially the pros. You do not want to see your scathing reviews compared to runaway sales figure, ever. By keeping your opinion to yourself, you give yourself an excuse for not being enthusiastic about it. And you really, really do not want to do a cursory review ever, only to see an amateur did it a lot better, while claiming to spend no more than one hour. And if you are the rare reviewer who lashed out to every criticism, then don't be surprised if you stay in the niche market because, really, you don't learn nor improve, and there is no hope for you.

Tuesday, September 4, 2012

Lotto Number Picker


There is something to be said for a well written plan. It's easy to follow through. OTOH, there is more than one way to skin a cat, and there's more than one way to code a program.

This Petit Computer program was done while docked at King Sooper, waiting to be unloaded. It's very easy to do, and it is proof that computer programming can be convenient, fun, and with potential of great reward!

Look at the algorithm and see if you can follow the code! Feel free to modify to suit!



'LOTTO NUMBER PICKER
'THE ALGORITHM:
'1. TITLE AND INITIALIZATIONS
'2. FILL BALL ARRAY 1 TO MAXNUM
'3. RANDOMIZE BALL ARRAY
'4. FOR I=1 TO NUMBER OF BALLS
'4.1 IF BALL NUMBER < 10, ADD LEADING ZERO
'4.2 APPEND TO TICKET ARRAY STRING
'5. REPEAT STEPS 3-4 UNTIL NUMBER OF TICKETS
'6. FOR I=1 TO NUMBER OF TICKETS
'6.1 PRINT TICKET$ ARRAY. ONE PER LINE
'7. PRESS ANY KEY TO CONTINUE. BACK TO TOP

@STEP1
CLS:CLEAR:COLOR 2
?"LOTTO GENERATOR"
?"(C) 2012 Harry M. Hardjono"
MN=42:'MAXNUM 1-42
NB=6:'NUMBER OF BALLS
NT=20:'NUMBER OF TICKETS

@STEP2
@STEP3
@STEP4
@STEP5
REM THEN A MIRACLE OCCURS...

@STEP6
FOR I=0 TO NB*NT-1:C$=CHR$(RND(MN)+101)
IF !(I%NB) THEN LOCATE 0,I/NB+3:S$="":COLOR I/NB/3%2+4
IF 0>INSTR(S$,C$) THEN ?RIGHT$(STR$(ASC(C$)),2);" ";:S$=S$+C$ ELSE I=I-1
NEXT

@STEP7
FOR I=0 TO 1:I=TCHST OR BUTTON(0):NEXT
GOTO @STEP1


Monday, September 3, 2012

Petit Computer Journal #5


Petit Computer Journal #5 - Buttons and Touchscreen

Let's take a little detour for now. We should be doing strings and graphics, but I want to do something else real quick: Buttons, Touchscreen, and Keyboard. In other words: INPUT.

We have done buttons, touchscreen, and keyboard inputs before. However, I'm interested in doing them all at once. And the trick is to do it without stopping the other input methods. That's not too easy.

Regarding keyboard input method that doesn't stop other process, we have INKEY$. We also have touchscreen variables TCHX,TCHY and all those. How about buttons? We have BUTTON(0), and that is sufficient. So, at the surface, we have all that we need.

The thing is, I don't want to have to structure the program into multi-threading format at this point in time. So, we will have to make some sacrifices. The INKEY$ is well enough. How about buttons and touchscreen?

In Touchscreen, it is convenient to have a drag-and-drop process. That means X1,Y1,X2,Y2,TouchStatus. Let's build that capability.

@SETT
IF TCHST==0 THEN TS1=TCHST:RETURN
IF TS1==0 THEN TX1=TCHX:TY1=TCHY:TS1=1
IF TS1==1 THEN TX2=TCHX:TY2=TCHY
TS1=TCHST
RETURN

That looks simple enough. Basically, we want to update the variables if TCHST==1. The first line takes care of that by returning from subroutine if TCHST==0. Next, we want to see which pair we want to update X1,Y1 or X2,Y2? And that's all there is to it!


The buttons isn't so simple, though. There are 4 possible arrangements that I can see:
1. No Wait+Multiple: BUTTON(0)
2. No wait+Single: @SETB1
3. Wait+Multiple: @SETB2
4. Wait+Single: @SETB3

Of these, we want no wait version. If the no wait version is equivalent to INKEY$, then the wait version is equivalent to INPUT. The whole process involve trying out different versions of the commands. You see the finished product as clean, but I assure you that the process involves repeatedly trying and failing to come up with that clean method. You do not see the hard work that is done. At least, if you ever wonder why my progress is at glacial pace, you know the reason: Lacking tutorial such as this, I do a lot of experiments, not all of them successful.

There are 4 cases and only 3 subroutines. The first case can be easily met via BUTTON(0). The rest is done with simple subroutines. It only works on the first 8 bits, corresponding to UDLRABXY. This is because the method I use requires string characters, and those only goes to 255. I typed in the character in the actual program, but for the purpose of tutorial there are two index variables used by INSTR()

1. IST1$=CHR$(128)+CHR$(64)+CHR$(32)+CHR$(16)+CHR$(8)+CHR$(4)+CHR$(2)+CHR$(1)
2. IST2$=CHR$(129)+CHR$(65)+CHR$(33)+CHR$(17)+CHR$(9)+CHR$(5)+CHR$(3)+CHR$(2)

I use this technique a lot as it simplifies things greatly. It's not the fastest running code, and so only amateur hobbyist would use it. Certainly not a professional quality code. If need be, I may changed the code later to a more efficient one. But I like doing rapid prototyping in the beginning.

One more thing, the no wait version is tricky. If you check out the clock, you will see that no-wait @SETB1 does cause the program to stop when you press the button for a long time. A way to fix this would be to use a variable, but I would rather just do it directly with BUTTON(0) or @SETB2.

@SETB1 :'SINGLE FIRE
INBUTTON$="":Z=BUTTON(0):IF!Z THEN RETURN
FOR Z=0 TO 1:Z=BUTTON(3):NEXT:Z=Z AND 255
Z=INSTR(IST2$,CHR$(Z)):IF Z< 0 THEN RETURN
INBUTTON$=MID$("YXBARLDU",Z,1)
RETURN

@SETB2 :'CONTINUOUS
INBUTTON$="":Z=BUTTON(0):IF!Z THEN RETURN
Z=Z AND 255:Z=INSTR(IST1$,CHR$(Z)):IF Z< 0 THEN RETURN
INBUTTON$=MID$("YXBARLDU",Z,1)
RETURN

@SETB3 :'WAIT
INBUTTON$=""
FOR Z=0 TO 1:VSYNC 1:Z=BUTTON(1):NEXT:Z=Z AND 255
Z=INSTR(IST2$,CHR$(Z)):IF Z< 0 THEN RETURN
INBUTTON$=MID$("YXBARLDU",Z,1)
RETURN

And those are the functions. Next, let's write a quick demo program to demonstrate the different functions. It may be best that you write a program and save it because you will be using this at all times. I know I do!

'BUTTON/TOUCHSCREEN TEST EXAMPLE
CLS:CLEAR:P1=0:P2=1
@MAINLOOP
LOCATE 0,0:?TIME$
VSYNC 1:A$=INKEY$():?A$
GOSUB @SETB1:'?INBUTTON$;
GOSUB @SETT

IF INBUTTON$!="" OR TS1 THEN GOSUB @DT
GOTO @MAINLOOP

@DT :'DRAW TEXT
IF INBUTTON$!="" THEN L1=(L1+1)%32:LOCATE L1,1:?INBUTTON$;

'DRAW BOX
IF INBUTTON$=="L" THEN P1=P1+15
IF INBUTTON$=="R" THEN P1=P1+1
IF INBUTTON$=="U" THEN P2=P2+15
IF INBUTTON$=="D" THEN P2=P2+1
P1=P1%16:P2=P2%16
SX1=FLOOR(TX1/8):SY1=FLOOR(TY1/8)
SX2=FLOOR(TX2/8):SY2=FLOOR(TY2/8)
FOR X=SX1 TO SX2:FOR Y=SY1 TO SY2:
C$=CHR$(151):COLOR P2:'0=BIG BLOCK CHARACTER IN PETIT COMPUTER
IF X==SX1 OR X==SX2 THEN C$=CHR$(150):COLOR P1:'1=VERT LINE
IF Y==SY1 OR Y==SY2 THEN C$=CHR$(149):COLOR P1:'2=HORZ LINE
IF X==SX1 AND Y==SY1 THEN C$=CHR$(152):COLOR P1:'3=UPPERLEFT
IF X==SX2 AND Y==SY1 THEN C$=CHR$(153):COLOR P1:'4=UPPERRIGHT
IF X==SX1 AND Y==SY2 THEN C$=CHR$(154):COLOR P1:'5=LOWERLEFT
IF X==SX2 AND Y==SY2 THEN C$=CHR$(155):COLOR P1:'6=UPPERRIGHT
LOCATE X,Y:?C$;
NEXT:NEXT

RETURN

For some reason, my computer does not read my memory card. That's a setback. I have to have those special characters, and so, I'm forced to do it the hard way, which is very annoying. However, either I overcome that setback, or I don't do this at all. I can work on the DSi no problem, but if I want to share it, I have to do this thankless work of translating those characters into their numeric equivalent. I wrote a simple program just for that:

'ASCII TABLE
S=0
@MAINLOOP
CLS
FOR I=S TO S+15
R=I%16
LOCATE 0,R:?I;:LOCATE 5,R:?CHR$(I)
NEXT

GOSUB @SETB3

IF INBUTTON$=="U" THEN S=S+16
IF INBUTTON$=="D" THEN S=S+256-16
S=S%256
?:?"S=";S;"   ";INBUTTON$:WAIT 60:'OPTIONAL FOR DEBUGGING
GOTO @MAINLOOP

And that's it. Not even 10 minutes. You need to provide Subroutine @SETB3, but that's trivial. Just copy the one above.



Problems and How to Ask Questions

You know how people say there's no such thing as stupid questions? I know I'm bucking the convention here, but I'd say there are! Here's a sample, quoted in its entirety:

"Help! SAVE doesn't work."

I'm not saying that SAVE command is so easy that it cannot fail. I am saying that the question doesn't even begin to show the framework in which the problem occurs. We need more data! You know how PRINT statement works, right? What if there's somebody who ask help like this: "How do you use PRINT?", following your answer with "It doesn't work."

You know it works, and you know how it works. The problem is, how does it doesn't work? You have no clue as to what problem this person encounter. So, here is how you handle a problem that you cannot solve, because the unwritten rule is, if you ask a question that you later answer without any prompting whatsoever, YOU JUST ASKED A STUPID QUESTION THAT YOU KNOW THE ANSWER TO!

Problem solving technique:
1. Ran into problem, WRITE IT DOWN!
2. Write down all the relevant elements.
3. Read the Manual/Help file
4. WRITE ALL THE POTENTIAL SOLUTIONS DOWN.
5. Implement them all.

That's step-by-step. You are not allowed to skip steps. Half of your problems can be solved this way. As for the rest, well, that's when it gets tricky.

Hard Problem Solving:
1. You are tired. STOP AND GO TO SLEEP!
2. Wake up. Eat something solid
3. Repeat problem solving steps above.

By this time, if you followed this advice, a lot of you would do a lot of face palming "Of course! Why didn't I think of that?" sequence. That happens to me, too.

Stubborn Problem Solving:
1. You are sadly misunderstanding the problem. YOU are at fault!
2. Find 3 different interpretations to the problem.
3. Also, find 3 different OTHER places where it may cause the problem.
4. Consult the manual for help.

It may help to pretend that you're a newbie who doesn't understand everything. Don't laugh. It works! I used that technique myself occasionally. For the next level you must first admit that you are stupid. No, really. You are! You may humbly ask other people for help. Ever seen somebody arrogantly ask for solution to their problem? That never gets resolved, does it? Bingo.

Impossible Problem Solving:
1. Explain What the Problem is
2. Tell what you think are the relevant elements
3. Show what you did to solve the problem
4. WRITE THE SIMPLEST, SHORTEST CODE to explain the problem.

That last element is vital. No one wants to read 200 lines of code just to debug your program. So, there. Problem solving explained. Either that or you explain your problem to a duck.

Haha, joking aside, the ability to properly explain your problem is crucial in getting it solved. You don't want to ask a question like a grade schooler if you can ask your questions like a professor!

My PRINT doesn't work!

1. Did you type it in RUN(direct) mode or EDIT (deferred) mode?
2. Did it give you Syntax Error?
3. Did it print 0?
4. Did you set VISIBILITY?

What if PRINT doesn't work because it was set to XOR Mode? How will you respond to that? You can't set Console to XOR mode, right? How does that work? This is where giving out sample code is crucial.

CLS
COLOR SET XOR ! DOIT
PRINT "HELLO WORLD"

SET and DOIT ARE not keywordS. Why is there an exclamation mark preceding it? Because when I put it after the word (DOIT!), the computer complained, DUH!

You see how sample source code clarifies the issue quickly and easily? Don't act like a grade schooler. Ask questions like a professor! When I see the words "I don't understand ..." it'd better be followed by "These are the things I tried in order to understand it."