Aaron L. A Gamer's Introduction To Programming in C# 2025
Aaron L. A Gamer's Introduction To Programming in C# 2025
Programming in C#
Turn your love of video games into a new love of programming by learning
the ins and outs of writing code while also learning how to keep track of
high scores, what video game heroes and loot boxes are made of, how the
dreaded RNG (random number generator) works and much, much more.
This book is the first in an ongoing series designed to take readers from no
coding knowledge to writing their own video games and interactive digital
experiences using industry standard languages and tools.
But coding books are technical, boring, and scary, aren’t they? Not this one.
Within these pages, readers will find a fun and approachable adventure that
will introduce them to the essential programming fundamentals like variables,
computer-based math operations, RNG, logic structures including if-statements
and loops, and even some object-oriented programming. Using Visual Studio
and C#, readers will write simple but fun console programs and text-based
games that will build coding skills and confidence.
Put on your cap of +5 courage and level up by joining the coding adventure
that awaits you inside!
Aaron Langille (PhD) is a public speaker and radio columnist, game player
and designer, writer and editor, and award-winning post-secondary
educator. After teaching computer science for almost two decades, he
recently leveled up to teaching video game design. In his current role,
Aaron sits happily at the intersection of video games, education, visual arts,
media, technology, and storytelling.
A Gamer’s Introduction
to Programming in C#
Welcome Brave Adventurer!
Aaron Langille
Cover Design by Joshua Roberson
Reasonable efforts have been made to publish reliable data and information, but
the author and publisher cannot assume responsibility for the validity of all
materials or the consequences of their use. The authors and publishers have
attempted to trace the copyright holders of all material reproduced in this
publication and apologize to copyright holders if permission to publish in this form
has not been obtained. If any copyright material has not been acknowledged please
write and let us know so we may rectify in any future reprint.
Except as permitted under U.S. Copyright Law, no part of this book may be
reprinted, reproduced, transmitted, or utilized in any form by any electronic,
mechanical, or other means, now known or hereafter invented, including
photocopying, microfilming, and recording, or in any information storage or
retrieval system, without written permission from the publishers.
For permission to photocopy or use material electronically from this work, access
www.copyright.com or contact the Copyright Clearance Center, Inc. (CCC), 222
Rosewood Drive, Danvers, MA 01923, 978-750-8400. For works that are not available
on CCC please contact mpkbookspermissions@tandf.co.uk
Typeset in Palatino
by MPS Limited, Dehradun
This book is dedicated to everyone who learned how to code because of video games.
vii
viii Contents
23. Code Quest 10: A Companion for Our Hero (Part 1)........................ 259
Main Quest – A Companion for Our Hero (Part 1) ............................ 259
Side Quests .................................................................................................. 260
25. Code Quest 11: A Companion for Our Hero (Part 2)........................ 284
Main Quest – A Companion for Our Hero (Part 2) ............................ 284
Side Quests .................................................................................................. 286
30. Bonus Level 3: Input Made (A Little Bit) Friendlier ....................... 308
TryParse() – Dealing with Unparsable Input ........................................ 308
TryParse() and Input Validation.............................................................. 310
Index .....................................................................................................................325
Acknowledgements
These books wouldn’t be possible without the help and support of a lot of
great people. First and foremost, to my amazing life-partner Victoria, thanks
for believing in me through the whole process, start to finish. Thank you to
my awesome editorial team, especially Randi and Solomon who helped me
turn a vague idea into something tangible. Thank you to Josh, the books’
illustrator, for levelling up the joy and fun in these pages. Finally, I owe a
debt of gratitude to all my past, present, and future programming students
who ever-so-patiently let me try out new wild and wonderful ways of
teaching people to code.
xiii
1
Cutscene 1: Backstory
DOI: 10.1201/9781003348481-1 1
2 A Gamer’s Introduction to Programming in C#
programs aren’t very exciting, but they are simple enough to see the direct
cause and effect of the code that we write.
The second way, more common for game design students, is to jump into
a game engine right from the start and learn to program with a specific
language (like GameMaker Studio, Construct3, or Stencyl) and in a specific
video game development context. When we do this, we combine learning to
program with learning the ins-and-outs of game engine functions. Without
a doubt, working in a game engine is exciting – the examples are more
interactive and visually awesome (when they work). But the complexity of
many modern game engines (like Unity or Unreal Engine) can also be
overwhelming, frustrating, and distracting if the primary goal is learning
how to program. It’s hard to stay focused on source code when we are
worried about which way our camera is facing, how the collision physics
are reacting, why our animations aren’t playing properly, and a whole
bunch of other game engine-specific issues. This level of distraction often
leads to “surface” learning – learning to program just enough to satisfy our
game engine’s needs, but not deeply enough for the skills to be used in
different contexts (like other programming languages, other development
environments, or even other game engines).
Both of these approaches can be effective – I don’t want to give the
impression that they don’t work – but their success depends a lot on your
specific goals and also on the materials and resources available to help
guide you. This series of books is designed with a different approach in
mind, an approach that sits in the space between ignoring games until you
are a confident programmer and immediately overwhelming you in a
complex game engine. In this first book, we’ll start our coding adventure
with simple but fun, game-centric text-based examples on the console
to build up some essential coding skills and confidence using Visual
Studio and C# (C-sharp). In the follow-up book, A Gamer’s Introduction to
Programming in MonoGame: Welcome Brave Adventurer, we’ll build on what
we’ve learned here and further level up our C# and Visual Studio skills by
using the MonoGame framework!
Along the way, we’re going to use games and game ideas to keep our
journey motivating and entertaining. In this book, you’ll learn how games use
fundamental programming elements like variables, math operations, random
numbers, decision logic, loops, and simple objects (more on the decision to
introduce object-oriented programming in Level 10). In the next book, you’ll
learn how to use sprites and images, make game objects collide and react to
each other, keep score, use keyboards, mice, and game controllers to move
characters around, create dynamic scenes using images and sound, and much
more. Don’t let all the fun fool you – these books are designed to help you
develop programming skills that are useful beyond simple games, and
everything you’ll learn on this adventure will apply to writing software of
many different kinds. When you’re done, my hope is that you’ll feel like a
“programmer”, even if these books are just the start of your journey.
Cutscene 1: Backstory 3
Now that we know where we’re going, let’s take a quick look at why this
journey uses C#, Visual Studio, and the MonoGame framework.
FIGURE 1.1
Even though the black console window isn’t as fancy as a video game, it’s very helpful for
writing programs that focus on coding fundamentals.
to work on and to worry about! These features are important for creating
publishable games, but it means that engines are complex tools with complex
interfaces full of buttons, toggles, and switches (oh my!) that can be
distracting when our goal is learning to program.
Unlike full-featured game engines, MonoGame is a framework (or set of
C# libraries) that runs under Visual Studio – which you’ll already be
familiar with from this first book. MonoGame may not be as convenient as a
full-featured game engine, but it does allow us to learn and practice our
programming skills while designing simple games and digital experiences
(Figure 1.2). Learning to program in MonoGame will help to ease your
transition to an engine like Unity if, or when, you’re ready to shift your
focus from programming to full-on game design.
If you want to know more about MonoGame, scroll through the online
showcase (https://www.monogame.net/showcase/) to see what it’s cap
able of – you might find that you’ve heard of, or even played, some of the
amazing games (Stardew Valley, Celeste, Bastion, Axiom Verge) that were
made using the language, tools, and environment that you are about to start
exploring.
Cutscene 1: Backstory 5
FIGURE 1.2
If you decide to continue your journey into the next book, you’ll build more coding knowledge
and write extended examples that are more complex but also more visual, interactive, and
more game-y!
Starting with Level 4, each level ends with a code reward – a complete
program that highlights the level’s key concepts and coding ideas. Code
rewards are the bonuses, loot, points, or extras that help to motivate you
through the level.
When we’re crushing candies, being chased by ghosts, crafting items, or
protecting the planet, sometimes it’s nice to have a break and catch our
breath. That’s where games use cutscenes – a break in the action where the
player gets to learn more about what they’re doing without having to do
much themselves. You’ll find cutscenes in these books as well. They push
our story forward, but with fewer code examples and no follow-up code
quests. Like video game cutscenes, they can also be skipped – but you’ll
know more in the end if you don’t.
At the end of each book, you’ll also find optional bonus levels with extra
content for those who want to complete the whole journey. Each bonus level
has information that, while cool and useful, didn’t make it into the levels.
• Full speed, all the material: The first two books in this series are based
on my experience teaching two back-to-back intro-to-programming
courses for game design students and computer science students in
a game design track. Some advanced material on inheritance,
polymorphism, and data structures has been removed, and some
optional material has been moved to the Bonus Levels. The contents
of both books could be compressed into a single term – particularly
if your students have some programming experience or are very
keen to dig in.
• Half speed, all the material (Option 1): If you prefer to deliver this
material in a more laid-back way, I recommend splitting the two
books across two terms. The first way of doing this would be to cover
the first book in one term and the second book in another term.
• Half speed, all the material (Option 2): My second recommendation for
a two-term delivery is to cover most of the C# fundamentals from
this book (up to Level 9), followed by some of the MonoGame
content from the second book in a single term. Several of the
Cutscene 1: Backstory 7
MonoGame Levels and Code Quests don’t use objects. This means
that some MonoGame content can be introduced earlier. In the
second term, you might start by finishing Levels 10 and 11 from
this book and then completing object-oriented levels from the
second book. If there are gaps in either term, you might consider
supplementing with topics from the Bonus Levels in both books,
introducing the ideas of inheritance and polymorphism, or intro
ducing another game design engine such as Unity to show the
similarities and differences with MonoGame.
• Breaking up the set: If you are interested in only one of these books for
your courses, they can be used separately. Keep in mind that this first
book covers only C# with console examples, while the second book,
which covers MonoGame, will require some C# coding experience.
All the examples from these books, as well as code rewards, starting
projects for code quests, and extra code quests, are available online. See
Bonus Level 6 for details.
8 A Gamer’s Introduction to Programming in C#
programming needs to AI instead? I’m going to say “no way!” to that idea.
Here are three reasons why you should feel confident in starting (or
continuing) your programming journey:
1. AI tools are still relatively simple. Of course, what they are able to
do will improve over time, but for now and for the foreseeable
future, programmers – along with their experience and skills – are
still very much in demand.
2. Code-writing AI, like most automation technology, is not perfect. If
you ask an AI tool to generate some code for a game project you are
working on, how will you know if it’s working as effectively and
efficiently as you need it to? You can certainly test the code to see if
it works, but the only way to be certain that you’ve received what
you actually need, and the only way to modify it if it’s not perfect, is
by learning to read and write code yourself.
3. AI might end up being part of your coding journey. This might be
the most exciting point of all. If we assume, as many people do, that
AI tools are here-to-stay, the best solution for many programming
projects will be a combo of skilled programmers and AI tools. Even
now, many programming environments are integrating AI tools to
help programmers (beginners through experts) code more
effectively.
In many video games, there are characters who join you for parts of your
journey. At specific times, often when they are needed most, they lend their
specific skills to help you solve puzzles or progress to the next stage. AI
tools are like those supporting characters – there when you need them.
You’re likely to find AI helping you early on in this journey too. Visual
Studio and other development environments routinely include tools that try
to anticipate what you’re typing and offer suggestions on what lines of code
should go next. The more you practice the fundamental programming
concepts from this book, the more you’ll feel confident in knowing when
these tools are making helpful suggestions and when they’re not.
FIGURE 1.3
This is me, your trusty author and quest-giver – more or less.
games from audio cassettes, several sizes of floppy diskettes, CDs, DVDs,
and cartridges, and downloaded them from the Internet. I’ve owned almost
every kind of console that Nintendo has sold, and a few from other
companies as well. I’ve battled countless dragons, robots, zombies, geo
metric shapes, evil toys, and even possessed foods. I’ve been the drummer
in a virtual rock band, a private investigator solving fictional crimes, and
I’ve even been a frog who just wants to survive crossing a busy street. I’ve
played good games, bad games, scary games, funny games, serious games,
games designed to make me smarter, rhythm games, adventure games,
story-driven games, casual games, free games, and expensive games – you
name it, I’ve probably played it – or something like it.
Not only did I grow up playing video games, like many of you, I also
grew up alongside video games. While I was getting older and (somewhat)
Cutscene 1: Backstory 11
more mature, so too were games moving from casual pastime to “serious”,
multi-billion-dollar industry. As every generation of video game hardware
became more powerful and less expensive, the games themselves became
more engaging and more readily available to a wider audience. This created
a need for more game designers. Unfortunately, when I was finishing high
school, there weren’t any college or university programs to help me get into
a game design career. So, I did the closest thing that was interesting to me –
I studied computer science and computer programming. Even though the
topics in many of my courses could have been applied to designing and
creating video games, it wasn’t something that was done at that time and I
didn’t really have the specific knowledge, opportunities, or tools to combine
my new skills with my favorite pastime.
Eventually, I became a computer science professor, and I would find ways
to bring video game ideas into my lectures and assignments to help make
learning to program more fun for my own students. A few years ago, the
department I was working for introduced a video game design minor to our
degrees and I was able to teach game-programming classes as well as
traditional computer science programming. Game design programs are now
common in colleges and universities, in part because the game industry
continues to grow and, in part, because many prospective students have
parents that, like me, grew up playing video games and recognize their
potential for career and personal growth.
Now, I’m at a college where I am the coordinator of a game design
program. Unlike the first program I helped to create, this new program is
all game design, all the time. How fantastic is that? From my perspective,
it’s very fantastic, but it also comes with new challenges. My former
computer science students would take 2 full years of general programming
courses before moving on to the unique challenges of game programming.
But my game design students need to learn both programming and game
design simultaneously – sometimes with little or no experience in either.
There is also a perfectly reasonable expectation that the programming
skills they learn are general enough to be applied outside of game design.
And that’s why this book exists – to bridge the gaps between learning
to program, learning to program games, and having some fun while we’re
at it.
Speaking of having some fun, aside from me as quest-giver, and you as
quest-doer, we have another brave coding companion joining us on our
journey. The awesomely talented Josh Robertson (Figure 1.4) teaches both
game design and animation, and when he’s not teaching, he’s probably
practicing his art, game design, and coding skills by joining a zillion game
jams. Josh’s drawings can be found throughout these books, and we hope
they help to illustrate and bring a bit of fun to concepts like if-statements,
loops, code-gremlins, algorithmic thinking, object-oriented programming,
and more.
12 A Gamer’s Introduction to Programming in C#
FIGURE 1.4
This is Josh – more or less – a brave companion and the illustrator for our coding journey.
DOI: 10.1201/9781003348481-2 13
14 A Gamer’s Introduction to Programming in C#
really quickly. A modern CPU can carry out BILLIONS of calculations every
second! What those calculations are and what they mean depend on what
we’re doing – playing games, surfing the Internet, grading homework,
writing this level in our book, etc.
When we are programming, we are writing the instructions that the CPU
will eventually carry out. Sometimes the instructions are relatively simple
(like when we want to add points to a player’s score), and sometimes the
instructions are more complicated (like when we’re telling our game camera
to follow the hero in 3D space). Either way, through the code we write, we
are telling the CPU what we want it to do.
getting into a big discussion about how poorly (or well) people can multitask,
the answer is only one. But how many videos do you have stored on your
computer at a time? Probably many more than one. The RAM handles the
current video you are watching the hard drive holds the rest for watching
later. Games work roughly the same way – you can play (part of) one game
at a time, so the RAM is active with the current game elements, while the
hard drive stores your ridiculously large (or not) game library to be played in
the future.
When we program, we use hard drives in two ways. First, we often write
programs that request data from the hard drive. For example, we may want
to load a user’s name, high score, and current progress level from a file that
we saved the last time they played. We might also need to load sprites
(game images), sound files, and more resources that our game needs to
function. Second, our game itself is stored on the hard drive.
when GPUs appeared to share the computational work by taking over the
graphics-specific tasks. CPUs still do a lot of work on modern computers, but
they work together with GPUs to make sure that our games run smoothly.
Now that we know a bit about the most common computer hardware
parts, let’s look at the programming languages, tools, and libraries these
books use for our coding adventures.
All the examples you come across in this book should work with any version
of Microsoft Visual Studio and should work on both Microsoft Windows and
Mac OS. If you are already familiar with Visual Studio, you might be using
Professional or Enterprise versions, but in all screenshots and examples I’ll be
using Visual Studio Community Edition – the free version – for Microsoft
Windows. As of the writing of this book, all versions can be found on
Microsoft’s Visual Studio web page. You might also find a product called
Visual Studio Code on the same web page – I recommend against installing
this version as it does not integrate as well with MonoGame.
This would be a great place to include some instructions and screenshots on
how to install Visual Studio, but you’ll soon notice there aren’t any. Modern
software is known for often changing the steps needed for installation, and I
worry that any instructions or screenshots would be out of date between the
time I write this sentence and when the book arrives in your hands or on your
screen. But don’t worry – as of right now – installing Visual Studio is a simple
matter of downloading the installer and following the prompts. During
installation you might see some options for installing different modules or
“Workloads”. Here are some that you should install if they are made
available to you: .NET desktop development, Universal Windows Platform
development, and Game development with Unity (if you’re going to try out
Unity at some point in the future). If all goes well, you’ll have it up and
running in no time! In the unlikely event that you run into any installation
issues, there is an active community a web search away!!
In the next book, we’re going to move away from console programs and
start developing more visual, interactive, and leveled-up examples using
MonoGame. The good news is this will still happen in Visual Studio. If you
are excited and want to install MonoGame now, you can – it won’t interfere
with any of the console examples in this book. If you would rather wait and
see how the first part of your adventure unfolds before installing anything
extra, you can skip to the next section and return here later – none of the
console examples in this book need the MonoGame framework.
For now (at the time of writing this sentence), installing MonoGame in
Visual Studio 2022 means:
Visual Studio will require a restart, but assuming all goes well (again),
you’ll be able to try out the Bonus Code Quest at the end of this book. If you
Level 1: Prepare for Adventure 19
do the installation now and if you run into any problems, MonoGame has a
very active online community – and a web search of the error you are
getting or a post to their support forum is very likely to help you get things
fixed up.
Write
As you might have guessed, in this step, we write source code – human-
readable instructions for the computer. To do this, we open Visual Studio
(or whatever development environment we are using) and write statements
in C# (or whatever language we are using). Statements are to code what
sentences are to everyday writing – they are how we express to our
program that we want something to happen. Many statements are a single
line in length, but they can be longer, as we’ll see in later levels. In the
example below, I’ve written three statements in a Visual Studio project
called SimpleDemo (Figure 2.1).
Pay attention to the dash-highlighted panel – this is where we write and
edit the C# code for our program. Writing code is “easy” – we can type
whatever we want in the editor window. But problems (and frustration)
pop up in the next step when we try to compile code that isn’t written
properly.
Compile
The second – and maybe trickiest – step of our three-step programming
cycle is compile. The compiler is a tool that is included when you install the
C# language and libraries. The compiler takes the code we’ve written and
20 A Gamer’s Introduction to Programming in C#
FIGURE 2.1
In Visual Studio, we write and edit code in the editor pane (dashed line).
converts it into something that the computer understands and can run (or
execute). As people, we process information differently than computers,
and it is important for us to be able to write programs in a way that makes
sense to us (or will eventually make sense to us when we’ve practiced
programming for a while). We write our programs in C#, which is a high-
level or human-understandable programming language, but our digital
devices prefer to receive their instructions in a more low-level or machine-
understandable language. The compiler takes our C# source code and
converts it to machine instructions that our device can then run. Think of
this this way – you, an English-speaking person, have written a letter to
your friend who only speaks Japanese. You hand your letter to a translator
who knows the words and structure (or syntax) of both languages. The
translator hands you back a version that you can’t read but that you can
now deliver to your friend who can read it. Compiling is a bit more
technical, but still very similar to this.
The compiler’s main job is to convert the high-level code that we’ve written
into machine language, but to do that, it has to check that we’ve written
something that can actually be translated. The compiler asks: Does this code
make sense, and can it be converted into something executable? More
formally, the compiler checks to see if the syntax of what we have written
is acceptable. In programming, syntax is the set of rules that determine how
the parts and pieces of a language can be correctly combined. Sounds a bit
Level 1: Prepare for Adventure 21
complicated, but let’s look at an example. Here is a silly sentence that I used to
write over and over in my high school typing class:
All the pieces are there, but it doesn’t really follow the syntax of how we
write and read sentences. In particular:
• the spacing is a mess, that makes it hard to figure out where words
begin and end
• capitalization is off, so we can’t really tell where the sentence starts
• the . is in the wrong place so we can’t really tell where the sentence ends
That’s a lot to worry about when you haven’t written your first program
yet. I mention some of these rules early to plant the following seed in your
mind – the code we write throughout this book needs to follow certain
22 A Gamer’s Introduction to Programming in C#
rules. The syntax rules listed above aren’t all the rules that C# syntax
follows, but they are some of the most immediate and important ones.
In the end, the point is this: you can write whatever you want in the editor
window, but Visual Studio and the C# compiler will tell you if you’ve put in
(or missed) something it doesn’t like. When we mess up or make mistakes,
we call these errors that the compiler finds “syntax errors”, and they stop
the compiling process. If that happens, we return to the writing step and fix
the issue before trying to compile again.
Here is what happens when my example is changed to have two syntax
errors (Figure 2.2).
The errors in the program are quite small – I changed the word Console to
console, and took away a ; (semicolon). Yup, that’s it, from C to c and a lost ;
and the whole thing fails (remember, C# is case-sensitive and uses a whole
bunch of punctuation types). The syntax highlighting of Visual Studio
shows some hints – those squiggly lines highlight syntax warning and
errors – and you can also look at the bottom panel (with the dashed box) for
more details. If you don’t see the errors in the bottom panel, try clicking the
red circle with an “x” in it. We’ll ignore the specific details for now (there’ll
be SO MANY opportunities to work through syntax errors as you practice),
but that’s where you can find them.
Until we fix all the syntax errors in our program (replace the capital c at
the start of line 11 and the ; at the end of line 9), we can’t successfully
FIGURE 2.2
Visual Studio shows us syntax errors in realtime, as we’re coding! Make sure to keep an eye on
bottom pane (dashed line) if your program isn’t compiling properly.
Level 1: Prepare for Adventure 23
compile it, and if we can’t compile it, we definitely can’t run it. Sorry, that’s
just how it is – I didn’t make these particular rules. In the beginning, much
of your time spent programming will be trying to satisfy the grumpy and
fussy compiler. But don’t lose hope, it does get easier with time and
practice. When the program successfully compiles, we can proceed to the
next, and maybe funnest step – Run!
Run (Finally!)
How do we know if our program does what we want it to? We run it! This
can be the most rewarding step, and sometimes the most heartbreaking. It’s
rewarding when the program does what we want and heartbreaking when
it doesn’t. But it’s a very important step, and we can’t skip it.
Once we have satisfied the compiler by writing a program that meets all
of C#’s syntax rules, we can ask Visual Studio to compile and run the fruits
of our labor. To do this, we click the green “go” button at the top, press the
F5 key on our keyboard, or use the Debug → Start Debugging menu option
(this is not the same kind of debugging that is described below, don’t worry
about the labeling in this menu). If our example program compiles
successfully, we’ll see a window that collects some input from the user
and shows a message we crafted (Figure 2.3).
FIGURE 2.3
When your program’s syntax is correct, and the compiler is happy, you’ll see the results of
your program in a console window like the one shown here.
24 A Gamer’s Introduction to Programming in C#
Success! The compiler is happy with our simple program, and the program
runs and does exactly what we asked. It won’t always be that way, but in this
example, we’re good-to-go. There will be times that our program will run, but
it won’t do what we expected. In these cases, we have either logic or runtime
errors in our program. Both of these error types are described in later levels.
In a perfect world, we would write source code, compile it into computer
or machine code, run it, and enjoy the amazingly perfect results of our
programming efforts. In this magical and imaginary world, the steps we
follow would always look like this (Figure 2.4).
But this rarely happens, especially as games and programs grow more
complex, so there is another unofficial “step” in the write-compile-run process –
debugging. Debugging is where we have a problem – or bug – in our code and
we need to repeat either the write and compile steps, in the case of syntax errors,
or the whole set of steps including write, compile, and run, in the case of logic or
runtime errors. In practice, the steps we follow look more like this (Figure 2.5).
This isn’t a beginner-only problem, even lifelong programmers need to
spend time debugging their source code. Here are a couple of tips to help:
• Compile and test often: Instead of sitting down to try and write an entire
program all at once, write only one or two (or a few) lines of code
before compiling and testing. This way, if there’s a syntax error or your
code doesn’t do what you thought it would do, you’ll be able to find
the problem more quickly. Thankfully, Visual Studio will keep track of
your syntax errors as they appear in your code, which saves you from
having to manually compile the code each time, but it’s still worth
keeping this idea in mind to keep your debugging time at a minimum.
• Read your code and error messages carefully: At times, the compiler will
feel like your enemy. When you are first learning to program,
you’re going to make a lot of mistakes – misspelled words, missing
semicolons, capitalization errors, missing { or }, etc. The compiler
FIGURE 2.4
First, we write the code, then we compile the code, then we run the code. Then, we do it all
again. This is the write, compile, run cycle.
Level 1: Prepare for Adventure 25
FIGURE 2.5
In reality, the write-compile-run cycle gets a bit messy when we’re fixing syntax errors and
troubleshooting problems with our program.
Side Quests
Want to Know More?
More hardware components: Those with more hardware experience might
notice that we skipped a whole bunch of standard computer components. If
you want to know more about digital hardware, look up these parts:
• computer cases and cooling – hold all the pieces together and make
sure they don’t overheat
• motherboard – connects all the main components like CPU, RAM,
hard drive, GPU, and input/output devices and coordinates all the
tasks and information
Want to Do More?
Code Quest 1: If you have Visual Studio installed, head to Code Quest 1 and
give it a try!
Bonus Code Quest: If you have Visual Studio and MonoGame installed and
want to test it out, head to the Bonus Code Quest at the end of the book and
give it a try!
3
Code Quest 1: Hello, World!
Here’s the moment you’ve been waiting for: It’s time to create your first
Visual Studio project and practice the write-compile-run cycle for yourself!
The programs in this first Code Quest are going to be low on complexity,
but the steps we follow here are going to be the same for every example that
you’ll see in this book. Going forward, we’re not going to repeat these initial
steps with the same level of detail because we’d be repeating the same
images and using up valuable page space. It might be worth putting a
bookmark on this page so that if you forget one or more of the steps you can
easily come back here for a quick reminder.
Ready to embark on your first Code Quest!? Excellent. Let’s go!
• To make sure we know how and where to write our code – in our
case, this will be the Visual Studio editor window.
• To make sure that we understand the correct code and syntax to
output a message to the screen.
• To make sure we know how to compile and run our code.
• To make sure that all the tools we need – development environ
ment, compiler, and so on – are installed and working properly.
DOI: 10.1201/9781003348481-3 27
28 A Gamer’s Introduction to Programming in C#
There’s nothing game-like about this example, but it’s still a great place to
start our coding adventure. If you’ve used other development environments
and programming languages before, and you want just a high-level
overview rather than all the details, the steps we’re going to follow for
our “Hello, World!” example are as follows:
FIGURE 3.1
For most of our new projects, we select the “Create a new project” option in Visual Studio.
FIGURE 3.2
It’s important to select the right type of application when we’re starting a new Visual Studio
project. In this book, we’ll be writing C# console applications.
30 A Gamer’s Introduction to Programming in C#
FIGURE 3.3
When you’re creating a new Visual Studio project, make sure to give it a descriptive name and
save it in a location where you can find it again later.
FIGURE 3.4
Your version of .Net might be newer than mine, but the important thing on this screen is to
check that box that says “Do not use top-level statements”.
Code Quest 1: Hello, World! 31
FIGURE 3.5
Here is our first Visual Studio C# console app project. It’s even got some code that is ready to
compile and run.
With those few steps done, we click the final “Create” button, and we
have our first Visual Studio project – and our first C# code too (Figure 3.5)!!
FIGURE 3.6
If all goes well, when you compile and run the code that was given to you when you created
your project, you’ll see a console window with the classic “Hello, World!” message.
everything went well, you can see a console window that says “Hello,
World!” along with a bunch of random-looking text below it. Even though
Visual Studio did the write part of write-compile-run for you, you’ve
officially taken your first courageous steps in this programming journey.
When you’re done admiring your work, feel free to close the console window
by clicking the “X” in the top-right corner. You can get it back (to admire your
work some more) by running the program again.
At this point, you might be wondering what you saw when you ran your
program. A console window is something we don’t really see anymore.
Operating systems like Microsoft Windows and Mac OS do a good job of
hiding old-school features like console windows because they favor visually
pleasing and user-friendly graphical windows instead. Console windows
are a throwback to a different time in programming and computing but
we’re going to see them for a while as we work through our first batch of
programming examples and concepts. Console programs are much simpler
in structure than MonoGame or other graphical user interface programs
(fancy windows), and the simpler structure or console programs makes it
easier for us to focus on the core programming ideas. After learning and
practicing the core programming elements, we’ll be able to move into
MonoGame (in the next book) and level up our programs! Don’t worry,
we’ll get there soon.
Code Quest 1: Hello, World! 33
Console.WriteLine("Hello, Aaron!");
Then, re-compile and re-run your program (“play” button, or F5, or Debug
→ Start Debugging). The output of your program should be different now,
here’s what mine looks like (Figure 3.7).
This is a very small change, but this time, we completed all three steps
ourselves – write-compile-run. Your coding adventure is now truly
underway!
FIGURE 3.7
What’s better than seeing “Hello, World!” for the first time? How about seeing “Hello, your
own name!” for the first time?
34 A Gamer’s Introduction to Programming in C#
Side Quests
Keep practicing: We haven’t learned much about C# yet, but we’ve learned
enough to practice creating new projects and the write-compile-run program
ming cycle. Try repeating the steps from Code Quest 1 a few times – create
more Visual Studio projects and change the “Hello, World!” text to whatever
you want! Remember to compile and run your program to make sure that it
works properly.
Make mistakes: This might sound weird, but try editing the code in one of
your projects to have on-purpose syntax errors! Here are some ideas to try:
Be sure to pay attention to the errors that Visual Studio reports – knowing
about different syntax errors and how they happen will help you solve
future problems more quickly.
4
Level 2: Adventure Begins
Level 1 Recap:
• Video games and other programs are software that run on physical
computer (and smartphone and console) hardware.
• As programmers, we write software or source code in a language
like C#. Then, we compile that code into machine code, and
run the software to make sure it works. We repeat these steps
often.
• C# has a syntax – or set of language rules – that need to be followed.
• Visual Studio is the development environment we’re going to use to
write our programs.
I am standing on a beach.
To the north is a palm tree. There is a parrot in the tree.
To the east and west, there is more beach.
To the south is the ocean.
What shall I do now? go north
You are standing near a palm tree. There is a parrot in the tree.
What shall I do now? chop down tree
I don’t know how to do that.
What shall I do now? shake tree
The parrot flies down from the tree and lands on your shoulder.
DOI: 10.1201/9781003348481-4 35
36 A Gamer’s Introduction to Programming in C#
Day 6.
The forecast for today is calling for sun and a temperature of 25 degrees C.
You currently have $12.25.
The cost of lemonade is 25 cents. How many cups will you make: 20
The cost of advertising signs is 50 cents. How many will you make: 10
These two examples are similar to the classic text games Pirate Adventure
and Lemonade Stand, but there were quite a few early PC console video
games that worked exactly this way. Even though they don’t really “look”
like modern video games, they show off two very important points. First,
games need to get information to the player – this is output. Output tells our
player the state of the game and lets them know if more information
is needed. Second, games need to get information from the player – this is
input, and it’s needed to change the game state (or move the game forward).
Even though the types of input and output are different, both of these
points are equally true for old-school, text-based console games, or modern,
graphically rich games. The console examples we write in this book will be
of the old-school, text-based kind. The simple nature of these examples will
allow us to focus on our programming fundamentals before we make the
leap to MonoGame and fancier graphics in the next book.
We’re going to start this level by writing our own code to display simple
messages on the console. Then, we’ll look at the different types of data
we can use in C# and revisit our output statements to see how we can
combine text with other data types. We’ll end this level by looking at some
of the structural pieces that are common in all of our games and programs.
Ready? Let’s write some code!!
namespace CodeQuest1
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
Despite all the structure – that we’ll learn more about at the end of this level –
the only line doing any real work here is:
Console.WriteLine("Hello, World!");
Maybe you already guessed the purpose of this statement from the
“WriteLine” part – write, or output a line of text. In general, we can think
of it this way:
We did this in Code Quest 1, but for some extra practice, create a new
console project and replace the “Hello, World!” with anything else. I‘ll
replace it with my full name like this:
Assuming I don’t make any syntax errors – like erasing a quotation mark or
round bracket or forgetting the semicolon – when I run the program, it will
display my name. Try it for yourself with your name, your pet’s name, your
favorite game character’s name, your favorite game title, or wherever inspires
you! Even though this exercise might seem simple, it’s a great way to practice
the write-compile-run steps that are part of all programming projects.
Now, let’s try adding a brand-new line of code instead of changing one that
was already there! You can do this in the same project you were using above
or create a new one – your choice. Try adding a new line of code below the
existing “Hello, World!” (or Hello, whatever) line, like this (don’t forget the
semicolons to tell C# where your new statement ends):
Console.WriteLine("Hello, World!");
Console.WriteLine("I like playing video games!");
38 A Gamer’s Introduction to Programming in C#
Since our output is getting a bit more complex, I’ll show what these two
lines will display:
Hello, World!
I like playing video games!
Maybe this output comes as no surprise, but let’s make sure we know
what’s happening – when we call Console.WriteLine() we print what’s
inside the round brackets (or parentheses, more formally) followed by a
new line character. We don’t “see” the new line character, but it’s there after
World! and after games! and because it’s there, we have two separate lines
of output. Here’s another example:
Console.WriteLine("Let’s");
Console.WriteLine("play");
Console.WriteLine("a game.");
Let’s
play
a game.
Console.Write("Let’s");
Console.Write("play");
Console.Write("a game.");
It can be hard to see at first because it’s very close to what we did before, but
these statements say Console.Write(), not Console.WriteLine(). If we
compile and run a program with this code, we would have the following
output:
Let’splaya game.
That output is a bit hard to read, and we can fix it up a bit by adding some
spaces after Let’s and play like this:
Console.Write("Let’s ");
Console.Write("play ");
Console.Write("a game.");
Level 2: Adventure Begins 39
Console.WriteLine(10);
Console.WriteLine(3.14159);
Console.WriteLine(-0.5f);
Console.WriteLine(true);
Console.WriteLine(’Y’);
Console.WriteLine("games");
The output for these statements might seem obvious – after all, C# simply
outputs what you ask it to. But something rather special is happening. Each
one of these statements is outputting a different built-in data type that we
can (and will) make use of in our games and other programs. What are
built-in data types? Let’s answer that question, and then we’ll take another
look at the statements above.
FIGURE 4.1
Built-in types are like toy model bricks – alone, they aren’t much. But, they can be combined to
create awesome and complex things.
these built-in, fundamental, or atomic data types are numbers and text (as
we’ll see below), and each type has a particular use in our programs.
With that in mind, let’s look at a few of C#’s built-in data types – some of
which we’ll use often and some we’ll use only in very special cases. I
recommend paying close attention to the frequently used ones, but a quick
read of the special case data types should be enough.
• int: int is short for integer and is used for whole numbers ranging
from roughly negative 2 billion to positive 2 billion. When we want
to work with numbers that have no decimal and are between ± 2
billion, int is the right choice. An integer is stored in 32 bits, or
4 bytes of memory.
• float: float is short for floating point number, which is a program-
ming term for decimal number. Floats can store large negative
decimal numbers and large positive decimal numbers between
±1.5 ×10−45 to ±3.4 ×1038. Float is our go-to for storing decimal
numbers. Like ints, floats are stored in 32 bits or 4 bytes of memory.
• bool: bool is short for boolean, and bools are quite different from ints
and floats. A bool stores a value that means true or false. We use
bools (or booleans) when we write code that needs to make a
decision. We call these decisions conditional logic, and we’ll get to
them in Level 7. Booleans are a bit unusual in that they are stored in
16 bits (2 bytes) of memory, even though they only store true or false
(which technically could be done in 1 bit – see Bonus Level 1).
• string: string is the type we use to store text. Strings are actually
made up of characters (or chars, see below), so the amount of
memory they use depends on the length of the string.
The four data types described above are the ones we use most of the time.
However, there are a few that can be used in special cases.
• char: char is short for character, and we use this type to store single
letters, numbers, punctuation, and a bunch of other printable and
non-printable stuff. Even though char can store single numbers, we
use it for printing or input and not for math – int and float are better
when we want to calculate something. A char is stored in 16 bits, or
2 bytes of memory. We could have put char in “frequently used”,
but in this book and when programming games in general, it’s
much more common that we’ll use strings instead.
• byte, long, and short: These are all whole number (no decimal) data
types. You choose which one to use based on your needs. If you
need to save on memory and only need to store numbers that fall
within a small range, using byte or short is the right choice. If you
need a really big range of numbers, you can use long, but it will cost
you more memory. The int data type strikes a balance between
memory usage and the range of numbers you can store, and that’s
why they are our default whole number data type.
• double and decimal: These are the other two floating point data types.
Double uses twice the memory that a float needs (hence the name
double) but is more precise (more decimal places), while decimal is
42 A Gamer’s Introduction to Programming in C#
four times the memory size of a float and provides even more
precision (even more decimal places)! Float is usually enough
precision for most of our decimal work, and at half the size of a
double (one-quarter the size of a decimal), is a good trade-off
between precision and memory requirements. There is one more
reason that we’ll use floats here instead of doubles – game engines
like Unity use floats as their default decimal data types.
You might be wondering why there are so many data types in C# (and in
many other languages). The technical answer has a lot to do with the
compiler, languages that came before C#, and how computers store data in
memory. A simpler explanation is that many data types give us program
mers more options to handle data in different ways. If we want to store
small integers and save memory (see Bonus Level 1 for more on bits and
bytes of memory), we can do that. If we want to be ultra-precise and
memory isn’t an issue, we can do that too. Don’t forget, there are only four
built-in data types that we’ll use consistently in our programs – int for
whole numbers, float for decimal numbers, bool for logic (Level 7), and
strings for text. Here are the key takeaways from our first look at built-in
data types:
Data Literals
For most people starting out in coding, literals are an unusual concept.
Literals are the data whose values we can see in our code, and they are
important for two reasons – it’s important to be able to identify the different
parts of our programs, and we use literals in almost every game or program
we write. Let’s try to illustrate this idea with a non-code example:
Store the result of 171.2 multiplied by 12. Divide that result by -0.19 and
subtract 10.
I don’t actually want you to do the math, but I do want to point out some
things that are happening in this “sentence”. First, we have some informa
tion that we know for sure, like the highlighted numbers:
Level 2: Adventure Begins 43
Store the result of 171.2 multiplied by 12. Divide that result by -0.19 and
subtract 10.
In this code, we have 4 literals – 171.2f, 12, −0.19f, and 10. Everything else
in the code is an operator (*, /, -, =, and ()) a variable (float tempResult,
float finalResult), or a syntax element (;) – all things we will explore in the
next couple of levels. Here are some more example literals for each of our
four commonly used built-in data types (and a special appearance by
double):
• int: 12, 10, 0, 1024, −369, 1000000, and −212458736712. int literals
have no decimal point, no commas, are positive or negative, and are
between −2147483648 and 2147483647.
• float: 12f, 10.168f, 0.0f, −3.69f, and −212458736712.9f. float literals
have f (or F) at the end, often have a decimal place, have no commas,
are positive or negative, and are between ±1.5 ×10−45 to ±3.4 ×1038.
• bool: true, false. bool literals can be only true or false – nothing else.
Bool literals have no double quotation marks around them.
• string: “apples”, “I like video games and have played 10 different
games this month”, “10”, “Y”, “true”. string literals have double
quotation marks around them. Even though some of these might
look like other data types, the double quotations mean they are
strings. Watch for this when you are first starting to code.
• double: 12.0, 10.168, 0.0, −3.69, −212458736712.9. I know what
you’re thinking – why am I showing you double literals when I
said that we would be using floats in our programs? It’s important
to recognize double literals because they are the default decimal
literal in C#. What this means is that if I write a decimal number in
my code and I don’t put f or F to label it a float literal, C# will treat it
like a double literal – and this will cause us problems when we try
to do certain operations or calculations. As a reminder, we’re using
44 A Gamer’s Introduction to Programming in C#
floats in our programs because game engines, like Unity, use floats
instead of doubles to save memory.
With our new understanding of literals, let’s revisit some code we’ve seen
before:
Console.WriteLine(10);
Console.WriteLine(3.14159);
Console.WriteLine(-0.5f);
Console.WriteLine(true);
Console.WriteLine(‘Y’);
Console.WriteLine("games");
When we last saw these statements, I said that something special was
happening – each one is outputting a different type of literal. Can you tell
which type of literal is being output for each statement? Here’s the answer
key for our little quiz:
Console.WriteLine(10); –
int, whole number, no decimal
Console.WriteLine(3.14159); –
double, decimal number, no f
Console.WriteLine(-0.5f); –
float, decimal number with f
Console.WriteLine(true); –
bool, true with no quotation marks
Console.WriteLine(‘Y’); –
char, single character with single
quotations
Console.WriteLine("games"); – string, anything in double quotation marks
Yes, maybe I was a bit sneaky by including double and char, but
did you get them all otherwise? If you didn’t, don’t worry you’re
going to see many more example literals throughout this book (and
beyond).
We’re going to take a closer look at the syntax and structure of
our console programs, but before we do, let’s practice coding some
output statements that use literals and a new operator called
concatenation.
This would display what we want to the screen … sort of. When we
output using statements like the ones above, we are outputting
unchangeable text. Every time we run the program, the output will
be the same. But, when we are writing games, we want to be able to
write the text “High Score:” followed by the actual high score that the
player earned. When another wave of enemies is about to enter the
battle, we want to know what wave number it is, not simply say “wave
#6” every time. The same goes for however many minutes the player
lasted and our hero’s stats. In short, we need a way to separate the text
that we print each time, from the data, information, or values that
change.
Unfortunately, we can’t simply put multiple things to output in the same
output statement like this:
Try coding that statement and reviewing the syntax error that Visual Studio
shows you. We could break our output up like this:
This will give us the output we want, but I’d rather do it in one line of code
instead of two. Some of the examples above would take 3, 4, or even 7
statements if we were to do it this way. A more efficient way to do this
would be to use a special operator like this:
to join strings and other data types into a single string, most often for
creating dynamic or changing output. These statements aren’t really flexible
because we’re still using literals for our score, wave number, survival time,
and hero stats. But, once we understand how variables work (Level 3),
using concatenation to build complex strings from multiple data types will
allow us to write code like this:
int waveNumber = 6;
Console.WriteLine("Enemy wave #" + waveNumber + " starting, now!");
Try coding the above examples before moving on – even the example that
uses variables, everything you need is there. Try changing the text and the
numbers; make the examples your own!
Before we move on to our next section, it’s worth repeating that the
concatenation operator is used to join strings with other strings and other
data types. If you try to use + without a string somewhere in
the expression, you’ll be doing regular math addition. Consider the
following:
Console.WriteLine("2" + "8");
Console.WriteLine(2 + 8);
Console.WriteLine("2" + 8);
namespace CodeQuest1
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
From Code Quest 1, we know this program simply prints “Hello, World!”
to the console window. We can see Console.WriteLine(“Hello, World!”);
and that’s the one statement that does something really cool and visible in
this program. But, what are all the other lines doing? Why is there so
much structure for such a simple program? And, what are all those curly
braces { } for? It’s important to remember that all programming languages
have a syntax that needs to be followed. Even simple programs need to be
set up in a specific way. As you practice and write more programs, these
“structural” pieces will fade into the background. In fact, the most
important thing to know for most of our console programs is that the
code we write will go between the curly braces that start on line 6 and end
on line 8. With that in mind, let’s take a very quick look at each of the
parts in this simple program, so that you feel a bit more comfortable with
this code.
namespace CodeQuest1
Namespaces are a way of saying “what’s in here belongs to this project” – in
this case the project CodeQuest1. It’s most useful in larger projects where you
might have more than one program or class (big projects often have many
parts that work together) and namespaces help keep all the parts organized.
The word namespace is a C# keyword – meaning it’s built into the language. In
this example, the specific namespace is CodeQuest1, and unlike the keyword
48 A Gamer’s Introduction to Programming in C#
“void” is another keyword and means this section of our program – called a
method – will not “return” any values. We’ll learn more about methods in
Level 10.
() are parentheses and they show where any arguments – data passed into
Main – will appear.
Level 2: Adventure Begins 49
string[] args are the specific arguments for Main – they are a way for use to
pass information into the program (if we need to) when it runs. We won’t
be using command line arguments in our programs, but this line will always
be there.
Curly Braces { }
Curly braces organize our code into “blocks” or sections. For example, the
curly braces on lines 2 and 10 create a block of code that says the whole
program is part of namespace CodeQuest1. The pair on lines 4 and 9 are a
block for the class Program, and the final pair on lines 6 and 8 are for Main –
these are the curly braces we are most interested in for now. Here are a few
things to keep in mind when it comes to curly braces:
mentioned clicking the checkbox that says “Do not use top-level state
ments” on the last step of creating your projects in Visual Studio. If you
leave that checkbox empty, the code in your Program file will look like
this:
Where are all the C# keywords, parentheses, square brackets, and curly
braces? In short, they are hidden in a way that’s designed to make
it easier to jump right in and do some programming. We won’t be
using this feature for our examples – instead, we’re going to immerse
ourselves in all the wonderful structure C# has to offer. If you
accidentally forget and create a project with this feature enabled, you
have a few options:
• Keep going! The code you write will look a bit different from my
examples, but most of it should work, at least for the first few
chapters.
• Delete the project and create it again with the checkbox enabled.
• Use the “Convert to ‘Program.Main’ style program” option that
shows up if you move your mouse to the line numbers to the left of
your code in Visual Studio. This will give you back most of the
structure, but not the namespace line.
Now that we know a bit more about how console programs are structured,
and what to do if we accidentally create a project with top-level statements,
let’s push forward to Level 3 and learn how to store data in variables, collect
user input, and write comments to help us keep track of what our games
and programs are doing.
Side Quests
Want to Know More?
Show me all the built-ins: Even though we’ve looked at the most common
built-in data types in this Level, there are more. Some of them are
pretty handy, some of them are for very specific cases and needs.
Find a list or description of all C# data types and their corresponding
literals.
A different way to output: There are actually two common ways to create
output strings in C#. I’ve shown you concatenation here because, in my
Level 2: Adventure Begins 51
Console.WriteLine("Hero has " + 51 + " health, " + 19 + " stamina, and " + 3
+ " mana points. Next move?");
Another way to do this uses a syntax called composite formatting like this:
Console.WriteLine("Hero has {0} health, {1} stamina, and {2} mana points
left. Next move?", 51, 19, 3);
Console.WriteLine($"Hero has {51} health, {19} stamina, and {3} mana points
left. Next move?");
Want to Do More?
What’s your data story: Write a program similar to the examples in
this level. Make the output something that is either about you, or
something that is interesting to you. Try to write at least five output
statements and use a combination of Console.Write() and Console.
WriteLine() statements.
52 A Gamer’s Introduction to Programming in C#
• review Level 2
• complete Code Quest 1
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
Games are full of great quotes. Sometimes they are part of meaningful dialog,
and other times they come from important moments in the story. They can
even come as sarcastic, thoughtful, or potent one-liners. Wherever they come
from, quotes help us remember and relive great game moments. In this Code
Quest, you’ll practice using Console.Write() and Console.WriteLine() to
output some popular, and perhaps familiar, game quotes to the console.
Here is my output – see how closely you can duplicate it! (Figure 5.1).
FIGURE 5.1
Can you write a program that duplicates my console output?
DOI: 10.1201/9781003348481-5 53
54 A Gamer’s Introduction to Programming in C#
Side Quests
A small change: Starting with the code from the Main Quest, change it so that
the output looks like this instead (Figure 5.2).
FIGURE 5.2
Here is some new output for you to duplicate.
Redo the Main Quest, but this time, use concatenation and some different
literals in your output. To help with this side quest, my non-string literals
have been bolded.
Make it your own: Most of the game quotes in this Code Quest come from
games that I’ve played or am familiar with. Redo the Main Quest or any of
the Side Quests using your own favorite game quotes.
6
Level 3: A Variable Inventory of Items
Level 2 recap:
DOI: 10.1201/9781003348481-6 55
56 A Gamer’s Introduction to Programming in C#
time something changes in the game, some variables change value. Variables
are also one half of what we need to make games interactive. The other half is
user input, and that’s the topic of Level 4.
If things aren’t clear from this quick introduction, worry-not-brave coding
adventurers – Level 3 is here to help us make sense of how our games use
variables!
A word of caution before we charge forward – variables are a truly
fundamental programming concept – almost everything we do going
forward depends on a solid understanding of variables. I recommend
“bookmarking” this level and coming back to it if, later on, you find
yourself needing a refresher on variables and how to use them.
Now, without further delay … 3, 2, 1. CHARGE!!!
In each of these lines, we are outputting text literals and number literals
to the console. That’s cool, but the way this code is written, it produces
the same output each time it’s run. If you’re skeptical, try coding these
lines in a new project and running the program at least five times. It will
produce the same output every single time you run it. These statements
are hard-coded to use literals – hard-coded means there is no input from
the user and the data is part of the code itself with no way to change it
apart from editing and re-compiling it. When we play games or run other
programs, we don’t edit and re-compile the code to change values.
Instead, programmers – like us – use a powerful combo of variables and
user input (Level 4) to make the game (or program) work with a range of
possible values.
Technically speaking, variables are named locations in RAM memory that
store data or values, but I like to think of them like labeled storage boxes
(Figure 6.1).
Level 3: A Variable Inventory of Items 57
FIGURE 6.1
Variables are like labeled storage boxes in memory. They have a name (that’s the label), and
they can only store a specific type of thing.
The fact that our labeled storage boxes exist in RAM memory is something
we don’t really have to worry about – C# will take care of that. What is more
important for us are the following facts about variables. They each:
• can store data values that we need for our game to work properly
• have a name that makes it easier for us to identify and access them
• can have their value changed – this is where the word variable
comes from
• can be accessed to see what is being stored
• can only store a specific data type
Let’s wrap these variable facts into an analogy that involves you flying to
Mars (stay with me on this).
In your Mars spacecraft, you have several fuel storage tanks. Each one has
a name, such as FuelTank1, which makes it easier for you to know which
fuel tank you’re currently working with or investigating. The fuel tank can
store something specific – fuel. The amount of fuel in FuelTank1 goes down
as you travel to Mars. But if there are any Mars-fuel refueling stations along
58 A Gamer’s Introduction to Programming in C#
the way, the amount of fuel in FuelTank1 might go up. In other words, the
fuel in FuelTank1 changes – it’s variable. At any point, we can check the
amount of fuel in FuelTank1. Oh, and in case this wasn’t clear, the only
thing FuelTank1 can properly and safely store is … fuel.
Here’s another example that is closer to what we’ll do with variables in
our game code.
To practice your programming skills, you are creating your own version
of PacMan (classic!). You have a variable named playerScore that keeps
track of the player’s current score. You store 0 (zero) as the starting value in
the playerScore variable. As the player moves around the screen and eats
dots and vulnerable ghosts, the player’s score goes up (so the value in
playerScore changes). The player’s up-to-date score is visible on the game
screen at all times. The playerScore variable is a whole number (because we
don’t need a decimal in our score).
Now that we have some idea of what variables are and what they can be used
for, we need to learn three variable-related programming tasks – creating a
variable, storing a value in a variable, and checking what value is currently in
the variable. We call these three tasks declaring, assigning, and accessing.
Declaring Variables
To create a variable, we must declare it – and to do that we need to tell C#
what type the variable will be (what kind of value it will store) and give it a
name (a programmer-defined label that will we use to access the variable).
In general, a declaration statement looks like this:
type variableName;
Here are four separate example declaration statements that use each of our
main built-in data types:
int playerScore;
float scoreModifier;
string playerName;
bool playerHasHighScore;
Try putting the four declarations in a project of your own. Remember to put
them inside of Main’s curly braces. You can compile and run this code, but
it won’t output anything. As long as it compiles and runs without errors,
you’ve succeeded with this mini-practice quest.
Each of these four lines is creating a variable to store some data for our
(hypothetical) game, and each has both a type and a name that match what
we plan to store:
Level 3: A Variable Inventory of Items 59
One of the reasons we need to specify the type of our variable is so that C#
knows how much memory to set aside. The actual amount of memory
space being used by each variable doesn’t matter too much – modern
devices have lots of RAM – but look back to Level 2 if you want a
reminder of how much memory our built-in data types need. The other
thing that is happening in our four lines of code is that our variables are
being given names. The name we give to a variable (sometimes called a
variable identifier) is up to the programmer and can be almost anything.
But there are some rules and conventions to variable naming that we’ll
discuss shortly. Also, like most statements in C#, variable declarations end
with a semicolon.
When you get more comfortable with declaring variables, you might want
to save a bit of space in your code by declaring multiple variables – of the
same type – on the same line, like this:
Here is the same code, with each of the declarations on a separate line:
int playerScore;
int playerHealth;
int gameHighScore;
float scoreModifier;
string playerFirstName;
string playerLastName;
bool playerHasHighScore;
bool gameOver;
variableName = someValue;
One of these pieces we’ve looked at before – the variable name. We set this
during the variable declaration. The = is new, and it’s known as the assignment
operator. The assignment operator basically says, “take the value on the right
and store it in the variable (memory storage box) named on the left” (Figure 6.2).
FIGURE 6.2
When we assign variables, we store a value in our labeled memory “box”.
Level 3: A Variable Inventory of Items 61
int playerScore;
float scoreModifier;
string playerName;
bool playerHasHighScore;
playerScore = 50;
scoreModifier = 2.5f;
playerName = "Aaron";
playerHasHighScore = false;
This last point is REALLY important and worth repeating – the right side of
an assignment statement must be of an appropriate type for the left side
variable (or box). Think of it this way – you can’t store a refrigerator in a
shoe box. In all of the examples below, I will be very careful to make sure
that the right-hand side is compatible with the left-hand variable. There are
some ways to bend these rules a bit, but we won’t look at them until Level 6
(or Bonus Level 2).
Here is another example with a single variable that stores the health of
our player character. We start by declaring it:
int playerHealth;
62 A Gamer’s Introduction to Programming in C#
int playerHealth;
playerHealth= 100;
Easy-peasy, right? Now, with a third statement, I can double check that it
worked properly by accessing (or reading) the value that was stored in the
playerHealth variable:
int playerHealth;
playerHealth= 100;
Console.WriteLine(playerHealth);
The final line of this example will print 100 – the value stored inside the
variable. Whenever C# comes across a variable name – and assuming
the variable has been properly declared and assigned a value – C# will
replace the variable name with the value stored inside. In other words,
the final statement in this example says: “output to the console the value
stored inside variable playerHealth, whatever it might be”. Since the
value stored in playerHealth is 100, the output statement is equiva
lent to:
Console.WriteLine(100);
Try coding this example for yourself. It might seem trivial, but with this
small amount of code you’re practicing three of the most common things
we do in programming – declaring variables, assigning values to
variables, and accessing the values stored inside variables. These steps
are so important that we’re going to do some more examples. Let’s try
working with a float:
float distanceToTarget;
distanceToTarget = 10.9f;
Console.WriteLine("Distance to target: " + distanceToTarget);
In each of the above examples, we’ve assigned a literal to our variables. While
these examples are good to show how assignment works in the simplest
cases, we can and will get fancier. Here’s a pretty common game scenario.
Our player earns a score through normal activities in the game. But they
might also earn an extra bonus by clearing out all the enemies or finishing the
game with a speedy time. Here’s how we might show off this idea in code:
int playerScore;
int bonusEarned;
int finalScore;
playerScore = 100;
bonusEarned = 15;
finalScore = playerScore + bonusEarned;
• access the values stored in playerScore and access the value stored
in bonusEarned
• add these two values together
• store the added values in finalScore
There are two new ideas for us in this one line of code. First, we can, and
very often do, use variables on the right-hand side of an assignment – the
values don’t need to be literals. Remember, when C# sees a variable name in
your code, as long as it has been previously declared and assigned a value,
that value will be substituted for the variable name. That means that our
new statement is essentially:
Reassigning Variables
If literals are values or data that we can literally see in our code, what are
variables? We know they are named storage containers in memory – but
why not call them memory boxes or data containers? As I’ve mentioned
before, they are called variables because they are variable (or changeable).
Once you’ve declared a variable, you can change its value any time you
want or need to! This is a good thing in programming because it means
we can use a single variable to store a player’s score, and it will be
updated as they move through a level. Changing the value in an existing
variable is called reassigning. With this new information, we start to see
games (and other programs) as a collection of constantly reassigning
(changing, updating) variables. Keep this idea in mind as we journey
onward.
The good news is that we don’t need to learn anything new to reassign a
variable. Take a look at the following code:
int numberOfEnemies;
numberOfEnemies = 10;
Console.WriteLine("There are " + numberOfEnemies + " enemies");
numberOfEnemies = 100;
Console.WriteLine("Oops, there are now " + numberOfEnemies + " enemies");
There really isn’t a practical reason that we would declare a variable, assign it a
value, and then immediately change it (the bold statement), but this code does
show us that it’s possible – variables can be changed by simply writing a new
assignment statement. This code will compile, and when it runs, it will output
Level 3: A Variable Inventory of Items 65
that there are 10 enemies, then it will output that there are now 100 enemies, all
using only one variable – numberOfEnemies.
It doesn’t matter what type your variables are, you can reassign them
whenever you want, as long as you remember to use the right type of
values:
string playerName;
float playerID;
playerName = "Aaron";
playerID = 1976.0211f;
playerName = "GamerAaron";
playerID = 2023.0101f;
Console.WriteLine("My new name: " + playerName + ", and my new ID: " +
playerID);
Accessing a variable that doesn’t exist yet: Trying to access a variable that
doesn’t exist is a surprisingly common error.
int playerScore;
playerScore = 100;
Console.WriteLine("Player score is: " + finalScore);
int playerScore;
playerScore = 100;
Console.WriteLine("Player score is: " + playerScore);
int playerScore;
playerScore = "Aaron";
string playerName;
playerName = 100;
Both of these assignments cause the same error – “Cannot implicitly convert
type X to Y”. I’ve used X and Y here because this error will always put the
variable type (int, float, string, bool, etc.) in X and the value type (int, float,
string, bool, etc.) in Y – this way you know what types are causing the
conflict. The fix this error, make sure that the values you want to assign are
appropriate for the variables you’re storing them in:
int playerScore;
playerScore = 100;
string playerName;
playerName = "Aaron";
int numberOfEnemies;
numberOfEnemies = 10;
Console.WriteLine("There are " + numberOfEnemies + " enemies.");
int numberOfEnemies = 100;
Console.WriteLine("Oops, there are now " + numberOfEnemies + " enemies.");
This one is pretty subtle – see if you can find the issue in the code above
before reading on.
The error is in the statement that reassigns numberOfEnemies to 100. It’s
very common for beginners to accidentally “redeclare” a variable when they
actually just want to reassign it. By putting the type in front of an assignment
statement, you are trying to recreate the variable, and this causes the error “A
local variable named ‘variable’ is already defined in this scope” (scope is
discussed more in Bonus Level 6). In other words, C# thinks you want
another variable with the same name in your program – which you can’t do.
Once you have declared a variable by giving it a type and a name, it will be
there for you to assign and reassign. The fix for this error is to remove the type
from your assignment, or reassignment statements:
int numberOfEnemies;
numberOfEnemies = 10;
Console.WriteLine("There are " + numberOfEnemies + " enemies.");
numberOfEnemies = 100;
Console.WriteLine("Oops, there are now " + numberOfEnemies + " enemies.");
string playerName;
playerName = "Aaron";
There are two considerations – one minor, one major – with these lines of
code. First, we have to repeat the variable name playerName in both lines of
code. Not a big deal, but if I can skip that and make my code more compact,
68 A Gamer’s Introduction to Programming in C#
I’m happy to do so. Second, and more importantly, after the first line, we
have an empty variable. If, for whatever reason, we forget to assign it a
value and then try to access it, the program will fail to compile (see
Accessing a variable before assigning it). There is a way to address both issues
at once – an initialization statement:
int playerScore;
float scoreModifier;
string playerName;
bool playerHasHighScore;
playerScore = 50;
scoreModifier = 2.5f;
playerName = "Aaron";
playerHasHighScore = false;
This has the same effect, but takes up half the code space and all of our
variables are guaranteed to have values if we try to access them immedi
ately after the initializations. Try coding this second example and then try
accessing the variables using output statements immediately after the
initializations.
I strongly recommend using initialization statements over separate
declarations and assignments, whenever you can. Why then, did we wait
so long to introduce them? Two reasons – first, at this point, we know what
declaration and assignment mean so combining them into a single
statement should make more sense, and second, there will be times where
it is actually more appropriate to declare a variable and assign it at a later
time. Initialization statements are often the best idea, but not always.
Knowing that declarations and assignments can be separate is still very
important.
Level 3: A Variable Inventory of Items 69
We have one more variable topic to cover before we get to the promise of this
level’s introduction – interactive programs. It’s a short topic, so let’s get to it.
Rules
• Variable names must start with a letter or an underscore. This one
can be a bit frustrating when you want to start a variable name with
a number, like 5thEnemy or 2DBackground, but it’s just not allowed
in C#. Instead, use enemy5 or background2D.
• After the first letter or underscore, I recommend sticking mostly to
letters, numbers, or underscores unless you have a really good
reason to use something else. Some unusual characters can be used,
but others will cause syntax errors.
• You cannot use a space in a variable name. A space actually splits
your variable name into two variable names and will confuse the
compiler. For example, playerScore is one variable, while player
Score will be treated like two variables.
Aside from the rules that we must follow, naming conventions are guide
lines, best practices, or the “way things are normally done”. They don’t
cause compiler errors, and they aren’t meant to stifle your variable-naming
creativity or make things harder for you as a coder. They are meant to make
variable names more consistent between programs, projects, and other
programmers (often we work with teammates on large projects) so that the
code we write is easier to understand. Consider adopting these C# naming
conventions.
Conventions
• Variable names should be descriptive. It’s easy to get lazy and start
using single letters as variable names – like x, y, and z. If you store the
player score in x, the number of enemies in y, and the player health in
z, how long is it going to be before those variable names cause you
or your coding teammates confusion. Instead, use descriptive
names like score, enemies, or health – or better yet, playerScore,
70 A Gamer’s Introduction to Programming in C#
By now, I hope you’re feeling good about variables. We know how to declare,
assign, and access them. We know how to make our code a bit more compact
and avoid some errors using initializations. We also know how to properly
name our variables and why it matters. With all this knowledge, we have
reached a truly awesome milestone in our adventure. In Level 4, we’re going
to write code that is interactive – and variables are going to lead the way.
Side Quests
Want to Know More?
The typeless var variable: This is another variable type known as var. It’s a bit
tricky because it’s not a type like int, float, string, or bool. Instead, var lets
the C# compiler guess at the type the variable should be based on what
you’re trying to store in it:
It seems like this would be the easiest way to declare all our variables, but
there are some very good reasons why we don’t do this. There are also some
solid reasons when it’s okay to use var instead of our other built-in types.
Do a bit of research to understand when you might and might not want to
try using var.
But I want to store a fridge in a shoe box: The data we store in our variables
must be of a compatible type with the variable itself. Most of the time, we
store ints in int variables, floats in float variables, strings in string variables,
and so on. But, occasionally we need to squeeze some data of one kind into
a variable of another kind. We’ll talk about this a bit in Level 6, but if you’re
Level 3: A Variable Inventory of Items 71
Want to Do More?
Make mistakes: Programmers make mistakes all the time when we declare,
initialize, and assign variables. Make some mistakes of your own so that,
with practice, you’ll be able to identify and fix them quickly. Try assigning
some variables before you declare them, accessing some declared variables
without assigning them, and mixing up your assignment variable (left-hand
side) and value (right-hand side) types – see which ones are compatible and
which ones are not!
Code more, change much: Code the examples in this level for yourself. Change
the numbers and text so that they are a better reflection of your gaming and
programming interests.
7
Code Quest 3: Character Builder
(Part 1)
• review Level 3
• complete Code Quest 2 (optional)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
Many role-playing video games, and also many tabletop role-playing games,
start by having the player “build” a character using a character sheet. We’re
going to write a program that does something similar – but we’ll keep it simple
by not having too many character traits. In this code quest, we’re going to
practice declaring, assigning, and accessing variables that could be used in a
character game character sheet. In Code Quest 4, we’ll continue this program
by adding some user input to make our character sheet program interactive!!
72 DOI: 10.1201/9781003348481-7
Code Quest 3: Character Builder (Part 1) 73
FIGURE 7.1
Try duplicating my output before getting more creative with your own version of this
program. Don’t forget the variables and literals in your code!
Side Quests
Less code, same output: Complete this quest using initialization statements
instead of separate declarations and assignment statements.
Go big or go home: My example has only four variables and five lines of
output. Add more variables and more output statements to make your
character sheet more interesting and detailed.
Make it your own: Do you find my output uninteresting or uninspiring? Do
you prefer pirate games? Do you prefer space games? Do you prefer space-
pirate games? Change the text and the variables to make this quest more fun
for you!!
8
Level 4: Some Friendly Input
Level 3 Recap:
• Each time you play through an action role-playing game, you pick a
different character type with different abilities. This changes how
the gameplay and storyline unfold.
• The first time you play a mobile platformer, you earn a score of one
star after finishing a level. The next time, by knowing what to
expect and by making different decisions, you get a score of three
stars (way to go)!
• There is a clay pot on the ground. You break it with your sword.
Ten gold coins fall out. You put them in your backpack. Later on,
74 DOI: 10.1201/9781003348481-8
Level 4: Some Friendly Input 75
Even though each of these examples seems different, there are a pair of
programming ideas that make them all work interactively. The first is a way
to “store” data or information. As we know from Level 3, games are made
up of all kinds of values that are constantly changing – player and enemy
positions, current score, pick-up items, dialog prompts, and much more.
This data is stored in variables. The second important concept we need for
interactivity is a way to get information from the player – what character
type they want to be, how do they want to move across the platforms, do
they choose to break the clay pot to collect the gold to buy the wooden flute,
do they take the shortcut or the long way around, and finally, which seeds
do they plant and then harvest the next day. We call this player-generated
information input, and our games need it to move the action forward.
A big part of the joy of video games is our ability to affect the outcome. To
make this joy happen, we, as programmers, need variables (check! thanks to
Level 3) and user input. Level 4 is going to start out simple, with console-
based keyboard input, but if you decide to continue your journey, in the
next book we’ll look at other input types like mouse input and game
controllers. We’re also going to start by focusing on the steps involved in
collecting input, instead of what we can actually do once we’ve collected it –
that will come over the next few levels.
Console.WriteLine();
Now, we want to read values from the console. Lucky for us, the C#
statement we’re going to use for input is very similar to the one we use for
output:
Console.ReadLine();
76 A Gamer’s Introduction to Programming in C#
It’s very similar in structure (just change the word Write to Read), but
Console.ReadLine() has a very different job – it collects input from the
keyboard that you can, and really should, store in a variable. This means
that a complete input statement looks more like this:
This initialization statement (Level 3) says – read input from the keyboard
and store it in a new string variable called playerInput. If you prefer a
separate declaration and assignment, you could use this code instead (but
I’ll most often use the more compact form above):
string playerInput;
playerInput = Console.ReadLine();
These two statements are really powerful. In the first, we collect a string from
the player and store it in the variable playerInput. In the second statement, we
access and output the contents of playerInput. We don’t know what the
player is going to input, but we output it no matter what it is. With these two
simple statements, we have our first truly interactive (but still simple)
program. Try coding this example for yourself and running it a few times
with different inputs – single words, sentences, gibberish with unusual
characters from the keyboard, etc. But be aware – when you run it, the
program will look like it’s just “stopped”. This is because Console.ReadLine()
pauses the program and waits for the input. Once the player hits enter (that’s
the signal that the input is complete), the program will continue, and the
contents of our variable will be shown on the console.
and trying the previous example, having our game (or program) simply
stop and wait is a bit awkward. We can make user input clearer and more
friendly by breaking it into three steps:
1. Prompt the player by telling them what kind of input you are
looking for. I recommend using Console.Write(); for your prompt,
instead of Console.WriteLine(); – using Write means the user input
will be on the same line as the prompt. Try them both (Write and
WriteLine) to see the difference.
2. Collect the input using a Console.ReadLine() statement.
3. Store the input in an appropriately named and typed variable.
Steps 2 and 3 are typically done in the same statement (variable initializa-
tion or assignment), but it’s worth remembering that two things are actually
happening. Here is a simple program that collects input using the steps
listed above and then outputs the input that was collected:
Can you see all three steps in this example? The player was prompted (first
statement), input was collected and stored in a variable (second statement),
and as a bonus, the stored input was output to the console (third statement).
Try writing this example for yourself and be sure to run it a few times.
When I run it, it looks something like this (my input is bold):
If you want to collect input more than once, simply repeat the three steps
multiple times:
Here, we’ve collected input twice in order to get the player’s name and
profession. As always, try coding this example for yourself and running it a
few times to get a feel for what the code is actually doing. Here is some sample
output to compare with (again, my input is bolded to make it stand out):
If I want to convert that string to an integer, I can call the int.Parse() method
like this:
Until we cover objects and methods (Level 10) in more detail, think of int.Parse
() as a helpful C# tool, like Console.WriteLine() and Console.ReadLine(), that
allows us to convert from one kind of thing to another. In this case, int.Parse()
lets us convert the string “12345” to an int 12345. We put the thing we want to
convert, in this case textNumber, into the round brackets ( ), and we store the
Level 4: Some Friendly Input 79
result in the variable intNumber. If all goes well, intNumber will have the
number 12345 that we can then do math (Level 4) and other integer-y
operations on. We can do the same type of operation with floats and even
booleans using:
There are Parse methods for all of the built-in data types (except for string,
which is converted using concatenation or a special ToString() method).
We can convert strings to number (and bool) types – but what does this
have to do with input? Since Console.ReadLine() only reads strings from
our console keyboard, if we want to read in other types, we need to “parse”
them. Suppose we want to ask our player how old they are, and we want to
treat their age as a number, not text. We need to make a slight change in our
prompt-collect-store steps:
int playerAge;
playerAge = int.Parse(playerAgeText);
To the player, the code in these two examples would run the same way. The
only difference between the two is that the second example more clearly
shows everything that is happening, step-by-step. Here is one last example
with a few differences, so pay close attention:
To be clear, there is nothing new in this code – you’ve seen it all in previous
examples. But, sometimes, as our programs get more complex, we can get
overwhelmed – the whole program seems somehow greater than the sum of
its statements. How can you work through this program? Start by coding
and running it for yourself. Before this level ends, I’ll show you another way
to help fight the growing complexity of our programs – using comments.
In each of these statements, we can clearly see the values in the code – because
100, “Aaron”, and 10.5f are all literals. This code is easy to understand but not
very flexible because the program always runs the same way. But what
happens when we write code that looks like this:
There are a few considerations that do matter. The value we are trying to
store has to be the right type, or the program will crash. The value might
also need to be within a certain range or the code we write won’t work
properly. Part of what we’re going to learn going forward is how to make
sure the input we get is appropriate for our code, but for now, simply
recognizing the “don’t know, don’t care” nature of variables is helpful.
Here is another example with a sneak peek at some code that we’ll cover
in Level 6:
As a mini practice quest, code these statements for yourself. What is the
output of this program? Given the title of this section, you might want to
jump right to “don’t know, don’t care”, but the answer is a bit more
interesting than that. The output will be a random number between 2 and 12
because two of the variables each have stored random numbers between
1 and 6, and the third variable stores the sum (addition) of the other two.
Essentially, this is a program that simulates two six-sided dice that are
added together! What are the specific numbers that will be stored in the
three variables (pssst, now is the time … .) – don’t know, don’t care! As long
as they are integers and can be added and output to the console, the
program will run perfectly!!!
Again, it sounds like we’re being flippant about our coding – we don’t
know what it’s doing and we don’t really care – but actually what we’re
doing is realizing that a lot of coding is working with players, users, and
data where we can’t really anticipate the exact values. So, we code in such a
way that the program will work even if we will never actually know the
specifics of what it will compute and output when it is run. Variables are the
key to this power – and games are made up of variables that are constantly
changing based on user input (and sometimes a bit of good old-fashioned
random number generation).
our code is getting (and will continue to get) more complex – both in terms
of what it can do and in terms of the amount we need to write to get things
done. Here’s an example from earlier in this level:
We went through this code before, but seeing it again now, how comfort-
able do you feel about describing what it does? It’s ok to say “I feel great
and understand it all”, and it’s equally ok to say “I don’t feel great and there
are things that I’m not quite sure of, yet”. As it turns out, we have ways of
making code easier to read, and understand. One way is to use descriptive
variable names, which we’ve discussed in Level 3, and you can see in the
example above. Another way is to add comments to our code. Take a look at
the updated example below:
/*
This program is very interactive. It collects three pieces of data
from the user, using input, and then displays that data to the console.
This program was written by: Aaron Langille.
*/
• Line comments: These start with // and extend only to the end of the
line they are created on. In the example above you can see that some
comments are the only things on the line – that is, the whole line is
just a comment. In other cases, the line comments are at the end of
the statement (after the ;). When the compiler reads a line, it reads
up to the // and determines that it, and everything after, is part of a
comment. Line comments are great for short notes that relate to
specific lines of code.
• Block comments: These start with /* and end with */. They have as
many lines as you choose to place between the start and end
symbols. You can see in the example above that my program has
four comment lines (Visual Studio will put the leading *’s in the
block comment for you). Block comments save us from having to
use // over and over again – they are more convenient when we
have lots to say. In the example above, there is only a block
comment at the top, but block comments can appear anywhere in
the code, as long as the proper start and finish symbols are in place.
Level 4: Some Friendly Input 85
When you’re first starting out, it might not be clear how and when to write
comments in your code. Some people write very short comments infre-
quently, while some write long, wordy comments often. Commenting style
is a somewhat personal matter (unless your team, employer, or instructor
gives you specific instructions), but here are some general tips for writing
good comments:
• put a comment anywhere that you think your code needs some
explanation
• use line comments for quick notes
• use block comments for longer, wordier explanations
• comment while you code, don’t leave it until the end
• don’t under-comment – make sure future-you, or your teammates
can understand what you’re doing in your code
• don’t over-comment – too many comments can make it harder to
read the actual code of your program, not every statement needs a
comment, and not every program needs your “life’s story”
Okay – you made it this far, so you must want to know why no one ever
considers commenting their favorite part of coding. Here are the main
reasons:
In a way, I hope the one takeaway from this section is that commenting
your code is like eating broccoli (or something else you don’t really enjoy) –
86 A Gamer’s Introduction to Programming in C#
not the most fun part, but definitely important and healthy. Do yourself a
big favor, write comments while you code and keep your comments up to
date. Future-you, and your coding teammates, will thank you!!
Side Quests
Want to Know More?
More data types, more parsing: In this level, we looked at how to collect and parse
the common built-in types, but C# has more data types that we can use. Look
up parsing examples for some of the less-common (but still cool) data types.
Stop the crashing: In this level, there’s no real solution to prevent your
programs from crashing if they try to parse bad input from the player or user.
You can also look up C#’s try/catch blocks to learn how to exit the program
gracefully if the user enters a bad value, or you can read Bonus Level 3 to see
examples of the TryParse method. These are interesting topics, but they’re
going to be less important if or when you move into MonoGame.
Professional commenting: There is a third type of C# comment called
Documentation Comments. They’re a bit more complicated than our line
and block comments and are most often used for professional projects with
large programming teams. Find some examples of how to use documenta-
tion comments in your code.
Want to Do More?
Crash the input examples: Code the examples from the parsing section
(“Reading in Numbers and Other Fun Things”) and try entering some
ridiculous input for any of the parsed values. How many different ways can
you crash your program!? Don’t worry, this still counts as programming
practice.
Our first code reward is a player introduction program, and it shows off
several programming concepts, including data types, variables, output, and
input. It’s similar to some of the examples from this level, but it’s a good
exercise to try for extra practice.
Imagine you’ve been contacted by a game tournament organizer. They
want a simple program that collects information about a player partici-
pating in the tournament. Lucky for us, they want to collect information that
we can use to practice our input skills!
Here are the steps to create this program:
Try this code reward for yourself. If you get stuck, take a look at my sample
solution for a hint. Even though it looks like a lot of code, pay attention to
how much of it is comments, and how many different inputs we are
collecting and outputting.
88 A Gamer’s Introduction to Programming in C#
/*
This is a program that uses variables, output, and input. The program acts
like a simple player info collection and output tool that could be used at a
(very small and very simple) game tournament.
/*
When we collect input, particularly numbers, the program can crash if the user
enters input that can’t be properly parsed. Assume the user enters meaningful
and parse-able input. Bonus Level 3 has more information.
*/
• review Level 4
• complete Code Quest 3 (recommended)
• review or try Code Reward 4 (recommended)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
90 DOI: 10.1201/9781003348481-9
Code Quest 4: Character Builder (Part 2) 91
FIGURE 9.1
This is the output from my version of this program, but it also has my input. Be sure to note
which text in this screenshot is prompts for the user, which is input from the user (me, in this
case), and which text is the final output.
92 A Gamer’s Introduction to Programming in C#
FIGURE 9.2
This is just another execution of the same program, but with different input. Notice how the
final output changes.
Side Quests
Less code, same output: Complete this quest using initialization statements
instead of separate declarations and assignment statements. Remember, you
can initialize your variables while collecting user input.
Go big or go home: My example has only four variables and five lines of
output. Add more variables and more output statements to make your
character sheet more interesting and detailed. Be careful here – each
variable you add will add a prompt, input, and output statement. Your
code will grow quickly as you add character traits.
Make it your own: Do you find my output uninteresting or uninspiring? Do
you prefer fishing games? Do you prefer farming games? Do you prefer
fish-farming games? Change the text and the variables to make this quest
more fun for you!!
10
Cutscene 2: Recipes for Success
DOI: 10.1201/9781003348481-10 93
94 A Gamer’s Introduction to Programming in C#
• flour
• salt
• sugar
• baking powder
• cocoa
• water (or milk if you prefer)
FIGURE 10.1
Algorithms are like recipes in our code. They have ingredients, like variables and literals, and they
have steps we need to follow in order to make something awesome for our program in the end.
Cutscene 2: Recipes for Success 95
Steps:
• the player’s final score (integer variable with player’s final score)
• the game’s current high score (integer variable with game’s high
score)
96 A Gamer’s Introduction to Programming in C#
Steps:
• Compare the player’s final score value to the game’s current high
score value.
• If the player’s score is greater than the game’s current high score,
update the game’s high score to be the player’s final score.
• If the player’s final score is less than or equal to the game’s current
high score, don’t do anything.
In these examples, our ingredients are variables (or literals in some cases), and
the steps we follow result in something that our game needs to function
properly – collect some input or update a high score value. As algorithms go,
these ones aren’t very complex. The input example is one we’ve already seen
in code (Level 4), and the high score example is coming up soon (Level 7).
We’re going to see many more examples of algorithmic problem solving in
the coming levels, and there are even more in the next book. As we proceed
on our adventure together, the algorithms will become more complex with
more code, but their basic properties will always be the same. Let’s take a
closer look at what those properties are.
instead of being more specific? Some of us would be eating raw cakes, and
some of us would be eating burnt ones. What if some of the steps were
missing or out of order? I don’t even want to imagine what would happen
to our delicious desserts in this case. And, finally, what if we didn’t even
know what we were making? Anarchy!
Now take a moment to think about what would happen in our code if our
algorithms had a never-ending number of steps, had instructions that were
unclear, confusing, missing, or out of order, or – perhaps worst of all –
didn’t even accomplish the task that we wanted to accomplish. Just like the
recipes, we’d definitely have some issues in our code if our algorithms
weren’t set up properly.
See if you can find all of these properties in the examples and Code
Rewards that are coming up. This is good practice for algorithmic thinking
and problem solving and will help you work through algorithmic solutions
for your own code quests and future game projects.
Side Quests
Want to Know More?
Algorithms are everywhere: If you want to know more about algorithms or
algorithmic approaches to problem solving, try an internet search on either
of those topics. You might be surprised at how many articles, blog posts,
and even books are dedicated to logic recipes. In fact, many of them aren’t
even about coding but instead are about everyday life tasks that can be
solved with a finite number of clearly defined steps or instructions.
Want to Do More?
Back to the beginning: At the start of this level, I described three game-related
tasks including an enemy patrol route, destroying pots and crates to find
treasure, and crafting a portal. With a pencil and paper, see if you can write
down the ingredients and steps that each of these tasks would need. Don’t
think about C# code because we haven’t covered everything we need to solve
these in code yet. Instead, just use words to tease out a possible solution.
Back to the beginning (Part 2, more detail): It is really common to miss some
steps (or even ingredients, variables, or data) the first time we try to write
out an algorithm. This is okay for a first pass, but a final solution is usually
more detailed than our first attempt. Take one or more of your word-y
algorithms (enemy patrol route, treasure pots and creates, or portal crafting)
and try to think of any steps that can be broken down into smaller steps. Try
to be as detail-focused as possible.
Back to the beginning (Part 2 again, less detail): It is really common to add extra
steps to an algorithm by mistake, trying to be too detailed, or even
misunderstanding the task we’re trying to complete. Review your word-y
algorithms one more time, but this time look for any steps that can be removed
without actually affecting how the algorithm will complete its task.
11
Level 5: Divide and Conquer
Level 4 Recap:
• User input can make our games and programs more interactive
and fun.
• For console input, it’s important to prompt the user so they know
what kind of input we are expecting.
• All console input is collected with Console.ReadLine(), but numbers
and other data types that aren’t strings need to be parsed before
they can be properly stored.
• “Don’t know, don’t care” is one way to express the power of
variables to store values that we, the programmer, never see.
• Comments are an important part of programming – without them,
our code is harder to read and understand.
DOI: 10.1201/9781003348481-11 99
100 A Gamer’s Introduction to Programming in C#
• + addition
• − subtraction (and negation, see below)
• * multiplication (this is an asterisk, not an x)
• / division
There are some other important ones like ++ and −− (increment and
decrement), % (modulus), and the compound assignment operators, but
they need a bit more explanation and will be covered later (or in Bonus
Level 2 for modulus). For now, let’s focus on the familiar operators and
keep things simple by working with number literals.
Each of the first four built-in math operators works roughly the same
way. They are known as binary operators because they each take two values
(or operands) to do their work. Take a look at these example statements:
Console.WriteLine(6.0f + 4.0f);
Console.WriteLine(6.0f − 4.0f);
Console.WriteLine(6.0f * 4.0f);
Console.WriteLine(6.0f / 4.0f);
If you code these for yourself, you should get output that looks like this:
10
2
24
1.5
There probably aren’t any surprises with that output – addition, subtrac-
tion, multiplication, and division all happened as expected. However, I used
floats in that example for a specific reason – so everything would work as
planned. If we change all the literals to integers:
Level 5: Divide and Conquer 101
Console.WriteLine(6 + 4);
Console.WriteLine(6 − 4);
Console.WriteLine(6 * 4);
Console.WriteLine(6 / 4);
10
2
24
1
Mostly, this is fine, but the last line of output is 1. If you take a second to
divide 6 by 4 on a calculator (or in your head if you prefer), you’ll see that
the answer is actually 1.5, like the float example shows. Why can’t C# divide
6 by 4 properly? There is a reason for this – it’s called integer division and
you can read more about it and the other unusual math things C# does in
Bonus Level 2. Aside from the one small hiccup when we divide two
integers, everything else in this level should work as we expect, mathemati-
cally speaking.
When we did math with float literals everything worked great. When we
used integers everything worked out except for division. But, what happens
when we mix types like this?
Console.WriteLine(6.0f + 4);
Console.WriteLine(6 − 4.0f);
Console.WriteLine(6.0f * 4);
Console.WriteLine(6 / 4.0f);
10
2
24
1.5
Even though it’s hard to tell from the output, two things are happening
– first, we have proof that we can do math operations with different, or
mixed, value-types, and second, when we do mix types, the result
is automatically converted to the “bigger”, or most appropriate, of the
two types. In this example, when you calculate something using both an
int and a float, the result is of type float – behold the decimal in the
last line of output. This automatic conversion is important to keep in
mind.
Before we start working with math and variables, I encourage you to
write some more simple expressions of your own to make sure you have a
good idea of how they work. Try some expressions with:
102 A Gamer’s Introduction to Programming in C#
If you code these, you should find that there are no issues getting them to
compile. If you write output statements to show what is stored in the result
variables (go ahead, I’ll wait here for you), you’ll see output like this:
125
75
2500
4
125
75
2500
4
There isn’t a really good reason for us to mix our baseScore and modifier
variable types, but it does help to make a point. Remember from our literal
example, if we write math expressions that have different types, the result is
automatically converted to the bigger or more appropriate type. This means
that for each of our four math statements, the right-hand side is converted
to type float. That’s cool, but we can’t store a float result in an integer
variable – integer variables can’t store decimals. So, we have to change our
code to this:
This change works because the resulting type from the math operation on
the right of the equal sign (float) now matches the variable type (also
float) on the left of the equal sign. This is around the time when people
usually ask me – “why don’t we work just with floats to make things
easier”? It’s a fair question, and many people, especially beginners, do
exactly that – work only with floats to avoid frustrating math and
variable issues.
But, this isn’t always the best plan. Int variables and literals exist for a
reason, and we use them (and other whole-number data types) to represent
things that can’t (or shouldn’t) be broken down into parts or decimals.
Think about this in a video game context – our game has a party, or group,
104 A Gamer’s Introduction to Programming in C#
int baseScore;
float modifier, finalScore1, finalScore2, finalScore3, finalScore4;
baseScore = 100;
modifier = 25.0f;
int result1 = 6 + 4 + 9;
int result2 = 6 + 4 − 9;
Level 5: Divide and Conquer 105
If you were to output the values stored in result1 and result2, you would see:
19
1
int result3 = 1 + 2 * 3 + 4;
This will store 11 in result3 (multiplication first, then the addition opera-
tions). If we want to force the addition to be done first, we can use round
brackets or parentheses () like this:
This will store 21 in result4. Why? Because now the operations in the ()‘s
happen first. As long as you make sure that every open bracket ( has a
corresponding closing bracket ), you can build your expression however it
makes sense to do so:
Try coding and compiling that last statement (and the previous ones) and
work through why the answer is the way it is (make sure to output the
value in result5). Hint: it’s BEDMAS – or order of operations.
A full review of BEDMAS (or PEDMAS) is outside of the scope of this
level. If you’re feeling rusty (or haven’t seen it before), I recommend a quick
internet search. Also, the examples in this section use ints for simplicity, but
the ideas here would apply to floats and other number data types. Order of
operations depends on the math we want to accomplish, not the data types
of the literals or variables we use.
106 A Gamer’s Introduction to Programming in C#
Unary Negation
The last of our “simple” operators is one that works a bit differently. We can
use − (dash, or minus) as a binary subtraction operator (two values or
operands), like this:
int subtractionResult = 6 − 4;
We can also use it for unary negation – turning a positive number negative
and turning a negative number positive. The simplest example is to do
something like this:
It doesn’t look like much, but this statement stores a negated 5 (or −5) in
negativeValue. It works for floats too:
Again, this might look weird, but this says add negative five (−5) to 10 and
store the result. As always, try coding it and outputting the result for yourself.
Unary negation can even be used with variables – in fact, this is where it
becomes quite powerful. Consider the following commented example:
//store 25 in score
int score = 25;
//store the number of hits taken by the player
int hitsTaken = 5;
//negate the number of hits and store in penalty
int penalty = −hitsTaken;
//add the score and the penalty
int finalScore= score + penalty;
//finalScore is displayed to the console
Console.WriteLine(finalScore);
Updating Variables
Does that title make sense for this section – didn’t we learn how to change or
reassign variables in Level 3? Indeed, we did, and it looked something like this:
int numberOfPlayers;
Now we’re going to learn how to update variables, and that’s a subtle but
important difference. When we update a variable, we usually make a change
that depends on the value that the variable had beforehand. For example, let’s
assume that our hero had 55 health points, and we wanted to add 10. We could
do it like this (I’ll do separate declarations and assignments for practice):
This example, like the previous one, has four statements but only two
variables – heroHealth and bonusHealth. How does this work? All of the
magic is in this statement:
108 A Gamer’s Introduction to Programming in C#
Try to think of it this way – you are a collector of antique game cartridges.
You keep your prized cartridges in a special display container, which you
then keep inside a box to keep the dust away. One of your Internet friends
sends you a new cartridge for your birthday. You take the display container
out of the box (access the variable), put the wonderful new cartridge inside
the display container (update the value), and put the display case full of
cartridges back into the box (update the variable).
Video games are full of variables that are updating constantly – it’s a very
important part of what makes them so dynamic. Here is an example of our
hero healing more than once. Take a look at these statements and see if you
can figure out the value stored in heroHealth in the end (also, I’m
simplifying things a bit and using some literals here):
We do this kind of update so often in games (and other programs) that there
are special operators designed to make your code more compact and your
programming life easier. Let’s take a look at some of them.
player1Score = 100;
player2Score = 100;
player3Score = 100;
player4Score = 100;
Before moving on, code this example for yourself and be sure to output the
values of the four final player scores. Even though it’s not likely something
we’d do in a real game, does the output make sense given the code?
105
95
500
20
Essentially, we’re doing what we did before – updating the score variables
using our built-in math operators. But, I promised you an “easier” way to
do these operations, and it looks like this:
player1Score += modifier;
player2Score −= modifier;
player3Score *= modifier;
player4Score /= modifier;
This compact syntax doesn’t save us a lot of space or code, but it does (in my
humble opinion) make it even more clear that we are updating a variable
instead of carrying out some sort of other operation. Each of these statement
pairs is equivalent:
You can use either the long form or the more compact compound operators
for updating your variables – they work the same way. I suggest using
whichever format makes the operation clear to you.
should fall nicely into that second category, and they serve a very specific
programming need – the need to add or subtract one from a variable. It feels
silly to write that, but we really do add and subtract one quite often,
especially when we learn to control our games with loops (Level 9).
Let’s say we wanted to keep track of the enemies that appear in our
game, and they are likely to appear one at a time. We could certainly do it
like this:
But, we could also use our awesome new compound operators like this:
Each one of the bold statements above adds one to our numberOfEnemies
variable – they are equivalent and perform exactly the same task. Which one
you use is up to you, but you should at least be aware of ++, or the
increment operator. We can do the same thing in reverse – decrementing by
one like this:
112 A Gamer’s Introduction to Programming in C#
This code works the same way as the previous example, except that
each bolded statement subtracts one from our numberOfEnemies
variable (we also cheated by starting with 3 enemies this time, so that
we didn’t have −3 enemies when we were done). The first decrement
uses the long-hand version of having the variable on either side of the
assignment. The second version uses the compound assignment oper-
ator (this time with subtraction instead of addition), and the final
version uses the – decrement operator to remove one from the value of
the variable.
Both ++ and −− are very important operators, and (as always) it’s worth
practicing with them so that you know what they are doing – adding or
subtracting one from the value of a variable!!
Console.WriteLine(10 + 4);
float result = 10.1f + 22.9f;
Now, what happens when we combine strings and number data types?
We’ve done a few examples like this:
Just like the mixed float and int example, if one of the operands is a string,
they all get converted to strings. Here’s another example:
Sum: 101.5
Since this expression starts with the string “Sum: ”, the 10 and 1.5 are also
treated like strings and are concatenated to the “Sum: ”. If we wanted the
numbers to be summed, we’d need to use our round brackets to force the
order of operations like this:
Try it out! In the end, + can be used to add numbers and concatenate
strings. The best way to get a feel for the difference is to try some
assignment and output statements – have some strings, have some
numbers, and have some fun! The more you practice, the more it will all
make sense.
114 A Gamer’s Introduction to Programming in C#
Math Functions
Sometimes, we want to do math that is more complex than we can easily
manage with the built-in operators. This is especially true in games when
we need to calculate collisions between objects, physics like gravity, and
movement in 2D and 3D space. Luckily, C# (and, by extension, MonoGame
and engines like Unity) have some terrific built-in functions that allow us to
easily call up some cool, useful, and complex math operations. In this
section, we’ll very briefly look at some of the simpler (but still cool and
useful) math functions that are available.
Before we dig in, we should discuss what a function is – a function is one
or more statements that perform a task and (typically) return a value to us.
We don’t usually see the specific statements, they operate in the back-
ground, but we can “call” the function to do some work for us. Sometimes
we pass data (or arguments) to the function so that it can do its job properly.
That might all seem a bit confusing, but some examples are coming up.
As a quick analogy – a function is a bit like a bank machine (or ATM). We
walk up to the bank machine, we press a few buttons, and we receive some
cash (and maybe a receipt). We don’t really see how the internal parts of the
bank machine work, but we know what buttons to press, and we can collect
what it gives us in return. We’re going to write our own custom functions
(also called methods) in Level 10, but let’s look at some functions that we’re
actually familiar with already:
These familiar examples are here as an anchor point so that what we do next
won’t seem totally new – you’ve used functions before, we’re simply going to
introduce a few more. What follows are examples of math functions and their
syntax. There are many math functions available in C#, but a lot of them won’t
Level 5: Divide and Conquer 115
If you look closely, you’ll see that some of the results need to be converted with
a special bit of code called a type cast – like (float) in some cases. We’ll see this
again in the next level, and you can read even more about it in Bonus Level 2.
As with everything else we’ve covered (and will cover), the more you
practice with these functions, the more they will make sense when it is time
for you to use them in your games and programs!
Side Quests
Want to Know More?
I’ve got 99 problems, but a lack of math functions isn’t one of them: C# has a lot of
math functions similar to the ones we looked at in the last section of this level.
116 A Gamer’s Introduction to Programming in C#
Many of them have to do with trigonometry (sine, cosine, tangent, etc.), and
others are used in pretty specific cases. It’s good to know what’s available, so
search for a list of C# math functions and give it a quick look-over.
But I want to store a fridge in a shoe box (revisited): This side quest appeared in
Level 3, but the idea of casting and type conversions has reappeared at the
end of this level. We’ll talk about this a bit more in Level 6 and again in
Bonus Level 2, but you can find more examples – online or in other books –
of casting and type conversions in C#.
Want to Do More?
Let the computer do the math: I wasn’t a very good math student in school. I
always found it to be my most challenging subject. When I learned to
program, I discovered that the computer could do the math for me – not in a
cheating sort of way, but in a way that helped me to better understand
many math concepts by coding them. Find some math problems of medium
complexity online, or better yet, in some books or notes from your math
classes. See if you can get C# to solve the problems and give you accurate
answers.
Functional practice: Try some more of the C# math functions, even the ones
you think you won’t use often. It’s really good practice to see what these
functions can do, what kinds of arguments they need to do their job, and
what types of results they give you in return. Keep the math library
documentation handy in case you get stuck.
Try this Code Reward for yourself. If you get stuck, peek at my solution for
a hint.
118 A Gamer’s Introduction to Programming in C#
/*
This is a simple math example. The player enters a base score, and this
program runs through some math operations to print an updated score.
/*
When we collect input, particularly numbers, the program can crash if the
user enters input that can’t be properly parsed.
Assume the user enters meaningful and parse-able input. Bonus Level 3 has
more information.
*/
• review Level 5
• complete Code Quest 4 (optional)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
In this Code Quest, we’re going to practice some of the built-in math
operations and call some of the C# math functions. To give our practice
some context – imagine we have been asked to write a program that
computes some stats from a player’s scores in a tournament. We’re a bit
limited in how complicated we can make this program, so the player will
only play two matches! Don’t worry, most of what we’re doing here would
apply to more than two matches, but without if-statements (Levels 7 and 8)
and loops (Level 9), we’ll have to limit our tournament to a pair of scores
for now.
1. Prompt and collect an integer from the player – this is their first
game’s base score.
2. Prompt and collect any penalties earned by the player in their first
game. A penalty is an integer amount that is subtracted from the
player’s base score.
3. Repeat Steps 1 and 2 for the player’s second game score. Remember,
we are trusting the user to enter positive integer values that can be
properly parsed. If they enter something unexpected, the program
may crash, and that’s ok.
4. Using built-in math operators and/or C# Math functions, compute
and display the following:
a. The player’s adjusted first and second scores (any penalties are
subtracted from each of the base scores).
b. The total points earned by the player (both adjusted scores
added together).
c. The average points earned by the player (total points divided
by number of games played – 2 in this case. Be careful, a
proper average can be a decimal number.)
d. The maximum score earned by the player. Use the appropriate
built-in math library function here.
e. The minimum score earned by the player. Use the built-in
Math library function here.
f. Hint: For Steps 4a through 4e, you’ll need to decide when to
create a new variable and when to update existing variables.
Try a few different approaches.
FIGURE 12.1
Try duplicating the tiny tournament stats program and using the numbers shown here as
input. This will help you to see if your math operations and functions are working properly.
FIGURE 12.2
Here is another run with different input. Check the output very carefully and if yours doesn’t
match, check for some of the common math issues like integer division.
Side Quests
Less code, same output: Can you eliminate two variables by updating the base
score to include the penalty values instead of storing them separately? For
an extra challenge, use the compound operator -= in your solution.
Bonus points: Add a prompt and the necessary statements so that the player
can enter any bonus points they’ve earned as well as any penalties. Bonus
points work like penalties except they are added instead of subtracted.
Mega-multiplier: Similar to the “Bonus points” Side Quest – but add a score
multiplier!! If the player enters 2.0 for the multiplier, the base score is
doubled. If they enter 0.5, the base score is cut in half. It should work for any
positive decimal value (but don’t be surprised if the output gets a bit messy.
Remember, if the value is going to be decimal, you’ll have to store this result
in an appropriately typed variable).
13
Level 6: A Random Opportunity
Level 5 Recap:
int playerScore;
float playerStrength;
string playerName;
Random rng;
But it’s also different. For one thing, we are introducing a new type. It’s not
one of our built-in types – it’s called an object type. We’re going to write our
own custom objects starting in Level 10, but Random is a great introduction
to the idea of C# object types. Even though the declaration of a Random
object seems similar to our built-in type declarations, things look a bit
different when it comes to assignment:
playerScore = 125;
playerStrength = 10.9f;
playerName = "Chell";
rng = new Random();
our random object might need a bit of explanation. Let’s put our Random
object declaration and assignment together like this:
Random rng;
rng = new Random();
When that’s done, we can ask our Random number object to create a
random integer for us like this:
In this case, .Next() is a special method (or function) that asks our Random
object – named rng – to give us the next Next() random integer. Think of our
Random object like a candy dispenser that hands out randomly-flavored
candies (or in our case numbers with values that we can’t predict) one at a
time. A simple program to illustrate how this works might look like this:
Level 6: A Random Opportunity 125
If I were to run this program three separate times I might get the following
output:
There we have it, three really big random numbers. I said “might get”
above, because if I were to run this program three more times, the numbers
would be different. If you code and run these statements for yourself (and
you should absolutely do that), you’re very likely going to get three
numbers that are completely different from mine.
According to the official C# documentation, calling.Next() like this
produces “an integer from 0 to a maximum value (Int32.MaxValue – 1)”.
Cool. Do you remember from Level 1 what the maximum integer value is?
Hint: it’s a little over two billion. This is why our example random numbers
are so big – they are between 0 and (roughly) two billion. Before we look at
other ways of generating (less gigantic) random numbers, we should make
it clear that once you’ve created a Random object, you can reuse it many
times – like a hammer that can hit more than one nail or a wand that can
cast more than one magic spell. We can absolutely do this:
Notice how there is only one initialization of our Random object (the first
statement), but we used it to create three different random numbers. Code
this example, run it a few times, and then adventure onward.
What can we do if we don’t want giant random integers? We control
how.Next() “behaves” by putting some values – called arguments – inside
the round brackets (). For example, let’s modify our code like this:
See the bold numbers inside the round brackets? Here is the output from
running the program twice:
This is random: 0
This too is random: 6
This is also random: 92
This is random: 0
This too is random: 8
This is also random: 51
Can you tell what has changed in our output? The argument we gave
to.Next() caused it to produce “an integer between 0 and a specific value”,
in this case, 0–1, 0–10 and 0–100. There’s a big LOOK OUT here – when the
documentation says between 0 and a specific value, it means “between
0 and a specific value, exclusively”, which is a fancy way of saying that the
specific value is not actually included. When I put 1 as an argument, it
means between 0 and 1, but the 1 isn’t included, so it’s actually between
0 and 0 – so helpful. When I put 10, technically, it will only produce values
from 0 to 9 and with 100 as the argument, we will only ever get values
between 0 and 99. It’s a strange thing that many random number objects
do, but it’s worth noting that if you really want 0–1 you should put 2 as
the argument and for 0–10 or 0–100 the values should be 11, and 101,
respectively.
There’s another version of .Next() that is pretty handy. This time, we’ll
put two arguments (and a comma) instead of just a single argument:
In this updated code, we have put a minimum and maximum limit on the
random number that .Next() will generate for us. The minimum is
inclusive (meaning it can be included) and the maximum is still exclusive
(like the last example), so these examples will produce random integers
from 1 to 4, from −10 to 9, and from 100 to 9,999. Adding 1 to each of the
second arguments would give us the range that we (probably) want.
Regardless, this example shows that we can coax our .Next() method to
give us random numbers between a minimum and maximum range. Here
are three sample outputs from this code (but keep coding them for
yourself too):
Level 6: A Random Opportunity 127
This is random: 3
This too is random: −5
This is also random: 7382
This is random: 4
This too is random: −8
This is also random: 3609
This is random: 2
This too is random: 2
This is also random: 2875
Here’s a short recap of what we’ve looked at, so far, in this section.
Now go forth and create some awesome random integer chaos!!! Or read the
next section to see how to create your own fake C# “dice”.
Simulating Dice
This would make a great code reward, but I’m just too excited to show you
a practical use for random integers.
Picture, in your mind, a standard die (singular of dice) that has six sides.
On its sides are representations (numbers or dots called pips) of 1, 2, 3, 4, 5,
and 6. How can we use what we learned in the previous section to create a
code-based standard six-sided die (or d6) in our games? It turns out, it’s
pretty easy, and it’s done like this:
that makes this like a d6 is the arguments we pass to .Next(). The 1 says the
lowest number should be 1, and the 7 says the highest number should be 6 (I
know it’s awkward, but you do get used to it after a while). With this little bit of
programming magic we have a simulated d6! Let’s push this example a bit
further by adding another simulated die to our program. A lot of games,
especially board games, use two dice to give a wider range of values for players:
Here is sample output from five separate runs of the code above:
As always, if you code and run this example for yourself, expect to see
different values because that’s exactly how Random objects work!
Maybe you’re not a board game player – maybe you’re more of a tabletop
role-playing game person. Do you need a d20 (20-sided die) for a critical roll
in your campaign but you accidentally left your lucky bag of dice at home?
How would you modify the d6 example above to simulate 20 sides instead
of 6? Where, in the code, did we tell the program that we wanted values
between 1 and 6? How would you change this to produce values between 1
and 20 instead? You can do this! I believe in you. As a mini practice side
quest, modify the previous example to roll two d20s instead of two d6s. If
you get stuck, I’ll post an example program at the end of this chapter.
Not every situation is well served with random integers. Sometimes, a
random decimal number is most appropriate and useful. In the next section,
we’ll see how to create random decimal values and explore the idea of
casting to manually convert from one type to another.
Once we have our generator, we can call the .NextDouble() method, instead
of .Next() like this:
There is a trio of things to note here in the code and the sample output:
The first point is probably the simplest – for random integers, we use Next
(), and for random decimal numbers, we use NextDouble(). For the
second point, NextDouble() gives us a random decimal number between 0
and something like 0.99999999999ish (thanks to that “exclusive of 1”).
Finally, NextDouble() gives us a double value, not a float. In Level 1, I
recommended we use floats in our programs instead of doubles. I made
this suggestion because game engines, like Unity, use floats as their
default decimal data type and, when you’re ready, I want you to feel
comfortable moving from C# + MonoGame to C# + Unity (or a similar
game engine) as your adventure continues beyond these books. Why am I
not showing you the handy NextFloat() method for creating random floats
– because it doesn’t exist. It might seem silly, but the only way to create
random decimal values in C# is to use NextDouble(). But there’s a handy
“trick” that allows us to change from one type to another. Here’s how it
works:
130 A Gamer’s Introduction to Programming in C#
These statements are almost identical to the previous example, except for two
small changes. First, our variables (myRandomFloat1, myRandomFloat2, and
myRandomFloat3) are floats, not doubles. Second, in order to make the
NextDouble() values fit in our variables, we use a technique called casting to
manually convert from double to float. To cast our double down to a float, we
put (float) in front of rng.NextDouble() – we lose a few decimal places of
precision by doing this, but we save half the memory (doubles take twice the
memory space of floats). If you don’t need “lots and lots” of precision, this is a
good trade-off!
Casting is an important tool in our programmers toolbox, but it’s only
needed in specific cases. Rather than distracting you from all the random
fun we’re having in this level, you can read a more detailed explanation of
casting and see more examples in Bonus Level 2.
Unfortunately, there’s no “easy” way to create a range of random decimal
values between a minimum and maximum – we can’t simply put one or two
arguments in NextDouble() like we did with Next(). If you want values
between 3.5 and 9.1, or between −3.14159 and 3.14159, you need to do a bit
of math to convert the default output (0 to 1) to something more useful.
Officially, according to C# documentation, the math looks like this:
That might look a bit complicated, but here are some examples:
It takes a bit of getting used to, but once you have the hang of it, you’ll be
making wonderful random decimal numbers for all your random decimal
number needs! Try this code out for yourself and put some more
interesting values in for max and min. Remember – all of this code would
also work if we were using variables instead of literals. Always keep that
in mind!
It may not be obvious yet, but we’ve now unlocked the key to many of the
ideas introduced at the start of this level. With our new knowledge, we’ll be
able to spawn game elements at random locations on our screen or in our
levels. We’ll be able to dispense common loot often and legendary loot
rarely. We’ll be able to simulate dice rolls, card shuffles, and coin flips. We’ll
be able to mix up our NPC dialog prompts. As we move forward through
the next few levels, random numbers will work their way into our
examples, code quests, and side quests. So, be on the lookout for interesting
ways to add some fun that surprises, or even frustrates, your players!
Review the Code Reward below, try the next Code Quest, and when
you’re ready, meet me at the start of Level 7!
Side Quests
Want to Know More?
Games use random: This level is shorter than the others, and until we learn
about if-statements, loops, and even MonoGame (in the next book), there’s
not really a lot we can do with our random numbers. While you’re waiting
to get to those awesome topics, do a bit of digging on the internet and find
some articles that talk about how video games use randomness. There are
also some great books on this subject as well.
Random is in your games: It’s true – aside from games of pure skill,
randomness is in almost every game we play. The next time you play a
board game or a video game, try to figure out where the randomness is – is
it in the dice, the cards, a loot crate, a power-up, or somewhere else? Also,
think a bit about how the randomness affects your experience of the game –
both positively and negatively.
Want to Do More?
Random “homework”: Okay, maybe homework isn’t the best word, but if you
skipped any of the examples from the level, go back and code them for
yourself. Run them a few times to convince yourself that the output is, in
fact, random.
132 A Gamer’s Introduction to Programming in C#
Old examples are new again: In Level 4, we learned about user input – a great
way to make our programs interactive and fun. In some cases, we can
substitute randomness for user input – instead of asking the user to enter a
value, we can generate one for them. Revisit some of the previous levels and
code quests to see if there are places where a random number generator
might make the program more interesting than using hard-coded values or
even user input.
Try this code reward for yourself. If you get stuck, peek at the solution for a
hint.
Level 6: A Random Opportunity 133
/*
This is a small program that simulates two dice rolls using random integers.
The program also calculates and displays the maximum of the two simulated
dice.
/*
When we collect input, particularly numbers, the program can crash if the
user enters input that can’t be properly parsed. Assume the user enters
meaningful and parseable input. Bonus Level 3 has more information.
*/
//Prompt and collect input for the number of sides on the two dice.
Console.Write("How many sides are on your two dice: ");
numberOfSides = int.Parse(Console.ReadLine());
//Use our Random object (called rng) to assign values to our fake dice.
die1 = rng.Next(1, numberOfSides + 1);
die2 = rng.Next(1, numberOfSides + 1);
//Use Math.Max (found in Level 5) to figure out which value is the highest.
maximumValue = Math.Max(die1, die2);
• review Level 6
• complete Code Quest 5 (optional)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
1. Prompt the user and collect a single integer that is the hero’s
starting health value. Ask the user to enter a value between 0 and
100, just so the rest makes sense. Assume the user will enter a value
that can be parsed and makes sense for the program.
2. Our hero encounters a deadly zombie. The zombie deals between 10
and 30 damage. Generate and store (in an appropriate variable) the
zombie damage value using a random number generator, and
deduct the value from the hero’s health.
3. Our hero finds a health potion. The health potion adds between 25
and 45 health points. Generate and store (in an appropriate variable)
the random number for this event, and add it to the hero’s health.
4. Our hero falls into a deadly trap and loses between 20 and 60
health. You know what to do (just like Steps 2 and 3)!!!
5. For each of Steps 2, 3, and 4, display a line of text telling the user
what happened to the hero and showing them the value that was
either added or subtracted from the hero’s health.
6. Finally, show the hero’s final health value.
Here is the output from running my version of this quest two separate times
(Figures 14.1 and 14.2).
FIGURE 14.1
Here are some example inputs and outputs to help guide you through this quest.
FIGURE 14.2
Here are some more example inputs and outputs to help guide you through this quest.
136 A Gamer’s Introduction to Programming in C#
In my solution, I have used variables for the hero’s health and one for
each of the random events that our hero experienced.
Side Quests
More code, more control: Not all games would handle the events the same
way. For each event, prompt the player to enter the minimum and
maximum values that event could generate. For example, prompt the
player for a minimum zombie damage amount and a maximum zombie
damage amount. Use the player-input numbers to generate your random
values.
Level 6 Recap:
• When the game loads, ask the player to press “start“ when they’re
ready. If the player indicates they are ready, start the game.
• Most games begin with the player character not moving – if the
player presses the appropriate keyboard keys or controller input,
the character moves.
• The player also decides how fast and in what direction the character
moves. If the player presses the turbo button, the character charges
ahead. If the player drifts right, their path is different than if they
drift left.
• If the player collides with an enemy or an obstacle, something
should happen to the player (lose some health, stop running,
change directions, earn points, etc.) and maybe something should
happen to the enemy or obstacle too.
• If the player reaches the end of the level in time and with some
health remaining, the game says “You win!” But if the player loses
all their health or they don’t find the end of the level in time, the
game should probably encourage them to try again.
• Finally, most games don’t simply shut down at the end of a level –
they shut down if and when the player asks to quit.
Most of us prefer when our games respond to certain conditions, like the
ones listed above. Notice that each of our improvements involves an “if“
condition – if the player does something, if there is a collision, if something
interesting happens, and so on. Each of these conditions is like a small
decision that changes the flow of our game in a way that makes it far more
interesting.
You are at a fork in the road, brave adventurer. If you push forward,
Level 7 will show you how to control the flow of your code using simple if
and if-else statements. You’ll also see how if-statements often introduce a
new type of coding error – logic errors. If you turn back now the wonders
and joys of conditional execution will be forever lost to you – unless you
decide to come back and tackle this challenge later. Which is also cool.
Conditional Execution
Until now, our programs have run from start-to-finish with no chance of
deviating. Consider this short example:
This program starts running at the beginning and goes straight through to
the last statement. Since we’re using a Random object, we’ll get different
values when we run this program, but it will always run from start to end –
with no deviation – every single time. If we were to draw a diagram of the
flow of this program, it would look something like this (Figure 15.1).
But what if we want the program to do something different based on the
value stored in our simulated die – maybe we want to display a message of
congratulations on a good roll? To do that, we need to change the flow of
the program like this (Figure 15.2).
These illustrations, called flowcharts or flow diagrams, are meant to show
how our code can execute “conditionally” – we only output “Great roll!” on
condition that the value stored in d20 is greater than or equal to 17. If the
value in d20 is less than 17, we don’t display that message at all – that part
FIGURE 15.1
Flowcharts, like this one, can help illustrate how our code “flows” from the start of the
program (or section of the program) to the end.
140 A Gamer’s Introduction to Programming in C#
FIGURE 15.2
Here, we see that the code flows differently depending on the value stored in variable d20. If
it’s less than 17 we go one way, if it’s greater than or equal to 17 we go another way.
of the code gets skipped. The decision to display “Great roll!” or not is
called a branch, and the code would look something like this:
//initialize our simulated 20-sided die variable
int d20 = 0;
//initialize a random object called rng
Random rng = new Random();
//assign d20 a value between 1 and 20
d20 = rng.Next(1, 21);
//IF the value stored in d20 is greater than
//or equal to 17 …
if(d20 >= 17)
{
// … display this positive message!
Console.WriteLine("Great roll!");
}
//output the rollvalue
Console.WriteLine("You rolled: " + d20);
direct our program to run certain lines of code and to skip others depending
on conditions that matter to us. Let’s take a closer look at how this is done.
if(1 < 2)
{
Console.WriteLine("1 is less than 2");
}
This example might seem silly, but it shows all the pieces we need for a
working if-statement. When C# is executing our code and reaches an if
statement, it evaluates the condition inside the round brackets (parentheses). If
that condition evaluates to true, the statements inside the curly braces get
executed. This can be a bit confusing at first, but think of it this way – the
condition inside the round brackets must be a question with a true or
false answer. In our example, the question is – 1 is less than 2? The answer
to that simple question is true (or yes) – 1 is less than 2. Before moving on, code
this example for yourself. You should see “1 is less than 2” as output. We’ll see
more of these simple true or false questions in all of our if-statement examples.
What happens if we change the condition, like this:
if(2 < 1)
{
Console.WriteLine("1 is less than 2");
}
142 A Gamer’s Introduction to Programming in C#
Now, we have a different situation. The question we are asking (or the
condition we are checking) is whether 2 is less than 1. The answer to this is
now false (or no). If you try to run this code, you’ll see no output at all.
The statement inside the curly braces is skipped because the condition
is false.
As always, where there are literals in our code, we can use variables
instead. Here’s a similar, but more interesting example for our future games:
Notice how the if-statement doesn’t really know, or care, where the value of
playerScore comes from. As long as the value in playerScore is an integer
and the condition in the round brackets can be evaluated to be true or false,
it won’t matter. Take a moment to code this example and try it with a few
different input values.
We can also have more than one statement inside the curly braces:
This example is our most “complete” program so far in this level – read the
code and comments carefully. Here we are using a random number to
simulate the player’s score. If the player’s random score is greater than the
current high score – update the high score to the player’s score and also
display a message. It’s very important to note that in most cases, you’ll have
code that goes before the if-statement, code that is part of the if-statement,
and code that is after the if-statement. The details of each specific case are
different, but here are some general guidelines:
• Before: make sure that any variables that the if-statement needs are
declared and have assigned values
• During: this is where the if-statement will check the condition and
execute any statements if the condition is true
• After: this is any code that runs after the if-statement has completed
Here is an example that has two if-statements in the same program. One
checks to see if a simulated 20-sided dice roll is very high, and the other
checks if the roll is very low. See if you can identify the before, during, and
after parts of each if-statement.
Try this code out for yourself. Be sure to run it a few times to see the range of
output possibilities. In this program, we are checking the value of d20 for two
possible conditions. In the first if-statement, we are checking to see if it has a
value of 18, 19, or 20 (all values greater than 17). If the condition is true, we
display a positive message. In the second if-statement, we are checking to see if
the value in d20 is less than 3 (2 or 1) and displaying a sympathetic message if
the condition is true. What happens if the value in d20 is between 3 and 17?
Nothing much. We display the value that the user (or player) rolled, but we
don’t comment any further. Outputting the value stored in our d20 is outside
of either of the if-statements, so it runs no matter what value d20 receives.
So far in this level, all of our examples do something if a checked
condition is true, but we don’t take any action if the condition is false. A
player with a high score is a great use of this kind of if-statement – if the
player earns a score greater than the current high score, we update the high
score to the player’s score. If the player’s score is less than the current high
score, what should we do? I’d argue that we should do nothing – there’s no
action needed if the player didn’t beat the high score. There are lots of
similar take-action-or-ignore cases in games:
• If the player collides with an enemy they lose health, otherwise the
player’s health stays the same.
• If the player presses the A-button on their controller, their hover
craft should accelerate forward, otherwise it should stay where it is.
• If the player presses any key the game should begin, otherwise it
should stay on the title screen.
There are also many examples of conditions that have both a true and a false
consequence, so let’s take a look at how to handle those cases.
An else clause is added when we want to take action if the condition is false.
Without an else, we ignore the false case and take no action, but when we
add the else our code will respond to a false condition. Here is one of our
previous simple examples revisited:
if(1 < 2)
{
Console.WriteLine("1 is less than 2");
}
else
{
Console.WriteLine("2 is NOT less than 1");
If you were to write, compile, and run this code (highly recommended), you
would see that the if statement is still true (like it was when we saw this
code at the start of this level) and “1 is less than 2” is printed. But if we were
to change the condition:
if(2 < 1)
{
Console.WriteLine("1 is less than 2");
}
else
{
Console.WriteLine("2 is NOT less than 1");
}
This new condition (2 < 1) is false and the else does its job and outputs “2 is
NOT less than 1”. This example isn’t very useful outside of illustrating how
else works, so let’s push forward with a more interesting example:
Console.WriteLine("Game over.");
Console.WriteLine("In the end, your hero has " + heroHealth + " health.");
Our program starts by prompting the user and collecting a hero’s health
value. Then, we initialize a Random object named rng and use it to generate
a value between 1 and 100. Now, our if-else kicks in. If the random number
we generated is less than 50, the hero finds a health potion and is healed for
10 health points. If, on the other hand, the random number we generated is
50 or greater, the hero steps into a trap and loses 15 health points. We’ve
created a 50/50 random chance of our hero healing or taking damage!! After
all the action is done, we display the hero’s final health value. Again, pay
attention to what happens before the if-statement, what happens during
(including the else), and what happens after. The variable(s) used in our
Level 7: A Fork in the Road 147
Comparison Operators
We know from previous levels that games are full of numbers – mostly ints
and floats for us. C# has several built-in comparison operators that allow us
to compare numbers and ask simple true or false questions:
Here’s an example program that tests all the built-in number comparison
operators. Pay close attention to how each one is used and the output it
produces. Try coding this multi-if example for yourself and running it with
some different values for playerScore and highScore. Don’t be surprised if
more than one if-statement is true when you run this program. Take some
time to look at the code so that you understand when each separate if-
statement condition is true:
int playerScore = 0;
int highScore = 0;
highScore = int.Parse(Console.ReadLine());
if(playerScore < highScore)
{
Console.WriteLine("Player score is less than the high score.");
}
if(playerScore > highScore)
{
Console.WriteLine("Player score is greater than the high score.");
}
if(playerScore <= highScore)
{
Console.WriteLine("Player score is less than or equal to the high
score.");
}
if(playerScore >= highScore)
{
Console.WriteLine("Player score is greater than or equal to the high
score.");
}
if(playerScore == highScore)
{
Console.WriteLine("Player score is equal to the high score.");
}
if(playerScore!= highScore)
{
Console.WriteLine("Player score is NOT equal to the high score.");
}
Remember, many of these examples use ints to keep things simple (and
because we use ints often in games), but they’ll also work with other number
data types including floats. Some of the built-in comparison operators even
work on strings and characters – let’s take a look at which ones.
In this example, we ask the user to enter an item that the hero has picked up
on their journey. We store the user input in a string variable and use == to
compare the user input (pickUpItem) with the hard-coded string literal
“health potion”. If the user enters the text “health potion”, we output a
healing message (and we could have also increased the player health if that
was part of our program), but if the user enters anything else, we simply
output a “can’t use that” message – it is a simple example, after all. Try to
code this example and enter a few common and bizarre items that your hero
might have picked up. As you try some different inputs, you might find that
this if-statement is a bit sensitive. For example, if you enter “Health Potion”,
or “healthpotion”, or “healthy portion”, the comparison is false, and the else
kicks in – it’s only true if you enter exactly “health potion”. Take a look at
Bonus Level 3 to see some helpful tips on how to make this kind of
comparison less sensitive to minor differences in the strings.
Although we didn’t cover the char built-in data type as thoroughly as we
did other data types in Level 2, they can be quite helpful – particularly in
our console programs. Here’s a quick overview on the char data type:
Can you see the error in this code? Try running it for yourself to see what
the final output shows. There’s no if-statement in this example, but there is
a logic error. In the first statement, we initialize a playerScore variable with
a starting value of 75. Then, in the next statement, we subtract 25 from the
player score – even though the comment says we want to ADD 25. Finally,
we display the final score. We are expecting an output of 100 (75 + 25), but
we actually see an output of 50 (75 − 25). The compiler doesn’t catch this
error – syntax-wise, everything is fine. We simply put the wrong math
operation and introduced a logic error to our code. Logic errors, like syntax
errors, are a very normal part of programming. What makes them a bit
trickier to handle is that the compiler tells us when we have a syntax error –
and usually gives us a sense of how to fix it. There is no such support for
logic errors.
What does this have to do with if-statements? If-statements are logic
structures that control the flow of our code, and it’s all-too-easy to make
small mistakes that have big and sometimes unfortunate impacts on our
program. Take a look at this familiar code:
Can you find the logic error? Take a moment to really look at this code, or
better yet, write, compile, and run it. This example is identical to our last
hero health checker, except for one small change – if(heroHealth <= 0) from
the original code has been changed to if(heroHealth >= 0). This very small,
and hard to see, change flips the logic of our program – now we are saying
that any hero with health points has perished and only heroes with negative
health (zombie heroes?) are healthy enough to continue. If you caught that
error on your own, awesome! If you didn’t, don’t be discouraged – it takes a
while to be able to find those types of small, and frustrating, logic errors.
Here’s one more example with the same small program:
152 A Gamer’s Introduction to Programming in C#
Do you see the error this time (hint: it’s in the same place). This time we
have the logic “almost” correct. We are checking to see if the hero’s health is
below 0 to pronounce them perished. But by leaving the = off the
comparison operator, we’re allowing zero-health heroes to continue ad
venturing when they should really be resting (at the very least). Again, if
you didn’t catch the error, don’t give up! The goal of this section is to show
you how easy it is to introduce logic errors with if-statements, and to
introduce the idea of logic errors in general. In Cutscene 3 (coming up
soon), you’ll find some tips on “debugging” – finding bugs and errors –
your code, especially when your if-statements become more complex, and
we add loops (Level 9) to our programs.
These If and If-Else Statements Are Pretty Simple. Can We Write More
Complex Logic for Our Games and Programs?
Level 7: A Fork in the Road 153
Absolutely! Watch out for Level 8, when we’ll take what we’ve learned here
and build even cooler if-statements to handle more complex logic.
if(1 < 2)
{
Console.WriteLine("1 is less than 2");
}
else
{
Console.WriteLine("2 is NOT less than 1");
if(1 < 2)
Console.WriteLine("1 is less than 2");
else
Console.WriteLine("2 is NOT less than 1");
The second version of this example works because both the if and else
have only one statement each. When you remove the curly braces, C# will
automatically run the next single statement after the if or else. Personally,
I prefer the second version of this example because it does save me a bit of
space and coding time. But I also have 30(ish) years of experience and can
(usually) remember when it’s ok to remove the curly braces or when I
need to add them. If you have more than one statement in your if or in
your else, you must include the curly braces, or the code will not run
properly – you’ll end up with either a syntax error or a logic error
depending on the situation.
When in doubt (and I recommend this for all beginners) include the curly
braces!
playerScore = 100;
if(playerScore = 100)
Side Quests
Want to Know More?
Strings are tricky to compare: As I mentioned in the level, comparing strings
with == and!= in C# is easy, but the other built-in comparison operators
don’t work. If you find yourself needing to compare strings beyond simple
equality, you’ll want to look up the String.Compare() method. It’s like
Level 7: A Fork in the Road 155
Want to Do More?
Make the guessing game more difficult: Change the guessing game example to
have a random number between 1 and 100 instead of the hard-coded value
of 47. Try the guessing game a few times – if you win, I’m sending you a
virtual high-five!
A small change with a big impact: Go back to the example where we asked if
you were enjoying your coding journey so far. Change this logic:
if(userResponse == ‘y’)
to:
if(userResponse!= ‘n’)
What impact, if any, does this have on the program? Be sure to test it with a
few different input options.
code reward – try it out for yourself and take a look at my code if you get
stuck:
If that seems like a lot to do, remember, we’re already on Level 7! We’ve
covered a lot of awesome coding topics, and our games and programs are
getting more complex – and fun. There are many ways to approach this
code reward and to complete it successfully, but I recommend taking your
time, working on one part of the program at a time, and testing often. If you
get stuck, head to my solution for a hint.
As always, if you’re not a big fan of the story in my example, change it to
something that you find more interesting. The code in this program would
work just as well if it was set in space, on a farm, or in an underground lair
populated by talking insects (just saying).
Level 7: A Fork in the Road 157
/*
This is a small program that simulates a simple RPG-style hero encounter.
//let the user specify the kind of obstacle or monster our hero is facing
Console.Write("What type of monster is hero facing: ");
string monsterType = Console.ReadLine();
//let the user specify how healthy the hero is, parse and store this input
Console.Write("How many hit points does your hero have: ");
int heroHealth = int.Parse(Console.ReadLine());
//just a line of dashes to separate the input section from the business
section
Console.WriteLine("-------------------");
//if the monster did damage, use our math operator to subtract it from the
health
heroHealth -= damage;
Console.WriteLine("Hero’s health after " + monsterType + " attack: "
+ heroHealth);
//if the user said y to poison, take off 10 health points, otherwise don’t.
if(poisoned == ‘y’)
{
heroHealth -= 10;
Console.WriteLine("Hero’s health after poison damage:
" + heroHealth);
}
else
{
Console.WriteLine("Your hero is not poisoned - no poison damage
recorded".);
}
158 A Gamer’s Introduction to Programming in C#
//if between damage and poison the hero’s health is less than 0, the hero is
dead.
//display a message.
if(heroHealth < 0)
{
Console.WriteLine("Your hero has died.:X ");
}
else
{
Console.WriteLine("Your hero lives to fight another day (or another
turn at least)! ");
}
• review Level 7
• complete Code Quest 6 (required)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
This quest starts where Code Quest 6 ended. What we’re going to do is
make our character encounter more interesting by adding some simple if
and if-else statements. This way, we can add the possibility that our zombie
is replaced by a vampire, and we can give our hero a health potion – but
only if they really need it. We can also determine if our hero survived the
encounter or perished.
If you haven’t completed Code Quest 6, go back and complete it before
charging forward with this challenge.
use for this task – if the hero’s health is less than 40, give them a
health potion otherwise, ignore their health status.
4. Finally, let’s figure out if our hero has perished or survived. If their
health is 0 or less, output a message of condolences. Otherwise, show
the player how healthy their hero is now that the encounter is over!
5. Remember, if you have completed Code Quest 6, you have a lot of
this code already! Oh, and leave the trap code in place from Code
Quest 6 - heroes are known to be pretty clumsy.
Here is the output from running my version of this quest two separate times
(Figures 16.1 and 16.2).
There are a lot of random factors in this program, and I’m only showing
output from two runs. Be sure to test your program to make sure that all three
FIGURE 16.1
This is my hero trying to survive a random and deadly encounter.
FIGURE 16.2
This is my hero trying to survive another random and deadly encounter.
Code Quest 7: A Random Encounter (Part 2) 161
Side Quests
More encounters, more fun: This program only has three events – but there
could be more! Add some more events and make them more interesting by
wrapping them in if-statements.
Dead heroes don’t drink health potions: As you can see in my second output
screen, my hero technically perished after the vampire attack – they saved
themselves with that VERY handy health potion only to perish (again) in
the deadly trap. What an unlucky hero. The main reason for this is that we
are only checking for hero-death at the very end of the program. Can you
think of a way to rewrite the logic to make sure that once the hero perishes,
they can’t come back to life?
Make it your own: Do you find my output uninteresting or uninspiring? Do
you prefer games with aliens? Do you prefer games with steampunk
technology? Do you prefer alien steampunk technology games? Change the
event text and the variables to make this quest more fun for you!!
17
Level 8: Decisions, Decisions
Level 7 Recap:
But there are also many times where our games need to check trickier
conditions:
• How much health does the hero have after all the current buffs
(positive bonuses) and debuffs (negative penalties) are applied?
• Which of the many enemies in the current scene is going to attack
next?
• The path in front of the player branches off in three directions –
which path will be easy, which one is neutral, and which one is
extra challenging?
• What is in that fancy loot box?
162 DOI: 10.1201/9781003348481-17
Level 8: Decisions, Decisions 163
//Create a Random object and use it to get a value between 0 and 100
Random rng = new Random();
int randomValue = rng.Next(0, 101);
//If the random values is less than 25 out of a possible 100 (25%), the hero
//finds a health potion and gains 10 health points.
if(randomValue <= 25)
{
Console.WriteLine("Your hero finds a health potion and feels
refreshed!");
heroHealth += 10;
}
//Finish with a helpful update.
Console.WriteLine("Your hero continues their journey with " + heroHealth +
" health.");
This is a program that uses lots of coding elements that we’ve covered in
earlier levels – variables, literals, user input, random number generation,
164 A Gamer’s Introduction to Programming in C#
and even a simple if-statement. If you run this program a few times, every
once in a while (about 25% of the time), your hero will gain 10 health points,
and the rest of the time they’ll continue their journey with the health points
they started with. To be clear, 75% of the time this program would seem to
be doing nothing – this is because our program responds to the true case of
our if-statement condition and ignores the false case since there is no else.
Let’s extend it a bit by adding an else like this:
//Create a Random object and use it to get a value between 0 and 100
Random rng = new Random();
int randomValue = rng.Next(0, 101);
//If the random values is less than 25 out of a possible 100 (25%), the hero
//finds a health potion and gains 10 health points. Otherwise, the hero finds
//an empty room.
if(randomValue <= 25)
{
Console.WriteLine("Your hero finds a health potion and feels
refreshed!");
heroHealth += 10;
}
else
{
Console.Write("The room is empty. Utterly empty.");
}
//Create a Random object and use it to get a value between 0 and 100
Random rng = new Random();
int randomValue = rng.Next(0, 101);
//If the random values is less than 26 out of a possible 100 (25%), the hero
//finds a health potion and gains 10 health points. If the random number is
//> 89 the hero finds a trap. Otherwise, the hero finds an empty room.
if(randomValue <= 25)
{
Console.WriteLine("Your hero finds a health potion and feels
refreshed!");
heroHealth += 10;
}
else if(randomValue >= 90)
{
Console.WriteLine("Your hero steps on a rusty nail. It really, really
hurts.");
heroHealth -= 10;
}
else
{
Console.Write("The room is empty. Utterly empty.");
}
As promised, we started with what we knew – the first two versions of this
program use simple if statements – and we ended with something new! This
final version adds what is called an if-else clause and it allows us to have
more than two paths in our if-statement. Here’s how it works:
rusty nail (or trap) output and we remove some health points. After
that, we jump to the final output of the program.
• If randomValue isn’t <= 25 and it also isn’t >= 90, the program runs
the else part. We tell the player the hero’s room is empty and then
proceed to the end of the program.
With the new code (the original example is back in Level 7), we are able to
respond to the three possible cases when the player guesses – they guess the
secret number correctly (playerGuess == secretNumber), they guess a
number that is too low (playerGuess < secretNumber), or they guess a
number that is too high (playerGuess > secretNumber). As with the
previous examples, only one of these three logic branches will run. Take
some time to try this little guessing game before moving on to our last
example of this section, where we’ll put a twist on our hero builder
examples:
Console.WriteLine();
int heroHealth = 0;
int specialSkillPoints = 0;
if(heroProfession == "fighter")
{
//is this a good starting setup for a fighter?
heroHealth = 100;
specialSkillPoints = 5;
}
else if (heroProfession == "magician")
{
//is this a good starting setup for a magician?
heroHealth = 55;
specialSkillPoints = 15;
}
else if (heroProfession == "bard")
{
//is this a good starting setup for a bard?
heroHealth = 75;
specialSkillPoints = 10;
}
else
{
Console.WriteLine("Sorry, that profession isn’t available.");
}
168 A Gamer’s Introduction to Programming in C#
Nested Ifs
So far, we’ve looked at ways to code multiple outcomes from a single
question or condition like what happens when my hero enters the room,
or how did the player’s guess compare to the secret number, or how
should I set my hero up given their chosen profession. Sometimes, we
want to ask more than one question or check more than one different
condition. In some cases, we might use more than one if-else or if-else-if
statements in a row. For example, if our hero visits three different rooms,
we might have three separate if-structures to deal with each room – one
at a time.
In other cases, we might want to check more than one condition at the
same time. Let’s say your game has a magic user that can cast a lightning
spell, but they need to be at least magician level 9 (because that’s when you
get to learn the lightning spell) and they need to have some mana points
left. Mana points are the magic equivalent of health points – no mana, no
spells. How can we express this in code? One way is to nest one if-statement
inside of another, like this:
int level = 9;
int manaPoints = 3;
Random rng = new Random();
int lightningDamage = 0;
170 A Gamer’s Introduction to Programming in C#
if(level >= 9)
{
if(manaPoints > 0)
{
lightningDamage = rng.Next(10,21);
manaPoints--;
Console.WriteLine("Lightning streaks across
the room for " + lightningDamage + " damage!");
}
else
{
Console.WriteLine("No mana points – can’t cast lightning.");
}
}
else
{
Console.WriteLine("Sorry, I need to level up first.");
}
In this code, we have an if-statement that checks to see if our magic-user has
leveled up enough to cast a lightning spell. If they are below level 9, this
condition is false, and we drop to the else that says our character needs to
level up. However, if they are at level 9 (or more), the condition is true, and
we proceed to the next condition – check that they have more than 0 mana
points. If there are no mana points to spare, we print a no-mana apology
and leave both if-statements. If our magic-user does have some mana points
then we execute a powerful (and crackly) lightning strike with a bit of
randomness for extra flair. Don’t forget to subtract a mana point for the
successful spell that was cast.
For some of you, this example might seem perfectly reasonable, and for
others, it might seem as though our code has become a lot more
complicated? In a way, everyone is right – this example successfully tests
one condition (are we level 9 or up) and if that initial condition is true, we
go on to test another (do we have any mana points). It does what we set out
to do, but it adds a lot of structure and you have to keep track of where the
code jumps to in both the true and false cases. There’s also no practical limit
to how deeply we can nest ifs. Our example only nests two conditions, but
we can definitely add more logic if we have more conditions to check –
maybe we need to be a level 9 magician, have enough mana points, be
wearing orange robes, have a dragon tooth wand, and it needs to be a
Tuesday to cast the lightning spell. Of course, this means even more
structure and logic branches to keep track of.
We could do more nested-if examples here, but there are few cases where
nested ifs are the best solution to a multiple-condition logic problem. In fact,
I recommend that you avoid them when you can. I’m showing them here
Level 8: Decisions, Decisions 171
for three reasons. First, you’re going to come across them if you are reading
other textbooks, working with other people’s code, or even searching for
helpful solutions on the Internet – you should know that nesting ifs is
possible and does work. Second, we all approach and solve problems
differently. If you have a coding challenge and you want to solve it with
nested ifs, go for it! You can always come back and change your solution if
all the nesting starts to get out of hand. Third, there are rare cases where
nested ifs are the best or only solution to a problem. But, for all the other
cases, let’s take a look at how we can simplify our multi-condition if-
statements using the C# boolean operators.
int level = 9;
int manaPoints = 3;
Random rng = new Random();
int lightningDamage = 0;
TABLE 17.1
Pairs of Values That Might be Used In the level and
manaPoints Variables
level manaPoints Did we cast the lightning spell?
6 0 ???
10 0 ???
6 3 ???
10 3 ???
This new version is much more compact – there is only one if and one else.
We were able to do away with the nesting by putting both conditions inside
the if round brackets (parentheses). Stop here and try coding this example
for yourself. Run the program a few times and try changing the values of
level and manaPoints in your code as shown in Table 17.1. As you go
through the table, consider each pair of values for the variables level and
manaPoints – will our character successfully cast lightning, yes or no?
If you try it out, and maybe fill in the ??? parts of the table, you should
end up with something like this (Table 17.2).
Were you able to successfully predict the chance of lightning given the value
pairs for level and manaPoints? If not, don’t worry. This kind of logical thinking
takes time and practice. Some of you might be intuitively realizing that we only
successfully cast our lightning spell when we are above level 9 and have more
than zero mana points. This is, after all, what we set out to do in our program.
But how does this actually work – how does the compound if-statement know
when the overall condition is true or false when there are two (or more)
conditions that need to be checked? Let’s build a new table (Table 17.3).
This new table is close to what we call a “truth table” in computer science. It’s
meant to illustrate how compound boolean operators like && (and || in the
next section) work. The takeaway from this table is that we only successfully
cast our lightning spell when both conditions on either side of our &&
operator are true. It’s worth thinking of && as being very strict – every
condition that is joined with an && must be true for the whole expression to be
true. When we look at || in the next section, we’ll see that it is much less strict.
In the meantime, let’s add another condition to our lightning example:
TABLE 17.2
Solutions for the ??? in Table 17.1
level manaPoints Did we cast the lightning spell?
6 0 No lightning.
10 0 Zippo, not a spark.
6 3 Still nothing.
10 3 Yay! Electricity flows …
Level 8: Decisions, Decisions 173
TABLE 17.3
A “Truth Table” That Makes it Clearer When Our Two Conditions Will Cause
Lightning
level >= 9 &&
level >= 9 manaPoints > 0 manaPoints > 0 Explanation
//If the player’s score is less than 50 then it’s 0 stars for them.
//If their score is between 50 and 99, 1 star. If their score is between
//100 and 199, 2 stars. If their score is 200 and above, 3 stars.
if(playerScore >= 200)
{
playerStars = 3;
}
else if(playerScore >= 100 && playerScore <= 199)
{
playerStars = 2;
}
else if(playerScore >= 50 && playerScore <= 99)
{
playerStars = 1;
}
else
{
playerStars = 0;
}
The boolean && (and) operator is a great way to check if values are between
a certain range – like our player’s score in the example above. Remember,
while we often use && (and) to check two conditions, it works with more.
Take a look at this last final example that might remind you of a certain
trademarked dice game where five-of-a-kind is the best roll you can get …
Console.WriteLine("To win this game, all your dice must show the same
value …");
Random rng = new Random();
int die1 = rng.Next(1,7);
int die2 = rng.Next(1,7);
int die3 = rng.Next(1,7);
int die4 = rng.Next(1,7);
int die5 = rng.Next(1,7);
Console.WriteLine("You rolled: " + die1 + " " + die2 + " " + die3 + " " + die4 +
" " + die5);
if(die1 == die2 && die2 == die3 && die3 == die4 && die4 == die5)
{
Console.WriteLine("Yahooooooo!");
}
Level 8: Decisions, Decisions 175
else
{
Console.WriteLine("Better luck next time.");
}
What is this program doing? It’s setting up five variables that each simulate
a six-sided die with random values. Then, we check to see if the first
variable has the same value as the second variable, the second variable has
the same value as the third, and so on. In other words, our single if-
statement is checking to see if all five of our variables (or dice) have the
same value. If they all have the same value, we shout (or output)
“Yahooooooo!” – you can shout (or output) whatever you want, but to
avoid a lawsuit from the makers of this dice game, I’m sticking with
“Yahooooooo!” here. Try this example out. If you get a “Yahooooooo!”
please let me know. For the curious, the odds of getting a “Yahooooooo!”
when running this program are approximately 1 in 1,297 or 0.08%.
Now that we know how to check multiple conditions using a single if-
structure and the && (and) operator, what can we do if we want our
conditions to be a bit more relaxed?
TABLE 17.4
Pairs of Values That Might be Used In the numberOfCoins
and levelTimer Variables.
numberOfCoins levelTimer Did the player get the bonus?
35 100 ???
60 100 ???
35 45 ???
60 45 ???
Level 8: Decisions, Decisions 177
TABLE 17.5
Solutions for the ??? in Table 17.4.
numberOfCoins levelTimer Did the player get the bonus?
TABLE 17.6
A “Truth Table” That Makes it Clearer When Our Player Will Receive a Score Bonus
numberOfCoins
numberOfCoins levelTimer > 50 || levelTimer
> 50 < 60 < 60 Explanation
extend this example a bit by adding a third bonus score condition if the
player captures enough enemies:
With this minor code update, we now give the player a bonus if they collect
enough coins OR complete the level in under 60 seconds OR capture at least
178 A Gamer’s Introduction to Programming in C#
10 enemies. As long as one of those conditions is true, the player will get the
bonus, even if one or more of the other conditions are false.
With this new understanding of || (or) logic, we can revisit our hero
profession example and add a few more professions:
Console.WriteLine();
int heroHealth = 0;
int specialSkillPoints = 0;
With very little code, we’ve doubled the number of professions that our
hero-building program can handle. In this case, we’ve said that fighters and
space marines are equivalent in starting stats, as are magicians and
professors, and bards and pirates. This might not seem entirely realistic,
but it does show off how helpful || (or) can be – and who says games need
to be realistic to be awesome, right?
We ended our && (and) section with the “Yahoooooo!” program, and I
want to end this section with a similar example to remind you that we can
create if-statements with more than two || (or) conditions:
Console.WriteLine("You rolled: " + die1 + " " + die2 + " " + die3
+ " " + die4 + " " + die5);
if(die1 == 6 || die2 == 6 || die3 == 6 || die4 == 6 || die5 == 6)
{
Console.WriteLine("Winner!");
}
else
{
Console.WriteLine("Better luck next time.");
}
This version of our 5-dice game is quite similar in code except for the if-
statement which is very different in logic. Our && (and)-based
“Yahoooooo!” game needed all five variables to have the same value before
we could called it a win. This version needs a six on any one (or more) of the
simulated dice variables. Again, for the curious, the odds of rolling at least
one 6 on five dice are approximately 59%. Compare that to the 0.08% odds
of all five dice having the same value and getting a “Yahooooo!” Which
game do you think will be easier to win? Try them both – it shouldn’t take
too many “rolls” to figure it out.
We now know that we can use && (and) to create strict logic expressions
where all of the conditions need to be true. We also know that we can use
|| (or) to create relaxed logic expressions where any of the conditions can
be true. But can we use && (and) and || (or) together?
180 A Gamer’s Introduction to Programming in C#
In this updated version, we are awarding stars based on the players score
OR the time it took them to make it through the level. Now the player earns
three stars if their score is 200 (or more) OR their time is under 30 seconds.
They earn two stars if their score is between 100 AND 199 OR their time is
Level 8: Decisions, Decisions 181
This is a fairly simple expression and it might seem like no big deal to go
from “if x is greater than 10” to “if not x is greater than 10”. The first
expression is true for numbers from 11 and higher, and the second one is
true for numbers 10 and lower. But I would like to make the case that the
following expression says the same thing as the “not” expression above and
is easier to understand:
Which one is easier for you to understand? I’m so convinced that the second
one is easier to grasp that you won’t see the ! (not) operator anywhere else
in this book (unless I come up with a really good reason to use it, and I’ll do
my best to let you know if it happens). It’s good to know that it exists
because you might see it in other people’s code, but I highly recommend
avoiding it whenever you can. It might seem like an easy “hack” to flip your
logic when you’re in a rush, but ! (not) never makes code easier to
understand or maintain.
Side Quests
Want to Know More?
More examples for more knowledge: Mixing and matching && (and) and ||
(or) is a great way to create cool and complex logic for your games and
programs. Look up more examples to see how interesting conditional logic
can be written.
Switch statements: There is another conditional structure that C# supports – it’s
called a switch statement. They’re a bit more complex than our if-statements
in terms of structure, but many people like them. Read up on switch
statements (including Bonus Level 4) and try them in some of the examples
from this level.
Want to Do More?
Make mistakes: Are you still making mistakes in your code? If you’re like me,
it’s a definite “yes”! Are you still making mistakes on purpose to learn even
more about C#? Here are some on-purpose mistakes to try out:
• Try changing any && to || (or any || to &&) to see what happens
when you test out the changed logic.
• Does this kind of expression work to say if x is less than 10 or
greater than 15: if( x < 10 || > 15)
A bolt of lightning: Rewrite the lighting spell example from this level and
change the player level and mana point variables from hard-coded to user
input. Then, test the program with a few input combinations to make sure
you can still cast lightning when appropriate.
More coding, more knowledge: Dig into this level’s code reward and Code
Quest 9. Remember to change the details to make them your own.
Level 8: Decisions, Decisions 183
• Prompt the user to enter the name of their hero. Store this in an
appropriate variable.
• Create a Random object and generate a random integer between 1
and 6. Based on the random number that was generated, use an if-
else-if and assign a variable to have one of six possible character
professions. I’m going to use the professions from the examples in
this level: fighter, space marine, magician, professor, bard, and pirate.
• Now, write another if-else-if that “randomly” assigns our charac-
ter’s health and skill points as follows:
• If the profession is fighter OR space marine – health between 80
and 100, skill points between 5 and 7.
• If the profession is magician OR professor – health between 55
and 70, skill points between 10 and 15.
• If the profession is bard OR pirate – health between 75 and 90,
skill points between 8 and 12.
• Let’s practice our && (and) logic with a fun potential character
boost. Generate and store two random integers between 1 and 4 – if
both random integers are 1s, give your character an extra 10 health
and an extra 2 skill points. Be sure to display a message if this
happens – so your player can be extra excited.
• Finally, display your player’s final character.
As always, work on one step at a time, and if you get stuck, take a look at
my solution for a hint.
184 A Gamer’s Introduction to Programming in C#
/*
This is a small program that builds a simple RPG-style character using some
randomness.
else
{
Console.WriteLine("Sorry, that profession isn’t available (yet).");
}
• review Level 8
• complete Code Quest 7 (optional)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
With this quest, we’re going to try and do something really practical. We’re
going to write the code to create our first loot box – or random loot drop, if
you prefer. Sure, it won’t have fancy visual effects, animations, or cool
sound effects yet (this example is revisited with some MonoGame flair in
the next book). What we’re going to write here is a simplified version of
what happens when you destroy a clay pot in a role-playing game, grab a
mystery box in a kart racer, or open a loot crate at the end of a match. We
have all the programming tools we need, including random number
generators, if-else-if statements, and boolean operators like && (and).
There is no user input in this program.
In this quest, you’re going to write code that will dispense “loot” with
different frequencies – this means that some of the loot will be very rare,
some will be sort of rare, some will be mostly common, and some will be
really common. Here are the approximate frequencies that I’ll outline
below, but you can change them (and the loot items) if you want (see the
side quests):
Here is the output from running my version of this quest 10 separate times.
To save some page-space I’m showing the output only instead of 10 full
screen shots:
Some loot has dropped – at your feet you see a glowing crystal sword. Awesome.
Some loot has dropped – at your feet you see a rusty steel sword. Awesome.
Some loot has dropped – at your feet you see a weak cardboard sword. Awesome.
Some loot has dropped – at your feet you see a rusty steel sword. Awesome.
188 A Gamer’s Introduction to Programming in C#
Some loot has dropped – at your feet you see a rusty steel sword. Awesome.
Some loot has dropped – at your feet you see a rusty steel sword. Awesome.
Some loot has dropped – at your feet you see a rusty steel sword. Awesome.
Some loot has dropped – at your feet you see a rusty steel sword. Awesome.
Some loot has dropped – at your feet you see a glowing crystal sword. Awesome.
Some loot has dropped – at your feet you see a weak cardboard sword. Awesome.
Side Quests
Shake off the rust: If you’re tired of getting rusty swords, try changing the
conditions so that they are all roughly equal – ranges of 1–20, 21–40, 41–60,
61–80, and 81–100. You should get fewer rusty swords, but you’ll get more sticks
and even flame swords too. Adjust the ranges to suit your own game ideas.
More loot, or maybe less: Our simulated loot box has five possible items and a
safety else clause. But there’s no reason we couldn’t have seven items, or maybe
just three. Adjust the number of branches in your if-else-if and either add more
swords (this is your time to shine!) or take some away. Don’t forget to leave the
safety else in place so that you can catch any logic errors in your conditions.
Make it your own: Do you find my output uninteresting or uninspiring? Do
you prefer loot boxes full of candy? Do you prefer loot boxes with spaceship
parts and pieces? Do you prefer loot boxes with candy spaceship parts and
pieces? Change the event text and the variables to make this Quest more fun
for you!!
My first loot box Part 2: Did you run the program a few times? Did you get
lots of rusty swords but no sticks and no flaming swords? Are you worried
that the loot frequencies aren’t working properly? In Code Quest 9, we’ll
use a loop to generate more loot and double check that it’s working the way
we intend. When you’re ready, work your way through Level 9 and into
Code Quest 9.
19
Level 9: Here We Go Again, and
Again
Level 8 Recap:
• Every time we press the jump button, our character leaps into the air.
• Every time we press the attack button, our super-charged laser
pistol fires a bolt across the screen.
• A wave of enemies appears, if we manage to defeat them, we
upgrade our defenses just in time for a new wave of enemies to
charge forward.
• We see a scene of colorful, shining jewels. We swap individual
jewels to make rows and columns of matching triplets. Those jewels
disappear, and random new ones take their place. This happens
again and again until we run out of time or match enough triplets to
move on to the next scene.
• We are given a quest. We complete the quest. Our experience points go
up. We become stronger, and we are given a new, more difficult, quest.
int myInt = 1;
if(myInt <= 3)
{
Console.WriteLine("Are you ready, brave adventurer, to learn about
loops?");
}
This code shows a simple if-statement with no else. All the familiar pieces
are there – a variable (called myInt), an if-statement that checks the variable
for a condition, and some code to run if the condition is true. When we
write, compile, and run it, we should get this output:
If that example isn’t clear, head back to Levels 7 and 8 for a quick review.
Now, let’s set up our first loop with only a couple of small changes (in
bold):
int myInt = 1;
while(myInt <= 3)
Level 9: Here We Go Again, and Again 191
{
Console.WriteLine("Are you ready, brave adventurer, to learn about
loops?");
myInt++;
}
Whoa – small change, big difference! The if-statement version was a one-off –
we checked one time to see if myInt was less than or equal to 3, and then we
moved on. The while loop, on the other hand, is like an if-statement that
runs and keeps checking until the condition is no longer true. That part is
important so let’s repeat it – a while loop keeps checking the condition until it
becomes false. If we draw a diagram of this program’s flow, it will look
something like this (Figure 19.1).
FIGURE 19.1
This is not our first flowchart, but this one shows how code can loop over-and-over again. Do
you see the condition that causes this looping in the illustration?
192 A Gamer’s Introduction to Programming in C#
int myInt = 1;
while(myInt <= 3)
{
Console.WriteLine(myInt + ": Are you ready, brave adventurer, to learn
about loops?");
myInt++;
}
With this small update to our code, the value of myInt will be displayed
along with the text of our message. This changes our output to:
Level 9: Here We Go Again, and Again 193
The important thing to take note of here is that thanks to myInt++, the
variable is changing – incrementing in this case – until eventually the
condition of our loop is false. This happens when myInt is no longer less
than or equal to 3. If we change our condition, then we change the number
of times our loop will run:
int myInt = 1;
while(myInt <= 5)
{
Console.WriteLine(myInt + ": Are you ready, brave adventurer, to learn
about loops?");
myInt++;
}
This new version will print our message five times – try it out for yourself.
Another important thing to notice is that the structure of our loops is always
(more or less) the same, even if we are doing different things in the loop
body. Here’s another example with similar structure, but performing a very
different task:
In this example, I’ve bolded the parts of the code that make up our loop –
a variable called rollCount that starts with a value of 1, a condition that
tells our loop to run as long as rollCount <= 10, and a variable update
statement inside the loop body so that eventually rollCount will be more
than 10 to stop the loop. The parts are almost identical to our previous
example – different variable names and a different stopping condition, but
otherwise the same purpose or flow. The difference is that we are now
simulating a twenty-sided die roll inside of our loop body instead of
simply outputting a text message. In my experience, it’s easier to learn
about loops when you can separate the looping structure from what is
happening inside the loop. Writing a loop to output a message, roll a die,
battle a bunch of enemies, or read user input values always starts out the
same way – with a variable, a condition, and a change that will eventually
make the condition false.
194 A Gamer’s Introduction to Programming in C#
This is a very small change that simply moves our stop value into a variable.
On its own, this change doesn’t do much – the program runs exactly the
same way. But it does help us to see how we can extend our example to
include user input:
Now, this program is getting cooler. In this update, our player gets to enter
how many die rolls they want to see. They might enter 1, or 10, or 1,000 – as
long as they enter a positive integer, our code should give them as many
dice rolls as they request. We’re talking about counting loops here and even
though we don’t know what value the user will enter, our rollCount
variable will start at 1 and count up until it reaches rollTimes. We don’t know
what the value of rollTimes will be and we don’t care either – our program
will take care of the counting for us. Code this program for yourself and run
it for a few different input integers. Make sure you really test it out with
some small integers, some large integers, and even some negative integers.
If you test it out with a really big integer, and you get tired of watching the
dice rolls scroll by, click the red stop square in Visual Studio to interrupt
the program.
Now, let’s take a look at some while loops that don’t count iterations –
instead, they just keep going until they’re told to stop.
Level 9: Here We Go Again, and Again 195
int healthPotions = 0;
Console.Write("How many health potions does your hero have (0 or more): ");
healthPotions = int.Parse(Console.ReadLine());
while(healthPotions < 0)
{
Console.WriteLine("Sorry, a negative number won’t work. Please try
again: ");
Console.Write("How many health potions does your hero have (0 or
more): ");
healthPotions = int.Parse(Console.ReadLine());
}
Console.WriteLine("Great! Your hero has " + healthPotions + " health
potions.");
I’ve bolded the key pieces of our loop structure – they are similar to our
counting loops, but also a bit different. We have a variable called
healthPotions, a condition that checks the value of healthPotions, and a
way for healthPotions to eventually change. The difference in this example
is that healthPotions doesn’t go up or down like in a counting loop; instead,
it is changed through user input. Try coding and running this example with
a few input values before reading the explanation of how this code works.
196 A Gamer’s Introduction to Programming in C#
In our health potion example, we are asking the user to enter a positive
number. If the user enters a positive number the first time we ask them, this
loop is actually skipped completely – if the user enters 5 for healthPotions
before the loop, the condition (healthPotions < 0) will be false. However, if
the user enters −10 or anything else that is less than 0, we’ll enter the loop
and try to get fresh new input from the user. Here’s the important part – as
long as the user keeps entering negative numbers, the code keeps executing
the loop! It could run forever if it never gets positive input from the user.
This is what makes a sentinel loop different from a counting loop – we
really don’t know when they will stop. Let’s take a look at how boolean
variables, sometimes called flags, can help with these kinds of loops.
I’ve hard-coded this example to simplify it a bit. Most of the code should
seem familiar, but the new part (in bold) sets a bool variable to true and
plugs that variable directly into our if-statement. It’s very important to
remember (from Level 7) that if-statements use true and false conditions
when deciding whether or not to run the attached code. Here, we’re simply
saving it the trouble of comparing anything and saying “our hero is truly
poisoned”. Try changing the value of isPoisoned variable to false and see
what happens (hint: our hero won’t take extra damage, and you won’t see
the poisoned output).
This kind of boolean variable is called a flag, and its job is to keep track of
some true or false fact in our program. Even though the above example uses
an if-statement, we can extend this idea to our sentinel (non-counting or
open-ended) while loops like this:
This program is very similar in structure to the if-version above, but now
instead of taking damage one time and moving on, our hero is going to keep
taking damage as long as they are poisoned. How will the loop end? In this
case, our hero has a 50/50 chance of self-curing each time the loop runs.
Inside our loop body, we are generating a random number between 1 and
100. If that number is less than 50 (about 50% chance), we will change our
poisoned flag from true to false, and when the code returns to check the
loop condition, the loop will stop.
If you’re going to use flags in your games and programs, be sure to name
them descriptively. I use variable names that read like questions –
isPoisoned, isMovingLeft, isDead – to help make it clearer that they are
addressing a true or false condition. Also, use comments to make sure that
you, or anyone else reading your code, can follow the logic of your flags.
198 A Gamer’s Introduction to Programming in C#
Speaking of logic, let’s see how we can put conditions that are more
complex into our while loops. Take a deep breath and battle-on brave
adventurer!
int healthPotions = 0;
Console.Write("How many health potions does your hero have (0 to 5): ");
healthPotions = int.Parse(Console.ReadLine());
while(healthPotions < 0 || healthPotions > 5)
{
Console.WriteLine("Sorry, a that’s not a valid number of potions.
Please try again: ");
Console.Write("How many health potions does your hero have (0 to 5): ");
healthPotions = int.Parse(Console.ReadLine());
}
Very little has changed in the program. The original version required our
user to enter a number greater than zero, and this update adds an || (or)
condition that says we aren’t going to allow any input greater than 5. Be
sure to read the while logic out loud to yourself – you should be saying
something like “while the value in healthPotions is less than 0 OR the value
in healthPotions is greater than 5”. Our while loop is now true (and will
prompt the user for fresh input) if the input value is less than 0 or greater
than 5. We can make this program a bit more interesting by writing another
loop to actually use the potions we’ve collected and introduce some &&
(and) logic:
int healthPotions = 0;
int heroHealth = 75;
Console.Write("How many health potions does your hero have (0 or 5): ");
healthPotions = int.Parse(Console.ReadLine());
while(healthPotions < 0 || healthPotions > 5)
{
Level 9: Here We Go Again, and Again 199
Our code is getting longer, and more complex, but here are some things to
notice from this extended example:
• There’s more than one loop – programs can have many loops!
• The second loop we’ve added has two conditions connected with
&& (and). This means as long as both conditions are true, the loop
will keep running. It also means that we need to update both
condition variables inside the loop body to make sure that, at some
point, the loop will stop.
• One condition increments, one condition decrements. It’s very
common to increment loop variables using ++. After all, it’s an easy
way to go up by one. But it’s not the only way that we can change a
variable. Our second loop decrements healthPotions using – – (to
show that we’ve used up one health potion), and heroHealth goes up
by 10. You can be as creative as you like with your changing variables
– just make sure that however they change, they will eventually cause
the loop to stop.
We’re going to see more examples of while loops that use && (and) and ||
(or) in the coming levels. In the meantime, let’s take a look at how to
restructure our counting loops in a more compact way.
It can be hard to track all three of these important elements when you’re first
learning about loops. Luckily, there’s a more compact way of writing our
counting loops using a for-loop structure. In general, it looks something like this:
Can you see all three of our loop elements in that brief and wordy
description? Let’s go back to our first while-loop example:
int myInt = 1;
while(myInt <= 3)
{
Console.WriteLine("Are you ready, brave adventurer, to learn about
loops?");
myInt++;
}
Before we do any more examples, let’s use this simple one to clarify some
important points about for-loops:
With those points in mind, here is our die-rolling example reworked to have a
for-loop (go back and take a look at the while loop version for comparison):
Again, notice that we aren’t updating rollCount inside the loop body, just in
the loop header. Here’s a version with user input for the value of rollTimes:
And finally, here’s a brand-new example – can you tell what this code is doing:
if(heroHealth <= 0)
Console.WriteLine("I’m sorry to say, your hero has perished.");
else
Console.WriteLine("After the battle, your hero has " + heroHealth + "
health remaining.");
202 A Gamer’s Introduction to Programming in C#
If you’re not sure from reading the code, try writing, compiling, and running
it for yourself – that might help to clarify how this little goblin battle simulator
works. Pay close attention to the for-loop that simulates our many attacking
goblins. It might be a bit more fun and game-like, but it has the same elements
as all the other examples in this level – a variable to watch, a condition to
check, and a change that eventually stops the loop. All the loop (both
counting while, sentinel while, and for) examples have something else in
common – they all have a before, during, and after phase. Let’s take a quick
look at what that means and why it’s important when writing looping logic.
• Before: Our loop hasn’t started yet – initialize any variables that we
will need in our header and loop body, display any pre-loop
information that is relevant to our user, and collect any input that
might affect how our loop functions.
• During: This is our loop body – any code that needs to repeat goes
in here. Don’t put any output (or input) statements in here that
aren’t necessary. The exceptions are if you are debugging a loop
problem (Cutscene 3), or if you need to collect input in order to stop
the loop from running (a sentinel condition). Try to avoid declaring
any new variables inside the loop body – they will not be accessible
after the loop ends.
• After: Our loop is complete – typically this is where we show off
(output) the results of our loop’s work or simply continue with our
program using our loop-updated variables.
Let’s revisit and extend our tiny tournament stats program from Code
Quest 6. Using loops, we can collect as many scores as we want and make
the program even more useful. We have to do a bit more work to calculate
the maximum tournament score, but it’s an example that demonstrates
what needs to happen before, during, and after the loop.
Try coding and running this program for yourself. It’s longer than some of
our other examples, but it’s also pretty useful (and maybe a bit cool). Pay
attention to the comments, especially the bolded notes. As a mini-practice
side quest, revisit some of the previous examples in this level and see if you
can identify what is happening in each of the three stages – before, during,
and after the loop.
Nested Loops
In another not-so-surprising similarity, we can nest loops just like we nested
if-statements (Level 7). We do this any time we want to repeat something
that repeats. Here’s our die-rolling example from earlier, but this time it’s
wrapped in a loop so that we can run the whole program more than once:
while(runAgain == "yes")
{
Console.Write("How many rolls of a d20 would you like?: ");
int rollTimes = int.Parse(Console.ReadLine());
for(int rollCount = 1; rollCount <= rollTimes; rollCount++)
{
currentDieValue = rng.Next(1,21);
Console.WriteLine("You rolled: " + currentDieValue);
}
In this example, we have our die-rolling code doing exactly what it was
doing earlier in the level, but now we can repeat that work over-and-over
again. By wrapping our old code in a “do you want to run this again”-style
sentinel loop, we have successfully nested two loops. Here are some points
to keep in mind when nesting one loop inside of another:
• The first loop is usually called the outer loop. The loop in the inside
is called the inner loop.
• You can nest any kind of loop inside of any other kind of loop – for
loops, while loops, counting loops, and sentinel loops.
• There’s no practical limit to how many times you can nest loops, but
it will eventually get tricky to keep track of the code and logic.
• Each loop still needs a variable to watch, a condition that will
change, and a way to change the variable to stop the loop.
• Remember, the inner loop is looping, and the outer loop is also
looping. This means that the inner loop is going to run often – make
sure that’s the logic you’re looking for. Don’t nest loops when what
you actually want is one loop that runs after another.
We’ll see more examples of nested loops as we work our way through the
upcoming levels. Here’s a little spoiler of what’s coming should you
continue your journey with MonoGame in the next book – every loop you
write in MonoGame is technically a nested loop because MonoGame is
itself a loop. Game engines and frameworks have built-in loops. Strange,
but true.
In the last two sections of this level, we’ll look at the most common
mistakes people make when writing loops – infinite loops and loops that
don’t run at all.
Level 9: Here We Go Again, and Again 205
Infinite Loops
Here’s some code that doesn’t work properly – there’s a logic error. Even
with this issue, try running this example to see what happens:
int myInt = 1;
while(myInt <= 3)
{
Console.WriteLine("Are you ready, brave adventurer, to learn about
loops?");
}
If all goes well, or not-so-well I guess, you’re going to get a whole lot of
“brave adventurer” messages. In fact, if you leave it alone, it will run
infinitely – forever! Writing your first infinite loop is a sort of programmer’s
rite of passage. This code comes from the very first example of this level –
with one small change. Can you see why this code has gone from outputting
three times to infinite messages?
We have two of the loop requirements – a variable to watch and a
condition to control when the loop stops, but it’s missing the variable
change (myInt++;). As a result, the value of myInt never changes, so the
loop condition is never-ever false and the loop goes on, and on, and on …
Forgetting to update or incorrectly updating the loop variable is a very
common logic mistake, and it’s one of the reasons that programmers use
for-loops when they can. Even though it’s possible to create an infinite for-
loop, it’s less common since we can see all three loop requirements side-by-
side in the for-loop header.
How can you make sure that you don’t create infinite loops in your code?
Always make sure you are updating your variable in a way that it will
eventually make the loop condition false. Oh, and how do you stop an
infinite loop from running in Visual Studio (maybe your example is still
running now)? When your program or game is running, a red stop square
appears to the right of the green play triangle that you used to start it.
Usually, our programs run quickly, and we don’t really notice this new
button, but with an infinite loop, it will stick around. Click the red stop
square, and you’ll be able to edit your code again.
int myInt = 5;
while(myInt <= 3)
{
Console.WriteLine("Are you ready, brave adventurer, to learn about
loops?");
myInt++;
}
Console.WriteLine("Program complete");
If you run this example for yourself, you won’t need to worry about the red
stop square that we discussed above – this will run very quickly. Even
though all three loop requirements are present – variable, condition, and
variable update – there is a small logic error that keeps this loop from ever
running. Look closely at the variable initialization – it sets the value of
myInt to 5. Unfortunately, the loop condition is looking for a value that is
less than or equal to 3. Like infinite loops, non-running loops are common
logic errors.
The best way to make sure your loops run is to carefully check that your
variables are properly set before the loop is set to start running. This is a bit
tricky if you’re setting the variable with user input, but if this is the case,
give your player or user clear instructions on what the program needs.
Side Quests
Want to Know More?
Another type of loop: Did you notice in our while-loop examples, it’s possible
for a while loop to not run at all if the initial condition isn’t true? In fact, all
while loop will run zero or more times depending on the value stored in our
variable and the condition that we’re checking. But what if you absolutely
need the loop code to run at least once – no matter what the variable and
condition are doing? That’s where do-while loops come in. They are very
similar to while loops but when they check the condition is a bit different
and that makes sure they run at least once. Look them up to see if they
might be helpful in your code.
Want to Do More?
Examples revisited, but with more excitement: Many of the examples in this
level have been hard-coded with values to help keep the code more
compact. Revisit a few of them (maybe your favorites) and rewrite them
to have random values or user input. I recommend checking out the poison
hero, the goblin attack, and the health potion examples.
Level 9: Here We Go Again, and Again 207
Make mistakes – before, during, and after edition: The before, during, and after
phases of loops (and if-statements) might make sense to you, but what
happens if you start moving some of the “before” statements into the
loop? Or what about moving some of the after statements into the loop?
Perhaps moving some of the statements in the loop to the before or after
sections.
The variable is changing, but not in a good way: For our infinite loop
example, I removed the variable-changing statement (myInt++;) com
pletely. What happens if you put that statement back but write it as
myInt--; instead? Sometimes we actually do change the variable, but we
change it “wrong”.
/*
This is a random number guessing game!
• review Level 9
• complete Code Quest 8 (required)
• create a new Visual Studio Console project. If you need a
refresher, detailed instructions can be found at the beginning of
Code Quest 1.
So, you completed Code Quest 8, and looking down at the pile of rusty steel
swords at your feet, you’re wondering if your loot box is working correctly or
malfunctioning. It’s a fair thing to wonder, and it highlights the importance of
testing our games and programs. It’s not always enough to simply write the
code and run some quick tests – sometimes we need to really dig in and make
sure that the code we’ve written works exactly the way we want it to. In this
quest, we’re going to use loops to really test our Code Quest 8 loot box. If
you haven’t completed Code Quest 8, go back and get it done – you’re going
to need it.
The main task here is to make sure that our loot box is working the way
we want it to. It can be hard to tell if random elements are working
properly without testing many, many times. We’re going to use a while
loop or a for loop – your choice – to pull many loot items from our loot
box. The user or player will decide exactly how many times. Then, we’re
going to use variables to keep track of how many of each different type
of item we’ve received. If we do this enough times, we should either find
out that the loot box isn’t working properly or find out that our code is
working perfectly.
This quest should work whether you completed Code Quest 8 as I
described or if you changed it up a bit. You’ll simply need to keep track of
the items and frequencies in your own code instead of comparing them
to mine.
Ready to empty our “infinite” loot box and count all the items? Head to
the Main Quest below.
1. Start with your code from Code Quest 8 – I recommend copying and
pasting all of the code from your previous Code Quest 8’s Main().
Test this to make sure it works before moving on to the next step.
2. At the top of your program, create some int variables to store the counts
of your different loot items. You’ll need a variable for each possible
loot item you might pull out. My version will need five variables: one
for the sticks, the cardboard swords, the steel swords, the crystal
swords, and the flame swords. If you added or removed loot items
from your version, adjust your number of variables accordingly.
3. Next, prompt the user to enter the number of loot items they want
to pull from the loot box. Parse and store the user’s input. This is the
variable that will tell our loop when to stop.
4. Below your user input statements, wrap the rest of the code in a
while or a for loop (your choice) that runs the number of times
input by the user. You’ll need to declare and initialize another
variable that counts how many times the loop has already run so
that your loop will know when to stop - in other words, you need a
counter variable to compare to the stopping number. What is in the
loop body should be all the code needed to create a random integer
between 1 and 100, the variable that stores the loot item text, and
the if-else-if that assigns the loot item text according to the random
number value – in short, all of the code from the Code Quest 8. If
you’re using a while loop, be sure to adjust your counting variable
before the end of the loop body or you will write an infinite loop.
5. In your if-else-if statement – the one from Code Quest 8 that picks a
specific loot item – update your loot count variables each time you
pull a particular type of loot. In my example, if I pull a stick out of the
loot box, my new stick count variable is incremented by 1. If I pull a
cardboard sword, that variable gets incremented, and so on. Make
sure you are specific variable that counts each of the loot types.
6. Finally, remove the old quest output from the program – we no
longer want to know each individual item that is pulled from the
loot box. Instead, after your loop body, output the value of your five
loot counting variables so that you (or your user) can see how many
items of each loot type were produced. With a bit of math (divide
212 A Gamer’s Introduction to Programming in C#
Here is the output from my version, run three separate times (Figures 20.1,
20.2, and 20.3).
FIGURE 20.1
Let’s open ten loot boxes to see what we find …
FIGURE 20.2
How about 1,000 loot boxes?
FIGURE 20.3
How about one million loot boxes?
Code Quest 9: My First Loot Box (Part 2) 213
You might notice that the more loot we pull from the boxes, the closer we
get to the expected loot frequencies that we set up in Code Quest 8 (or that
you set up in your changes to Code Quest 8). This kind of testing is one way
that we can make sure our random number generation and logic structures
are working the way we want them to.
Side Quests
Show me all the loot: If you’re really curious about each individual piece of
loot, print them out as they are created. There will be a lot of output, but
sometimes it’s cool to see everything that’s happening instead of just the
summary values.
Percentages are easier to see: It can be hard to tell from one million loot box pulls
if we have 40% steel swords and 5% flame swords. To make it a bit easier, do a
bit of math using the number you collected from the user to convert your raw
counts into percentages before outputting them to the console. This will give
you rough percentages that should be easier to digest.
Make it your own (again): Do you find my output uninteresting or
uninspiring? Do you prefer loot boxes full of jewels? Do you prefer loot
boxes with armor and trinkets? Do you prefer loot boxes with bejewelled
armor and trinkets? Change the event text and the variables to make this
quest more fun for you!!
21
Cutscene 3: Yikes, Gremlins!
FIGURE 21.1
Gremlins may be cute, but they can cause lots of tricky errors in our code. Silly gremlins.
problem. As a programmer, I use the term “bug” when there’s something not
working. Gamers often say that a game “glitched-out” when something bad
or unexpected happens. During World War II, pilots would use “gremlins” to
describe mysterious issues with the machinery of their planes. I know,
gremlins isn’t a common software or programming term, but I think it’s a fun
and game-y way to describe weird problems (Figure 21.1).
Whatever you want to call them, errors in our programs or games are
(almost1) never a good thing. At best they cause unexpected and erratic
behavior, at worst they can cause crashes (the program stops working
completely) and even corrupt our data (like game saves that can’t be loaded
any more).
Before we look at ways to avoid, seek out, and eliminate problems in our
code, let’s be clear on what we’re actually looking for.
Kinds of Errors
Programming errors come in three common varieties – two we are familiar
with and one that is being introduced for the first time:
216 A Gamer’s Introduction to Programming in C#
Let’s take a look at some of the ways to avoid, find, and remove errors from
our code and we’ll start by removing some of the code itself!
Reduce Code
There’s an old programming joke that I heard when I was (much) younger
and it goes something like this:
Okay, maybe you’re not doubled over in a fit of laughter, but there are some
nuggets of truth hidden between the setup and the punchline – all programs
potentially have errors, and the more code we write, the more likely it is
that errors will pop up. This is why many experienced programmers tell
beginners to write only the statements that are needed – and no more.
Cutscene 3: Yikes, Gremlins! 217
What does this mean? Let’s revisit some code from Level 7:
If we have the same statement in both the if and else parts, we should
really move it outside the if-statement. Why is this such a big deal? After
all, we saved only one line of code in an example that is about 10
statements long. The big deal is that every extra statement we write in
our code is:
• more to write
• more to review when we’re debugging or looking for problems
• an opportunity for more errors to creep into our program
It’s easy to dismiss this example as trivial or silly, but in reality, every
statement we are able to remove is a statement that future-us doesn’t have to
worry about. Beginners often write “extra” code because it helps to make the
logic clearer to them (at least at first). I strongly agree with this approach –
you should start out writing code you understand, even if it is longer than it
needs to be. With practice, you’ll learn to avoid some of the unnecessary lines
of code. Until then, try reviewing your code from time to time and ask, “could
any of these statements be removed without affecting how the program
works”? If the answer is yes, get rid of them!
There are other ways that we reduce the amount of code in our games and
programs. We use loops (Level 9) to repeat the same task(s) over and over.
Methods (Level 10) are another way for us to write a set of related
statements once and call or execute them repeatedly. Even objects (Levels
10 and 11) can help us reduce and reuse the code we write.
We’re not here to write programs and games that have so few statements
that they aren’t fun or interesting. Instead, try to accept the idea that bug
hunting and extermination are easier when we reduce the amount of not-so-
useful code.
Now, what do we do after we’ve chopped a few useless statements from
our program but it’s still not working the way we want it to? Begin the
testing phase!
A-OK, we can be confident that our players and users will have an enjoyable
experience. If we test and things go a bit (or a lot) sideways, then we should
really find the problem in the code and fix it. Testing our programs for errors
is something that becomes more important and also more difficult as our
games and programs get more complicated. Entire textbooks have been
written on how to test complex software. In part, this is because complexity
increases the chance of introducing errors, but it’s also because there are
more “things” to test. Think about our very first example – Hello, World! This
program has a single output line that uses a string literal. As long as there are
no syntax errors and we are able to compile and run it, if we see the correct
output, then we’ve officially and thoroughly tested the code. In fact, many of
our beginning examples are like this – if they successfully compile and run,
they’ve also been tested. But, as we’ve journeyed through the levels together,
our coding skills have improved, our programs have grown in complexity,
and so has our need for more testing.
We can’t always guarantee an error-free experience, but there are some
common testing strategies that will help you to find and squash some of
them – and that’s a lot better than finding and squashing none of them. Here
are some testing tips to consider as your programs get more complex:
• Test often: Sometimes we get “in the zone” and write lots of code
before taking a break to compile and run what we’ve written. The
more code we write, the more we have to work through if an error
appears - especially if it’s a tricky logic error. Try to write only a
few lines (or whatever you think is reasonable) between test runs.
If an error shows up, it’s likely because of the few lines you just
added – that should be easier to solve, right?
• Comment out some code and test again: One of the easiest ways to
remove misbehaving code is to simply comment it out. If you’re
worried that a particular statement or set of statements are causing
you problems, use line comments (//) or a block comment (/* */)
to remove that suspicious code temporarily. If the error still shows
up – it’s not in the commented-out code, and you can uncomment it
to bring it back. If the error is gone, you’ve found the troublesome
statements, and you can zero-in on that pesky bug. Many beginners
delete code (instead of commenting it out) only to find that the
deleted code wasn’t actually the problem. Commenting is often a
much easier and safer way to isolate errors.
• Test more than the minimum: Don’t be “lazy” with your testing.
Sometimes errors will show up in very obvious and immediate
ways, other times they hide in the obscure shadows of what our
players and users will try to do. How much you need to test and
what kinds of input or data you need to test with will very much
depend on the complexity of what you’re testing and how confident
220 A Gamer’s Introduction to Programming in C#
you want to be that it’s error-free. In general, test more than the
minimum of cases, input, or operations.
• Don’t overdo it: If you spend all your time testing, you won’t spend
much time working on your programming project. Yes, testing is
important, but so is pushing forward and getting your awesome
game or program completed. Find the happy place where you have
tested enough that you feel confident things are likely to work most
of the time (if not all of the time) and move on. You can always
come back and do more testing later if new issues come up or when
you need a break from coding.
• Ask for help: I have a sure-fire way of making my students grumpy.
When they are quietly staring at their code in frustration, I’ll wander
over and ask them “what seems to be the problem”? They’ll quickly
explain the issue and – usually in no time at all – I’ll show them
exactly where the problem is. It seems like I’ve got super-debugging
powers, but in truth, it’s a combination of two things: 1) I have more
than 30 years of experience solving errors in my code and in my
students’ code and 2) fresh eyes can often see a problem where tired
(and frustrated) eyes can’t. It’s good to practice finding and
squashing your own bugs, but when you’ve been at it for a while
and the frustration is starting to build, try asking someone else to take
a look for you. Whether you ask them to run the program a few times
to help find the bugs or ask them to help review the code to squash
them, a fresh perspective can often be really helpful.
Let’s say you’ve decided to buy into what I’ve recommended so far in this
cutscene. You’ve written a small console game of moderate complexity. You
found a few lines of code that weren’t doing anything useful, and you’ve set
them free (deleted them or commented them out). You and your game
design teammates have tested the game and found 10 bugs – nine of which
you were able to find and exterminate. But no matter how many times you
test and no matter how long you all stare at the code, no one can seem to
track down the final gremlin in the game.
It’s time to put on your cap of +1 initiative and learn how to track down
the trickiest errors with the help of tracer code.
if (playerGuess == secretNumber)
{
Console.WriteLine("Amazing!!! You actually guessed it!");
}
else if (playerGuess < secretNumber)
{
Console.WriteLine("Your guess was too low.");
}
else
{
Console.WriteLine("Your guess was too high.");
}
}
What I really like about this example is that it has almost every topic we’ve
covered so far, it’s a complete game (even if it’s a simple one), and it’s
complex enough that if there’s an error in the code, it might be tricky to find.
If you haven’t already coded and run this game a few times, I strongly
recommend doing that before moving on. What I’m going to show you next
is going to make the output much more “messy” but also much more
helpful in finding any errors that might come up. Take a good look at the
bolded output statements in the updated version:
int guessCount = 0;
int playerGuess = -1;
Console.WriteLine("Starting value of guessCount: " + guessCount);
Console.WriteLine("Starting value of playerGuess: " + playerGuess);
while (playerGuess!= secretNumber)
{
Console.WriteLine("At the start of the while loop.");
guessCount++;
Console.WriteLine("Value of guessCount: " + guessCount);
Console.Write("I’m thinking of a number between 1 and 100, your guess
is: ");
playerGuess = int.Parse(Console.ReadLine());
Console.WriteLine("Current value of secretNumber: " + secretNumber);
Console.WriteLine("Current value of guessCount: " + guessCount);
Console.WriteLine("Current value of playerGuess: " + playerGuess);
if (playerGuess == secretNumber)
{
Console.WriteLine("Amazing!!! You actually guessed it!");
}
else if (playerGuess < secretNumber)
{
Console.WriteLine("Your guess was too low.");
}
else
{
Console.WriteLine("Your guess was too high.");
}
Console.WriteLine("End of the while loop.");
}
I know what you’re thinking – what a mess! But the point of all these output
statements is to give us more information about the game while it runs.
Running this version of the game produces output like this:
It’s likely that I’ve got too much tracer code in this example since I would
normally only add statements where I think there might be errors in my
program. But, despite the messier code and messier output, it does show the
places that we often want to get a bit more clarity on how our program is
running – variable values, double-checking user input, loop starting and
ending conditions, and so on. Normally I’d put some tracer code in my if-
statement logic, but this example only has output statements in the if, so
they are acting like tracer code already. Don’t forget, when you find your
mysterious logic or runtime error, be sure to comment out (or delete) your
trace code output statements – your player isn’t going to want to see that
messy output.
There is no right or wrong way to use tracer code in your programs,
though some companies or teams might have standards that you’ll need to
follow. It’s simply another programming tool that helps us to write fun,
engaging, and bug-reduced games and programs.
update, and use your code if it looks unorganized and confusing? Maybe …
but maybe not.
Following the style guidelines below will help your code look organized,
clear, and even professional – the kind of code that is either bug-free or
where bugs can more easily be found and squashed:
• Always use descriptive variable names that describe the value being
stored – score, health, and numberOfGoblins are better variable
names than s, h, and gobs.
• Use proper camel case (Level 3) for your variable names –
numberOfGoblins is easier to read than numberofgoblins.
• Code inside curly braces (for if-statements, loops, or methods) should
be indented. Visual Studio will help you with this one.
• Blank spaces can be used to break up chunks of code as-needed.
• Try to avoid excessively long statements – break them into multiple
statements when you can.
• There should only be one statement per line (wherever possible).
• Line comments and block comments should be used to describe any
statements or blocks of code that need some description.
For examples of proper style, take a look at any of the code rewards at the
end of the levels. I’ve tried to set a good example for you to follow with
each one.
All this talk about avoiding, finding, and fixing problematic code can
feel rather discouraging. Let’s finish this cutscene with a bit of positive
reinforcement and shed some light on where these errors come from.
making sure that it works properly, but testing takes time and we
often do just enough to say we “checked it”. Many bugs are found
in games that are “under tested” before they are released. Players
love to test the limits of what they can and can’t do for input
(keyboard, mouse, controllers, you name it), where they can and
can’t go in your levels, and what they can and can’t do with
mechanics – if there are glitches left behind by the developers, the
players will (unfortunately) find them.
• Poor style: Despite my mom’s insistence that I clean up my room as a
kid, I never did. To me, it was a “waste of time”. Even though
everything was scattered everywhere, I knew that I didn’t want to
spend my time tidying – it made more sense to me to spend the
time looking for things when I needed them. Turns out, I probably
wasted way more time looking for things than the time it would
have taken me to tidy up my chaotic room. If you’re scratching
your head wondering what this has to do with programming, here
it comes – messy code is like a messy room. It’s easy to write code
“fast and messy” today, but when it comes to fixing issues
tomorrow, you’re going to spend way more time working through
the mess you left for yourself. Do yourself a favor and follow the
C# style conventions from the last section. They may take you a
bit more time and effort in the moment, but future-you will be so
grateful that past-you wrote clean, easy-to-read code.
In case you’re wondering, I now keep my room and code very clean. Present
and future-me are both very grateful that past-me eventually learned this
lesson.
Side Quests
Want to Know More?
Is this everything there is to know about bugs and testing? No, it’s not! There are
lots of ways to test and remove errors from your code. Do a bit of digging
and see what other cool tips, tools, and techniques you can find.
But you didn’t actually tell us how to deal with runtime errors: This is true, I
skipped that one. Runtime errors are most common when dealing with
user input on the console, when accessing array elements, and when trying
to use an object that hasn’t been properly initialized. It’s important to deal
with these errors when we are writing software that will be used by more
Cutscene 3: Yikes, Gremlins! 227
people than us, our classmates, and our friends, but it’s not really
important in the context of our adventure together. There are examples
of dealing with runtime errors caused by bad user input in Bonus Level 3,
and I encourage you to look up try and catch blocks, the TryParse()
method, and other ways of handling runtime errors.
Note
1. There are cases where an accidental logic error actually turns into a new game (or
program) feature. A mistake in writing the jump mechanic might lead to a flying
mechanic that ends up in the final game. But these kinds of bug-becomes-feature
events are rare.
22
Level 10: Crafting Custom Objects
Level 9 Recap:
Level 11 will make our games and programs much more interesting and
(eventually) easier to code.
In this level, we’re going to learn how to write object classes or “blueprints”
that will define what potential objects can do. Don’t worry if you’re not sure
what that means yet, that’s what we’re here to figure out together. We’re also
going to introduce the idea of methods (sometimes called functions). In the
next level, you’ll learn how (and where) to use these blueprints to create
objects that we can use and control in our programs and games.
If you want, you can skip this level (and Level 11) and jump to some of the
early MonoGame content and examples in the next book. You have lots of
awesome programming knowledge now about data types, variables, math
operations, random numbers, if-statements, and loops. This is all you need to
get started with MonoGame. However, to make things more interesting, fun,
and like real game design, the later MonoGame examples use objects – so
come back here and go through this level (and Level 11) when you need them.
Either way, we’ve saved the most challenging – and rewarding – levels for
the end of our console journey. In true video game fashion, you’re going to
use everything you’ve learned so far. So, put on your best armor, recharge
your energy, and rev the engine of your kart to maximum, because our
adventure is about to become objectively (pun intended, yet again) cooler.
It would be great if this series of books could introduce you to all of the major
programming paradigms that are commonly used today. Who knows, maybe
more “Welcome Brave Adventurer” books will do just that. But, for now, let’s
focus on the popular, robust, and effective OOP!
FIGURE 22.1
Object-oriented programming means thinking of our game elements as having attributes and
behaviors. It really helps if you can visualize your objects like this courageous hero and sturdy die.
Before now, we wrote code that calculated, looped, and logiced, but in
this level, we are going to simulate. To better understand what this means,
let’s revisit the code for our die and our simple hero (from previous levels).
The die is just a few statements like this:
This works, but it doesn’t seem very natural. In real life, we would pick up
our pre-configured die and then roll it. When the die stops rolling (across
the counter top, the table, the floor, or down the stairs), we would then take
a look at what number was facing up. Our code – while functional – is
missing those real-world “behaviors”. What about our simple hero:
Again, we have the same issue as our die – this code works, but it doesn’t
feel very heroic. Instead of a loose collection of variables and statements,
our hero should feel more whole with behaviors like taking damage,
dealing damage, and even perishing (oh no!). If we’re going to put this hero
in a game, we might also want them to have coordinates (where they are on
the screen), inventory (what they are carrying), and even animations.
Surely, there has to be a better way to represent these cool game-y things
than just a bunch of loosey-goosey C# statements, right? What we’re going
to start doing in this level is bundle our game object statements into a new
structure called an object class. Using object classes, we’ll be able to create
attributes (properties) and behaviors (actions) that more easily and realisti
cally simulate the things we find in games (Figure 22.2).
Don’t worry! The code we’ve learned so far isn’t going away – many of
the statements we write will seem very familiar it’s the structure and how
we interact with that structure that’s going to be new.
It is very natural to find the idea and the structure of OOP to be confusing
and even frustrating at first. In the case of simple objects, like dice, we’re
actually going to create way more code than we need to – just so we can
treat them like objects instead of simple variables. But, as our programs and
games get more complex and as you become more comfortable with
writing and using objects, you’ll find that it really is the most effective,
efficient, and natural way to represent our game elements. Give it time, and
more importantly, give it practice, and your games will spring to life with
OOP-y goodness!!
My goal with this level (and Level 11) is to introduce you to the
fundamentals of OOP so that you can make more interesting console (and
MonoGame) programs. You can certainly do cool things without learning
to write your own objects, but with them, your games and programs will
really shine. It’s also worth repeating that most modern game engines
and app frameworks take an object-oriented approach. It is definitely an
important topic, and while we’re only going to touch on the essentials
here, I encourage you to keep learning about OOP after you’re done with
these books.
232 A Gamer’s Introduction to Programming in C#
FIGURE 22.2
Sometimes it’s helpful to simplify our idea of what objects are. Here we are using circles to
show what attributes each of our object types has, as well as the behaviors that allow us to
inspect and alter them.
But my way isn’t the only way. There are lots of useful shortcuts and
creative efficiencies that are built into C# (and Visual Studio) that can
help you write awesome objects in the future. As your programming
adventure extends beyond these books, you might find yourself on a
team that has its own “best practices” for writing object classes. Even if
you decide to code-solo, you’ll develop your own way of doing things
over time.
This level is where we’re going to learn the first part of OOP – writing
object classes, sometimes called object blueprints. There will be lots of
code examples for you to follow along with, and while you’ll be able to
compile the code to make sure the syntax is accurate, you won’t really be
able to run and test the objects until our journey reaches the next level. In
Level 11, we’re going to look at how to use the object blueprints to create
and interact with objects. We’ll use die and hero object examples through
both levels, and there will be a bonus object waiting for you in the code
reward.
Alright adventurer, we’ve set up the quest, we’ve given it some back
ground, and we know what’s waiting for us when it’s complete. Are you
ready to leave the inn (or space dock, or cozy farm, or start screen) and get
started?
Either way, you should see a window that looks something like this
(Figure 22.3).
Make sure you highlight the option that says Class and has the small C#
icon beside it. Now, and this part is very important, at the bottom you’re
going to specify the name of your class. By default, it says Class1.cs, but
234 A Gamer’s Introduction to Programming in C#
FIGURE 22.3
Creating a new class file in our Visual Studio project means opening a new window like this
one. Be sure to name your class file in a way that properly reflects the type of object you are
going to describe with code.
when we write object classes, we need to name the file with the same name
as the type of object we’re creating. What does this mean? If I’m creating a
die class, then I want my file to be named Die.cs. If I’m creating a hero class,
then I want my file to be called Hero.cs.
If you’re following along with this level’s examples, create a new Visual
Studio project and add a new Die.cs and Hero.cs file. When you’re done,
you should see the files in your solution explorer like this (Figure 22.4).
If you want to see the code that Visual Studio creates by default, click each
of the files in the solution explorer. They should open in the main editor
window, and, aside from a different project or namespace, the Hero class
should look like this:
namespace MyFirstObjectClasses
{
FIGURE 22.4
The Solution Explorer pane will help you keep track of all your object class files.
I know, neither file has much to look at yet, but let’s consider what code is
(and isn’t) there:
• In terms of structure and syntax, the two object classes are similar to
the code we’ve seen and written before.
• There is a namespace, just like all our previous C# files – mine is
MyFirstObjectClasses because that is the name of my Visual Studio
project.
• Previously, we would have worked in a class called Program
(internal class Program), and we will again in Level 11. Here our
example classes are called Hero and Die.
• There is no Main().
236 A Gamer’s Introduction to Programming in C#
The fact that there isn’t much code in these files is to be expected – Visual
Studio doesn’t know what we want in our Die and Hero class blueprints.
It’s our job to write that code, and we’ll learn how to do that in the
coming sections of this level. But the lack of Main() deserves a bit of
explanation.
Try to remember, all the way back to Level 3, where I said that Main() is
the “entry or starting point for our program”. A game or program can only
have one entry or starting point, and for all of our projects, it belongs in
Program.cs – even if or when we move on to work with MonoGame. Both
the Hero and Die object classes will describe objects that we can use in our
game or program, but they don’t, and shouldn’t, impact how the program
starts running. In Level 11, we’re going to see how we can use Main() in our
project’s Program.cs file to access our Die and Hero object blueprints and
create objects that we can manipulate. Until then, the fact that Die and Hero
have not Main() is both normal and appropriate.
Now brave adventurer, let’s give our object classes their most important
element – attributes.
TABLE 22.1
An Example of Determining Which Attributes Are Most Appropriate, What Data
Types They’ll Be, and What Names They Will Have
Die Hero
Data type 1. int (we can’t have a decimal 1. String (because text)
number of sides) 2. int (but a float could work if
2. int (we can’t have a decimal that’s better for you)
side up)
For each of our objects, it’s good to write down the attributes we’ll need to
code in the object classes. It’s also good form to write down the data type
and variable names that we’ll use for these attributes. In Table 22.1, you can
see how I would approch the three attribute questions for our upcoming Die
and Hero objects.
With answers to these questions, we start adding some code to our object
class. The code for our object attributes goes inside the class curly braces,
like this for the Hero:
namespace MyFirstObjectClasses
{
The attributes for the Die also go inside the class curly braces, like this:
namespace MyFirstObjectClasses
{
You might notice that in both cases we are simply declaring variables with
two minor differences from examples in previous levels. The first difference is
that they are inside object classes, and we’re calling them something new –
attributes – because they now have a special job. Remember, we are writing
blueprints that will define how future objects are created and used. For now,
we’re simply saying that all our heroes will have string names and int health
values, and that all our dice will have integers for the number of sides and
side-ups. We might imagine them to look like this for now (Figure 22.5).
Some of these values will be set once, like the hero’s name and the die’s
number of sides, and some will change throughout the game, like the hero’s
FIGURE 22.5
This is a simplified view of our objects and their attributes. We’ll add more complexity to our
image as our object classes get more code.
Level 10: Crafting Custom Objects 239
health and the die’s side up. The specifics of how those values are set and
changed will be revealed in the coming sections. The specifics of when those
values are set and changed will be revealed in Level 11.
The second difference from previous variable declarations is the keyword
private. We haven’t seen that keyword before, mostly because we haven’t
needed it for anything. However, privacy and protection are very important
ideas in OOP. By setting our object attributes to private, we are encapsu
lating (or protecting) them from direct access from any other object or code
that is, or might be, interacting with our objects. I know, from lots of
experience and conversations with new coders, that what I just said doesn’t
make a lot of sense right now. Privacy? Protection? Encapsulation? Direct
access?
Think of it this way, in the real world, objects have built-in mechanisms
that prevent you from doing undesirable or silly things to them. When dice
are manufactured, the number of sides is firmly set, and they arrive in our
hands or pockets in a non-changeable state – usually in the form of
hardened plastic, metal, or unbreakable glass. Also, the only fair way to
change the side up is to roll them properly – we can’t (or shouldn’t)
manually flip them to whatever “side up” that we want.
Similarly, once a real-world hero’s name is set, it usually sticks (though a
heroic name change could certainly be possible), and their health is usually
protected by bones and muscle and skin and armor (and maybe some cool
magic). The only acceptable way to affect a hero’s health is for them to
engage in battle, have a run-in with the environment, drink a health potion,
or spend a restful night in a friendly inn – we can’t simply force them to
have an arbitrary health increase or decrease.
In code, we represent these kinds of protections by labeling our object
attributes as private. When they are private, attributes can only be directly
modified by code inside the object class or blueprint. We allow our game to
safely change and manipulate our objects through special methods called
mutators and accessors (or setters and getters), which we’ll write later in this
level and we’ll learn to use in Level 11. This way, we can say “hey, a die object
must be rolled properly to get a new side up” or “hey, a hero’s health can only
be changed through damage they suffer, or through proper healing”.
There are alternatives to private but they should only be used in special
cases, and for now, I strongly recommend that you leave your attributes
private until you feel more comfortable writing and using object classes.
Making your attributes public allows other objects and parts of your game
(or program) code to modify and access them without any safeguards – like
setting the side up on your die to any integer (even negative ones) or
changing your heroes’ health in a way that might not make any sense at all
(Figure 22.6).
You can also make them protected, which allows them to be directly
accessed and manipulated by “related” classes or objects but protected from
others. This is a feature of object inheritance that we won’t cover in these
240 A Gamer’s Introduction to Programming in C#
FIGURE 22.6
When we use the private keyword on our attributes, we are protecting them from accidental
changes that might happen in other files or programs in our project. The idea of protection, or
encapsulation, is a bit tricky at first, but it’s still worth putting private in front of your attributes.
books. For now, it’s enough to know that setting your object attributes to
private is a good and safe object-oriented coding practice. With that in
mind, let’s learn how to set our attributes’ default values.
namespace MyFirstObjectClasses
{
A first constructor for the Die class might look something like this:
namespace MyFirstObjectClasses
{
internal class Die
{
//Die attributes to store the state of a Die object
private int numberOfSides;
private int sideUp;
• The first line, called the header, starts with the word public (yes,
this one needs to be public so that it can be used to create objects
later), followed by the type of the object (Hero or Die), and round
brackets.
• The constructor has its own set of curly braces { }.
• Inside the curly braces is the code we need to give all our attributes
a value.
• The constructor including the header (first line), its curly braces and
the code inside all sit within the class curly braces.
What we’ve done with these few lines of code, is define the values that our
attributes will have when we eventually create objects using these blue
prints. When we create a Hero object, it will have the name “Unknown
Hero”, and it will have 100 health points. When we create a Die object, it
will have six sides and the one will be the side that is up. The constructor’s
job is to set the objects’ default values, and the way we’ve written these
constructors, all objects of the same type will have the same starting
attribute values. If we create a thousand Hero objects, they will each have
the name “Unknown Hero”, and they will each have 100 health points.
242 A Gamer’s Introduction to Programming in C#
Similarly, if we create a million Die objects they will each have six sides and
they will all be showing one as their side up.
To make our objects more interesting, we can write constructors that are
more flexible using parameters. For our Hero class, it would mean writing
something like this:
namespace MyFirstObjectClasses
{
internal class Hero
{
//Hero attributes to store the state of a Hero object
private string heroName;
private int heroHealth;
In this updated version, we’ve added a second constructor. Notice that the
header is very similar to the one we had before, but now there are variables
inside the round brackets. These variables are called parameters, and when
the time comes to create our Hero objects (look out for Level 11), these
parameters will let us specify custom name and health values. The parameter
types match the types of our attributes, but the names are different from our
attribute names – this helps us to keep track of which variable is the object
attribute and which is the constructor parameter. In Level 11, we’ll take a
closer and more illustrated look at how this works and how we can use these
parameters to pass interesting values to our constructors and create objects
that have unique attribute values. For our Die example, we might set up a
second constructor, including a parameter like this:
namespace MyFirstObjectClasses
{
internal class Die
{
//Die attributes to store the state of a Die object
private int numberOfSides;
private int sideUp
Level 10: Crafting Custom Objects 243
Notice that for the Die constructor, we only have one parameter to set the
number of sides. The number of parameters in your constructor header is a
matter of how many variables you think you might need to pass in when an
object is created. For the Hero, it might make sense to pass in a value for
both the name and the health attributes. But for our Die, it doesn’t make
much sense to set a starting value for the sideUp variable. Don’t get me
wrong – you certainly could add a parameter for sideUp if that’s what you
need for your program or game, but in this example, I’m going to allow
different dice objects to have a different number of sides, but they’ll all start
with a sideUp value of 1 (for now!).
At this time, both our Hero and Die object classes have two constructors
each. They both have a zero-parameter constructor that hard-codes default
values for the attributes, and they both have a parameterized constructor
that allows us to specify different starting attribute values during object
construction. This is a common thing to do in OOP. Sometimes you might
want to create objects and pass no information in, and other times you
might want your objects to be created with specific starting values. By
writing different constructors in our object classes (blueprints), we give
ourselves (or our coding teammates) options. We’ll see how to take
advantage of those options in Level 11.
Before we leave constructors behind, let’s take a moment to consider our
two parameterized constructors from our two examples. The constructor
parameters allow us to specify the starting values of heroName and
heroHealth for our Hero and the value of numberOfSides for our Die. If
we use meaningful values like “Heroic Hannah” and 100 for our Hero, and
6, 12, or 20 for Die, we have nothing to worry about – these values make
perfect sense. But if we were to use −501 for our healthIn parameter or
248371615 for our sidesIn parameter we’re likely to run into problems later
in our game(s). A negative heroHealth value means our hero is starting out
244 A Gamer’s Introduction to Programming in C#
with some major health issues and a die with 248-million sides is rather
unusual indeed. With a bit of extra, but familiar, code in our parameterized
constructors, we can make sure that our objects start with reasonable
default values. Here’s a modified version of the Hero’s constructor:
Like our Hero example, this updated constructor has a bit of extra code to
make sure that the numberOfSides for our Die object isn’t set to something
silly or unreasonable. In fact, this code makes sure that our Die has only 6,
12, 20, or 100 sides – any other number for the sideIn parameter results in
the Die defaulting back to a safe and familiar six sides.
Writing code like this in our constructors helps to make sure that our
object attributes are always set to safe and useful starting values – this is
Level 10: Crafting Custom Objects 245
another important part of OOP. You can write any code that you need or
want in a constructor, and what I’ve written in the examples above is just
one way to handle potentially “bad” parameter values. As you journey
beyond this book and learn more about OOP, writing object classes, and
writing methods like constructors, you’ll also discover many ways of
making sure that your objects start and stay in a usable state.
If you’re following along with the examples in this level, take a moment to
update your constructors with the new parameter-checking if-statements.
When you’re ready, we’ll take a look at how we can access and update our
object attributes after they have been constructed.
• Like the constructor, these methods have a header (the first line of
each method) that starts with the word public (so that we can call
these methods later on).
• Unlike the constructor, there is a data type next in the header. This
data type (string in the first method, int in the second) tells the
method that we are allowing something to exit the method.
• The next part of the header is the name of the method (it can be
anything, but I recommend naming it so that it’s clear what it does),
followed by round brackets. Neither GetName nor GetHealth need
any parameters so the round brackets are empty.
• Both methods have curly braces to contain their statements.
• Both methods return a value – GetName returns heroName, and
GetHealth returns heroHealth. “return” is a C# keyword that says
“let this value exit the method”.
• Like the constructor, getter/accessor methods sit between the curly
braces of the class definition. Be careful not to accidentally put your
accessors inside of your constructor.
For very little code, that’s a lot to process. The purpose of both the GetName
and GetHealth methods is to give us a copy of the attribute values so that
we can see (but not change) the current values stored in our object
attributes. Here are the accessors for the Die class. They are very similar
to the Hero class accessors, except that they both have int return types since
both Die attributes are ints:
Even though I’ve written two accessor (or getter) methods for each of our
object class examples, you’re not required to write one for every attribute in
your object classes. If you have a complicated object with many attributes,
you can start by writing accessors for the attributes that you are most likely
to access at some point in your program. You can always return to your
object class and write more accessors when you need them. Here’s where
our objects stand now (Figure 22.7).
Level 10: Crafting Custom Objects 247
FIGURE 22.7
With our accessor methods in place, we now have a way to inspect or access our object
attributes, and our illustration looks like this.
It’s worth noting that the accessors in our object classes return the
attribute values instead of outputting them using Console.WriteLine() – a
very common thing for beginners to do. There’s nothing stopping us from
outputting the values, and occasionally it makes sense to do so, but in
general, I don’t recommend it. When we output (or display) an attribute
value in an accessor method, we temporarily see the value, and then it is
gone. By returning values instead of outputting them, we leave ourselves
with options on how we might use the values – we might print what side of
the Die object is up, store a Hero object’s health value for use in a calculation
(how much health do all the heroes in your game have), or even use it in an
if-statement condition that says to stop the game if one of the Hero object’s
health values is less than or equal to zero. By returning values in our
accessors (and sometimes mutators), we provide a way to get the value, but
we decide somewhere else (Level 11) what to do with the values when we
get them.
Accessing or getting our object attributes is really important. But it’s
setting or mutating the attributes that makes our objects seem dynamic and
alive. Let’s take a look at how we code “changeability” into our objects.
248 A Gamer’s Introduction to Programming in C#
Here’s how I imagine our objects with the mutators in place (Figure 22.8).
While the point of accessors is a bit simpler – get the values stored in
the attributes – mutators can feel a bit more complex. For example, I have
written two “setter” methods for the heroHealth attribute and none for
Level 10: Crafting Custom Objects 249
FIGURE 22.8
With our mutator methods in place, we now have a way to mutate or modify our object
attributes, and our illustration looks like this.
What about a mutator for our Die class? Since the number of sides on a
die is set when it’s constructed, we don’t want to add a mutator that allows
us to change the numberOfSides attribute. In the real world, we can’t turn a
d6 into a d20, so let’s not allow our game to do that either. What we need is
a mutator that changes our sideUp attribute. This is one of my favorite
mutator examples – the infamous Roll method:
The Die class’ Roll method is pretty cool. It has the same structure as the
Hero mutators (a header with public and void, followed by the method
name), but this time there are no parameters. We don’t want someone
saying “I want a 5” and then allowing them to pass that value in. Our
players can wish for a 5, but we’re going to let randomness determine
their fate. Inside our Roll mutator we create a Random object (yes, we can
do this). We then generate a random number between 1 and
numberOfSides + 1, and this is the value we use to update our sideUp
attribute. Pretty neat, eh? Our sideUp attribute is dependent on the
number of sides that our Die has (remember to use + 1 because of the way
Random’s Next method works), and the result is randomly generated.
We can even call this method (more on method calls in Level 11) in our
Die’s two constructors like this:
}
//Parameterized constructor (one parameter)
public Die(int sidesIn)
{
numberOfSides = sidesIn;
Roll();
}
With this small change, our Die objects seem more realistic – when a Die is
created, it will have a random sideUp value. This isn’t something you have
to do – you can have all your dice start with the same value if you want. I
just think it’s a small, fun change for this class.
Level 10: Crafting Custom Objects 251
This is a really simple method that has an int return type and returns 10
every time it’s called. While it does show that the hero’s health and name
have nothing to do with dealing damage, this method is a bit boring. A
more interesting version might look like this:
This version uses a Random object to deal damage between 8 and 20 every
time this method is used. You can modify it anyway you want – as long as
there is a return statement with an int value, this method will work fine.
You can even change it to have parameters for the minimum and maximum
damage amounts (seems like a good idea for a side quest).
As I mentioned before, not all objects need helper methods. Our Die
example doesn’t do much other than roll, so there’s no helper method for
that object class. But if there’s something your object class needs to do, and
to do this thing it doesn’t need to access or change the attributes, a helper
method is the way to go!
If you want more examples of object classes, including attributes,
constructors, accessors, mutators, and even helper methods, be sure to
check the online resources for this book. Also, consider extending your
journey into the next book, where we’ll see the examples from this level and
other object classes put to work in MonoGame.
252 A Gamer’s Introduction to Programming in C#
namespace MyFirstObjectClasses
{
internal class Hero
{
//Hero attributes to store the state of a Hero object
private string heroName;
private int heroHealth;
namespace MyFirstObjectClasses
{
internal class Die
{
//Die attributes to store the state of a Die object
private int numberOfSides;
private int sideUp;
return numberOfSides;
}
Side Quests
Want to Know More?
Oops, I want more OOP: We have only scratched the surface of OOP in this
level. If you want to know more about OOP and ways to use it in your games
and programs, look up some more OOP tutorials and resources online.
OOP, but make it faster: Since this book is about learning the fundamentals of
programming, I’ve taken a very deliberate approach with our object
examples. What I didn’t show you is that there are some shortcuts for
creating set/get methods in C#. These shortcuts aren’t applicable in all
cases, but they’re a cool thing to know. Look up C# {get; set;} to learn more.
OOP, but make it even more awesome: Learning how to write and use object
classes is just the start of your OOP journey. The next step is using those
object classes to create related classes using inheritance. This opens the door
to object polymorphism! Once you feel like you have a handle on objects,
look up C# object inheritance and polymorphism to create even more
powerful objects.
OOP, but make it more debuggable: Game objects are sometimes simple, like
the examples in this level, but they can also be very complex with many
attributes. Sometimes we want to see an object’s state, and we don’t want to
call every accessor one at a time. Luckily, there’s an “easier” way to get your
object’s state. Look up the C# object ToString methods to see how you can
write a special accessor that is very useful for debugging your objects.
Level 10: Crafting Custom Objects 255
OOP, but with shared attributes: In this level’s examples, we defined instance
attributes – this means that each object we create will have its own copy of
the attribute. In other words, each Hero object has their own heroName and
heroHealth – changing the health of one Hero object has no impact on the
others. The same idea holds true for the Die objects and their
numberOfSides and sideUp attributes. However, sometimes it’s helpful to
have attributes that are shared between all objects of a specific type. Look
up static attributes in C# objects to learn more.
Want to Do More?
This Hero is out of control: In our Hero class, we wrote overly simple
TakeDamage and Heal mutators. When we’re learning new concepts, we
often start simple and work our way to more complex code. The issue with
the TakeDamage and Heal methods is that they will allow the Hero’s health
to go below zero and above 100. Let’s assume that our game won’t work well
with negative health or extreme health above 100. Write an if-statement in
TakeDamage that says if the updated health is below 0, set it to 0. Write an if-
statement in Heal that says if the updated health is above 100, set it to 100.
This Hero is too simple: In our Hero class, we wrote a simple but cool
DealDamage helper method. Modify the method header to take two int
parameters – one for the minimum amount of damage and one for the
maximum amount of damage. Update the method code to use the minimum
and maximum parameters instead of hard-coding the damage to be
between 8 and 20.
• The loot box object will have two attributes – a string to store the
loot item and a boolean to keep track of whether the loot box is full
(true) or empty (false). Make your attributes private to encapsulate
and protect them from any accidental shenanigans.
• The loot box has one constructor that takes no parameters. The
constructor sets the full boolean to true and uses an if-statement
256 A Gamer’s Introduction to Programming in C#
If you try this code for yourself, remember to create a new file to store your
loot box object class. I used a project called Level10CodeReward, which
you’ll see reflected in the namespace.
Level 10: Crafting Custom Objects 257
/*
If you’re going to copy and paste all of this code (from the online version),
remember to store this in a file called LootBox, in a project called
MyLootBoxObject.
This is a simple loot box object class! Even though it looks a bit more
complex, especially the constructor, it has the same properties as the Hero
and Die from Level 10.
namespace Level10CodeReward
{
internal class LootBox
{
}
else if (lootValue >= 95)
{
loot = "a scorching sword of pure flame";
}
}
• review Level 10
• complete Code Quest 9 (optional)
• create a new Visual Studio Console project. If you need a refresher,
detailed instructions can be found at the beginning of Code Quest 1.
1. After creating your Visual Studio project, create a new class file
called SimpleNPC.cs. All of your code will go in this file.
2. Create three private string attributes for your NPC – one for their
name and two separate variables for different barks (things the
NPC says).
3. Write a non-parameterized constructor that sets the name attribute
to “Monty”, the first bark to “I’m glad to be on this adventure with
you.” and the second bark to “I really hope this adventure is over
before dinner”.
4. Write a parameterized constructor with parameters for the NPC
name and both bark strings. In the body of the constructor, set the
attributes to the parameter values.
5. Write an accessor method called GetName that returns our compan
ion’s name.
6. Write an accessor method called TalkToMe that returns one of the
two bark attributes. One way to do this is to create a Random object
in the body of this method and use it to create a random number
between 1 and 100. If the random number you create is less than 50,
return the first bark. If the random number is 50 or greater, return
the second bark. This method header takes no parameters but does
return a string.
7. Write two mutator methods – one to change the first bark to a new
string, and another to change the second bark to a new string. Both
of these method headers return nothing (void) but do need to take
in a single parameter each.
Since there’s no output until we create some NPC objects using our blueprint
(Code Quest 11), here is an outline of my NPC object class to help you with
this Quest (Figure 23.1).
Side Quests
Clean up the parameters: In both the Hero and Die classes, we added some
code to check that our incoming parameters would be appropriate for our
objects. We could do some of that here too. Consider adding some checks in
the parameterized constructor and mutators – if the incoming string is
empty (“”), set the NPC name or barks to something more appropriate.
Make it your own: Do you find my NPC uninteresting or uninspiring? Do
you prefer adventure companions that are more complex? Do you prefer
adventure companions that are less complex? Do you prefer adventure
companions that are entirely different? Change the attributes and behavior
methods to make this quest more fun for you!!
Code Quest 10: A Companion for Our Hero (Part 1) 261
Really make it your own: Do you always refuse requests for NPCs to join your
party because you prefer to adventure in quiet and comfortable solitude? If
so, write an object (preferably a game-y one) that is more fun and interesting
for you. Decide on some important attributes, then add a parameterized and
non-parameterized constructor that will set the default values. Finally, add
some accessors (getters) and mutators (setters) to give your object class
some meaningful behaviors.
FIGURE 23.1
This quest is a bit different, so here’s a code outline to help you on your journey.
24
Level 11: A Factory for Our Objects
Level 10 Recap:
FIGURE 24.1
All objects in our games and programs have three phases in their life cycle – a blueprint phase
where the objects are described, a construction phase where they are manufactured with
starting values, and a regular use phase where they are accessed and changed as needed.
Level 11: A Factory for Our Objects 265
In these two lines of code, you’ve seen the construction and regular use
phases of the Random object class (even though you probably haven’t
looked at the object class code). You’ve also instantiated an object by calling
a constructor from the Random object class, and called a behavior method
too (.Next()). We’ll clarify all of this (and more!) in the next few sections by
revisiting our Hero and Die object classes from Level 10.
FIGURE 24.2
Although we write our object blueprint code in the specific object class files, we create and use
our instances of our objects in the Program.cs file. Remember, Program.cs is where we wrote all
of our code before we started our object-oriented journey.
string playerName;
int playerScore;
float playerScoreMultiplier;
Random rng;
Hero myHero;
Die d20;
In either case, built-in types or object types, a declaration specifies the type
and the name of the variable. No real difference there. Where things change
is during assignment. For our built-in types, we might write:
playerName = "Arthur";
playerScore = 112;
playerScoreMultiplier = 1.25f;
But, for our object types, we need to use the new keyword and call one of
the object class constructors:
Calling an object class constructor means we put the new keyword followed
by the name of the object (Random, Hero, or Die in this case) and then some
round brackets. If we want to pass any information to our constructor, and
assuming we have a constructor in our object class that can accept
information, it goes inside of the round brackets. For the sake of complete
ness, initializations (combined declarations and assignments) for our object
variables would look like this:
Let’s try to answer some of the questions that might come up at this
point:
Let’s take a closer look at what it means to call constructors that have no
parameters. In our Hero and Die object classes, we have constructors that
look like this:
Level 11: A Factory for Our Objects 269
public Hero()
{
heroName = “Unknown Hero”;
heroHealth = 100;
}
public Die()
{
numberOfSides = 6;
Roll();
}
In the case of the objects above, both myFirstHero and mySecondHero will
have “Unknown Hero” and 100 for their heroName and heroHealth values,
while both Die objects will have numberOfSides set to 6 and sideUp set to a
random value because of the roll() method call. I imagine our objects
looking something like this (Figure 24.3).
FIGURE 24.3
Is this how you imagined our objects as well?
270 A Gamer’s Introduction to Programming in C#
How do we know that our objects will look like this? Well, that’s what we
defined as our starting values in the unparameterized constructors of our
two object classes – look back at the code above. When we learn how to call
our accessor (getter) methods in the next section, we’ll verify the starting
values of these four objects.
In the meantime, both our Hero and Die classes have parameterized
constructors as well.
If were we to try calling our constructors like this, we would get syntax
errors in Visual Studio:
FIGURE 24.4
If we change the attribute values in our objects, we need to change our illustration of
them too.
In the case of our Hero constructor, the parameters need to be the right type
and they need to be supplied in the right order. Try these statements for
yourself to see the complaints that you get from Visual Studio.
When we successfully supply the right parameters, in the right order, our
four new objects might look like this (Figure 24.4).
One thing that new programmers find confusing is how the values
(literals or variables) we put inside the round brackets of our constructors
become the values for our object attributes. When we define parameters in
our object classes, we are saying “by putting a value here when you call this
method, it will be passed to the method”. Here’s a diagram with partial
classes to help illustrate this idea (Figure 24.5).
When we call a parameterized constructor, the values we put inside the
round brackets are passed to the variables that were defined in our object
class – in this case, “Sam” and 100 are passed to the parameters nameIn and
healthIn (dotted arrows). These are special variables, defined in our
constructor header, that can then be used inside the body of our constructor.
Here we are simply using them to set the values for our object attributes –
heroName and heroHealth, respectively (solid arrows). Using parameters
like this is the safest and most common way of sending values from the
Main() method in Program.cs to our objects during construction. We’ll see
272 A Gamer’s Introduction to Programming in C#
FIGURE 24.5
Sometimes, it’s hard to imagine the flow of values in our code, especially when we’re calling
methods and passing values through arguments. This is how the data flows when we call our
Hero constructor with argument values.
in the next section that return statements are the safest and most common
way of passing values from our objects back to Main().
Knowing when to call one constructor or another (or many others) is a
matter of what you want to do in your game or program. For example, if I
want a six-sided Die, I can simply call the unparameterized constructor of
my Die class – it creates a Die object with six sides, and I don’t have to go
through the extra work of passing anything when I call the constructor.
However, if I want a 20-sided Die, I have no choice but to call the
parameterized constructor – that’s the only way I can say that I want
20 sides. It’s the same idea for the Hero class – if I want to specify my hero
name and starting health value, I have no choice but to call the parameter
ized constructor. In the unlikely event that I want anonymous heroes with a
guaranteed 100 starting health, I can use the non-parameterized constructor.
If an object class has multiple constructors, choose the one that best suits
your object-constructing needs.
Level 11: A Factory for Our Objects 273
In this section, we’ve taken a look at how to instantiate (or create) objects
by calling the object class constructors – both unparameterized and
parameterized. But this only brings our objects into existence. To peek
inside our objects, or to change their state, we need to call our accessors and
mutator methods. Let’s start with our accessors.
To call any of an object’s accessors (see Level 10 for object class definitions of
our Hero and Die accessor methods), you use the object name, followed by a
period (or dot), then the method name, and then round brackets. Like this:
myFirstHero.GetName();
myFirstHero.GetHealth();
myD6.GetSideUp();
myD6.GetNumberOfSides();
If you put these accessor calls in Main() below the object initialization
statements, your program should compile and run without any errors
(assuming you have the complete Die and Hero classes in your Visual
Studio project). However, there’s no output, so how can we be sure that
everything is working properly?
It’s important to remember that accessor methods return values – like
Random’s Next() method, something comes out of the method call. We could
rewrite the above accessor calls like this:
When we do this, the values that are returned from the accessor methods
are passed to the Console.WriteLine() statements and we are able to peek
at some of our object attributes. The above statements should produce
output like:
274 A Gamer’s Introduction to Programming in C#
Of course that last value will be a random value between 1 and 6, thanks
to the Roll() method call in our Die object’s constructor. These statements
are an easy way for us to see what our object’s attribute values are at any
point when our program is running. But what if we wanted to do
something more with the values in our objects? For example, let’s
compute the total health of all our Hero objects by adding the following
statements:
Let’s also add some code that will roll our 20-sided die and tell us how
lucky we are:
myD20.Roll();
int myD20Roll = myD20.GetSideUp();
Console.WriteLine("You rolled: " + myD20Roll);
if(myD20Roll == 20)
Console.WriteLine("Amazing!! That’s an amazing roll!");
else if(myD20Roll == 1)
Console.WriteLine("Ouch, that’s tough.");
else
Console.WriteLine("Not bad, not bad at all.");
In this code, we’ve jumped ahead a bit and called our Roll() mutator –
spoiler alert, it’s very similar to calling an accessor. But we’ve also called
our GetSideUp() accessor to see what value is stored in our sideUp attribute
after the Roll(). We’ve stored this value in a variable called myD6Roll so that
we can tell the player how good, bad, or mediocre their roll was.
In both of these examples, we’ve called our object accessor (getter)
methods to retrieve the value of the object attributes. When we call an
object method that has a return value, the value is “returned” to the code
where the method call was made. Then we can decide what to do with the
value that has been returned to us – usually output it or store it for later use.
Here’s a diagram with partial classes to help illustrate how return
statements in our object classes send values back to our accessor method
calls in Main() (Figure 24.6).
Level 11: A Factory for Our Objects 275
FIGURE 24.6
Sometimes it’s hard to imagine the flow of values in our code, especially when we’re calling
methods and returning values to be stored in variables. This is how the data flows when we
call our Die’s GetSideUp accessor method.
In the end, with accessor methods, we get the state, or values, stored in
our object attributes. Now, let’s take a look at how to manipulate and
change our object attributes by calling the mutator methods.
mutator needs a parameter, you must supply it (the same way we did for
our parameterized constructors). Assuming we have our Hero and Die
objects still initialized from the previous section, we should be able to call
the mutator methods like this:
myFirstHero.TakeDamage(10);
myFirstHero.Heal(5);
mySecondHero.TakeDamage(15);
mySecondHero.Heal(5);
myThirdHero.TakeDamage(3);
myThirdHero.Heal(4);
myD6.Roll();
myD20.Roll();
myD100.Roll();
There are a few things worth mentioning about these mutator calls:
• It’s common for mutators to use parameters to get values that will
modify the object attributes. For our Hero object class, TakeDamage
takes an integer parameter that is the amount of health the hero loses.
It’s the same idea for the Hero’s Heal method, except we add the
parameter amount instead of subtracting it. Parameters can be passed
to methods as literals (as in the example above) or as variables.
• Not all mutators use parameters. For example, the Die object’s Roll
method needs no information or value to do its job.
• Most mutators don’t return anything, so there’s no need to put
them in an assignment statement – like we do when we call
accessors. Most mutators have void in their header, which means
no value is returned. Sometimes mutators return a value, but this is
somewhat unusual.
• Mutating one object does not change the attributes of another object.
Each object is a separate entity with its own copy of the attributes. If
one Hero object takes 10 damage, the others are not affected.
Similarly, if I use the Roll() method on one Die object, it has no effect
on the others.
In most programs, we move back and forth between accessors to see the
state of the object and mutators to change the state of the object. If there are
any helper methods in our object class, they usually get called as well (the
same way as accessors and mutators). Here is a short example using the
Hero class:
//constructor call
Die firstDie = new Die();
//constructor call
Die secondDie = new Die(20);
//accessor call
Console.WriteLine("First die is showing: " + firstDie.GetSideUp());
//accessor call
Console.WriteLine("Second die is showing: " + secondDie.GetSideUp());
//accessor calls
int sumOfDice = firstDie.GetSideUp() + secondDie.GetSideUp();
Console.WriteLine("Sum of the Dice is: " + sumOfDice);
//mutator call
firstDie.Roll();
//mutator call
secondDie.Roll();
//accessor call
Console.WriteLine("First die is now showing: " + firstDie.GetSideUp());
//accessor call
Console.WriteLine("Second die is now showing: " + secondDie.GetSideUp());
//accessor calls
sumOfDice = firstDie.GetSideUp() + secondDie.GetSideUp();
Console.WriteLine("Sum of the Dice is now: " + sumOfDice);
In the Code Reward for this level, we’ll extend these last examples by
creating two Hero objects that will wage an epic life-and-death battle using
two Die objects to determine battle damage and healing amounts. This
battle will use almost every tool, technique, and trick we’ve learned, so take
some time to review how the objects are created, how their accessors and
mutators are used, and how they seem to interact in this dynamic example.
278 A Gamer’s Introduction to Programming in C#
Side Quests
Want to Know More?
Please, I want some more: one of the most effective ways to understand object
classes and object instances is to look at examples. Seeing how other coders
set up their attributes, constructors, accessors, mutators, and even helper
methods is a great way to understand what is possible with object-oriented
programming. Take some time to look up object classes in C# and explore
different examples, different styles, and different ideas.
Want to Do More?
What’s in that loot box: in Level 10’s Code Reward, you’ll find a full object
class for a loot box. Write some Main() statements to create a LootBox object
by calling the constructor, check to see if your LootBox object is full by
calling the only accessor method, and call the mutator to dispense the
precious loot! Be sure to store your loot in an appropriate variable when you
call the mutator.
What’s in those loot boxes: repeat the work from What’s in that loot box, but this
time create five separate loot box objects and dispense the loot from each
one. Be sure to show off all the awesome loot that was dispensed.
Objects of your very own: did you create your own object classes after Level
10? If you did, head to your project’s Program.cs and Main() to bring your
objects to life – instantiate your objects. Be sure to test out any constructors,
accessors, and mutators that you defined in your object classes.
Before you continue, you should know that the way that I have coded this
reward, the battle isn’t “perfect”. For example, the first hero always attacks
first, the second hero might attack even if their health is less than zero, and
it’s possible that both heroes perish in the end. When we code complex
programs, we often start with a program that is close to what we want, even
if the details aren’t perfect. Sometimes, it takes a lot of energy and code to
get everything just right, and while I encourage you to modify this program
to make it as perfect as you want it to be, I’ve left it a bit imperfect to keep
the code a bit simpler.
If you want to try coding this on your own, here’s how my solution
works:
• Be sure to include the Hero and Die object classes in your project.
• Prompt the user and collect string input for your two hero names.
• Create two Hero objects but call the parameterized constructor.
Each hero has 100 starting health (you can use a literal here) and a
name from the input you collected.
• Create a 20-sided Die object by calling the parameterized Die
constructor.
• Here is the action part of the program:
• Roll the Die object by calling the Roll() mutator method.
• Store the Die object’s side up value by calling the SideUp()
accessor method and storing the result in an integer variable.
This is the first hero’s attack value.
• Display the attack value to the user so they can see the strength
of the first hero’s attack.
• Subtract the first hero’s attack strength from the second hero’s
health value by calling their TakeDamage() method and passing
the attack value.
• Display the second hero’s remaining health to the user.
• Duplicate these action steps for the second hero – roll the Die
object, store and display the attack value, subtract the attack
value from the first hero’s health, and display the first hero’s
remaining health to the user.
• Now, to automate the battle, write the action part of the program
in a while loop that runs as long as both heroes have health
that is greater than 0. If either hero has health less than or equal
to zero, the battle will end. Don’t worry about the change
portion of your loop – if you’ve coded the action parts properly,
eventually one of your heroes will have zero or fewer health
points.
280 A Gamer’s Introduction to Programming in C#
/*
Remember, this code goes in Program.cs in your project. You’ll need to make
sure the Hero and Die classes are also in your project.
This is a program that uses the Hero and Die object classes to simulate an epic
battle between two heroes.
namespace Level1CodeReward
{
internal class Program
{
static void Main(string[] args)
{
//Giving our heroes names will make the battle more fun.
Console.Write("Hero 1, what is your name: ");
string hero1Name = Console.ReadLine();
//Create two Hero objects with user-input names and 100 health
Hero hero1 = new Hero(hero1Name, 100);
Hero hero2 = new Hero(hero2Name, 100);
//Eventually, one of our Hero objects will have health <= 0 and
//this loop will start.
while (hero1.GetHealth() > 0 && hero2.GetHealth() > 0)
{
//Who perished?
if(hero1.GetHealth() <= 0 && hero2.GetHealth() <= 0)
{
}
}
}
25
Code Quest 11: A Companion for Our
Hero (Part 2)
• review Level 11
• complete Code Quest 10 (required)
• for this Code Quest, you can actually use the project you created for
Code Quest 10. All the code for this Code Quest will go in the
Program.cs file, in the Main() method.
To create and test your SimpleNPC objects, complete the following tasks:
FIGURE 25.1
One more time, here is some output to help guide you through this quest.
286 A Gamer’s Introduction to Programming in C#
In the end, this program is going to create a lot of text. In most games, we
don’t ask our NPCs or companions to talk (or bark) constantly like this, but
this is a good way to test that everything is working the way it should.
Figure 25.1 is the output from my solution to this Code Quest.
Side Quests
Make it your own: Did you write a somewhat or completely modified object
in Code Quest 10? If you did, try to test your objects using the steps that are
outlined above. Try creating more than one object using each of your
constructors, call all your accessor methods (be sure to output the results),
and change your objects by calling your mutators.
26
Cutscene 4: The Adventure Ends … Or
Does It?
Game Over!
There is a very special feeling of accomplishment that comes from
finishing a video game. You know that feeling, right? It happens when
you’ve explored every corner of a strange land and found every piece of
rare and valuable loot. It happens when you’ve leveled up a thousand
times, memorized every attack and defense combination, and sent every
minion, mini-boss, and mega-boss back into the darkest depths. It
happens when you’ve finally sorted all the jewels and crushed all
the candies. It happens when you’ve watched every cutscene, read
every line of dialog, and revealed every piece of lore. It happens when
there are no more platforms to run or jump across. It takes practice,
patience, and, most of all, perseverance – but it’s worth it in the end
because great games change us forever by teaching us something about
ourselves.
I hope that when you look back on the adventure that you started in
this book, you’ll feel a similar sense of accomplishment through the
practice, patience, and perseverance that you put into learning how to
code. Afterall, you’ve battled your way through variables, literals, built-
in data types, and collecting input. You’ve leveled up your knowledge of
powerful math operations and the chaos that is random number
generation. You explored all the twists and turns of if-statements, and
in true gamer fashion, you chose to grind your way through loop logic
like a pro. You even managed to make new objects out of nothing but a
handful of C# statements. However you started this journey, hopefully
you have learned something about yourself and a bit about program
ming too.
Congratulations, brave adventurer. Your adventure is over …
FIGURE 26.1
In true video game fashion, you’ve unlocked an achievement! Congratulations, you definitely
earned this.
A Fond Farewell
Wherever your journey takes you next, thank you for joining me through
this part of it. Until we meet again, brave adventurer, happy gaming, and
happy coding (Figure 26.1).
27
Bonus Code Quest: Cornflower Blue!
(MonoGame Sneak Peek)
FIGURE 27.1
For the first time, we’re not selecting a console application for our project. For this bonus quest,
we’re going to use the MonoGame Cross-Platform Desktop Application option. Woohoo!
FIGURE 27.2
If this is your first MonoGame project, be sure to spend a bit of time to look at the different files
in the Solution Explorer and the different code that is given to us in the editor pane.
292 A Gamer’s Introduction to Programming in C#
Remember, this is just a preview of where we’re heading for our next code
adventure, so don’t let what you see overwhelm or worry you – the next
book is all about MonoGame and the code that you now see in front of you.
Take a moment to scroll through the 50-ish lines of code and appreciate the
difference between our console-based Code Quest 1 and what you see in
this example.
FIGURE 27.3
If all goes well, you’ll see a lovely blue window when you compile and run the default code.
This window, believe it or not, is the basis of all the examples in the next book in this series.
Bonus Code Quest: Cornflower Blue! (MonoGame Sneak Peek) 293
GraphicsDevice.Clear(Color.CornflowerBlue);
GraphicsDevice.Clear(Color.Firebrick);
Then, re-compile and re-run your program. Did the color change to a fiery
brick red? If you make any mistakes – or syntax errors – the program won’t
re-compile or re-run until you fix them. When you do see your “fire brick”
window, take a moment to appreciate completing this quest – you made this
happen, and it could be just the start of our next awesome adventure together.
Side Quests
Keep practicing (MonoGame): If you completed A More Fiery Start, you might
have noticed that Visual Studio gave you a list of color options when you edited
GraphicsDevice.Clear(Color.CornflowerBlue). Follow the steps from A More
Fiery Start again, but try some of the other color options. It’s not a big change,
but it is fun to see the window change colors with very little coding effort.
Make mistakes (MonoGame): Even though the MonoGame code is more
complex and creates a cooler output window, the idea of coding errors is
exactly the same. Try making a few on-purpose syntax errors like these:
• What does it mean to say ints and floats are 32 bits, but doubles are
64 bits?
• Why do built-in data types have maximum and minimum values
like the roughly −2 billion to +2 billion for ints?
• Why can’t we put double values in int variables (more on this in
Bonus Level 2)?
• Why do ints (and other data types) overflow when we try to go past
their minimum and maximum values (more on this in Bonus Level 2)?
states can the bulb be in? Assuming we keep it simple and forget about
dimming bulbs and different colors (modern light bulbs are so cool), one
light bulb can have two states – off and on. This is kind of like a single
transistor or bit inside our computers, consoles, and other digital devices.
With one bulb, we can have two states (Figure 28.1). Normally we would
say those states are off and on, but there’s nothing stopping us from calling
them stop and go, no and yes, false and true, zero and one. We could even
say that off means stand still and on means jump around. That might sound
odd, but stay with me on this.
Now, what happens if we add a light bulb – now we have two bulbs. How
many different states can we have? Take a moment to think this through
before reading on.
If you take the time to sketch this out, we can have four different states
with two bulbs – or two bits (Figure 28.2). Now we can represent four
different pieces of information instead of two. Maybe it’s the numbers zero
to three, or the colors red, blue, green, and yellow. Whatever it is that we
want to assign to each pattern of off and on bulbs (or bits), we get four
options with two bulbs. What happens with three bulbs?
That’s right, we’re up to eight possible states with only three bulbs
(Figure 28.3). Without illustrating it (too much page space), 4 bulbs mean 16
states, and 5 bulbs mean 32. If you haven’t noticed the pattern yet, every
bulb (or bit) we add to our scenario doubles the number of states we can
represent. If I’m teaching in a room that has 10 light bulbs (they can even be
those annoying fluorescent ones), and if each bulb has a dedicated switch, I
FIGURE 28.1
Computer transistors (or bits) are like light bulbs that can be off or on.
FIGURE 28.2
Two light bulbs, four distinct patterns.
296 A Gamer’s Introduction to Programming in C#
FIGURE 28.3
Three bulbs, eight distinct patterns.
can make 210 or 1,024 different configurations with those 10 bulbs. If you
don’t like the notation 210, start with 1 bulb and 2 states (off and on) and
keep doubling 10 times – 2, 4, 8, 16, 32, 64, 128, 256, 512, and 1,024!
So, what does this have to do with ints and other built-in data types? In order
to make C# operate efficiently, each built-in data type is given a certain number
of bits (or bulbs) to work with. In the case of ints, C# sets aside 32 bits. If you do
some quick math (or a lot of doubling), you’ll see that 32 bits gives us
4,294,967,296 different combinations of off and on. That’s 4 billion (and change).
But I said that an int stores numbers between −2 billion and +2 billion – and if
you remembered that fact, good for you! To make this happen, C# takes one of
the 32 bits and uses it to note whether the number is positive or negative, and
the other 31 bits are for the number part. The 31 bits is 2,147,483,648 different
combinations of off and on. In other words, with 32 bits, an int variable or literal
can be positive or negative and have a number part of roughly 0 to 2 billion.
because ints are one of the first data types that we use and they are fairly
simple because they don’t have any pesky decimals to worry about. But our
other built-in data types are also stored with bits including floats with 32
bits (just like ints) and doubles with 64 bits.
When you declare an int variable (or use an int literal in your code), C#
sets aside 32 bits of memory space. The fact that it’s declared as an int tells
C# that it won’t need to store a decimal portion. When you declare a float
variable (or use a float literal in your code), C# sets aside 32 bits – same as an
int – but this time those 32 bits will be used to store both a whole number
portion and a decimal portion for our number. Exactly how C# and other
languages store decimals in binary is a bit more complex than I want to
explain with light bulbs that are off and on, but if this subject interests you, I
encourage you to do a bit more reading elsewhere.
We’ll talk some more about the difference between ints and floats in
Bonus Level 2, including why we can’t store a 32-bit int value in a 32-bit
float variable.
Not all data is stored using 32 bits. Doubles, for example, use 64 bits
(that’s why they are called doubles – double the memory of an int or float).
Other built-in data types that we didn’t use in this book like longs (really
big whole numbers) use 64 bits, shorts (smaller whole numbers) use 16 bits,
and decimal (really precise decimal numbers) use a whopping 128 bits.
Don’t worry – you don’t really need to memorize all of these bit-facts to
be a good programmer. You can always look up the number of bits and
value ranges of the built-in data types if you need to. What is important to
keep in mind is that all data in your games and programs is stored using
bits and the number of bits determines the properties of each data type.
In fact, 8 bits or 1 byte is so small in today’s digital world that you’re even
more likely to hear the terms kilobyte, megabyte, gigabyte, and terabyte – a
kilobyte is one thousand bytes, a megabyte is one million bytes, a gigabyte
is one billion bytes, and a terabyte is one trillion bytes. We don’t use these
terms when talking about individual variables or literals in our code, but we
do use them when we talk about all the data that makes up our finished
game – really small games can be a handful of megabytes while AAA titles
can be hundreds of gigabytes. Having lots of games (and other software) on
your computer or console might mean investing in a terabyte of storage
space – that’s 8,796,093,022,208 bits (in case you’re curious). Now, how
many ints could we store with that much space?
29
Bonus Level 2: A Slight
Miscalculation
As gamers, we like to think that our consoles, PCs, and other digital devices
are really, really accurate. It’s most comforting to think that we lost a match
because the people (or bots) we were playing against were better than us –
or maybe luckier than us in some cases. But, how would you feel if I told
you that you lost, or even worse won, that last game because of a math
error? Now, I’m not saying this happens often, but it turns out that
computers aren’t always as accurate as we expect them to be. Let’s take a
look at a few cases where the math in our code doesn’t quite add up (that’s a
rare math pun, in case you missed it).
Overflow
You’ve written an awesome new game and you’ve invited some people
to playtest it. One of the players is having an epic run – they’ve collected
all the collectables, cleared all the areas, earned every bonus and score
multiplier imaginable. In fact, they’ve earned a score of 2147483647 with
only one thing left to do – jump over the final flagpole. As expected, the
player manages to clear the pole and earn another 100 points. On the
final screen of the game, you congratulate the player on their fantastic
play and their amazing score of −2147483549. Way to go player! Wait,
what?
In this example, our player’s score is so good that it can’t be contained in
an int variable. Here is some code so that you can see this issue for yourself:
If you read Bonus Level 1, you saw that integers are stored using 32 bits and
that their maximum positive value is 2147483647. Yes, I cheated by setting our
score variable to this value in our example, but when you try to go past this
value, by adding 100, or 10, or even 1, the value exceeds what the int variable
can hold and it actually wraps around to the negative values. This is called
overflow – our int variable is at its maximum capacity and when more is
added it overflows into the negatives. This isn’t a common problem in games
since we rarely deal with numbers in the billions, but it does happen from
time to time. Here are some things to know about integer overflow:
• It affects all whole number data types – int, long, short, byte, etc.
• It works in reverse too – try the code above, but start at −2147483648
and subtract 100. You’ll end up at 2147483548.
• Multiplication can also cause overflow – set an int variable to 1 and
write a for loop that doubles it 32 times.
So, your player’s score is too big to store in an int variable – what can you
do? Luckily, there are some other data types that are larger to accommodate
an epic run in your game. We don’t use them often because they use double
the memory, but long variables would work in this situation:
Decimal Errors
Most new coders seem to accept the idea of integer overflow pretty easily –
integer data types can only hold so much, and when you go too far in either
direction (positive or negative), there are consequences. But decimal errors,
that’s another story. We want to think that computers are unerringly
accurate, but take a look at the code below:
Bonus Level 2: A Slight Miscalculation 301
If you’ve gone through all of the regular levels including Level 9 (loops),
there’s nothing particularly special about this code. We have a float variable
called myFloat that starts with a value of 0.1. Then, using a for loop, we add
0.1 to the variable, five times, and display the results. The output from this
code is:
Adding 0.1
0.2
Adding 0.1
0.3
Adding 0.1
0.4
Adding 0.1
0.5
Adding 0.1
0.6
No problem here, right? If you were to update your for loop to run 10 times
instead of 5, the output would be:
Adding 0.1
0.2
Adding 0.1
0.3
Adding 0.1
0.4
Adding 0.1
0.5
Adding 0.1
0.6
Adding 0.1
0.70000005
Adding 0.1
0.8000001
Adding 0.1
0.9000001
Adding 0.1
1.0000001
302 A Gamer’s Introduction to Programming in C#
Here are two sets of statements that calculate the average score of two
players that have played three matches each and have earned a total score
across the matches of 100 points. The first set of statements uses integers for
both the score and the number of matches, while the second set uses floats.
The output from this code is:
Average score 1: 33
Average score 2: 33.333332
Now, you might want to jump up and yell “C# can’t do division
accurately!”, but C# has done exactly what it is supposed to do. When
both operands (the variables or literals in the operation) are integer types,
C# does the division and simply drops (or truncates) the decimal portion!
That’s right, it just ignores the decimal part of the division completely.
When at least one of our operands is a float, we get a more expected answer
with the decimal intact.
It’s very common to think that the loss of decimal is an error, but this is
actually how C# is programmed to work. There are cases where this
behavior is actually useful. For example, imagine that you have 4 players in
your game, and as a team, they have earned 123 coins that are to be divided
equally among the players. How many coins does each player get?
If you take the time to write and run this code, you’ll see that each player gets
30 coins. You might realize that you could do this with float variables (and
literals) and get the same answer by simply dropping the decimal part yourself
(using floats, the players get 30.75 coins each). What, then, are the advantages
to using ints when we can get a reasonable, and possibly more accurate,
answer using floats? First, some data should be represented as ints. We can’t
have a fraction of a player or a fraction of a coin, so using float variables would
be a misrepresentation of our players or coins. Second, while 30.75 is a more
accurate answer, it hides one fact that might be of interest to us – how many
coins are left over after the players have each taken their cut of the treasure? It
turns out that there is a handy operator that pairs perfectly with our integer
division operator – modulus (%). Check out this updated example:
The modulus (%) operator is the remainder from an integer division operation.
When we divide two integers like 123/4, the result (called the dividend) is the
whole number portion, or 30 in this case. When we use %, the result is the
remainder, or 3 in this case. Think back to your long division days – 123/4 is 30
with a remainder of 3. Even though it might seem like more energy and
thought-power to use integers in this example, it does represent the number of
players and coins properly as non-decimal variables and has the added bonus
of being able to quickly determine how many coins are left over.
Personally, I like to use modulus to have code run at regular intervals. Try
coding and running this example for yourself:
This code is a pretend game loop that runs for 100 steps. The player is
awarded with a point every time they survive 3 time steps – of course,
there’s nothing happening to the player to jeopardize their survival, but let’s
not worry about that. The magic in this program is the if-statement that says
“if the current step is evenly divisible by 3 (evenly divisible means a
modulus, or remainder, of 0), the player gets a point added to their score”. If
it’s not evenly divisible, we skip that step. We can change this code by
adjusting the interval – try changing it to 1, 2, 3, 5, and 10.
In the end, both integer division and the modulus operator have a place in our
code, and the missing decimal part isn’t technically a calculation error. But it is
something to watch out for. If you have int variables in your code or calculations
and you’re getting an unexpected result, check for some sneaky integer division.
and all of our text shows up as strings. However, there are times when we
need to change from one type to another. In some cases, these type changes
can happen automatically, but in other cases, we need to write code to
change them manually. Let’s take a quick look at both cases, starting with
automatic conversions.
Throughout most of the levels in this book, I’ve tried to write examples
that avoided any confusion with data types. One of our examples from
Level 3 was the following initialization statements:
This code will compile without any issues because int values (literals or
variables) are compatible with float variables. Afterall, they are both 32 bits
in size, and an int value is simpler (no decimal to worry about) than a float
so there is no issue automatically converting our integer literal 10 into a
float so that it can be stored in our scoreModifier variable. In general,
smaller and simpler data types can be stored in bigger and more complex
variables, but I leave it to you, brave adventurer, to try out some different
combinations to see which ones work and which ones don’t. These kinds of
automatic conversions are often called “widening conversions” because the
smaller, simpler, or narrower data type is converted into a larger, more
complex, or wider type.
There is a similar automatic conversion when we use multiple types in a
single expression like this:
Since one of the operands on the right is a float, the resulting type is also a
float – there are more examples of this in Level 5. This is true when we use
variables as well:
This will result in a syntax error because C# knows that, even though they
are both 32 bits in size, a float is more complex than an int and this
statement isn’t allowed. We can take charge of the situation and force C# to
convert the float by casting like this:
like the previous example, but it does show how casting can be used to
temporarily change the type of a variable to get a more accurate result.
For more examples of casting and type conversions, be sure to revisit
Math Functions in Level 5 and Generating Random Numbers and Casting
in Level 6.
30
Bonus Level 3: Input Made (A Little
Bit) Friendlier
In this bonus level, we’re going to look at two input-related issues – what
happens when the user enters unparsable input on the console and how
to make our string input a bit less sensitive to capitalization. We’ve been
working around these problems by assuming the user would always
enter parsable input – integers that had no decimals, numbers that were
formatted properly, and so on – and string input that was perfectly
accurate. In part, we did this because very few people are going to write
console programs outside of the examples in this book. But, for those of
you who want to practice their coding skills with more console programs
and also want to know how to deal with trickier input, the explanations
and examples below are for you.
In this example, we are asking for three types of input from the player – string
for their name, int for their age, and float for the number of hours they play
per week. For what it’s worth, the string input is almost impossible to break.
Whatever the user enters on the console will be interpreted as a string and the
program will carry on.
The problems happen when the user enters something unparsable for the
integer or float values. The Parse() methods’ job is to make sure that the
input is appropriate for converting. For example, 100, 57312, and −19 can all
be parsed and converted to int while 100.65, 57,312, and “applesauce”
cannot. Similarly, 51.11, −421.45, and 0 can all be parsed and converted to
float while 51.1.1 and “applesauce” cannot. When the program is running
and tries to parse input that can’t be properly converted, the program will
stop and a runtime error will pop up (see Cutscene 3 for more on runtime
errors). What we need is a way to handle bad input without the program
crashing.
This is where the TryParse() methods come in handy. I didn’t use this
code in the previous levels because the syntax is a bit unusual and I wanted
to keep our examples as simple as possible. But I know that there are some
of you who really want to know how to deal with this input issue, so here
we go!
The TryParse() methods return a boolean (or bool) value so they are 100%
usable in if-statements or loops. I’m going to jump straight to loops because,
in most cases, that’s how you’re going to want to use these methods –
you’re going to want to re-prompt and re-collect input from the user if they
make an unparsable input error. Let’s break our example up a bit to
simplify this new code:
int playerAge = 0;
while(!int.TryParse(playerAgeString, out playerAge))
{
Console.Write("Please try again. What is your age: ");
playerAgeString = Console.ReadLine();
}
Take a close look at what has changed before reading on. First, we are now
collecting the player age as a separate string. Second, we now pass this
string to the TryParse() method that is in the while loop header. The
TryParse method returns true or false depending on whether or not the
parse operation is successful. If you look closely, we have a ! (not) in front
of our int.TryParse() which tells our while loop to keep running as long as
we are unsuccessful in our parsing. Third, we have a separate int variable
for the player age that is set using the special “out” parameter in the
TryParse() method. Maybe now it’s more clear to all you brave adventurers
why I waited for a bonus level to throw this useful, but somewhat-complex
bit of code at you.
When you run the program for yourself and try a few bad int inputs, you
should see something like this as output:
310 A Gamer’s Introduction to Programming in C#
Console.Write("How many hours of video games do you play per week: ");
string playerGameTimeString = Console.ReadLine();
float playerGameTime = 0;
while(!float.TryParse(playerGameTimeString, out
playerGameTime))
{
Console.Write("Please try again. What is your age: ");
playerGameTimeString = Console.ReadLine();
}
Console.WriteLine"Awesome, you play " + playerGameTime +
" hours of games per week.");
Code this for yourself and try some bad input. It should loop until you enter
a parsable float. It’s more code, it’s more complicated, but it’s also more
robust – your program won’t crash with bad number input.
int playerAge = 0;
while(!int.TryParse(playerAgeString, out playerAge) || playerAge < 18)
{
Bonus Level 3: Input Made (A Little Bit) Friendlier 311
}
Console.WriteLine("Awesome, you are " + playerAge +
" years old.");
In this update, our while loop checks for two conditions – bad input or a
value less than 18. Trying it out would look something like this:
And, while I don’t want to discriminate against older players, here is proof
that an upper condition could also be added:
Code this example for yourself and try some bad input, low values, and
high values to see how this works in the end.
if(playerName == "Aaron")
Console.WriteLine("Cool, that’s my name too!");
There isn’t much to this little example – the player enters a string,
presumably their name, and if it’s the same name as mine, we output an
extra message. If the player runs the program five times and they enter
these as the input strings, how many times will “Cool, that’s my name too!”
pop up:
312 A Gamer’s Introduction to Programming in C#
aaron
aarON
I’m the ultimate player!
Aaron
Aaron The Brave Adventurer
If you’re not sure, code this example and try it out – don’t forget to run it
five times and enter the five strings above. When you’re done, you should
find that only the fourth input – “Aaron” – makes the if-statement true and
triggers the “Cool” message. When we compare strings using ==, we are
asking for an exact match, and unlike number input, with strings, we have
to worry about uppercase and lowercase input too. To be clear, the only
string that will make our if-statement true is “Aaron”, and while “I’m the
ultimate player!” and “Aaron The Brave Adventurer” are completely
different, so too are aaron and aarON.
To make our program a bit less sensitive to strings with strange
capitalization, we can use the ToLower() method like this:
if(playerName.ToLower() == "aaron")
Console.WriteLine("Cool, that’s my name too!");
ToLower() does exactly what you might think it does – it turns all uppercase
letters to lowercase. Notice that I also adjusted my string literal to be all
lowercase as well. Now, whatever string the player enters, we’ll compare a
lowercase version to our literal “aaron”. If we were to run it again with the
same five input strings as before, the comparison would see:
Now, three out of five of our strings will successfully make our if-statement
true. This doesn’t really help with wildly different strings like “I’m the
ultimate player!” or “Aaron The Brave Adventurer”, but it does help when our
players make small capitalization mistakes in their input. If you’re wondering,
there is also a ToUpper() that converts all lowercase letters to uppercase if you
prefer to go in that direction. And it’s worth making a note that neither
ToLower() nor ToUpper() affect any numbers, spacing, or punctuation in our
strings. Finally, both ToLower() and ToUpper() work on the char (character)
data type as well. I’ll leave you with this updated example to try out:
Bonus Level 3: Input Made (A Little Bit) Friendlier 313
In this very short Bonus Level, we’re going to take a closer look at how
conditional logic (if-statements and loops) work and we’ll introduce a new
kind of decision structure called a switch statement. Let’s get right to it,
shall we?
if(1 < 2)
{
Console.WriteLine("1 is less than 2");
}
If you’ve gone through Levels 7, 8, and maybe even 9, that statement might
seem pretty simple now. But, we can actually write ifs that are even simpler
in syntax like this:
if(true)
{
Console.WriteLine("The truth shall set you free!");
}
Some people find this example helpful, and others find it confusing – that’s
why I didn’t put it in Level 7. It’s helpful because it makes the idea that if
statements run when the condition is true very clear. It’s confusing because
it’s code that we wouldn’t normally write. It would be very unusual to use
boolean literals in our conditions because they are hardcoded “true” or
“false” values:
if(false)
{
Console.WriteLine("The truth shall set you free!");
}
else
{
Console.WriteLine("This appears to be a falsehood!");
}
When we hardcode our conditions, we are saying that they will always be
true (or false), no matter what happens elsewhere in the program. This
means that our conditional logic is more-or-less useless and that’s why we
wouldn’t normally write code like this. However, I do find that it makes the
use of boolean flags a bit clearer.
}
Console.WriteLine("Hero final health: " + heroHealth);
In this example from Level 9, we’re using the boolean variable isPoisoned as
a flag for our loop to say that we are going to reduce the hero’s health by
15 until they are cured. This is very similar to the if(true) example above,
but now we’re using a variable that can change instead of a hardcoded, and
not-so-useful, literal.
Intuitively, we eventually come to understand that conditional operators
like >, <, <=, ==, and so on drive our if-statements and loops, but did you
know that they actually produce true and false values? Try running the
following code to see the output:
If everything goes well, you should see some true and false output on the
console. Conditional operators supply the boolean values that our decision
and looping logic need. Maybe this was clear to you before, but if not, I
hope it’s clearer now.
And here’s the same example using a switch statement instead of the if-
else-if:
Bonus Level 4: The Return of Decisions, Decisions 317
switch(randomValue)
{
case <= 25:
Console.WriteLine("Your hero finds a health potion and feels
refreshed!");
heroHealth += 10;
break;
case >= 90:
Console.WriteLine("Your hero steps on a rusty nail. It really,
really hurts.");
heroHealth -= 10;
break;
default:
Console.Write("The room is empty. Utterly empty.");
break;
}
Take a few minutes to look at the structure and syntax of that switch
statement before reading on. Be sure to look at what’s different and what’s
similar to the if-else-if example.
Here are some reasons to consider switch statements in your code.
In the end, it’s up to you if you want to use switch instead of if-else-if
statements in your program. If you find the structure more intuitive and clear,
or if you are writing complex logic that is slowing your program down, give
them a try. If you find them confusing with the different keywords,
punctuation, and syntax, or if you’re writing fairly simple programs that
aren’t running slowly, feel free to stick with if-else-ifs for now. If you’re
uncertain and want more examples before deciding, I encourage you to do
some more digging and carry on with a self-directed switch-focused side
quest of your own.
32
Bonus Level 5: A Matter of Scope
Variable scope is a programming topic that you don’t really notice until
something goes wrong. In fact, if you code all of the examples in the book
as they appear, scope isn’t a topic that we really need to cover at all. But
when you start writing your own programs and games, you’ll eventually
create a variable in one part of your code, and it won’t be accessible in
another part. Maybe this has already happened to you and you want to
know how and why this happened. This bonus level is here to shed some
light on variable scope.
There’s actually nothing wrong in this example. The player is prompted for
their score and if it’s higher than 100, they earn a 10-point bonus. Let’s make
a small, but problematic change:
If you code this example, Visual Studio will flag that new (bolded) statement
with a syntax error related to scope. Since the variable bonus was declared
inside the if-statement curly braces, it is “local” to the if-statement and doesn’t
actually exist outside of those braces – strange, but true. Just to make it clear
that it’s the curly braces and not the if-statement that cause this issue, try
writing and running this weird example:
{
int bonus = 10;
playerScore += bonus;
}
Even though this code doesn’t make a lot of sense, it should still run
properly if we move the declaration of bonus outside of the curly braces
like this:
Hopefully, from these examples, it’s clear that the curly braces are doing
something, and what they’re doing is defining different scope regions in our
code. Variables that are declared before a set of curly braces can be accessed
and modified before, during, and after the braces. Variables that are defined
inside of a set of curly braces can only be accessed and modified within those
Bonus Level 5: A Matter of Scope 321
braces. This is why, in the first examples, playerScore can be used before,
during, and after the if-statement, while bonus can only be used during.
Scope also has a role to play in our object classes as well. Take a look at
this simplified version of our familiar Die class (pay close attention to the
bolded variable declarations):
There are many sets of curly braces in this code – one set for the class, one
set for the constructor and each of the methods, and a set for the namespace
that I removed to save some space. Each of these sets of curly braces defines
a particular score region in our code. By declaring our attributes –
numberOfSides and sideUp – at the top, or more accurately, inside of our
class curly braces, we have ensured that they can be accessed throughout
the class. This is an important feature of attributes since we need to be able
to access and modify them in the constructor, accessor, mutator, and helper
methods. On the other hand, any parameter variables like sidesIn will only
be available in the method where they are defined – in this case, our
constructor. Similarly, rng is only available in the Roll method. Trying to
access sidesIn or rng outside of their constructor or method will result in a
syntax error because their scope is limited.
The takeaway from this short bonus level is this – if you’ve declared a
variable and can’t seem to access it, you might be trying to use it outside of
its scope. Take a look at where it’s declared and even how you’re trying to
322 A Gamer’s Introduction to Programming in C#
use it, and adjust the scope if necessary. Scope is especially important for if-
statements, loops, methods, classes, and anywhere else that curly braces are
lurking in your code.
I’ll leave you with this analogy that I’ve used many times – curly braces
are like little fences. Anything “born” outside the fences can go inside the
fences and leave again. Anything that’s born inside the fences spends their
whole lives inside and can’t escape. Maybe that’s helpful to you, and maybe
it’s just a glimpse inside my overactive imagination.
33
Bonus Level 6: Let’s Take This Online
WelcomeBraveAdventurer.ca
To help you on your journey, a companion website has been set up at
https://welcomebraveadventurer.ca. There, you’ll find:
• extra content
• links to other helpful sites and resources
• updated (or corrected) examples
• alternate Code Quests
You can explore the GitHub site using a browser, but to download code to
Visual Studio, use the “Clone a repository” feature at the top of the “Get
started” panel (Figure 33.1).
Then, simply type or paste the link to any of the projects (called
repositories) in the “Repository location” field (Figure 33.2).
Finally, click “Clone”, and enjoy!!
FIGURE 33.1
“Cloning” is an easy way to get a copy of a project that’s publicly available on code storage
sites like GitHub.com.
FIGURE 33.2
To clone a project, you’ll need the URL (website address) from the GitHub (or similar) project.
Index
Note: Locators in italics represent figures and bold indicate tables in the text.
Accessing variables, 60–64, 60 Built-in data types, 39–42, 40, 43, 50, 59,
Accessor methods, 245–247, 247 78, 79, 80, 296, 297
Addition, 112–113 bool, 43
AI. See Artificial intelligence byte, 41
Algorithms, 93, 94 char, 41
problem solving, 97–98 decimal, 41–42
properties of, 96–97 double, 41–42
as recipes for code, 94–96, 94 float, 43
&& (and) and | | (or) operators int, 43
multiple decisions with, 180–181 long, 41
&& (and) operator short, 41
strict decisions with, 171–175 string, 43
Artificial intelligence (AI), 8–9 Built-in math operators, 100–104,
Assigning and accessing variables, 110, 119
60–64, 60
Assignment errors, 65–67 Case statements. See Switch statements
Assignment operator, 60, 61 Casting, 130
Assignment statement, 60, 61, 103, 276 Char, 41, 149
Attributes, 236–240 Comments, 82–86
Attribute values block comments, 85, 86, 219
changing, 248–250 documentation comments, 86
default attribute values, setting, line comments, 85
240–245 Comparing strings and characters,
retrieving, 245–247 148–150
Automatic conversions, 112–113, Comparison operators, 147–148
305–306 Compiling process, 19–23
Compound assignment operators,
BEDMAS, 105 109–110
Binary, 51, 294 Computer hardware components, 14
storing data in bits, 294–296 central processing unit (CPU), 14–15
Bits, 51 graphics processing unit (GPU),
and bytes, 297–298 16–17
different bits for different data types, hard drive storage, 15–16
296–297 input and output devices, 16
storing data in, 294–296 random access memory (RAM), 15
Block comments, 85, 86, 219 Concatenation, 44–47, 51, 112–113
Bool, 41, 43 Conditional execution, 138–141
Boolean flags, 196–198, 315 Console programs, 3, 4, 18, 27, 32, 47–49,
Bugs, glitches, and gremlins, 214–215 290, 308
reasons for, 224–226 Constructors, 240–245
325
326 Index