The Programmer's MindsetEverybody is different; it goes without saying. That's why some people will excel at programming while others will excel at literature or psychology or... whatever. But we humans are really good at adapting and picking up different ways of thinking. It might take a lot of effort and you might not ever be able to do it as naturally as another person, but you CAN do it. If you don't have it already, I'm going to attempt to explain and get you into the programming mindset. Just as a disclaimer: like I said before, everyone is different. That means that my method of problem solving won't be the same as somebody else's, and you/they may disagree with me. That's totally fine; I'm just hoping this helps someone. Successfully creating a program requires analyzing the problem, breaking it down to its smallest pieces, then coming up with solutions to each piece and putting them all together into the full solution. This is the same process you use for most logical problem solving by the way.
Problem AnalysisAnything you want to create on a computer (like a game) using a programming language (like SmileBASIC) will consist of a series of programming problems. Identifying these problems is the extremely important first step, and it's quite often the step many people get hung up on. "What do I even need to do... where do I even start?" are thoughts that cross every programmer's mind, even if they're really experienced. But you don't have to tackle the whole problem at once! Like I've been trying to show in the previous tutorials, you can chip away at it in small steps. The order in which you spot and process problems and the solutions you come up with don't have to follow some rigid formula. Maybe you look at problems in order of importance, or maybe you go chronologically based on how the program will be used, or maybe you'll look for the easiest problem and work up to the hardest ones. It doesn't matter; you don't have to have some grand plan for your whole program before you start (although as you get better, a general idea of the whole thing greatly improves your code and reduces the time it takes to program). All you have to do is FIND the problems so you can solve them. This is analyzing the problem, and it's what all good programmers excel at (in my opinion). You can't come up with a solution if you don't understand the problem.
An ExampleI think the top is the best place to start: "What am I trying to accomplish?" If you lose sight of your original goal, it's easy to program yourself into a hole. From there, you can move on to the next highest (largest) problems, and keep going down until you identify something you can actually work on. You don't have to flow from the top down like I'm doing here; as long as you are able to identify the discrete programming problems, it doesn't really matter how you do it. Let's use the example of making an RPG. The main goal is the game itself: you want to end up with an RPG that has these mechanics and this setting and that plot. OK great, but where do you go from here? An RPG will neeeed.... mechanics, sure. Maybe this is an RPG where you walk around the overworld and encounter random battles. Ah, now we have a major mechanic identified: walking around. But it's hard to just start programming "walking around", so let's keep stripping the problem down: walking around requires displaying the world and character, and using buttons to move the character around. I get the FEELING it'd be more fun to program "use buttons to move around". If you've programmed directional movement before, you could just start coding right here. If, however, you've never done this before, you should break this problem down further until you find something doable. Using buttons to move around requires gathering button data then converting button data into movement. I was able to break the movement problem down into these two parts because I know how SmileBASIC works: there's a function that returns the pressed buttons, and those buttons must translate into movement (because that's how games work). Once you see how you can use the tools in the programming language to solve the problem, you've probably broken down the problem enough. NOW we have small, discrete programming problems that we can tackle.
Small Solutions Stack UpRemember, it's not about getting the whole program to work at once. We first break down the problem into such small parts that we can finally wrap our heads around it, THEN we tackle the solution to those parts. Now that the problem is small, it's easier to experiment until you find a solution. Or, maybe the solution will just naturally come to you because the problem is so small. Either way, it's important to test your solution to see if it solves the small problem. Don't just assume that something you come up with will work; ALWAYS prove it. Let's look at the RPG example where we got down to the "gather button data" problem. In this case, you know you have a "how do I gather button data" problem, so you should experiment with the button functions until you know how to gather button data (and thus solve that small problem). You could just make a program that prints whatever button is pressed in order to understand (and PROVE) how the button data works. Once you understand that, you can move on to the next problem, then the next. Each problem you solve is a building block in your program and the foundation for understanding the larger problems as a whole. Eventually, you'll solve enough problems in that RPG example such that the entirety of "walking around" is solved, and you can move on to more mechanics. Solving problems and doing small tests like this can be very exciting and motivating, as you actually see your program start to take shape. You very quickly have something to play with.
Keeping Things SimpleIf you feel like things are getting too complicated, that probably means you're doing something the hard way. The complexity of a program is very dependent on the programmer, and only SLIGHTLY dependent on the thing you're programming. An RPG might SEEM massively complex with lots of associations and mechanics and ugh, but each individual part by itself can be very simple if you design it right. This is a skill that will come with time, so don't worry about it too much now. We're still going to look at an example though: for instance, the overworld mechanic for the RPG might be a thousand lines of code, but you can easily break it into just two independent pieces: "walking" and "random encounters". Then the "walking" piece can then be two smaller pieces: "moving with buttons" and "displaying the area", each of which might only be a few dozen lines. All of this code can be independent, which means it does not depend on other pieces to function. Furthermore, the logic is packaged into manageable pieces, where we assume a piece just does what it's supposed to. It's like a car: the car has an engine and a frame and we assume it just works, but the engine itself has a whole bunch of distinct parts like the alternator and the transmission, and each of THOSE parts is made up of cables and gears and bolts. If you were to take apart the whole car, it would be tens of thousands of pieces. But you (as the driver) assume the car will work without having to fathom how those thousands of pieces work together because the engine as a whole works (and obviously the frame is probably fine). And if the engine doesn't work, a mechanic doesn't have to inspect every tiny individual gear and bolt in the entire car; they just look at the larger parts (like the alternator or the transmission) to see if one of those needs to be repaired. They don't have to inspect every tiny thing because all the tiny pieces aren't dependent on each other: a little screw in the alternator isn't dependent on a tiny gear in the transmission. This is how you should design your programs: with easy to manage pieces that are as independent as possible, where each piece is logically small. Really though, don't worry if this is a hard concept to grasp. I didn't really explain it well and it works best with an example, so I'll go over this concept in a more advanced tutorial. Just keep those "simplicity" and "small independent pieces" ideas in mind.
Actually Doing ItSo, let's put all that to use with a real world example: let's make something move around the screen with the D-pad. This is a programming problem you'll have to tackle a lot, and even though you can use the same solution for most programs, it's good to think about the concept every time you do it. You might come up with something better the next time you do it.
Breaking it downWe'll start by making sure we understand the problem: we want to move something around the screen with the D-pad. When we press left, it goes left, etc. The first thing we should probably break apart is the ambiguity: what "something" are we going to move around? How are we going to display the "thing"? For this lesson, I want to display the something on the console output, which means printing some character of choice using PRINT. Now, we COULD start looking at the other problem of "button input", but I want to focus on this display problem. We know how to PRINT something to the screen, but it always shows up under the last thing printed. If your program was just:
PRINT "#"It would just print a "#" right under your RUN command. We have to solve more problems: how do we print something at a particular location? We want to print "#" wherever our "player" is, and there has to be a way to do that.
ResearchNow if you're a super nerd, maybe you read the whole manual and you know all the commands by heart already. Or, maybe you peeked at someone else's program (which is a good thing) and discovered this command. Or maybe you asked someone and they told you... IDK. The point is, you have a concise problem of "how to print at a location" which has a concise answer: use the LOCATE function. This is one of those rare times where the solution to a programming problem is just a command you have to memorize. Luckily, there aren't that many commands in SmileBASIC, and you'll quickly memorize them (or at least the most used ones). If you remember from the last tutorial, you can type LOCATE and use the ? to see how to call this function. It's one of those silly functions that doesn't return anything, so no parenthesis are used. It places the cursor at the given location so the next thing to PRINT will show up there. We can learn a bit about the console from the LOCATE manual entry too: the X location (left and right) can range from 0-49, and the y location (up and down) can range from 0-29 (remember, 0 is usually the first position in programming). That means the console window is 50 characters wide and 30 characters tall, which is probably important.
ExperimentingRemember: part of the programming mindset is to test things and experiment, so let's see what X and Y really mean by printing at 0,0:
ACLS 'Remember, this clears everything on screen. You can use CLS to just clear the console if you like. LOCATE 0,0 'Put the cursor at 0,0 PRINT "#" 'Print # at 0,0...Huh, it just puts # at the top left like always. We should experiment more: we'll make x = 5 and y =1:
ACLS 'Clear everything LOCATE 5,1 'Put the cursor at 5,1 PRINT "#" 'Print # at 5,1Ah, it printed the # 5 from the left and 1 from the top. Through experimentation, we found out that the X coordinate is the distance from the left of the screen, and the Y coordinate is the distance from the top. Sure you could've asked someone, but it was very easy to try it for yourself. It's part of the problem solving process to experiment a bit, and it's OK if you mess up.
Keep GoingSo we solved the problem of display: we can now put a character anywhere we want on the screen. The next problem is to move it around with the D-pad, but there's still a lot of unknowns, so let's stick with the simpler problem of "how to get dpad input". We know we can capture typed input with the INPUT function, so there must be a way or possibly just a single command to do the same for buttons. I'll spare you the research: it's the BUTTON function. If you look up BUTTON in the manual, you'll see that it returns a value, and there's an optional "Feature ID" parameter. The manual also mentions the return values, but it uses this weird notation |b01|. This means the value is a bit, and the number next to it is which bit gets set. If you remember from the number systems tutorial, binary is a number made of just 0's and 1's. Each position in the binary number is a "bit", just like each position in a decimal number is a "digit". When SmileBASIC says |b00|, they mean the very first bit. |b01| is the second bit, |b02| is third, etc. If BUTTON is confusing, just experiment! Let's just try using the function. Remember, it returns the buttons (or something):
DIM B% 'Declare integer variable B B%=BUTTON(0) 'Set B to the return value of BUTTON PRINT B% 'Print value of BChances are, it just printed 0 without waiting. What gives? Why doesn't it wait like INPUT? What are these different "Feature ID" things? It can be overwhelming to figure out a new command without someone to show you, but just like with making a program, you should go slow and tackle one thing at a time. We can kinda guess why BUTTON doesn't wait: think about how a program runs. It runs the statements from top to bottom (unless you jump somewhere) and the next statement CAN'T run until the previous command is finished. Now imagine you're trying to make a game and the player doesn't press anything. If BUTTON waited for you to press something, the whole game would stop and wait every time you didn't press anything. It would get stuck on the BUTTON command, just like the whole program stops and waits for INPUT to finish. That's not how games work, right? If you stand still, the game still goes and the enemies come and kill you or something. By making BUTTON a non-blocking function (one that returns immediately even if there is something to wait on: ie doesn't block code execution), SmileBASIC allows a game or program to continue running even if there is no input, which is exactly what we want. The second strange thing about BUTTON is this "Feature ID" thingy. You could change the parameter in our current program, but it ends so fast that you can't really get any values. As a general rule, non-blocking functions like BUTTON return 0 or -1 if there is "nothing" to return. In this case, "no buttons pressed" means 0. We could call this function over and over, and it'll only return something other than 0 if we have button input. We're still experimenting and we want to see BUTTON in action, but we want the program to wait until we have some meaningful button input. One trick is to repeatedly sample (check the value, usually periodically) the value of BUTTON until it becomes something. Remember back to the loop tutorial: the WHILE loop will repeat all the code within itself while the given condition is true. So, we'll just keep checking BUTTON over and over again while it's still 0:
DIM B%=0 'A place to store the button return value. Init to 0 so the loop condition defaults to true. WHILE(B%==0) 'Keep repeating what's inside while B% is 0 (no buttons) B%=BUTTON(0) 'Sample button WEND PRINT B% 'When B finally ISN'T 0, the loop will end. Then this will finally print the value read from BUTTONNow if you run this code, the program will keep looping and checking the BUTTON value until it's finally SOMETHING, then it'll print. It makes it LOOK like we're waiting for input. Ah, but only seeing one button press is kinda lame. We want to see how BUTTON REALLY works, and having to run the program over and over just to see what each one does is slow. Instead, let's change our loop so it runs forever (we can just break the program with start/select) and print the button value IF there's a button to print. A WHILE loop continues repeating while the condition is true, so setting the condition to ALWAYS be true will make the loop repeat forever. We'll continue checking the button value as always, but we'll move the PRINT inside the loop so we can repeatedly see what the value is. Printing 0 is useless though; we only want to see when there's actual button input otherwise the screen will be covered in 0's (remember, BUTTON is non-blocking and the loop repeats forever, so it would just be a monstrous stream of 0's until you finally press something). Finally, we're throwing a VSYNC in there. I'm not sure if I explained VSYNC before, but it is a function of sorts that blocks execution there until the current frame is "finished". A frame is just what it is in video games: some games run at 60 frames per second, so the system renders (draws to screen) 60 frames every second. SmileBASIC also runs at 60 frames per second, so by making each iteration of the loop wait for the current frame to finish, we are basically "locking" the loop to the framerate. The loop will only run 60 times per second. Keep in mind we're still just experimenting with BUTTON to figure out how it works. I'ts a vital step in the problem solving process:
DIM B% WHILE(TRUE) 'Loop forever B%=BUTTON(0) IF B%!=0 THEN PRINT B% 'ONLY print the button value if there's one to get VSYNC WENDRun this, and you should get a stream of numbers whenever you're holding down any of the buttons. Now you can actually SEE what BUTTON is doing in real time, all because we decided to experiment and figure out the commands we needed (even if we're veering off course from our original program solution a bit). Try using the D-pad and see what the values for each button are. Then try holding down any of the face buttons. Now, hold two or three at the same time. Intuitively, you might start to see how BUTTON works, but I'm going to explain it anyway. Each button sets a different bit in the return value to 1. Each bit is a power of 2 when you convert from binary to decimal (remember from the number systems tutorial). We're printing the decimal in our program. Effectively, this means each button on the system has a unique ID that's a power of 2, and BUTTON returns the SUM of all the IDs for the buttons that are pressed down. No two button combinations can be the same because really, each button has its own bit in the return value. See the example chart below:
RLYXBA><V^ 0000000000 'nothing pressed 0000010000 'pressing A (convert binary to decimal: it's 16) 0000101000 'pressing B and right (this binary in decimal is 40) 1111110010 'pressing L, R, all face buttons, and downOK cool; the mystery of BUTTON is solved... almost. We still don't know what this Feature ID thing is, but we're now in the prime position for checking. Just change BUTTON(0) to BUTTON(1), then 2, etc. I'll spare you the effort if you're lazy (but make sure you understand): Mode 0 means BUTTON will return values whenever the button is held at all. This is what we've been using this whole time. Mode 1 is how the buttons work in the editor: they will give the value as soon as you press it, then wait a bit before spamming you with repeats. This allows precise movement with short presses, but you are still able to move fast by holding. Mode 2 is single press: BUTTON will only return a value the moment the button is pressed and no more. Mode 3 is single press but the value is returned upon releasing the button. It has limited uses. Note that mode 1 works in conjunction with the BREPEAT function; since we didn't use that in our code, mode 1 and 2 will appear to be the same in your experiment program.
Piecing Knowledge TogetherHOOOooo so we discovered how LOCATION and BUTTON work, and now the only thing left to do is to string it all together into a "buttons moves thingy around" program! ...But wait, what's the next step? There's still some things left, so we'll take it slow and look for the next problem to solve. We can put our player on the screen wherever we want with LOCATE. We can figure out what button they're pressing with BUTTON. But how do we connect the two so BUTTON moves the player around with LOCATE? This is the intuitive leap you'll have to make; some people will immediately see what you'll have to do and others might not. If you think about it enough, you'll see it though. It's like a puzzle: all the information is there, you just have to see where the pieces fit. LOCATE requires an X and Y parameter, right? Well, the player's location IS that X and Y, and their location is variable since they can be anywhere on screen. We put variables in.... variables, right? So the first thing we should probably do is make some variables for our player location. We can even initialize them to be the center of the screen (remember the dimensions):
DIM X%=25 DIM Y%=15That WHILE loop we came up with earlier seems to work almost exactly like we want our game to work: when we press buttons, things happen (and it's locked to the framerate). We want to sample the button input every frame to see if we need to move the player. We'll need a place to store the button value and the while loop to actually read the button values:
DIM X%=25 DIM Y%=15 DIM B% WHILE(TRUE) B%=BUTTON() 'Parameter optional (defaults to 0) VSYNC WENDHmmm and we want to print the player at their location, right? Let's throw that in there
ACLS 'Don't forget to clear and reset all graphics before the program starts (this is a slow command btw) DIM X%=25 DIM Y%=15 DIM B% WHILE(TRUE) B%=BUTTON() 'Parameter optional (defaults to 0) LOCATE X%,Y% PRINT "#" VSYNC WENDAnd now the conversion: when right is pressed, we want the player to move right. Right is farther away from the left side, so X will increase. So left will decrease X. Pressing up should make the player move up, which is CLOSER to the top, so Y will decrease. Pressing down will make the player move farther away from the top, so Y will increase. Remember, we use conditional logic to do things... well, conditionally:
ACLS DIM X%=25 DIM Y%=15 DIM B% WHILE(TRUE) B%=BUTTON() 'Parameter optional (defaults to 0) IF B%==#RIGHT THEN X%=X%+1 'There are constants for the IDs for each button so you don't have to memorize each ID IF B%==#LEFT THEN X%=X%-1 'Remember, the value on the right is computed first, then assigned. If X% is 25, it will be assigned 24 if this condition is true IF B%==#UP THEN Y%=Y%-1 IF B%==#DOWN THEN Y%=Y%+1 LOCATE X%,Y% PRINT "#" VSYNC WENDYo. You have a working program! ...Kinda. There are a few problems, and just like before we can solve them one at a time.
DebuggingThe first problem you probably noticed is that the player leaves a trail behind them. This can be cool, but most of the time we don't want this. You should try to think about why this happens... think about how you used PRINT in the past and how it looked when you printed multiple things. The old things you printed didn't disappear, right? It's the same here: we are repeating the PRINT "#" over and over so it's just leaving that crap all over the screen. There are a few solutions to this problem: what do you think they are?
Solution 1Just clear the screen whenever the player moves. It will take care of the trail. You can either put CLS (only clears console; ACLS clears and resets everything) in every IF statement for movement, or you can pull off some fancy math in a conditional to do it once. Or even simpler: just put CLS in the loop. However, this will make the character flicker, as it will be rapidly cleared, then redrawn.
The second problem is that going off the edge of the screen crashes the program. LOCATE doesn't accept values outside the screen, so you should do boundary checks to make sure X and Y don't go outside the screen. As with most of programming, there are several ways to solve this problem. I will present one here, but if you had a different idea, you should try that one! Experiment! The THIRD problem is that you stop moving when you hold two directions at the same time. This is because we are checking the return value of BUTTON against a single constant each time. When two buttons are pressed, the value is not just the one button. You don't have to account for every combination of buttons though; that would be time consuming and wasteful. Instead, we can use some of the binary math we learned before to check if a bit inside the return value is set. If two directions are held, both conditions will thus be true and you'll move diagonally. There's a fourth problem but I'll fix it for you. PRINT always brings the cursor to the next line, which will shift the entire screen up if it's the last line. This makes things REALLY weird if your player is at the bottom line, as the screen will keep moving up. This is remedied by telling PRINT not to go to the next line with ;
Solution 2Store the last position of the player (the "ghost" so to speak) and print an empty character there. Again, you should probably only print the empty space when they move, otherwise their character will flash on and off rapidly. You can once again use the fancy math. If you're slick and order the code right, you don't even need to store the old position.
FinalHere's the final code. I opted for solution 2 to the ghost problem, which is more complex but also more performant:
ACLS DIM X%=25 DIM Y%=15 DIM B% WHILE(TRUE) B%=BUTTON() 'Parameter optional (defaults to 0) IF B% AND 15 THEN '15 is the sum of all the direction buttons. AND returns the bits that are set in both values. Thus, if ANY of the direction bits are set, this conditional will be true and we'll erase the old player LOCATE X%,Y% 'X and Y still represent the previous location since we haven't updated them yet (happens below) PRINT " "; 'Erase the player ENDIF 'Changing == to AND here means we're checking just the one bit. Other bits can be set or unset and it doesn't matter. 'Remember, more than one direction can be set, which makes B% not equal to just a single direction like #RIGHT 'Also remember, && is the conditional "and" operator, which is different from bitwise AND. The conditions on both 'sides of the && must be true for the whole thing to be true. Those extra conditions are the boundary checks: 'notice that we only increase X if X is not on the edge (less than the rightmost allowed position) 'We use parenthesis around AND because we don't trust order of operations in programming languages. The AND 'MUST happen first, as it produces either a 0 or nonzero value, which becomes false or true (respectively) IF (B% AND #RIGHT) && X%<49 THEN X%=X%+1 'There are constants for the IDs for each button so you don't have to memorize each ID IF (B% AND #LEFT) && X%>0 THEN X%=X%-1 'Remember, the value on the right is computed first, then assigned. If X% is 25, it will be assigned 24 if this condition is true IF (B% AND #UP) && Y%>0 THEN Y%=Y%-1 IF (B% AND #DOWN) && Y%<29 THEN Y%=Y%+1 LOCATE X%,Y% PRINT "#"; VSYNC WENDThe basic idea of this has changed a little bit from our original analysis, but it's mostly the same: "gather button data", "convert button data to movement", "draw player" (plus some extra crap to fix problems). It is common to end up with a different set of problems than what you originally planned, as you almost never perceive all the potential problems right from the start.