Just as it's important to make your game friendly for users, it's important to make your code friendly for coders... namely, you. As we learned in the first chapter, most of programming isn't in initially writing the game, but in going back over it to polish it and fix bugs, so writing code in a way that makes it easy to work with saves time and frustration.
If you look at our latest code for checkers, you'll see we have the CanJump(), CanJumpAgain() and ForwardCheck() procs attached to the piece prototype, and the Capture() proc attached to the mob prototype. We could have done that in the opposite way—for instance, instead of having a piece/ForwardCheck() that asks, "Is the piece moving forward?" we could have had a square/ForwardCheck() proc that asked, "Would landing on this square allow the piece to move forward?" Or we could even have made it a mob/ForwardCheck() proc that asked "Would the player be moving a piece forward by putting it on this square?" In the end, the best object to attach a proc to is the one that's most obvious to you—that way, when you revisit your code, you won't keep looking for the right procs in the wrong places.
It's also good to program your if statements so that they're almost like sentences you would use in real life. Consider these two alternatives:
"If the piece can jump to the square, it's legal" for "if (src.CanJump(T)) return 1"
"If the square can't be jumped to by the piece, it's illegal" for "if (!T.JumpTo(src)) return 0"
Either option would have worked in terms of actually checking to see whether the piece can jump, but one's easier to read and understand.
In this latest bit of code, I've given you only what works; but when writing the book (and creating the checkers program along with it), I made several mistakes. It's so hard to get into the perspective of a computer that even after a couple decades of programming, I still can't predict ahead of time which is the right code for the job. I have to test it.
Don't be afraid of having to deal with bugs or errors in your code. Discovering some is inevitable; the key is to test often in development, so you can catch them as soon as they arise. Try playing checkers now!
Our game is starting to become more robust. "Robust" means strong and healthy; in programming terms, a robust game is one that can stand up to all kinds of use—including players doing things they're not supposed to do—without breaking.
The vocabulary of game design is useful to learn. What does it mean to break a game? Certainly not to damage or alter the code in any way; that will stay just as you wrote it no matter how many people play. A game or program "breaks" when its use is interrupted or halted by some bug or other issue. That could mean a crash, a freeze, an error, or the game's failing to respond when you're trying to do something.
It's good to deliberately try to break your game during testing. As you play checkers, try everything you can think of that might mess your game up. Click on things you're not supposed to click, try to put pieces where they're not supposed to go, try moving when it's not your turn.
If you play very long, you'll be reminded that we still don't have any code for kinging pieces or winning the game. This isn't exactly a bug. A bug is something that that works differently from how the designer meant it to. Kinging/winning isn't meant to work at all, yet. In that sense, it does exactly what it's supposed to, which makes it a "feature," or intended aspect of our game. But since we didn't so much add this "feature" to our game as neglect to add something, we'll use the slightly paradoxical term of "unintended feature"... a kindly way to address something that can seem like a bug to players.
We must code in some procs with algorithms to determine when a piece is kinged and when a player has won. For the kinging proc, we'll return to our old "How do you tell" question: "How do you tell when a piece is kinged?" Why, when it lands on the edge of the board. That answer gives us a hint about where to put the code for kinging: in the increasingly important square/Click() proc. In square/Click(), in the if block for "if distance == 1," above "usr.holding = null," put:
And toward the bottom of square/Click(), put new code above CanJumpAgain():
We can remove the "selected" icon from the piece's overlays with the -= operator, which says "the thing on the left now equals itself minus the thing on the right." To ensure that the computer doesn't then go on to execute the CanJumpAgain() check, put the word "else" in front of that line. Now we have an if statement and an else if statement.
And we'll need a KingCheck() proc too, of course. Above piece/ForwardCheck(), put:
We're checking whether the piece is on the end of the board (and the correct end depending on its color) by looking at its y coordinate. In the top line, we check it against world.maxy. This is a built-in variable that will give us the dimension of our map, 8. We could have simply used "8" instead of maxy... but by using maxy we're casting an eye toward the future. If we ever wanted to change the shape of the board, we could do it without having to change the KingCheck() code too.
We'll also need to make a new .dmi file called "kinged.dmi" to overlay on top of a piece that's been kinged, as an indicated. The simplest way would be to make it a big K, or maybe a crown or just a cross, as in chess.
Now there are two possible overlays for a piece to have... but remember, we remove all the overlays when a piece is moved. Since we don't want pieces to stop looking like kings whenever they're moved, we'll need to change that code to remove only the "selected" overlay. Hit control-H in the Dream Seeker to use the Find/Replace function. In the "Find text" box, put "overlays.len = 0"... in the "Replace with" box, put "overlays -= 'selected.dmi'" and hit "Replace all." This is a very handy way to alter things in your code.
Time to test our changes. Unfortunately, it's very time-consuming to try to play through half a game to make sure kinging works. So we're going to create a shortcut that will allow us to test it without needing to expend quite so much time.
Find the StartGame() proc and comment out the for loop. You can do this by putting the double slash in front of every line, or by putting a /* at the beginning and a */ at the end of the code section. These symbols also indicate a comment, and they allow you to comment out large blocks with little fuss. You should see the "commented out" section turn grey. Then, above the that section, put:
var/turf/T = locate(6,6,1) //testing code
We use the T variable three times to hold the value of three of the squares on the board, which we find with the locate() proc. We also use the O variable three times to create a new piece on the square, and alter the piece's icon state. Now the game will give us these three pieces in a convenient setup for testing. Run the game and practice kinging!
Recognize when someone has won
Check to see if all of one player's pieces have been captured
Check to see if one player is blocked from moving
At last, we need a way to declare the end of the game. How do you tell when a checkers game has been won? It's either when all of a player's pieces have been captured, or all her moves have been blockedÃ¢â¬Â¦ both events that are brought into being by her opponent moving. So a good place to check for a win is at the end of a move. We can do that easily at in NewTurn(), before the turn is actually changed. At the top of the proc, put:
So if the WinCheck() proc returns 1, we use the built-in sleep() proc to pause execution for 10 seconds (sleep() takes times in tenths of a second). Then we reboot with the world's Reboot() proc, and return 1, because the determined little proc will want to finish before the world reboots, but we don't want it getting to the rest of the lines.
Now let's make a WinCheck() proc below mob/Capture(), like this:
And so we need a piece/CanMove() proc to see if the piece has any legal moves:
This is very similar to our CanJumpAgain() proc (hey, it's performing a similar function).
It'll be easier to test winning if we alter the starting setup again. In the StartGame() proc, change the color of our third testing piece to red. Then run the game and jump the sole remaining white piece to see if you can win by capturing all white's pieces.
After that, see if you can alter the starting setup "shortcut" to be able to test wining by blocking white.
When you're done, visit StartGame() and uncomment the "real" piece placement code. Comment out the test code rather than deleting it, in case you want to use it later. Here's our checkers code all together...
//Checkers created by <your name> on <today's date>
Technically, there's one more rule to checkers: if you can jump another piece, you must. Some players already neglect to play with that rule in the real world, and we'll skip it here too, for the sake of space (though you can certainly code it if you want!).
Checkers is a very familiar gameÃ¢â¬Â¦ so familiar that you might not have thought very much about what makes it, well, checkers. What are the elements of our version?
It's a computer game.
It's a board game.
It has a board that's:
Eight by eight
Alternate light and dark squares
It has 12 pieces per player that:
Are set up at the start of the game
Are set up in a particular pattern
Come in two colors
Can be moved only diagonally
Can be moved only forward, unless they have been "kinged" by landing on the far edge
Can "jump" other pieces
It has turns.
To win, you have to:
Capture all your opponent's pieces
The game is all strategy, no luck.
Each session lasts 20 minutes or more.
It's a good bet that with an old, common game like checkers (or chess, or backgammon) that many variants have already been tried throughout history. If these variants had been better than the versions of the games we play to day, we'd probably be playing the variants instead (or alongside: consider Chinese checkers). So unless you have hundreds of hours to devote to testing, you may decide to skip the idea of making an age-old game better by changing some strategic element of it, whether that's the board size or number or setup of pieces, or even what's required to win.
If all those elements are placed off-limits, it might seem like not much is leftÃ¢â¬Â¦ but we actually have plenty to work with.
Our game has turns. What would checkers be like without turns? What if a player could move one of his pieces whenever he wanted? It sounds ridiculous, but the computer is the perfect venue for testing the idea. Action checkers would be a game of speed and reflexes, in which players raced to be able to jump the opponent before being jumped. To try testing an action version of checkers, use control-H to find all instances of "whoseturn." This will show you all the parts of the code that deal with turns. Double-click on any of them in the bottom window to be able to jump to that line, then comment it out. Search for references to NewTurn() and comment out those too. Testing your action game won't be as simple as testing regular checkers; you'll need a friend to play against. Host your game but set visibility to "Private." If your friend adds you to his pager, he can see when you're in the game, and can join you.
Another key element of checkers is that it's totally based on strategy; no luck is involved. A great way to adapt a strategy game is to add an element of luck to it. With luck, the equation of fun is looser.
A game such as chess is like a wooden puzzle where the pieces all fit exactly. If one of the pieces were shaped a little bit differently, the puzzle wouldn't work. On the other hand, a game such as Monopoly is like a puzzle made out of foam pieces. If the pieces don't fit exactly, it's okay; the randomness creates a cushioning effect so that the puzzle still works. In fact, the random element (the foam) is so "squishy" that for the designer, it sometimes doesn't even make sense to put a lot of time into balancing the game's strategic elements (the shape of the pieces) exactly; they're just going to get distorted anyway.
Players have different preferences for what they want in their games. Some prefer games that are all strategy; some would rather have a little (or a lot of) luck involved, for various reasons. Luck can make a game less intimidating; players know they might be able to win without having to spend hours practicing first. It makes champion players less invincible. From that standpoint, some players like luck because it seems to make a game easier. Ironically, other players like luck because it seems to make a game harder. With a random element, the game is less predictable, and players are required to "think on their feet" to cope with ever-changing situations. The important thing is that both kinds of games have many admirers.
So what sort of a random element could we introduce into our checkers game? We'll start by looking at our above list for inspiration. It doesn't make much sense to make the number of players random. Making the size of the board or the setup of the pieces random would be possible, but wouldn't make the game all that random in the end, since presumably the pieces would be set up randomly at the beginning and then remain beholden only to skill (though a checkers variant in which the size of the board kept changing during the game would certainly be interesting!).
Perhaps we should step outside what we can see on our elements list. The original elements of checkers were limited by its being a tabletop game. With the computer, we can make more fundamental alterations to the board as the game progressesÃ¢â¬Â¦ say, by removing certain squares from play as the game progresses. We can even add a little theme to our game by deciding on a particular cause for the squares' being out of play.
Let's introduce some whimsy by putting a mouse on the board. Any square he leaves droppings on is now out of commission. Furthermore, players won't be able to land on the mouse, and if the mouse is on top of one of your pieces, you can't pick it up. (This game could even be advertised as "Checkers you play with a mouseÃ¢â¬Â¦ literally.")
So we'll introduce an automated mouse that will walk randomly about the board and occasionally leave droppings. This will not only introduce randomness to our game, it'll also make the game go faster as more squares are removed from play.
We could make our mouse a mob or an obj; the best option for us is obj. Why is that, considering the mouse will be a mobile entity? Let's see how the mouse's behavior fits with the code we've already written. The mouse will, as stated, prevent players from landing on whatever square it's on... quite like the pieces themselves do already. In our CanMove() proc and elsewhere, we use lines like "if (locate(/obj) in T.contents)..." Our procs are looking for objs, not just pieces, so if we make the mouse an obj, we won't have to edit them much. Perfect.
So, to begin coding this mouse, find your obj code. Tabbed beneath "obj," put:
In our MouseStep() proc, we use rand() to generate a number between 1 and 5. If it's 1, the mouse will change direction. We'll only let it go diagonally, since that will put it in the most direct contact with the squares players will be landing on.
The get_step() proc returns the turf one step away from who we specify, in the direction we specify. What we're checking here is whether the mouse is about to go off the edge of the board. If there's another square in front of it, we'll allow it to moveÃ¢â¬Â¦ but first we'll see if it leaves droppings.
We count all the pieces left in the world. Then, if the mouse isn't sitting on a piece, and if a random number between 1 and 24 turns out to be higher than the number of pieces left, we let it leave droppings. This means that the fewer the pieces left in the game, the greater the chance the mouse will leave droppings, taking squares out of play. If there aren't already droppings in the world the first time we make some, we'll send out a messageÃ¢â¬Â¦ so the players will get a droppings message the first time it happens. After that, they should get the picture.
At the bottom we have an else statement, saying that if there's no turf in front of the mouse, it should change direction.
As you can see, we'll need some new icons. Create a new .dmi icon file called "mouse." When it opens, click the movie camera. This allows us to edit animations, which are like states that can be shown when the object changes direction, when it moves, or whenever it performs some other action we specify. On the bottom left, set Frames to 1, then Dirs to 8. Since the mouse will only be moving diagonally, we'll only use the four diagonal directions. Double-click on the Southeast box and draw a teardrop shape with eyes, ears and a tail to be your mouse. Flood it with a shade of grey. (This is what's called a "placeholder" icon. If you end up liking the game, you can go back and make the icon prettier.) Go back to the direction screen, select the Southeast icon and copy it, then paste it onto the other diagonal boxes. Edit each to rotate the mouse into the direction it should be facing.
For a "droppings" icon, be as creative as you likeÃ¢â¬Â¦ just remember to use a color that will show up on both your light and dark squares (or outline it).
We'll create the mouse when the game starts. At the bottom of the StartGame() proc, put:
Under "proc," create the new proc:
With this proc, we introduce another DM tool: the do/while loop. The computer will keep performing the tasks in the "do block" as long as the conditions in the parentheses after "while" hold. If what's in the parentheses evaluates as untrue, the computer forgets the do block and goes on to the next bit of code.
In our do block, we start off saying it's okay to put a mouse on a square. We then grab a square with random x and y coordinates. If it has a piece on it, it's not okay; if it's a light square, it's also not okay. So when the "while" line is reached, if our square is not okay, the computer will go back and do the procedure again. If it is okay, the computer will put a mouse on it.
In the past, we've lopped through squares with a "for" loop that went through every square on the board in turn. If we did that here, the computer would choose the same square every time since they'd always go in the same order. Do/while can be very useful. There is a danger, though: if for some reason the conditions always check out as true, the computer will hang in the loop infinitely, and the program will freeze up!
Now to prevent the player from picking up a piece if the mouse is on it. In the piece/Click() proc, below the check to see whether the usr has the right piece, put:
if (locate(/obj/mouse) in src.loc) //this
The droppings should be an obstacle, not a way to get ahead, so we'll have to change our CanJump() proc to keep players from jumping over them. Between the last two lines of the proc, put a new line:
if (istype(O,/obj/droppings) && midsquare.contents.len == 1) return 0
Note that when we're working with types of object using istype(), locate() or the "new" command, we preface them with a slash. The istype() proc is a built-in proc that allows you to see whether a particular object has a particular prototype. We're seeing if O was made from the obj/droppings prototype (as opposed to being an obj/piece or obj/mouse). So, if what's being jumped over is droppings, and the droppings are the only thing on the square, the player can't jump. However, it might be fun if players could jump the mouse. Change your mob/Capture() proc like so:
The mouse will be deleted at the end of a proc just as a piece would be. We'll make a new mouse appear at the end of the turn. Let's do that at the beginning of NewTurn(), before the new turn actually comes. At the very beginning of that proc, put:
var/obj/mouse/O = locate(/obj/mouse) in world
Ã¢â¬Â¦and we're done. Try your mouse checkers!
Mouse checkers doesn't differ from checkers nearly enough to be considered an original game. It's a checkers variant. In fact, people are so familiar with the diagonal, jumping aspect of checkers that you'd probably have to change that aspect before your audience regarded your game as something other than "a version of checkers." But change enough, and you could still make it very interesting. A great way to come up with game ideas is to try putting two games together. What aboutÃ¢â¬Â¦
Checkers and Monopoly?
Checkers and Stratego?
Checkers and MineSweeper?
Our own checkers variant still needs the final touches, though. Even if you don't plan on keeping mouse checkers, read on to discover how to add sounds to your BYOND games.
There are a couple things we can do to make our checkers game more professional and appealing. The appearance of any BYOND game can be improved by tinkering with the panels, or tabbed windows that usually appear below the map. In the next chapter we'll discover how to put whatever information you want in a panel. Right now we're going to discover how to get rid of the one we have.
Like our last game, checkers has just one panel, Commands. This panel automatically lists all the verbs available to the player. Since we only have one verb, it doesn't make much sense to keep the panel around.
If you visit the DM Reference online you'll find on the left-hand side a list of the types of objects and all their built-in variables and procs. (Feeling lazy or no, go on and visit it now, if you haven't already; the sooner you get past the initial hump of loading it, the sooner you can enjoy the wealth of assistance it has to offer.) Scroll down to the "client" section, with everything that deals with the Dream Seeker client, the program through which players connect to BYOND games. Look at all the procs we could override! So far, the only one we've taken advantage of is Click(). And look at the variables! Find show_verb_panel and click on it to read the description.
We'll also use another client variable, command_text. This will allow us to keep something "pre-entered" on the player's command line. In your checkers code, near the top and above the world code, put:
Test it to make sure it works. Now the player can't see any verbs, but he will easily be able to say something by typing it in and hitting enter. (Incidentally, this alteration of the command text is what happens when a player clicks on the little chat button to the left of the command line.)
Another way to polish our game isÃ¢â¬Â¦ at lastÃ¢â¬Â¦ to add sounds. The importance of sound in games is sometimes misunderstood and often underrated. We may think of sounds as existing to add realism to a game, but their real purposes are broader: sounds can make the game more engaging by appealing to more than one sense, and they can provide important information without cluttering the visual field. Less importantly, sounds are also good for adding humor value.
Where do you find sounds? For sounds that aren't meant to represent living things, it's quite easy to record your own by hooking a microphone up to your computer. You can record the sound of an actual checkers piece being set down on your desk for use in the game. But if you don't have a microphone, or need human or animal sounds, there are many free sound sites on the internet (such as findsounds.com). Look for files with a .wav extension.
We wouldn't get much searching for "free checkers .wav," since anyone who'd bothered to record such a sound probably wouldn't put "checkers" in the name. Think of a generic way to describe the soundÃ¢â¬Â¦ say, "click" or "clack," and try that. When you find a sound you like, right-click on the link and choose "Save Link AsÃ¢â¬Â¦" to save the sound to your Checkers folder (which is probably in C:\Program Files\BYOND\bin).
In Dream Maker, click the Update button at the bottom of your file tree. Now you can see the sound file. We want this click sound to play whenever a piece is set down. In square/Click(), find the places where usr.holding.loc is changed. That's when a piece actually gets set down. After each such line, put:
world << 'click.wav' //or whatever your sound is called
Ã¢â¬Â¦making sure to use single quotes. Now test the game to see how the sound works. (If you're playing against yourself on the same computer, you may hear it twice.)
As you can see, sending a sound to a player is a lot like sending text. Let's also use a sound to provide information to the player—the information that his turn has arrived. In turn-based computer games, such a sound is essential. Many players can't resist the temptation to surf other windows when it's not their turn, and you'll need a way to call them back so the game doesn't get stalled when their turn does arrive. The sound could be any kind of sound you want, but since the players will hear it many times a game, it's good to make it pleasant. A chime sound is a solid pick.
When you've found a nice one and saved it to your checkers folder, go to the NewTurn() proc. Underneath the "It is now your turn" message, put:
whoseturn << 'chime.wav' //or whatever your sound is called
Ã¢â¬Â¦and test it.
Finally, if you're working with mouse checkers, you can use sounds to add a little humor. Search for "mouse" or "squeak" to find a mouse sound. Save it to the folder, then find the piece/Click() proc. After the if blocks that can return 0, put:
if (rand(1,10) == 1) world << 'mouse.wav' //or whatever your sound is called
Now there's a one in ten chance that the mouse will squeak when a player selects a piece. I think our game is polished!
In the next chapter, we'll reuse what we've learned to duplicate another popular game, Uno. We'll also start learning about an important new tool in the DM language: lists.