Your menu is a good example for learning how to use FOR loops effectively. (edit: It's good for learning lots of things so I wrote lots! Better get some snacks or something, you're gonna be here a while!)
We have four options and the code for each looks quite similar, this hints that they might be able to be combined, but what exactly is different between them? and is there enough similarity to warrant combining them?
The ink commands and X positioning of the text is the same for each option.
The Y position is different, but the difference is incremental: each option's Y position is 50 greater than the previous. Incremental differences are perfect fodder for FOR loops because they are themselves incremental.
There is only one aspect of the code which is non-identical nor incremental: the string holding the option's name. We're going to need a trick to make something that is non-incremental (strings) behave incrementally, we do so by storing the strings in an array.
Arrays have numbered entities, each can store any value of the specified type; essentially allowing us to create any sequence of values without a logical pattern.
data "Form Words", "Build Sentences", "Save", "Quit Game"
dim opt$(3)
for x = 0 to 3 : read opt$(x) : next x
Before I give you the complete code I want to address a few rudimentary change to the original:
if SelectedItem = 2
ink rgb(255,0,0),0
center text scrwid/2, 350, "Build Sentences"
ink rgb(255,255,255),0
else
center text scrwid/2, 350, "Build Sentences"
endif
The text command is identical regardless of the outcome of the condition, therefore it should not be in the condition:
if SelectedItem = 2 then ink rgb(255,0,0),0 else ink rgb(255,255,255),0
center text centrex, 350, "Build Sentences"
Notice how I have also replaced
scrwid/2 with the constant
centrex (which has a value of
screen width()/2); repeating the same calculation is a waste of time, it's better to store the result in a variable. This may not affect a program this small but in a bigger FOR loop it can cause a noticeable delay. If you can spot the other two calculations that could be stored in variables I'd be very pleased!
If you didn't spot it you probably don't realise what rgb() does. I wont go into detail here but essentially it takes the three colour values, performs the necessary calculations on them, and returns a single value that represents the colour you asked for. If we store the returned values in variables we need only perform the calculations once!
red = rgb(255,0,0)
white = rgb(255,255,255)
So here's the full code:
set display mode 1024,768,32
sync on
centrex = screen width()/2
red = rgb(255,0,0)
white = rgb(255,255,255)
data "Form Words", "Build Sentences", "Save", "Quit Game"
dim opt$(3)
for x = 0 to 3 : read opt$(x) : next x
rem ---- Menu ----
do
cls
set text size 30
ink white,0
center text centrex, 50, "Menu"
set text size 20
for x = 0 to 3
if SelectedItem = x then ink red,0 else ink white,0
center text centrex, 300+x*50, opt$(x)
next x
sync
rem Controls
if upkey() = 1 then dec SelectedItem : wait 0300
if downkey() = 1 then inc SelectedItem : wait 0300
if SelectedItem < 0 then SelectedItem = 3
if SelectedItem > 3 then SelectedItem = 0
rem Exit to game
if keystate(59) = 1
repeat : until keystate(59) = 0
` gosub game
endif
loop
The main loop is already a lot smaller and looks neater. I hope the FOR loop makes sense to you now, if not take a moment to examine it and experiment before moving on, then I will tackle the control and exit blocks.
Delays are never a good thing. You forced a delay to stop the cursor going haywire but we need a better way of taming the input that doesn't inhibit the user; we need to train our dog to stay rather than forcing everyone to keep the front door closed. Just like training a dog we are going to use the natural behaviour of the input to help us achieve our goal, not fight against it.
Key input is very simple, it returns a 1 while the key is pressed and a 0 when it is not. So how do we train it to only register once for each new key press? We need a system that deactivates the input after one iteration, then resets when the key is released. In other words we are defining two events: key press (input changing from 0 to 1) and key release (input changing from 1 to 0), how do we distinguish between these events? How do we know which is happening? Both events are changes so we need to store the old input value and compare it to the new value.
do
oldkey = newkey
newkey = spacekey()
rem event check here
loop
This might look a bit confusing at first but follow the loop around and you'll see that
newkey is updated before the event check but
oldkey is only updated afterwards. So
oldkey is using the value of
spacekey() from the previous loop.
The key press event is a positive change (0 + 1 = 1) and the release is a negative change (1 - 1 = 0), so detecting an increase in input reveals a key press while a decrease reveals a key release.
sync on
do
oldkey = newkey
newkey = spacekey()
if newkey > oldkey then print "KEY PRESSED"
if newkey < oldkey then print "KEY RELEASED"
sync
loop
The great thing about this system is it resets itself! If we want to change options only on a key press we only need to test for when
newkey >
oldkey because
newkey will have to return to 0 (which then gets passed to
oldkey) before it can be greater again.
We want to use two keys, we could double the code and test the two inputs separately but I want to do something a bit cleverer. Since UP and DOWN move in opposite directions we can combine them and treat one as positive and the other as negative.
oldin = newin
newin = downkey() - upkey()
We have to write the condition slightly differently because now a decrease in input doesn't always indicate a key release: -1 would indicate that the
upkey() is being held while
downkey() is released. We have to think about which states are useful to us; 1 and -1 indicate down and up so they are useful, but 0 isn't because we don't want anything to happen on a 0. So let's start by removing 0 from the condition:
If you haven't seen the ! symbol before it simply means "not equal to" (I'm not sure if this is still used in DBP, if not use "<>"). This line ensures we ignore when the new input is 0 and only look for when it changes to 1 or -1. This exclusion has reduced the possible values of
newin to two (1 or -1) inside the condition, and so we can perform a simple comparison as before:
sync on
do
oldin = newin
newin = downkey() - upkey()
if newin ! 0
if newin > oldin then print "UP"
if newin < oldin then print "DOWN"
endif
sync
loop
Now let's insert that into the menu program:
set display mode 1024,768,32
hide mouse
sync on
centrex = screen width()/2
red = rgb(255,0,0)
white = rgb(255,255,255)
data "Form Words", "Build Sentences", "Save", "Quit Game"
dim opt$(3)
for x = 0 to 3 : read opt$(x) : next x
focus = 0
rem ---- Menu ----
do
cls
set text size 30
ink white,0
center text centrex, 50, "Menu"
set text size 20
for x = 0 to 3
if focus = x then ink red,0 else ink white,0
center text centrex, 300+x*50, opt$(x)
next x
sync
rem Controls
oldin = newin
newin = downkey() - upkey()
if newin ! 0
if newin > oldin then inc focus
if newin < oldin then dec focus
if focus < 0 then focus = 3
if focus > 3 then focus = 0
endif
rem Exit to game
if keystate(59) = 1
repeat : until keystate(59) = 0
` gosub game
endif
loop
(I changed
SelectedItem to
focus just because it's a shorter name.)
We're not quite finished with the control routine, let's take a closer look:
oldin = newin
newin = downkey() - upkey()
if newin ! 0
if newin > oldin then inc focus
if newin < oldin then dec focus
if focus < 0 then focus = 3
if focus > 3 then focus = 0
endif
When you examine the code you will realise how we are patronising the computer. We are asking it to add 1 to
focus when
newin = 1 and subtract 1 when
newin = -1, as if it is not capable of basic arithmetic!
oldin = newin
newin = downkey() - upkey()
if newin ! 0
if newin ! oldin then inc focus, newin
if focus < 0 then focus = 3
if focus > 3 then focus = 0
endif
Much better! Now you should be looking at the bounds checks below and rubbing your chin, they also have > and <, can they be combined as well? They could be but I can't find a way that doesn't introduce unnecessary calculations. Since we are trying to write neat and efficient programs, not show-off, we'll leave it how it is.
And so we come to the end of the menu, people are getting bored looking at options and want to be let out! Since we have no game to go to yet I've added a place-holder.
rem Exit to game
if keystate(59) = 1
repeat : until keystate(59) = 0
print "YOU WILL NEVER LEAVE! >8D"
endif
This works but I'd rather we change it to act on a key press. We've already done this earlier so if you don't know how to then you weren't paying attention!
Okay, here it is:
oldexit = newexit
newexit = keystate(59)
if newexit > oldexit then print "YOU WILL NEVER LEAVE! >8D"
Here's the finished program:
set display mode 1024,768,32
hide mouse
sync on
centrex = screen width()/2
red = rgb(255,0,0)
white = rgb(255,255,255)
data "Form Words", "Build Sentences", "Save", "Quit Game"
dim opt$(3)
for x = 0 to 3 : read opt$(x) : next x
focus = 0
rem ---- Menu ----
do
set text size 30
ink white,0
center text centrex, 50, "Menu"
set text size 20
for x = 0 to 3
if focus = x then ink red,0 else ink white,0
center text centrex, 300+x*50, opt$(x)
next x
rem Controls
oldin = newin
newin = downkey() - upkey()
if newin ! 0
if newin ! oldin then inc focus, newin
if focus < 0 then focus = 3
if focus > 3 then focus = 0
endif
rem Exit to game
oldexit = newexit
newexit = keystate(59)
if newexit > oldexit then print "YOU WILL NEVER LEAVE! >8D"
sync:cls
loop
I was reminded how important it is to know where your sync and cls are: my "exit" text wasn't displaying because it came after the sync and before the cls, so it was being wiped before it had been displayed. This problem is why I like to keep sync and cls next to each other at the end of the loop.
I hope you've read all this and can understand my weird way of explaining things!
Challenge: play a sound when a key is pressed. Bonus: give the sound a higher pitch when moving up the menu and a lower pitch when moving down by using SET SOUND SPEED.
WARNING: The above comment may contain sarcasm.