Systematic Problem Solving in Programming
Systematic Problem Solving in Programming
than just code written using a programming language. Remember that a program is
a solution to a problem. Therefore, a program has a design, code, examples of how it
works, and tests. That is, it communicates how the problem is solved and illustrates
that the solution works. If any of the mentioned components are missing, then we
have an incomplete program. Would you believe someone who simply told you that
n2 , where n is a nonnegative integer, is the sum of the first n odd numbers? Many
readers would be skeptical. What if they also provided the following examples:
02 = 0
22 = 1 + 3
42 = 1 + 3 + 5 + 7
It is very likely that most readers would now feel more confident that the claim is
true. It is the same in programming. We cannot simply say that here is a function
that does this or that. We need to explain how the function computes its value, and
we need to have examples that show how it works. The steps taken to design a
program in a systematic manner is called a design recipe. In this textbook, you shall
study many different design recipes. Each design recipe shall become a tool in your
problem-solving toolbox.
There are two problem-solving techniques that are emphasized throughout the
book: divide and conquer and iterative refinement. Divide and conquer is the process
by which a large problem is broken into two or more smaller problems that are easier
to solve and then the solutions for the smaller pieces are combined to create an answer
to the problem. Iterative refinement is the process by which a solution to a problem
is gradually made better—like the drafts of an essay. Mastering these techniques is
essential to becoming a good problem solver and programmer.
Finally, problem solving ought to be fun. To this end, this book promises that by
the end of it you will have designed and implemented a multiplayer video game that
you can play with your friends over the internet. To achieve this, however, there is a
lot about problem solving and programming that you must first learn. The game is
developed using iterative refinement. As we learn about programming, we shall apply
our new knowledge to develop increasingly better versions of the video game. In
fact, every skill you develop for problem solving and program design is transferable
to other (non-programming) domains and to other programming languages.
The book uses the Racket student languages to write programs. These languages
are chosen for several reasons. The first is that they have an error-messaging system
specifically designed for beginners. This means that unlike common programming
languages the error messages are likely to make sense to beginners. If you do not
understand an error message, do not hesitate to ask your professor or search for
help online. The second is that the syntax is simple and easy to understand. This
is important because the emphasis is always on problem solving and not on how
1 The Languages and the Parts of the Book ix
to correctly write expressions. The third is that the student languages progressively
become richer. At the beginning, you have fewer features at your disposal and,
therefore, the possible errors are fewer. The fourth reason is that the student languages
come with powerful libraries to create graphics, animations, and video games. These
libraries allow students to inject their own personalities in the development of games
and animations. You are strongly encouraged to be creative. Finally, the fifth reason
is that the Racket student languages are likely to put all students on the same playing
field. Most students will be learning the syntax of the programming language together
for the first time.
The book is divided into five parts. Part I focuses on the basics. It starts with
how to write expressions. Once expressions are mastered, the first abstraction lesson
introduces us to functions. In addition, this part introduces you to conditional expres-
sions that allow you to write programs that make decisions. Just this much knowledge
allows us to write interactive programs and puts us on our way to a multiplayer video
game. As you shall discover, decision-making is fundamental to solving problems
that involve information that has many varieties. For example, the whole numbers
may be positive or negative—two varieties—and how a whole number is processed
depends on which variety a given number belongs to. Think about how to compute
the absolute value of a whole number.
Part II introduces you to compound data of finite size. Compound data has multiple
values associated. For example, a point on the Cartesian plane is compound data of
finite size. There are two values: an x coordinate and a y coordinate. Being able to
define compound data of finite size to represent elements in the real or an imaginary
world is a powerful skill to develop.
Part III introduces you to compound data of arbitrary size. This is data that has
multiple values, but the number of values is not fixed. Once again, think about a
grocery list. Sometimes there are no items in the list and at other times there may
be 10, 6, or 17 items in the list. This is where you are introduced to structural
recursion—a powerful data-processing strategy that uses divide and conquer to
process data whose size is not fixed. The types of data that are introduced are lists,
intervals, natural numbers, and binary trees. The knowledge developed is used to
develop a video game that is more challenging for the player.
Part IV delves into abstraction. This section is where we learn how to eliminate
repetitions in our solutions to problems. In fact, we learn how different data can
be processed and different problems can be solved in exactly the same way. You
are introduced to generic programming, which is abstraction over the type of
data processed. This leads to the realization that functions are data and, perhaps
more surprising, that data are functions. In other words, the line between data and
functions is artificial—a fact that is not emphasized enough in Computer Science
textbooks. This realization naturally leads to object-oriented programming—a topic
that you are likely to study extensively.
Part V introduces you to distributed programming—using multiple computers to
solve a problem. This is a topic that until now has never been addressed in a textbook
for beginning programmers. The fact that you develop proficiency in program design
makes it possible for this topic, common in modern computer applications, to be
x Preface
discussed. If you have ever sent a text message or have ever played a game online,
then you have benefitted from and have used a distributed program. It is impossible,
of course, to discuss all the nuances of distributed programming in this textbook.
Nonetheless, you are introduced to a modern trend that is likely to be common
throughout your professional career and beyond.
2 Acknowledgments
This book is the product of over ten years of work at Seton Hall University build-
ing on the shoulders of giants in Computer Science. There are many persons and
groups who deserve credit for informing my work. The Racket community has been
unequivocal in its support for the techniques that I have developed. There is an un-
payable debt of gratitude owed to Matthias Felleisen from Northeastern University
for our discussions over the years about Computer Science education, Liberal Arts
education, and program design. My students and I have greatly benefitted from his
support. Other Racketeers who have deeply influenced me are Shriram Krishna-
murthi, Matthew Flatt, Robert Bruce Findler, and Kathi Fisler. This textbook is a
tribute to our debates and their published work.
I would also like to thank the Trends in Functional Programming (TFP) and
the Trends in Functional Programming in Education (TFPIE) communities. These
communities provided (and continue to provide) a venue to discuss and present
work advancing Computer Science education. I am grateful to many individuals
including Peter Achten, Jurriaan Hage, Pieter Koopman, Simon Thompson, and
Marko van Eekelen. Their insightful feedback has informed much of the material in
this textbook.
Finally, I would like to thank Seton Hall University and its Department of Com-
puter Science for supporting the development of the work presented in this textbook.
In particular, the support of John T. Saccoman, Manfred Minimair, and Daniel Gross
is appreciated. Most of all, I am grateful to all my CS1 students over the past decade
who have informed my Computer Science education efforts. It is likely true that my
students have learned a great deal in my courses, but it is an absolute certainty that
I have learned more from them. They have refined the delivery of every idea found
in this textbook. I am especially grateful to all my undergraduate tutors and teach-
ing assistants, including Shamil Dzhatdoyev, Josie Des Rosiers, Nicholas Olson,
Nicholas Nelson, Lindsey Reams, Craig Pelling, Barbara Mucha, Joshua Schappel,
Sachin Mahashabde, Rositsa Abrasheva, Isabella Felix, and Sena Karsavran. Without
my dedicated students at Seton Hall University and their insight into what students
understood, this textbook would have been impossible.
Contents
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
1 The Languages and the Parts of the Book . . . . . . . . . . . . . . . . . . . . . viii
2 Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . x
xi
xii Contents
7 Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
38 The posn Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
39 Going Beyond the Design Recipe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
40 Revisiting in-Q1? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
41 What Have We Learned in This Chapter? . . . . . . . . . . . . . . . . . . . . . 166
12 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
65 Creating and Accessing Lists in ISL+ . . . . . . . . . . . . . . . . . . . . . . . . 266
66 Shorthand for Building Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
67 Recursive Data Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
68 Generic Data Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
69 Function Templates for Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
70 Designing List-Processing Functions . . . . . . . . . . . . . . . . . . . . . . . . . 277
71 What Have We Learned in This Chapter? . . . . . . . . . . . . . . . . . . . . . 279
Part IV Abstraction
21 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
119 Local-Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
120 Lexical Scoping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
121 Using Local-Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483
121.1 Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483
121.2 Readability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488
121.3 Furthering Functional Abstraction . . . . . . . . . . . . . . . . . . . . 490
121.4 One-Time Expression Evaluation . . . . . . . . . . . . . . . . . . . . 492
122 What Have We Learned in This Chapter? . . . . . . . . . . . . . . . . . . . . . 497
Part VI Epilogue
We all solve problems every day. Have you ever thought about how you go about
problem solving? Do you randomly go about trying potential solutions to a problem
or do you think about how to solve the problem? Most of the time you probably think
about the problem to find a solution. A natural question that arises is how do we think
about a problem to find a solution. In other words, what steps do we take to arrive to
a plausible solution? The solution is plausible until we test it and feel confident that
it works. If it does not work, of course, we go back to thinking about the problem to
obtain a refined solution. Understanding how to think about problems and solutions
is where Computer Science and programming are beneficial to everyone.
Computer Science is not the study of computers just like Chemistry is not the
study of test tubes nor Astronomy is the study of telescopes. So, why is it called
Computer Science? Although there is no clear answer to this question, the best
guess is that Computer Science is an umbrella term for many disciplines whose
primary tool is the computer, such as programming language theory, algorithmics,
software engineering, data mining, robotics, and artificial intelligence. If Computer
Science is not the study of the computer, then what is Computer Science and what do
computer scientists do? Computer scientists solve problems. Unlike biologists who
solve problems in Biology or diplomats who solve problems in Diplomacy, computer
scientists solve problems that are relevant to all fields of study and to all facets of
human life. Stated simply, Computer Science is multidisciplinary. Therefore, the
best way to describe Computer Science is to say that it is the science of problem
solving. Programming is how computer scientists express solutions to problems.
Although Computer Science is a relatively young discipline,1 it has developed many
effective techniques to solve problems. This is why everyone ought to learn to design
programs. Problem solving is an essential skill just like reading, writing, and doing
arithmetic. Your journey through this book will help you learn effective problem
solving techniques.
1
The first Computer Science Program was established in 1953 at the University of Cambridge in
the United Kingdom. The first Computer Science Department in the United States was established
at Purdue University in 1962.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2022 3
M. T. Morazán, Animated Problem Solving, Texts in Computer Science,
[Link]
4 1 The Science of Problem Solving
Two of the best-known problem solving techniques are divide and conquer and
iterative refinement. Problem solvers use divide and conquer when a larger problem
is decomposable into smaller problems. Instead of solving an entire problem in one
huge step, the problem is divided into a set of smaller problems. These subproblems
are all solved independently, and then their solutions are combined to formulate the
answer to the large problem. For example, consider computing your quiz average.
You can divide this problem into two smaller problems: sum the quiz grades and
count the number of quizzes. Once these subproblems are solved, the results are
combined by dividing the former by the latter to formulate the quiz average.
Iterative refinement is used to develop a solution in steps. This is particularly
useful to manage the complexity of a problem. Instead of developing a full answer
at once, you solve a simpler version of the problem. Once that is done, you add
complexity to the problem and re-solve it. This process continues until you have
a full answer. For instance, consider the problem of developing a video game like
Pacman. You may first create a game that only has Pacman. Then you create a game
that has Pacman and food. The next version of the game has Pacman, the food, and
one ghost. Finally, the last version of the game has Pacman, the food, and multiple
ghosts.
You have been taught to solve problems most of your life. This means that you have
been taught to compute. For example, you have been taught to compute meaning from
English expressions. You know that Thelma is that pig has a different meaning from
That is Thelma’s pig. Other languages that you have been trained to do computations
with are Arithmetic and Algebra. For example, you know how to compute the value of
the following expression: 5 * (8 + 2). In essence, we use expressions to describe
computations and we evaluate expressions to derive meaning. You may have never
thought explicitly about divide and conquer or about iterative refinement, but you
have been taught to use these techniques. Consider writing an essay. The first step
is to create an outline. This is where you decompose your argument into different
points. Perhaps, every point is implemented as a different section. You combine
the different sections into an essay by making sure that your argument easily flows
from one section to the next. As you can observe, you are using divide and conquer.
Staying with essay development, you first write a rough draft and then repeatedly
make improvements until you are happy with the result. In other words, you are using
iterative refinement.
If we are going to express solutions to problems using a computer, we need to use
a programming language much like we use English to communicate. A programming
language allows us to communicate to the computer what we want it to do. There are
many programming languages. Each has its strengths and weaknesses. You will learn
many programming languages as you explore the world of Computer Science. We
will start with a programming language called Beginning Student Language (BSL).
BSL is a programming language specifically designed to teach beginners like yourself
how to design and implement solutions to problems. A solution to a problem written
in BSL (or any other programming language) is called a program. When a program
is evaluated, we obtain its meaning. That is, we obtain the solution to an instance of
a problem.
3 Getting Started 5
3 Getting Started
2
A programming development environment is commonly called an integrated development envi-
ronment (IDE).
6 1 The Science of Problem Solving
definitions area, you may click the RUN button to evaluate your program. Let us try
this out. Type the following string3 in the definitions area:
"Hello World! I am DrRacket."
Now click on RUN. In the interactions window you will see the string printed.
Congratulations! You just wrote your first program. When you clicked on the RUN
button, you told DrRacket to evaluate your program. The value of a string is just
the string itself. Once DrRacket knows the value of a program, it is printed in the
interactions area.
** Ex. 3 — Write and run a program that prints your name and your age.
3
A string is anything inside,"", double quotes.
3 Getting Started 7
To do so, we need to know how to write valid expressions. That is, we need a
mechanism to describe how to write expressions. We will use a grammar to describe
expressions. A grammar consists of a series of production rules. A production rule
tells a programmer, for example, how to type valid expressions.
Figure 3 displays our initial grammar for expressions. Each production rule con-
sists of three parts. The leftmost part is the syntactic category being defined. If this
is missing, it means that it is the same syntactic category as the production rule
above it. The middle part is always the symbol ::=. The symbol ::= may be read
as is or as may be substituted by. The rightmost part is the definition of the syntactic
category. The first two rules state that an expression, expr, is either a string or a
number. The next rule states that a string starts and ends with ". In between, you
may have 0 or more characters. The * means 0 or more.4 Observe that character
is in a different font and surrounded by < and >. This indicates that it is a syntactic
category we only describe verbally. You may think of a character as anything that is
produced by a keystroke such as letters, spaces, and punctuation marks.5 The next
rule states that a number is either a positive or negative. A positive is a mag
(magnitude) and a negative is a - followed by a mag.
There are three rules for mag. These state that a mag may be an integer, a real, or
a fraction. An int is 1 or more digits (the + means 1 or more and is called Kleene
plus). A digit is an integer in [0..9]. A real is 0 or more digits followed by a .
followed by 1 or more digits. Finally, a fraction is an int followed by a followed
by a nonzero integer. In the grammar, the | means or. Therefore, a nonzero-int is
any digit from 1 to 9 followed by an integer.
4
In a grammar, a * is called Kleene star, named after the American mathematician Stephen Cole
Kleene.
5
Characters inside a computer are represented by an ASCII code. For example, the ASCII code for
a is 97. You will learn more about ASCII codes in a Computer Architecture course.
8 1 The Science of Problem Solving
We are now ready for our initial grammatical definition of a program. The last
rule in Fig. 3 states that a program is 0 or more expressions. Why do we need 0
or more expressions? Given that a program may compute an arbitrary number of
values, a program may have 0 or more expressions.
Let us try to write a new program. To write a new program, open a new tab in
DrRacket by using Ctrl-T. Ctrl-T means pressing the Ctrl and the T keys at the
same time. Now write a program in the definitions area to compute the name of this
textbook, the number one million, and negative 8.7 as follows:
"Program By Design"
1000000
-8.7
The result in the interactions area after clicking RUN is
"Program by Design"
1000000
-8.7
* Ex. 4 — Write and run a program that prints your year of birth and your age.
** Ex. 5 — Write and run a program that prints your favorite color, your lucky
number, and the largest prime number less than 25.
So far, all our programs have computed constant values, that is, values that we wrote
before running the program. In order for programs to be truly useful, we need to
be able to compute new values. That is, we need to be able to combine values to
create new values. Fortunately, BSL provides us with application expressions. An
application expression applies a function to one or more arguments. The function,
in essence, combines its inputs to create a new value and returns this new value.
Therefore, an application expression evaluates to the value returned by the function.
We extend the expr’s production rules in Fig. 3 to include application expressions
as follows:
expr ::= (<function> expr+ )
This production rule states that an application expression starts with an opening
parenthesis, followed by a function (the operator), followed by 1 or more expressions
(the operands), followed by a closing parenthesis. The function and each of its
arguments must be separated from each other by one or more spaces. BSL provides
us with many functions, for example, to combine numbers and to combine strings.
Table 1 lists some of the functions BSL provides along with sample uses.
At this point, you may feel that the syntax notation is a bit awkward. In your
Mathematics textbooks, for example, the basic operations (i.e., +, -, *, and /) are
4 Computing New Values 9
written using infix notation. This means that the operator is written in the middle
of the operands. For example, the sum of 1, 2, 3, 4, and 5 is written as 1 + 2 +
3 + 4 + 5. A function application, on the other hand, is written operator first and
then in parentheses the operands. For example, applying f to 3 is written as f(3).
In contrast, BSL uses prefix notation. That is, inside parentheses we first write the
operator and then the operands. Observe that the basic operations are functions, and
therefore, BSL treats them no differently than other functions (i.e., they are written
using prefix notation). Thus, the sum of 1, 2, 3, 4, and 5 is written as (+ 1 2 3 4
5) and applying f to 3 is written as (f 3). Always remember that when you want
to apply a function to some arguments, you must put in parentheses the function
first and then the arguments. As you can see, the translation from the syntax used
in Mathematics textbooks to BSL syntax is straightforward. One of the advantages
of BSL syntax is that it may be less typing as is the case for the sum of 1, 2, 3, 4,
and 5. More importantly, however, is that ambiguity cannot arise. For example, in
Mathematics syntax what is the value of 10 + 4 * 5? It depends if you add or you
multiply first. You probably remember operator precedence and believe the correct
value is 30. In BSL, we do not rely on remembering operator precedence. Prefix
notation and the use of parentheses always make it clear what arguments a function
is applied to. If the value of 10 + 4 * 5 is 30, then we write (+ 10 (* 4 5)) in
BSL. Observe that BSL syntax clearly communicates to the readers of our code that
the arguments to * are 4 and 5 and that the arguments to + are 10 and the value of
(* 4 5).
This last example explicitly shows us that application expressions may be nested.
In (+ 10 (* 4 5)), (* 4 5) is nested. There is no limit to the number or the depth
of nested expressions as subtly suggested by the above production rule for application
expressions. Always keep in mind that in an application expression for every opening
parenthesis, there must be a matching closing parenthesis. Furthermore, remember
that a closing parenthesis cannot simply be written anywhere in an application
expression. A closing parenthesis must appear after the last argument to a function.
Otherwise, the meaning of the application expression (i.e., what it evaluates to)
changes. For example, the following two expressions have different meanings:
(* (+ 4 1) 7 (- 15 5)) (* (+ 4 1 7) (- 15 5))
10 1 The Science of Problem Solving
In the first, the last argument to + is 1 and 7 is an argument for *. It evaluates to 25.
In the second, the last argument to + is 7 and 7 is not an argument for *. It evaluates
to 120. As you can see, the meaning of the above expressions is different. Always be
careful about where you close your parentheses.
There is one more thing you may be wondering. Do all functions take as input an
arbitrary number of expressions like + and *? The answer to this is no. It depends
on the function that is being applied. For example, string-length only takes one
input. If you are not sure if a function accepts an arbitrary number of arguments,
you have two options. You can try using it in a program or in the interactions area.
Consider string-append from Table 1. Can we give it more than two arguments?
Let us try it by typing after the prompt, >, in the interactions area the following:
(string-append "Hello" " " "World" "!" " " "I am DrRacket.")
After hitting Enter, the following value is printed:
"Hello World! I am DrRacket."
The answer to our question is that we can give string-append more than two
arguments. Below the output string, you get the DrRacket prompt again. DrRacket
is ready to evaluate another expression for you.
The second option is to consult DrRacket’s Help Desk as illustrated in Fig. 4.
A browser pops up and you can type BSL in the search box. After clicking on BSL,
5 Definitions and Interactions Areas Differences 11
you are taken to the documentation page for the BSL language. On this page a search
for string-append yields
(string-append s t z ...) → string
s : string
t : string
z : string
> (string-append "hello" " " "world" " " "good bye")
"hello world good bye"
The . . . means that the function takes as input an arbitrary number of arguments.
The arrow means returns. After the arrow, we find the type of value returned. Further
down, we see the type of each input: all are string type. The types of the inputs and
the type of the output define the signature of the function. It clearly informs anyone
who wishes to use string-append that if they provide strings as input, the function
returns a string as its value. In terms of high school Mathematics, you may think of
the input types as the domain of the function and the return type is the range of the
function. Finally, at the bottom we see a brief description of the function’s purpose
and an example of its use. Whenever you are unsure if BSL has a function or you are
unsure of how to use a BSL function, you may look for it in DrRacket’s Help Desk
to determine if it exists and, if so, the signature tells you the expected inputs and the
expected output.
As you have seen, expressions written in both the definitions and the interactions
areas can be evaluated. In the definitions area you need to click RUN. In the interactions
area you need to hit Enter. So, why are there two areas?
As stated before, the two areas are used for different purposes. In the definitions
area, we write programs. These are solutions to problems that we may save and
run multiple times. In the interactions area, we ask DrRacket to evaluate one-time
expressions, that is, expressions that we are not interested in running multiple times.
The difference may seem subtle, but as you advance through the first few chapters
of this book, the differences will become better delineated.
A good rule of thumb to follow is that if you wish to save the expressions you
write to solve a problem, then you write them in the definitions area. If you only
want to quickly have DrRacket evaluate an expressions for you, then write it in the
interactions window.
12 1 The Science of Problem Solving
It is an unfortunate fact that computers may crash. When this happens your unsaved
work is likely to be lost. Therefore, it is important that you regularly save your work.
It is a good idea for you to create a directory for the programs you will develop as
you read this book. You may even want to create subdirectories for each chapter.
This is an effective way to keep your files organized.
To save your work go the File menu. If it is the first time saving your cur-
rent program, click on Save Definitions As... as illustrated in Fig. 5. After
this, find the directory you wish to save your work in and enter a filename. If you
choose the filename Exercise-2-Chapter-4, your work is saved in a file named
[Link]. The .rkt extension is automatically added to indi-
cate that it is a Racket file. In reality, it is a BSL file, but it is saved as a Racket
file given that BSL is a subset of Racket. If it is not the first time saving the current
program, click on Save Definitions inside the File menu. Alternatively, you
may simply use Ctrl+S.
7 Error Messages
Errors are part of the problem solving process and even the most experienced problem
solvers write programs with errors in them. Do not panic when you get an error
message. Remember the famous saying errāre hūmānum est (to err is human6). The
error message is DrRacket’s attempt to help you diagnose the error.
6
The complete saying, coined by Alexander Pope in his Essay on Criticism, is to err is human; to
forgive, divine.
7 Error Messages 13
An important lesson to absorb is that error messages help us diagnose and correct
bugs7 in our programs. An error message itself does not diagnose the bug nor does it
tell us how to fix the bug. That is left to us as program developers, because only the
program developers really know the intended meaning of expressions in a program.
DrRacket approximates where an error occurs, but the programmer must diagnose
and remedy the bug.
As you now know, essays and programs are both written following the production
rules of a grammar. The English grammar tells you how to write a valid English
expression. Similarly, the BSL grammar tells you how to write valid BSL programs.
When you write a valid BSL program and click on RUN, DrRacket evaluates your
program and prints the answer.
If you fail to write a valid BSL program and click on RUN, DrRacket prints an
error message. When DrRacket detects a grammatical error, an informative error
message is printed in the interactions window. The message details the grammatical
error. For example, type the following in the definitions area:
"Hello World! I am DrRacket.
After clicking RUN, the following error message appears in the interactions window:
read-syntax: expected a closing '"'
The error message states that it was expecting to find a closing double quote. As you
have probably already observed, the opening double quote does not have a matching
closing double quote. Always make sure to read error messages, because they are
usually helpful in determining the cause of errors.
Grammatical errors may also occur writing application expressions. Type the
following program:
(sting-append "I’m done" " " "and going home!")
After clicking RUN, the following error message is displayed:
sting-append: this function is not defined
DrRacket is telling us that it does not know the function sting-append. We can
clearly see that the name of the function is misspelled and we correct it as follows:
(string-append "I’m done" " " "and going home!")
7
The term computer bug stems from a moth found trapped in a computer relay in 1947. The bug was
removed and taped to the log book. The log book along with the moth are part of the Smithsonian
National Museum of American History collection in Washington D.C.
14 1 The Science of Problem Solving
Another kind of error that we may make is called a type error. Type errors mostly
occur in two ways. The first is when the wrong number of arguments are provided
to a function. Let us revisit a program from the previous subsection.
(+ (* 4 5 (/ 50 10)))
After clicking RUN, the following error message is displayed:
+: expects at least 2 arguments, but found only 1
DrRacket is telling us that it believes that we are not correctly using +. We are not
providing the right number of arguments to +. This is a very common mistake done
by beginners and it is important that we keep this in mind as we implement solutions
to problems.
The second is when the wrong type of argument is given to a function. For
example, type the following after the prompt in the interactions area:
(string-append "My lucky number is: " 8)
After clicking RUN, the following error message is displayed:
string-append: expects a string as 2nd argument, given 8
The string-append function expects all its inputs to be strings. In this example,
the second argument is a number. To fix this bug, we must make the second argument
a string as follows:
(string-append "My lucky number is: " "8")
After clicking RUN, the string is printed in the interactions area. It is worth highlight-
ing that "8" is not the same as 8. The former is a string and the latter is a number,
that is, they are different types of data.
Providing a function with the wrong type of input is a very common mistake.
Therefore, type theory is an important and growing discipline within Computer
Science. As we progress through this textbook, we will learn design techniques to
help us avoid type errors. Furthermore, we will learn how to exploit the type of data
being processed to design solutions (and programs) to problems.
You need to be aware of a third kind of error called a runtime error. Unlike gram-
matical and type errors, runtime errors are not detected until after an evaluation has
started. For example, type the following program in the definitions area:
(* 5 0)
(+ 5 0)
(/ 5 0)
(- 5 0)
The results after clicking RUN are displayed in Fig. 8. We can observe that the first
two expressions are evaluated and their results are printed. The third expression is
highlighted in pink signalling that an error has been detected during its evaluation.
The error message in the interactions window informs us that there has been an
attempt to divide by 0. The fourth expression is highlighted in black signalling that
it has not been evaluated.
Our programs may also contain runtime errors that do not generate an error
message. For example, write the following program to compute f(4), where f(x)
= 2x2 + 10x + 3:8
(+ (* 2 (sqr 4)) (* 10 4) 4)
After clicking RUN, no error message is generated and 76 is printed in the interactions
window. Observe, however, that 76 is not the value of f(4). These are probably the
hardest errors to fix, because no error message is generated. In this example, the bug
is rather easy to spot. The last 4 in the expression is a typo and should be 3.
8
sqr is a BSL function that squares its input. Look it up in the Help Desk!
18 1 The Science of Problem Solving
To help protect ourselves against this type of error, most programming languages,
including BSL, allow programmers to write unit tests. A unit test validates that two
different expressions evaluate to the same value. In order to write unit tests, we need
to expand our grammar as follows:
program ::= {expr | test}∗
test ::= (check-expect expr expr)
Now, a program is 0 or more expressions or tests. The curly braces are used to
delimit the scope of the Kleene star and should not be typed as part of any program.
In BSL, we write a unit test by typing inside parentheses check-expect and two
expressions. The first expression is the one that we wish to test and it provides what is
called the actual value. The second expression provides what is called the expected
value of the first expression.
We can now rewrite our buggy program as follows:
(+ (* 2 (sqr 4)) (* 10 4) 4)
9
You may configure DrRacket to display line numbers in the View menu.
8 What Have We Learned in This Chapter? 19
** Ex. 9 — For a rectangle displayed in Fig. 10, write a program that computes
[Link] rectangle’s area
[Link] rectangle’s perimeter
[Link] rectangle’s diagonal distance from the lower left corner to the top right
corner
Make sure you write unit tests to validate your computed values.
You have learned to write expressions using numbers, strings, and applications.
Although this has enabled you to write programs, we need a richer set of data
types to make problem solving easier. This chapter introduces a broader set of data
types that are part of BSL. These are divided into two broad categories: primitive
and compound. A primitive data type is any type of data that is indivisible. For
example, numbers are a primitive data type. A compound data type is one that
contains multiple pieces of data. A piece of compound data can be divided into its
component parts. For example, a coordinate on the two-dimensional Cartesian plane
is compound data. Every coordinate is composed of an x and a y value. The new
data types introduced in this chapter are Boolean, symbol, character, and image. This
chapter also revisits the number and string data types to present them in more detail.
In addition, this chapter introduces how to define constants to avoid having to type
the same expression multiple times in programs.
Consider computing the area and the perimeter of the rectangles in Fig. 12.
Thinking about the problem leads us to conclude that area is computed by multiplying
the width and the length of a rectangle and perimeter is computed by adding the
doubling of the width and the doubling of the length. Based on this analysis, Fig. 13
displays a solution in the form of a program. The first thing you notice is that
comments are used to document a program. Anything after a ; is a comment in
BSL. Specifically, comments are used to document how area and perimeter are
computed. This helps the programmer and anyone else who reads the program to
understand the solution to the problem. Comments are also used to identify the
computation performed for each rectangle. Finally, unit tests are written to make
sure that the computations yield the correct values. Observe that there is one test for
each computation.
The program in Fig. 13 contains many repeated expressions. This is considered
poor programming, because repetitions are boring and lead to errors. Quite frankly,
no one wants to do the same thing over and over again. Therefore, we need a
mechanism to avoid repetitions in our programs. One idea that is promising is to
compute a value once. Instead of (typing and) evaluating each expression multiple
(a) Rectangle 1.
(c) Rectangle 3.
4 2
5
2
times, use the value of the expression multiple times. For this we need to learn a
little more BSL syntax to define variables. Variables are used to store the values of
expressions.
9 Definitions
(define i (* 4 25))
10
Some characters are not allowed such as #.
9 Definitions 23
Fig. 13 Version 1 of the area and perimeter program for the rectangles in Fig. 12
;; Area = width * height
;; Perimeter = (2 * width) + (2 * height)
These two definitions bind DELTA-X to 5 and bind i to 100. After clicking RUN, typing
the expression and pressing Enter at the prompt in the interactions area returns 5
and typing the expression i and Enter at the prompt returns 100. Defining variables
is how typing and evaluating the same expression more than once is avoided. They
store the value of an expression allowing programmers to avoid multiple evaluations
of the same expression. Instead, the defined variable is used multiple times.
By convention, two types of variables are distinguished: those whose value may
change and those whose value may not change. The latter are called constants. Both
are defined using the def syntax. The convention in this textbook is to use uppercase
letters for constants and lowercase letters otherwise. In the above example, DELTA-X
is considered a constant and i is not considered a constant. In BSL, there is no such
convention and it is up to the programmers to use this convention.
Armed with the def syntax, the program in Fig. 13 may be rewritten as displayed
in Fig. 14. Observe that for each value computed a constant is defined. Instead of
using generic variable names like x and i, the constants are given names that inform
24 2 Expressions and Data Types
Fig. 14 Version 2 of the area and perimeter program for the rectangles in Fig. 12
;; Area = width * height
;; Perimeter = (2 * width) + (2 * height)
(check-expect AREA3 4)
(check-expect PERIM3 8)
any reader what they represent. This is a good practice and it is a practice that
is followed in this textbook. It is a habit that every programmer is encouraged to
follow. The value of each constant is defined by an expression. Subsequently, the
variable is used to refer to the value of the expression instead of reevaluating the
same expression again. For instance, in the unit tests the constants are used. Observe
also that it is no longer necessary for the program to return each area and perimeter
value, which eliminates the need to clutter the interactions area with printed values.
If a value needs to be examined, type the name of the variable at the prompt and hit
Enter.
Which version of the program is clearer? Most people will argue that the program
in Fig. 14 is clearer. Any interested reader can see what the constants represent.
Furthermore, they can easily see that the tests are there to make sure that the values
of the constants are correct. Admittedly, for this rather small and simple program, it
may not seem significant. Nonetheless, it is important for you to realize now that as
the size of programs grows, it is harder for a programmer (and a reader) to remember
10 Numbers 25
all the details. Therefore, the goal of a program is twofold. First, a program must
correctly solve a problem. Second, a program must clearly communicate the solution
to the problem. Both of these goals are equally important and you must adhere to
them as responsible problem solvers. It is never acceptable to get the code to work
without documenting the solution to the problem. Think about this carefully. If you
were working on a team programming project, would you trust code that you do not
understand?
10 Numbers
In Chap. 1, a string is described as anything that is inside double quotes. The word
“anything" suggests that there may be more than one piece of data in a string. In fact,
a string is our first example of a compound data type. Compound data is constructed
using several pieces of data. For example, the string "cat" may be constructed using
the strings: "c", "a", and "t". We build a string by using double quotes. Putting cat
inside double quotes glues together "c", "a", and "t" as a single piece of data. This
is exactly the same as typing (string-append "c" "a" "t"). In other words,
the use of double quotes is simply shorthand for using string-append. Functions
that build compound data, like string-append, are called constructors.
When data is compound, it is possible to access some or all of its components.
Functions that extract a component from an instance of compound data are called
selectors. For strings, we can use the following BSL function to extract substrings:
;; string N [N] → string
;; Purpose: Extract from s the substring starting in
;; position i and ending in position j-1
(substring s i j)
The signature is stating that the function substring requires at least two inputs. The
first is a string and the second is a natural number less than or equal to the length of
s.11 The [] indicates that the third argument is optional. The optional third argument
is a natural number, j, such that 0<=j<=length of s. Think of i and j as defining
the interval [i..j) and substring as extracting the substring from position i to
position j. Not providing the third argument is shorthand for j being the length of the
s. For example, (substring "Wow!" 2) is shorthand for (substring "Wow!"
2 4). Finally, it is important to note that strings’ positions start at 0 (not 1).
The following program illustrates that strings are compound data:
11
A natural number is an integer greater than or equal to 0.
28 2 Expressions and Data Types
* Ex. 13 — There are many useful BSL functions to manipulate strings. Be-
come familiar with, for example, string-ith and string-length by looking
them up in DrRacket’s Help Desk. Write a program that appends these 3
11 Strings and Characters 29
string: "Program", "By", and "Design". Include tests to illustrate the follow-
ing:
[Link] appended strings result in "Program By Design".
[Link] length of the appended strings is 17.
3."P" is the substring in positions [0..1].
4.\#P is the first character of the appended strings.
5."By" is the substring in positions [8..10].
[Link] last character of the appended strings is \#n
* Ex. 14 — What happens if we add the following tests to the CAT program
above? Why does each test pass or fail?
(check-expect (substring CAT 3) "")
(check-expect (substring CAT 4) "")
(check-expect (substring CAT 0 3) CAT)
(check-expect (substring CAT 0 4) CAT)
(check-expect (substring CAT -1 1) CAT)
(check-expect (substring CAT 3 1) CAT)
* Ex. 17 — Write a program that adds the lengths of "Sena" and "Isa".
30 2 Expressions and Data Types
12 Symbols
Symbols are like strings that are not decomposable. That is, symbol is a primitive
type like number. Symbols are used when we are not interested in manipulating or
accessing the characters as can be done with strings. Symbols require extending the
BSL grammar explored. The following is a new expr production rule for symbols:
expr ::= '<character>+
An expression may be a symbol, which is written as a ' followed by one or more
characters with no spaces.
DrRacket denotes most symbols as a ' followed by the name of the symbol. For
example, entering (string->symbol "Apple") at the prompt returns a symbol
that is printed as 'Apple to the screen. Entering (string->symbol "1024")
returns a symbol, but it is printed differently. Numeric symbols are printed as ',
followed by |, followed by the number, and ending with |. Therefore, the symbol for
"1024" is printed as '|1024|.
Not surprisingly, a symbol is convertible to a string and vice versa. The following
program exemplifies this observation:
;; NAME PROGRAM
Do not panic if you do not remember or recognize function composition and inverses
right now. These will be discussed in more detail later in this book. We mention them
here, because they are important in programming. For example, you may compress
and uncompress your files, encrypt and decrypt your sensitive data, or marshal–
unmarshal your data for computer communication. All of these are achieved by a
pair of functions that are inverses of each other.
** Ex. 18 — Write a program that defines a symbol for your first name and
that computes a symbol that is your first name reversed without the last letter.
Hint: Make a plan for the values that need to be computed to solve this problem.
For example, the program first converts the symbol to a string, then computes a
new string for your reversed name without the last letter, and finally computes
a symbol from that new string.
* Ex. 19 — Explain what happens and why when the following tests are added
to the name program above:
(check-expect NAME STR-NAME)
(check-expect SACH STR-NAME2)
13 Booleans
Boolean is a primitive type of data. There are only two kinds of Boolean values:
#true and #false. This type of data is important because it allows to perform
computations that depend on whether conditions hold or fail to hold. For example,
in Mathematics you have studied the absolute value function:
x if x ≥ 0
absV alx =
−x otherwise
How do you compute absVal(-100)? You plug in −100 for x, evaluate the condi-
tion, −100 ≥ 0, and determine that it is false. Since this condition is false, the value
of the function is given by −(−100). Evaluating this expression tells you that the
value of absVal(-100) is 100.
Two new expr production rules for Booleans are needed:
expr ::= #true
::= #false
These rules state that a # followed by either true or false represents a Boolean
value in BSL. Nothing else is a Boolean.
32 2 Expressions and Data Types
x y x∧y x∨y ¬x
false false false false true
false true false true true
true false false true false
true true true true false
Just like numbers have basic functions (i.e., +, -, *, and /) to create new numbers,
Booleans have the following basic functions: and (also denoted by ∧), or (also
denoted by ∨), and not (also denoted by ¬). A truth table, as the one in Table 2, is
used to illustrate the four basic Boolean functions. From the table, we can infer that
1. and is true when all inputs are true and is false otherwise;
2. or is true when any input is true and is false otherwise;
3. not is true when the input is false and is false otherwise.
The above is a complete description on the basic Boolean functions. It is noteworthy
that it is not always necessary to evaluate all the arguments to and or to or. Consider
the following two expressions:
(and #false (≤ x 20)) (or #true (> i 100))
After evaluating the first argument to and, the other arguments may be ignored,
because the result is known to be false. Similarly, after evaluating the first argument
to or, the other arguments may be ignored, because the result is known to be true.
BSL takes advantage of the fact that not all arguments to and and or may have to
be evaluated to make them faster. Instead of evaluating all the arguments to and or
or, BSL stops evaluating arguments to and after the first that evaluates to #false.
Similarly, BSL stops evaluating arguments to or after the first that evaluates to #true.
To achieve this, and and or are not functions in BSL. Instead, they are new types
of expressions. The BSL grammar includes the following two production rules for
expr:
expr ::= (and expr expr expr∗ )
::= (or expr expr expr∗ )
These production rules tell us that and and or expect at least two values as input. In
BSL, not is a one-input function.
13 Booleans 33
After running this program, all the tests pass. Observe, as displayed in Fig. 16, that
not all the expressions are evaluated. The expressions that remain unevaluated by
the tests are highlighted in black. These expressions are unevaluated, because their
value is not needed to determine the value of an and or an or expression. In fact,
there is no way to test the unevaluated code. As programmers, this means that, at
best, thorough testing gives us confidence in the program. Testing, however, never
establishes that the program is correct.
** Ex. 21 — Add the following definitions and tests to the basic Boolean func-
tions validation program:
(define AND-FERR (and #false (/ 5 0)))
(define OR-FERR (and #false (string-append "Is this "
"a Boolean?")))
13.2 Predicates
Functions that return a Boolean are called predicate functions. They are useful to
determine if the input meets some condition. In BSL, built-in types have a predicate to
determine if the input is of that type. These are the predicates for the types presented
in this chapter:
Type Predicate
number number?
string string?
character char?
symbol symbol?
boolean boolean?
image image?
For example, (string? "Hi there!") evaluates to #true and (boolean?
103167) evaluates to #false.
You have used predicates in high school Mathematics. For instance, <, >, ≥, ≤,
and = are all predicates. They are all functions in BSL with the following names: <,
>, >=, <=, and =. These functions all require 1 or more inputs. Typing (< 10) at
the prompt returns #true. Why does this make sense? It turns out that a numerical
predicate returns #true unless one of its input makes it #false. There is nothing
in < 10 that makes it #false, and therefore, this expression evaluates to true.
What does < 10121831 evaluating to #true mean? This is the BSL expression for
10 < 12 < 18 < 31. Remember that BSL expressions use prefix, not infix, notation.
It evaluates to #true, because 10 < 12 ∧ 12 < 18 ∧ 18 < 31 is true.
13 Booleans 35
Strings share an important property with numbers: they are ordinal. This means
that they may be ordered. Strings may be lexicographically ordered (i.e., alphabet-
ically ordered). Therefore, we can ask if one string is less than another or if one
string is greater than or equal to another. We cannot, however, use numerical pred-
icates with strings. The corresponding string predicates are string<?, string>?,
string<=?, string>=?, and string=?. Observe that the ? suggests that a question
is being asked. For example, (string<? "a" "b" "c") is asking if "a" comes
before "b" and "b" comes before "c".
Unlike strings, symbols are not an ordinal type. This means that there are no
functions to compare symbols like > or string<?. There are two predicates to test
symbols. The predicate symbol? tests if its input is a symbol. The predicate eq?
may be used to test if two symbols are equal. In fact, eq? does not exclusively
work with symbols. For example, we can write (eq? 1 1) and (eq? "Alpha
Centauri" "Alpha"). The first evaluates to #true and the second evaluates to
#false. Functions, like eq?, that work for many types of inputs are called generic
functions. We will explore generic functions later in this textbook. For now, we use
=, string=?, and eq?, respectively, to test for numeric, string, and symbol equality.
BSL predicates for Booleans include boolean? and false?. The first tests if its
input is a Boolean. The second tests if its input is #false. You may ask yourself,
why is true? not a predicate in BSL? The answer is that the creators of BSL cannot
possibly implement every conceivable function. Therefore, they must choose what
functions to provide. One criterion that is used to decide is whether or not a value
may be computed using other functions. Can we write expressions to determine if
a variable is #true? To answer this question, we must carefully think about what it
means to be #true. First, the value must be a Boolean. Second, the value must not
be #false. With this insight, we can write an expression to determine if a variable is
true using not, and, and false?. This is a sample program to test our observations:
(define A-STRING "This is not true.")
(define A-SYMBOL 'not-true)
(define A-NUMBER 87)
(define A-BOOL #true)
(define A-BOOL2 #false)
* Ex. 22 — BSL provides many useful predicates to us. What are the signature
and purpose of the following predicates:
1. odd?
2. even?
3. positive?
4. negative?
5. string-uppercase?
6. string-lowercase?
7. string-numeric?
8. string-alphabetic?
9. string-contains?
*** Ex. 25 — The nand (not and) boolean function has a property called func-
tional completeness. This means that any Boolean expression may be written
only using nand expressions. The truth table for nand is
A B (nand A B)
false false true
false true true
true false true
true true false
The value of nand is given by the expression (not (and A B)). We can
compute (not X) using a nand expression as follows: (not (and X X)).
14 Images 37
Write a program to demonstrate how to compute and and or using only nand
expressions.
14 Images
This teachpack contains many functions that are split into three broad categories:
Basic Constructors These are functions to create basic images like circles, rectan-
gles, ellipses, stars, and text.
Property Selectors These are functions to extract properties of images such as
width and length.
Image Composers These are functions that combine existing images to create new
images.
It is useful to understand two basic definitions before using the 2htdp/image
teachpack:
mode This refers to how basic geometric shapes are drawn: not filled or filled. Use
'outline or "outline" when an outline of a shape is desired. Use 'solid or
"solid" when a solid shape is desired.
image color This is a string or a symbol representing the desired color. Search for
image-color? in DrRacket’s Help Desk to see the list of predefined colors.
Figure 17 illustrates outline and solid geometric shapes. Figure 17a displays an
outline red rectangle created using the rectangle function. Figure 17b displays a
solid green circle created using the circle function.
To illustrate the use of the basic image constructors, consider the program in Fig. 18
to construct the images in Fig. 17. First, constants for the characteristics of the
rectangle and the circle are defined. Next, a constant for each image is defined. The
rectangle is created using the basic constructor rectangle. It requires four inputs: a
width, a height, a mode, and a color. The circle is created using the basic constructor
circle. It requires three inputs: a radius, a mode, and a color. This is followed by
tests. We can write the expected value of a test using a function application or an
image given that both are valid expressions in BSL. The tests that use images require
that the image be created first and then copied and pasted as part of the test.
There are too many basic image constructors to describe them all in this textbook.
Therefore, search for 2htdp/image in the Help Desk to learn about and experiment
with them. Be creative!
(check-expect RED-OUTLINE-RECT )
(check-expect GREEN-SOLID-CIRCLE (circle 25 solid green))
(check-expect GREEN-SOLID-CIRCLE )
Consider the problem of computing half the width, half the height, and the number
of pixels for the following images:
(define A-STAR-IMG (radial-star 10 5 35 'outline 'gold))
(define A-RHOMBUS-IMG (rhombus 60 75 'solid 'red))
The first step is to clearly identify what needs to be computed and how it is computed.
We need to compute three values as follows:
Half the Width Divide the image’s width by 2.
Half the Height Divide the image’s height by 2.
Number of Pixels Compute the image’s area.
The next question that arises is how are we to test our computations. We know that
every image is rectangular, but this does not tell us what is the width or the height
of a star image or of a rhombus image. When we do not know what a specific value
ought to be or it is too difficult to write a specific value, we can use property-based
testing to write tests. Instead of testing for the specific value of an expression, we
test properties that the value of the expression ought to have. For our problem, we
40 2 Expressions and Data Types
;; Star computations
(define STAR-W (image-width A-STAR-IMG))
(define STAR-H (image-height A-STAR-IMG))
(define STAR-HALF-W (/ STAR-W 2))
(define STAR-HALF-H (/ STAR-H 2))
(define STAR-PIXELS (* STAR-W STAR-H))
;; Star tests
(check-expect (* 2 STAR-HALF-W) STAR-W)
(check-expect (* 2 STAR-HALF-H) STAR-H)
(check-expect (/ STAR-PIXELS STAR-W) STAR-H)
(check-expect (/ STAR-PIXELS STAR-H) STAR-W)
;; Rhombus computations
(define RHOMBUS-W (image-width A-RHOMBUS-IMG))
(define RHOMBUS-H (image-height A-RHOMBUS-IMG))
(define RHOMBUS-HALF-W (/ RHOMBUS-W 2))
(define RHOMBUS-HALF-H (/ RHOMBUS-H 2))
(define RHOMBUS-PIXELS (* RHOMBUS-W RHOMBUS-H))
;; Rhombus tests
(check-expect (* 2 RHOMBUS-HALF-W) RHOMBUS-W)
(check-expect (* 2 RHOMBUS-HALF-H) RHOMBUS-H)
(check-expect (/ RHOMBUS-PIXELS RHOMBUS-W) RHOMBUS-H)
(check-expect (/ RHOMBUS-PIXELS RHOMBUS-H) RHOMBUS-W)
know that an image is always rectangular. This means that twice half the width ought
to be the width, twice half the height ought to be the height, the number of pixels
divided by the image’s height ought to be the image’s width, and the number of
pixels divided by the image’s width ought to be the image’s height.
Now that we have a game plan, we can proceed to write our program as displayed
in Fig. 19. Observe that related definitions and tests are kept together. First, a data
item is defined like A-RHOMBUS-IMG. Second, the result of computations is defined
like RHOMBUS-HALF-W. Third, tests are written. Organizing code in such a manner
is a good practice because anyone who reads the code (including yourself 6 months
from now) will be able to understand the goals.
14 Images 41
Image composers are used to create new images from existing images. The image
teachpack provides a myriad of such functions and you ought to experiment with
them as you read the documentation in the Help Desk.
Consider the problem of creating the image in Fig. 20. As always, the first step
is to determine what needs to be computed. The image consists of 6 nested squares.
The largest square is yellow and at the bottom. The smallest square is blue and on
the top. This suggests a divide and conquer strategy. First, compute all the needed
squares. Second, overlay the squares to create the desired image. We can test the
result using property-based testing. A program to implement this strategy is
;; Tests
;; NOTE: SQUARE0 is the largest square.
(check-expect (image-width NESTED-SQS) (image-width SQUARE0))
;; The squares
(define SQUARE0 (square 60 'solid 'yellow)) ;; largest square
(define SQUARE1 (square 50 'solid 'blue))
(define SQUARE2 (square 40 'solid 'yellow))
(define SQUARE3 (square 30 'solid 'blue))
(define SQUARE4 (square 20 'solid 'yellow))
(define SQUARE5 (square 10 'solid 'blue)) ;; smallest square
the 6 needed squares from largest to smallest. Finally, the needed image is created
by using the function overlay always placing a smaller image over a larger image.
It is natural to wonder how the placing is done. Recall that an image consists of
many pixels. One of these pixels serves as an anchor point around which images
are placed. Unless otherwise specified, image composing functions use the pixel at
the center of the image as the anchor point. This means, for example, that overlay
places all the center points of the images one on top of another. In our program,
the anchor point of SQUARE1 is placed over the anchor point of SQUARE0. Next,
the anchor point of SQUARE2 is placed over the anchor point of the SQUARE1 and
SQUARE0 composed image, and so on until all the squares are overlayed. You may
contrast this behavior with the behavior of the application expression:
(overlay/xy img1 i j img2)
For overlay/xy, the images start aligned with respect to their top-left corner pixel
(not the center pixel). The function overlays img1 over img2 but moves img2 i pixels
to the right and j pixels down. If i is negative, img2 is moved to the left. If y is
negative, img2 is moved up. Experiment with image composers!
Another noteworthy characteristic of our program is that the testing is not very
satisfying. Consider what happens if we mistakenly define NESTED-SQS as follows:
(define NESTED-SQS (overlay SQUARE0 SQUARE1 SQUARE2
SQUARE3 SQUARE4 SQUARE5))
No tests fail when running the program despite that NESTED-SQS is the image of
a yellow square. Clearly, this is not the desired result and this testing shortcoming
ought to be addressed. To remedy such a situation, add unit tests using actual values
that are computed. In this case, first experiment with our program. Once the desired
image is computed, copy and paste it as the expected result for testing NESTED-SQS.
In this manner, any reader of your code can see that both your model and your result
are consistent with expectations. You must be very careful when employing such
a strategy to write tests. If a computed value is wrong, then its use in a test will
make the test pass. Your program, however, still computes the wrong value. It is best
to limit the use of this strategy to test computed images that are easy to visually
validate.
There are two image composing functions that are used extensively to create anima-
tions and video games:
empty-scene Creates an empty image of some given width and height. Option-
ally, the color of the empty image may be provided.
place-image Places the first given image at the given x and y coordinates in the
second given image.
As characters and elements change in a video game, the displayed image must change.
This is done by computing a new image. The empty image is used to create the base
14 Images 43
image of the video game or animation. Think of this base image as the empty canvas
of an artist. The function place-image is used to place elements.
To properly use place-image, a brief description of the computer graphics coor-
dinate system is needed. Figure 21 displays the coordinate system used in computer
graphics. The possible values of x grow from left to right. To place an image further
to the right, the x value is increased. To place an image further to the left the x value
is decreased. The possible values of y grow from top to bottom. To place an image
further down, the y value is increased. To place an image further up, the y value is
decreased. For a given image with dimensions WIDTH x HEIGHT, the valid x values
are in [0..WIDTH-1] and the valid y values are in [0..HEIGHT-1]. If any part of
a placed image falls outside these coordinate ranges, the image is cropped and parts
of the placed image do not appear in the result.
Consider the problem of creating an image with three different alien ships in a
black empty scene. To make the task more manageable, use a divide and conquer
strategy:
• Define the dimensions of the empty scene.
• Compute three different alien ships.
• Compute an image with one alien ship.
• Compute an image with two alien ships.
• Compute an image with three alien ships.
• Write tests.
There are an infinite number of ways a program can be written following this strategy.
Figure 22a displays a proposed partial solution. A black empty scene, 200 x 100,
and 3 different alien ships that differ in color are defined. After this, 3 images are
defined using place-image. Each successive image contains one more ship.
The resulting image with the 3 alien ships is displayed in Fig. 22b. Immediately,
you can see there is a bug in the program. The pink alien ship is cropped and only
part of it appears in the image. This example highlights the value of testing. The
program in Fig. 22a does not contain a test. Although the divide and conquer strategy
is a good one, it is always necessary to perform thorough testing to establish a degree
of confidence in a problem’s solution.
44 2 Expressions and Data Types
Fig. 22 Program for an image with 3 different alien ships in a black empty image
Program.
(require 2htdp/image)
** Ex. 27 — Debug and complete the design of the program in Fig. 22a. Make
sure to test all defined values.
* Ex. 28 — Write a program that creates an image of a banner with your name
on it.
* Ex. 29 — Write a program that creates an image of a triangle that is over an
image of the triangle flipped over the x-axis.
*** Ex. 31 — Write a program that creates a starry night image. Your com-
position must have a representation of at least one moon, three stars, and a
stickman. Figure 23aa and 23bb display sample starry night images created by
students. Be creative and have fun!
a
Courtesy of Lindsey M Reams.
b
Courtesy of Shamil Dzhatdoyev.
• When solving a problem, outline the values that must be computed to guide the
development of a solution based on divide and conquer.
• In addition to number and string, BSL includes the character, symbol, Boolean,
and image types.
• Primitive type instances, like a Boolean, cannot be decomposed.
• Compound type instances, like a string, can be decomposed.
• The BSL syntax developed so far is displayed in Fig. 24.
– New expressions for characters, symbols, Booleans, and images.
– andand orexpressions are syntax for the Boolean functions and and or.
– not is a Boolean function in BSL.
– | means or. For example, the first production rule for expr states that it can be
a number, a string, or a variable.
• Computations involving numbers and images may contain errors.
• Use check-within to test the computation of a real number.
• Boolean functions are called predicates and allow programmers to determine if
the input satisfies a property.
• Model checking tests properties of a computed value.
• The 2htdp/image teachpack provides functions to create, compose, and extract
properties of images.
Chapter 3
The Nature of Functions
Every high school student has studied functions. What for? Have you ever wondered
where functions come from? Why do they exist? Certainly, many students can state
that a function is a mapping from a domain element to a range element. That,
however, does not tell anyone where functions come from. Perhaps, looking at an
example helps. Consider the following function:
f(x) = x2 + 3x - 10
Certainly, many students can state that x is an input element from the domain and
that the value of f(10) is 120. Now, we know something about functions. We can
use functions to compute values. This is done by "plugging in" 10 for x. That is, all
instances of x are substituted with 10 to obtain
f(10) = 102 + 3·10 - 10
Now, it is all a matter of evaluating function’s body (i.e., the expression to the right of
the equal sign). This is screaming to us that functions are likely to play an important
role in programming. After all, BSL, as well as other programming languages, is
exceptionally good at evaluating expressions. A program in BSL to evaluate f(10)
looks like this:
;; Evaluating f(x) = x^2 + 3x - 10
(define F-X10 (+ (sqr 10) (* 3 10) -10))
(check-expect F-X10 120)
We still, however, do not know where f(x) came from.
This chapter introduces the process of abstraction over expressions that leads to
functions. It also introduces the BSL syntax needed to define functions (like f(x)).
More importantly, it introduces the first design recipe to guide problem solvers in
function development. A design recipe is a series of steps, each with a specific
outcome, that defines a systematic process to implement functions. A design recipe
does not tell you the solution to a problem. That you must figure out. It does, however,
prove useful to take you from a problem statement and a blank IDE to a program that
solves the problem in a manner that is understandable to others.
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2022 47
M. T. Morazán, Animated Problem Solving, Texts in Computer Science,
[Link]
48 3 The Nature of Functions
Writing a program to compute the value of f(10) has proven rather easy. Now you
are asked to also have your program compute f(0), f(1), f(20), and f(100).
After some thought and a bit of typing, your program looks as displayed in Fig. 25.
A different constant is defined for each value of f(x) obtained from a different value
of x. The tests validate that each value of f(x) has been correctly computed.
Now, you are asked to have your program compute all the values of f(x) for the
integers in [11..19]. This is another 9 different values of x and, frankly speaking,
many are unlikely to want to do a lot of the same typing over and over again. To
avoid this, some may decide to copy, paste, and edit code to account for the changes.
This is dangerous, because it is error-prone. It also means that for a new batch of x
values you will be doing a lot of copying and pasting again. There must be a better
way to get the job done.
Why would anyone decide to cut and paste to write more code? It turns out there
is something interesting about the expressions used to compute the different values
of f(x). Observe that they are almost identical (see Fig. 26). There is only one
difference from one expression to the next: the number plugged in. The elements
that vary among similar expressions are called variables. That is, we can abstract
away the differences among similar expressions by associating each difference with
a variable. For the expressions above, let us call the single difference x (as done in
the mathematical function f(x)). This reduces all the expressions above to a single
expression:
(+ (sqr x) (* 3 x) -10))
In order to exploit such an expression, a mechanism is needed to provide x with a
value.
Let us go back and see how mathematicians provided this mechanism. They chose
the syntax f(x) = e, where e is a mathematical expression. We say that f(x) is
the function header. A function header contains two parts: the name of the function
and the name of the input variables. The input variables are called the function’s
parameters. For f(x), f is the function name and x is the only parameter. We say that
16 The Rise of Functions 49
;; number number
;; Purpose: To compute f(x)
(define (f x)
(+ (sqr x) (* 3 x) -10))
• Instead of asking you to modify the program for every value of f(x) needed, can
others use the program to compute these values?
To answer the first question, consider a single test. Most individuals find more
readable and prefer to type
(check-expect (f 11) 144)
over
(check-expect F-X11 144)
(define F-X11 (+ (sqr 11) (* 3 1) -10))
The answer to the second question is yes given enough memory. As a program is
evaluated, memory is allocated to store values. Since memory is finite, it is possible
that a program may be asked to compute a value that requires more memory than
is available. This is something to keep in mind. In practice, however, people around
the world execute programs every day without running out of memory. The answer
to the third question is an unequivocal yes. Anyone running the program only needs
to type the correct application expression at the prompt in the interactions area.
Why were the tests using constants not rewritten to eliminate the need to define
the constants? The answer is that many times it is useful to see how a value is
17 General Design Recipe for Functions 51
computed to understand a program. Therefore, you should write both types of tests:
tests that use concrete values (like the last 9 tests) and tests that use defined constants
for expressions that show how a value is computed (like the first 4 tests).
is computed by the function. The function header is written to satisfy Step 5. Pick a
function name that helps identify the purpose of the function. For example, naming
a cubing function f is a poor choice. A better choice is naming the function cube. In
addition, the parameters from Step 3 must be written in the order that corresponds
to the types in the signature. For Step 6, tests are written using an application of
the function as the tested value and using both the constants from Step 2 and other
values not defined as constants. The body of the function is developed to satisfy
Step 7. β-reduction is performed on any one of the sample expressions. That is, the
differences are substituted with the correct parameter. Finally, Step 8 requires the
running of the tests. If all the tests pass, you may have guarded optimism in having
designed a correct function. If there are any errors, then you need to check your
answers to each step of the design recipe.
The design recipe suggests a function template may be used for function design.
A function template is like a skeleton for functions. It captures the main components
a problem solution needs. Figure 29 displays the basic function template. Every time
a problem is being solved, the template is copied and specialized. When an answer
for a step of the design recipe is formulated, the copied template is edited to reflect
the answer.
17 General Design Recipe for Functions 53
To illustrate the use of the design recipe, consider the problem of creating name
banner images for Craig, Julia (Ohio), Steve, Little Nick, Big Nick, and Jeremy. The
name image should contain only the name in a green 36 size font.
The answers to each step of the design recipe are outlined as follows:
STEP 1
Represent each name as a string. The banner image is computed by applying the
text function to a name, 36, and 'olive.
STEP 2
Three constants for the value of sample expressions are
STEP 3
The only difference among the three sample expressions is the string representing
the name. The variable for this difference is name.
STEP 4
There is only one difference which is a string and the value computed is an image.
The signature and purpose statement are
;; string → image
;; Purpose: Create a banner for the given name
;; using font 36 and the color olive
STEP 5
The function header contains only one parameter, because a single difference was
identified in Step 3. The name make-banner is suggestive of the purpose of the
function. The function header is
(define (make-banner name)
STEP 6
The tests using the defined constants and sample values are below.
;; string image
;; Purpose: Create a banner for the give name using
;; font 36 and the color olive
(define (make-banner name)
(text name 36 olive))
)
(check-expect (make-banner "Big Nick")
)
(check-expect (make-banner "Jeremy")
STEP 7
The body of the function uses name instead of a concrete string:
(text name 36 'olive)
STEP 8
All tests pass.
Figure 30 displays the full program written in BSL inside DrRacket.
18 Auxiliary Functions 55
* Ex. 32 — Follow the steps of the design recipe to write a program to compute
the area of an ellipse.
** Ex. 33 — Follow the steps of the design recipe to write a program to com-
pute the image of a smiley face.
* Ex. 35 — Follow the steps of the design recipe to write a program to deter-
mine if a string is of even length.
18 Auxiliary Functions
Seldomly, programs only need to compute many instances of a single value as the
program in Fig. 30. It is far more common to have to compute several different values
to solve a problem. A good designer develops a function for each different value.
Such a practice makes it easier for the programmer and for others to understand
the solution to the problem. It also endows programs with modularity. Modularity
enables re-usability of expressions and reduces code duplication. In other words,
modularity is tightly connected to abstraction.
If problem analysis reveals that different kinds of values need to be computed,
consider which values ought to be computed by an auxiliary function. If special
knowledge is required to compute a value or if different instances of the same value
are needed, then designing an auxiliary function is a good choice. On the other hand,
if the expression needed to compute a new value is short, simple, and used only once,
then designing a new function may add little clarity to the solution.
If a programmer determines that several functions are needed, she can follow
two basic strategies: bottom-up or top-down. Bottom-up is a programming style in
which the simplest (usually the smallest) functions are designed first, and then these
functions are used to design more complex functions. Top-down is a programming
style in which the complex functions are designed first, and then simpler functions are
designed. The most commonly used approach is top-down, because a programmer
rarely knows in advance all the simpler functions that are required. Following a top-
down approach allows the programmer to discover, as the design advances, needed
simpler functions.
56 3 The Nature of Functions
STEP 3
The only difference among the sample expressions is the radius. The variable for
this difference is a-radius.
18 Auxiliary Functions 57
STEP 4
The signature and purpose statement are
;; R>= 0 → R>= 0
;; Purpose: Compute the area of the circle with the given
;; radius.
STEP 5
The function header is
(define (area-circle a-radius)
Observe that the chosen function name suggests the purpose of the function.
STEP 6
The tests using defined constants and sample values are
Observe that the tests include, 0, the lowest possible value for a-radius. It is
always good practice to test border values in the domain of a function.
STEP 7
The body of the function is
(* pi (sqr a-radius))
STEP 8
All tests pass.
With area-circle designed, implemented, and tested, the function for the area
of a washer is designed. The steps of the design recipe may be satisfied as follows:
STEP 1
The area of a washer is given by the area difference of its outer and inner circles.
STEP 2
Three constants for the value of sample expressions are
Observe that function composition is used to write the tests. The output of
area-circle is used as input to -. In addition, observe that the constant names
are suggestive of the value they represent.
STEP 3
There are two differences in the sample expressions: the radii of the outer and the
inner circles named, respectively, outer-radius and inner-radius.
STEP 4
The signature and purpose statement are
The name of the function is suggestive of its purpose. Compare this name choice
with a name like g or area.
STEP 6
The tests using defined constants and sample values are
STEP 7
The body of the function is
(- (area-circle outer-radius) (area-circle inner-radius))
STEP 8
All tests pass.
The complete program is displayed in Fig. 32. An advantage of bottom-up design
is that functions may be tested as soon as they are implemented. For example, the
area-circle function may be tested as soon as it is written.
60 3 The Nature of Functions
* Ex. 36 — Use a bottom-up approach following the steps of the design recipe
to write a program to determine if f(a) equals f(b), where f(x) = x2 - 9.
*** Ex. 37 — Use a bottom-up approach, following the steps of the design
recipe, to write a program to implement not, or, and and using only nor (not
or). The truth table for nor is displayed in Fig. 33. For example, (or A B) =
(nor (nor A B) (nor A B)).
* Ex. 38 — Use a bottom-up approach following the steps of the design recipe
to write a program to determine if two strings of length 3 contain a.
14
A compiler is a program that converts programs written in a language like BSL into machine
code that is executable by a computer.
19 Top-Down Design 61
* Ex. 39 — Use a bottom-up approach following the steps of the design recipe
to design and write a program to compute the flag images of Ghana, Honduras,
Lithuania, Niger, and Syria.
19 Top-Down Design
To illustrate the top-down design process, consider the problem of computing images
like those in Fig. 34. Each image has 4 overlayed squares at a 45◦ angle. Instead of
first trying to figure out how to compute each square in the image composition, start
by writing expressions to compute the image composition. For unknown values,
like the length of a square, use a constant. The definitions of these constants are a
different problem from the problem of creating the desired image. In other words,
use a divide and conquer strategy.
Under this design approach, the steps of the design recipe may be satisfied as
follows:
STEP 1
The image is computed by overlaying squares of alternating colors and differing
lengths. Every other overlayed square, starting with the largest square, is rotated
by 45◦ .
STEP 2
Observe that it is already known that several squares of different colors and lengths
are needed. This immediately suggests that we abstract away the image compu-
tation for squares and design (later) a function for this purpose. Furthermore, we
know that this function requires two arguments (one for each difference): a length
and a color.
Constants for the value of sample expressions may be defined as follows:
Observe that we do not concern ourselves at this point with how make-sqr is
implemented or with how to define the different lengths. We assume that they can
all be defined.
STEP 3
There are 6 differences in the sample expressions: 4 lengths and two colors. We
name the differences: bot-len, len2, len3, top-len, botclr, and topclr.
STEP 4
The signature and purpose statement are
STEP 5
The function header is
(define (make-nested-sqs bot-len len2 len3 top-len
botclr topclr)
19 Top-Down Design 63
STEP 6
The tests using defined constants and sample values are
)
(check-expect (make-nested-sqs 20 14.14 9.99 7.06 'pink 'purple)
STEP 7
The body of the function is
STEP 8
All tests pass.
At this point, we cannot run any tests because make-sqr and the length constants
are undefined. The answer written for Step 8 assumes that the tests pass after all
required functions and constants are defined. If all tests do not pass, then checking
the steps of the design is necessary.
Let us turn our focus now to the design and implementation of make-sqr. We
arrive to this function with some clear ideas about the abstraction. There are two
differences: side length and color. If we were to discover that more values are needed,
64 3 The Nature of Functions
then we have a signal that there is a bug in our abstraction. We proceed assuming
that the function can be designed with the stated differences.
The steps of the design recipe may be satisfied as follows:
STEP 1
The image of a solid square is computed using the square function and the given
side length and color.
STEP 2
Constants for the value of sample expressions may be defined as follows:
STEP 3
There are 2 differences in the sample expressions as expected: a length and a
color. We name the differences: side-len and a-color.
STEP 4
The signature and purpose statement are
STEP 5
The function header is
(define (make-nested-sqs botlen len2 len3 toplen
botclr topclr)
STEP 6
The tests using defined constants and sample values are
Fig. 35 The length of the next smaller square is the length of a hypotenuse
STEP 7
The body of the function is
STEP 8
All tests pass.
The next task is to define the constants for the lengths. For any image of nested
squares, the length of the largest square is arbitrary. For example, the length of the
largest square can be 100. The length of the next smaller square is not arbitrary as
it depends on the length of the outer square. How do we compute the length of the
next smaller square? The problem must be carefully analyzed. Figure 35 displays the
placement of a smaller square inside a larger square. The length of the outer square
is w. Observe that two adjacent midpoints and the center of the outer square form
a right triangle. The lengths of the sides forming the 90◦ are both w2 . The length of
the inner square is the length of the hypotenuse of the right triangle. We know that
the length of the hypotenuse is given by the Pythagorean theorem:
x2 = ( w2 )2 + ( w2 )2
x = ( w2 )2 + ( w2 )2
= 2 * ( w2 )2
2
= 2 * w4
w2
= 2
66 3 The Nature of Functions
A function can be defined to compute the length of inner squares. The only difference
in computing one inner square length from another is the length of the outer square.
Instead of abstracting over similar expressions, we have developed a mathematical
expression for the length of an inner circle that is easily translated to BSL. Both
approaches to converging on an expression for the body of a function are valid and
ought to be part of any problem solver’s toolbox.
The steps of the design recipe may be satisfied as follows:
STEP 1
The length of the next smallest square is equal to the length of the line between
the two midpoints of two adjacent sides of the
enclosing square of length w. The
2
length of the next smaller square is given by w2 .
STEP 2
Constants for the value of sample expressions may be defined as follows:
STEP 3
There is a single difference in the sample expressions as expected: a length. We
name the difference side-len.
STEP 4
The signature and purpose statement are
;; R>= 0 → R>= 0
;; Purpose: Compute the length of the next smallest square
STEP 5
The function header is
(define (compute-new-sqr-len side-len)
STEP 6
The tests using defined constants and sample values are
Given that constants for the square lengths for a second image are needed, the
function being designed may be used to compute these. The tests using sample
values based on these constants may be written as follows:
;; Tests using sample values
(define IMG2-LEN1 150)
(define IMG2-LEN2 (compute-new-sqr-len IMG2-LEN1))
(define IMG2-LEN3 (compute-new-sqr-len IMG2-LEN2))
(define IMG2-LEN4 (compute-new-sqr-len IMG2-LEN3))
STEP 7
The body of the function is
STEP 8
All tests pass.
An important point to highlight is when a defined function may be applied in BSL.
A defined function may be applied any time when it is part of a test. All other times,
however, a function must be defined before it is applied to any arguments. That is,
the definition of a function must appear in a program before its first use outside tests.
* Ex. 41 — Use a top-down approach following the steps of the design recipe
to write a program to compute the flag images of Armenia, Austria, Bulgaria,
Colombia, and Estonia.
* Ex. 42 — Use a top-down approach following the steps of the design recipe to
write a program to compute f(x) = 4(x3 - 1)3 - 6(x3 -1)2 + 2(x3 -1)
+ 8.
68 3 The Nature of Functions
* Ex. 43 — Use a top-down approach following the steps of the design recipe
to write a program to append two double copies of a string separated by a space.
For example, "abc" results in "abcabc abcabc".
*** Ex. 44 — Use a top-down approach following the steps of the design recipe
to write a program to compute ((a ⇒ b) ∧ (b ⇒ c)) ⇒ (a ⇒ c). i
⇒ j is ¬i ∨ j.
It is time to put your newly acquired skills to work. This chapter develops the first
version of a video game that we shall call Aliens Attack. This chapter, however, will
not develop a full video game. You need to learn more about problem solving and
more BSL syntax to write a fully working video game. Therefore, the video game
is developed using the process of iterative refinement. That is, as you learn more
about problem solving and about BSL syntax, a more complete video game shall be
developed. This process culminates with a multiplayer game that you may play with
your friends over the internet. That is a promise. If you work hard and absorb all the
lessons contained in this textbook, then you will acquire enough skills to develop a
multiplayer video game.
Figure 37 displays an image of a single-player version of Aliens Attack. In the
scene there is an army of aliens attacking earth and there is a single rocket defending
earth. There is no placating the aliens and earth must defend itself. The rocket is at
the bottom of the scene over earth and may move left to right without going off the
edge of the image. The rocket may also shoot in an attempt to destroy the aliens.
The army of aliens starts toward the top of the scene. All the aliens move in the
same direction: right, left, or down. The aliens move right one step at a time until
an alien reaches the right edge of the scene. At this point all the aliens move down
a step. After moving down a step, the aliens move left one step at a time until an
alien reaches the left edge of the scene. The aliens then move down a step. This cycle
continues until an alien reaches and conquers earth (the player loses the game) or all
the aliens are destroyed (the player wins the game). Observe that, like the rocket, the
aliens may not go off the scene as they move. When the rocket shoots, a shot starts
at the position of the rocket and moves up until it hits an alien or it goes off the top
each of the scene.
The first version of Aliens Attack is not truly a video game. That is fine because
it will be refined. To start, the focus is on problems that you can solve with the skills
you have already developed. The development of Aliens Attack version 0 focuses on
the creation and placement of images to render the video game as an image. This is
a good starting point because you know the basics of creating and placing images
and of writing functions. The first goal is to design the scene where the images of
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2022 71
M. T. Morazán, Animated Problem Solving, Texts in Computer Science,
[Link]
72 4 Aliens Attack Version 0
the game are rendered. The second goal is to develop functions to create the images
of the rocket, the alien, and the shot. The third goal is to create functions to draw the
elements in a scene.
Recall that graphics are created using a coordinate system in which the x coordinate
grows from left to right and the y coordinate grows from top to bottom. Also recall
that a w1 x h1 empty scene into which images are placed may be created as follows:
(empty-scene w1 h1 )
Creating an empty scene is only part of what is needed. The question that immediately
arises is how do we reason about this empty scene. Figure 38a displays the view of
the empty scene as a grid of pixels. Each square in the grid is a pixel and items, like
the blue square, are placed in the scene using pixel coordinates. The blue square in
Fig. 38a is at pixel coordinate (15, 19). This can work well but seems to mismatch
our goal. Our goal is to place images in a scene, not manipulate pixels. If we choose
a maximum size for each image in the video game, we can reason about the empty
scene as boxes where an image fits. Figure 38b displays this view of the empty scene.
A square is placed using image coordinates, not pixel coordinates. For instance, the
blue square in Fig. 38a is at image coordinate (3,3).
21 The Scene for Aliens Attack 73
So, why should we care about the perspective we adopt? It turns out that data
representation strongly influences how a problem is reasoned about. Consider, for
example, how is an alien hit by a shot is detected. We shall first analyze the problem
using the pixel perspective of the game’s scene. Assume that the image of an alien
has dimensions W x H as illustrated in Fig. 39 and the alien is located at pixel
coordinates (x, y). We define a hit as the shot’s position being inside the alien’s
image. That is, if the shot’s position is any pixel covered by the alien’s image, then
the alien is hit. In Fig. 39, for example, the shots at (xr1 ,yr1 ) and at (xr2 ,yr2 ) have
hit the alien. The shots at (xr3 ,yr3 ) and (xr4 ,yr4 ) have not hit the alien. Now,
how is a hit detected? After thinking about it, you can see that a hit means that the
shot can be at most half the alien’s image width (i.e., W/2) away on the x-axis and
at most half the alien’s image height (i.e., H/2) away on the y-axis from the alien’s
coordinates (i.e., (x, y)). This reasoning leads to a function to detect a hit alien
that looks as follows:
(define HALF-ALIEN-IMG-WIDTH (/ (image-width FUEL-IMG) 2))
1 1
74 4 Aliens Attack Version 0
H/2
H/2
This function is much simpler and easier to understand than the first. The important
lesson here is that different representations, like pixel coordinates versus image
coordinates, may lead to vastly different solutions/programs. It is worth exploring
different representations to determine if any lead to a simpler and easier to understand
solution/program.
Given that we have carefully analyzed how to represent coordinates in the game’s
scene, we may be cautiously confident that using image coordinates leads to simpler
problem solutions. We can also surmise that we need to be careful about defining
the data representation in the program. For this, we need data definitions. A data
definition is written to describe a new type of data. For Aliens Attack, the following
data definitions and constants are used:
;; Character Image Maximum Dimensions
(define IMAGE-WIDTH 30)
(define IMAGE-HEIGHT 30)
#|DATA DEFINITIONS
A character image (ci) is an image which is at most
IMAGE-WIDTH x IMAGE-HEIGHT pixels
21 The Scene for Aliens Attack 75
;; Sample image-x
(define AN-IMG-X (/ MAX-CHARS-HORIZONTAL 2))
(define MIN-IMG-X 0)
(define MAX-IMG-X (sub1 MAX-CHARS-HORIZONTAL))
;; Sample image-y
(define AN-IMG-Y (/ MAX-CHARS-VERTICAL 2))
(define MIN-IMG-Y 0)
(define MAX-IMG-Y (sub1 MAX-CHARS-VERTICAL))
Creating the images needed for Aliens Attack provides the opportunity to practice
the steps of the design recipe. In addition, it provides us with the opportunity to write
an application programming interface (API). An API defines the data formats, the
conventions, and the functions that may be used to create and access data. For Aliens
Attack images, the API is rather simple: the dimensions of any character image must
be 30 x 30 pixels or less and must be created using functions in the image teachpack.
One of the benefits of using or creating an API is information hiding. This means,
for example, that the implementation details of a function are hidden from any
programmer who uses the function. A programmer uses the function independent of
how the function is implemented. To be successful, the signature and the purpose of
the function must be clear. Otherwise, programmers will not know how to properly
use a function.
Observe that according to the data definitions, every ci is an image. Every image,
however, is not a ci. A natural question that arises is determining if an image is a
ci. Testing image values is useless, because everybody’s idea of a shot, an alien, or
a rocket is different. Furthermore, the data definition ci only specifies ci properties
(not values). This is a clear situation where property-based testing is needed. For
this, a predicate to determine if a given image is a ci is needed. Following the steps
of the design recipe, the predicate may be designed as follows:
STEP 1
Determine that both the image width is less than or equal to IMAGE-WIDTH and
that the image height is less than or equal to IMAGE-HEIGHT.
STEP 2
Definitions for the value of sample expressions are
(define IS-CI
(and (<= (image-width (circle 10 'solid 'red))
IMAGE-WIDTH)
(<= (image-height (circle 10 'solid 'red))
IMAGE-HEIGHT)))
(define NOT-CI
(and (<= (image-width (square 40 'solid 'blue))
IMAGE-WIDTH)
(<= (image-height (square 40 'solid 'blue))
IMAGE-HEIGHT)))
(define NOT-CI2
(and (<= (image-width
(rectangle 2 50 'solid 'blue))
IMAGE-WIDTH)
22 Creating Aliens Attack Images 77
(<= (image-height
(rectangle 2 50 'solid 'blue))
IMAGE-HEIGHT)))
Observe that sample expressions are developed for both images that are and that
are not ci.
STEP 3
The only difference among the sample expressions is the image tested. The
variable for this difference is an-img.
STEP 4
The signature and purpose statement are
;; image → Boolean
;; Purpose: To determine if the given image is a ci
STEP 5
The function header is
(define (ci? an-img)
STEP 6
Testing may be satisfied as follows:
Observe that both images that are and are not ci are tested.
STEP 7
The body of the function is
STEP 8
All tests pass.
Recall that this step is only satisfied when the tests are executed and pass.
78 4 Aliens Attack Version 0
Now that ci and non-ci images are distinguishable, attention turns to the con-
struction of cis. There are three image constructors that are needed: one for a shot,
one for an alien, and one for a rocket. The development presented here is only illustra-
tive. This is your video game and you are encouraged to be creative and personalize
the game to your liking. Understand the principles for developing the images and then
design functions for your own images. Remember that, unlike homework problems
in high school Mathematics, there may be many solutions to a problem.
23 Shot Image
The image of the shot is the simplest one in Fig. 37. The shot’s image is a radial
star and the color is orange. These, of course, may be different depending on your
preferences. The goal is to design a constructor for shot images. The steps of the
design recipe may be satisfied as follows:
STEP 1
A shot image is a ci of a radial star created using the function radial-star.
Observe that the data definition of a shot image is described in terms of the type
for character images previously developed.
STEP 2
Definitions for the value of sample expressions are
The constants are defined to facilitate changing the color of sample shots. Observe
that the outer diameter of the radial star is IMAGE-WIDTH/2 = 15. Therefore, the
image of a shot is a ci.
STEP 3
The only difference among the sample expressions is the color of the shot. The
variable for this difference is a-color.
STEP 4
The signature and purpose statement are
;; color → image
;; Purpose: Create a shot image of the given color
STEP 5
The function header is
(define (mk-shot-ci a-color)
STEP 6
The tests are below.
Observe that the last two sample computations and sample values tests are
property-based testing. They illustrate that the image returned by mk-shot-img
is a ci.
STEP 7
The body of the function is
STEP 8
All tests pass.
24 Alien Image
The alien image in Fig. 37 is the composition of two images of the same color: an
X and a circle. Specifically, the circle is overlayed over the X. A function to achieve
this uses function composition. The steps of the design recipe may be satisfied as
follows:
STEP 1
The alien image is computed using function composition. The outputs of text
and circle are inputs to overlay. The circle image is overlayed over the X
image. The alien image must be a ci.
STEP 2
Definitions for the value of sample expressions are
STEP 3
The only difference among the sample expressions is the color of the alien. The
variable for this difference is a-color.
STEP 4
The signature and purpose statement are
;; color → image
;; Purpose: Create an alien image of the given color
STEP 5
The function header is
STEP 6
The tests are below.
STEP 7
The body of the function is
STEP 8
All tests pass.
82 4 Aliens Attack Version 0
25 Rocket Image
The rocket image is the most complex of the images in Fig. 37. It is composed of the
images displayed in Fig. 40. To design a function to create a rocket image, a divide
and conquer bottom-up strategy is followed. That is, the simplest components are
independently designed first. Then simple components are combined to create more
complex figures.
It is important to keep in mind that creating images for the components of a larger
image requires experimentation. Rarely, if ever, are the dimensions of a component
right after the first attempt. This is another reason why it is important to write sample
expressions. Sample expressions may be repeatedly refined until the desired visual
effect is achieved. The steps of the design recipe presented for each component
below are the final result after several refinements improving the dimensions of each
component. Do not be afraid to experiment. Experimenting with sample expressions
may lead to insights on how to solve a problem.
Creating a rocket window image is a simple exercise with which to practice the steps
the design recipe.
STEP 1
A rocket window is a solid vertical oval.
STEP 2
Definitions for the value of sample expressions are
25 Rocket Image 83
STEP 3
The only difference among the sample expressions is the color of the oval. The
variable for this difference is a-color.
STEP 4
The signature and purpose statement are
;; color → image
;; Purpose: Create rocket window image
STEP 5
The function header is
STEP 6
Testing may be satisfied as follows:
STEP 7
The body of the function is
STEP 8
All tests pass.
84 4 Aliens Attack Version 0
STEP 1
The fuselage is a solid circle.
STEP 2
Definitions for the value of sample expressions are
STEP 3
The only difference among the sample expressions is the color. The variable for
this difference is a-color.
25 Rocket Image 85
STEP 4
The signature and purpose statement are
;; color → image
;; Purpose: Create the fuselage image
STEP 5
The function header is
(define (mk-fuselage-img a-color)
STEP 6
Testing may be satisfied as follows:
STEP 7
The body of the function is
STEP 8
All tests pass.
The single booster image is an equilateral triangle pointing down. Computing this
image requires function composition. The output of triangle is input to rotate.
STEP 1
A rocket single booster is an equilateral triangle image rotated 180◦ .
STEP 2
Definitions for the value of sample expressions are
86 4 Aliens Attack Version 0
Observe that in Fig. 40, the single booster image is the same color as the nacelle
image. Therefore, constants NACELLE-COLOR and NACELLE2-COLOR are defined
and used to write sample single booster (and nacelle) expressions.
STEP 3
The only difference among the sample expressions is the color. The variable for
this difference is a-color.
STEP 4
The signature and purpose statement are
;; color → image
;; Purpose: Create single booster image
STEP 5
The function header is
(define (mk-single-booster-img a-color)
STEP 6
Testing may be satisfied as follows:
STEP 7
The body of the function is
STEP 8
All tests pass.
25 Rocket Image 87
Observe that the booster image in Fig. 40d is two copies of a single booster image
side by side. This suggests that a constructor for a booster image needs a single
booster image as input.
STEP 1
A booster image is two copies of a single booster image side by side.
STEP 2
Definitions for the value of sample expressions are
STEP 3
The only difference among the sample expressions is the single booster image.
The variable for this difference is a-sb-img.
STEP 4
The signature and purpose statement are
;; image → image
;; Purpose: Create booster image
STEP 5
The function header is
(define (mk-booster-img a-sb-img)
STEP 6
Testing may be satisfied as follows:
STEP 7
The body of the function is
STEP 8
All tests pass.
Constructing an image for the rocket’s main body, as displayed in Fig. 40f, requires
three images: a window image, a fuselage image, and a booster image. It also requires
the use of function composition given that the fuselage goes above the booster and
the window goes on the fuselage.
STEP 1
The rocket’s main body is created by placing the fuselage above the booster and
then placing the window one quarter of the way down the height and half away
across the width of the fuselage.
STEP 2
Definitions for the value of sample expressions are
Observe that previously defined sample images are used to write the sample
expressions.
STEP 3
There are 3 differences among the sample expressions: the window, the fuselage,
and the booster images. The variables for these differences are, respectively,
a-window, a-fuselage, and a-booster.
STEP 4
The signature and purpose statement are
STEP 5
The function header is
(define (mk-rocket-main-img a-window a-fuselage a-booster)
STEP 6
Testing may be satisfied as follows:
(check-expect (mk-rocket-main-img
(mk-window-img 'green)
(mk-fuselage-img 'skyblue)
(mk-booster-img
(mk-single-booster-img 'lightred)))
STEP 7
The body of the function is
(place-image a-window
(/ (image-width a-fuselage) 2)
(/ (image-height a-fuselage) 4)
(above a-fuselage a-booster))
STEP 8
All tests pass.
90 4 Aliens Attack Version 0
The image of the rocket’s nacelle in Fig. 40e is deceptively simple. At first glance,
it looks like an ordinary rectangle. A closer examination of the rocket image, as
displayed in Fig. 41, reveals that the nacelle and the booster are the same color. In
addition, the nacelle must be the same width as the rocket’s main body and about a
quarter of the height of the main body. With these observations, the design of the
constructor for nacelle images follows.
STEP 1
The image of a nacelle is a rectangle that is the same width as the rocket’s main
body and a quarter of the height of the rocket’s main body height. The color of
the nacelle is the same color as the booster in the rocket’s main body.
STEP 2
Definitions for the value of sample expressions are
Observe that the same constants used to construct single booster images are used
to construct the corresponding nacelle images.
STEP 3
There are two differences among the sample expressions: the main rocket image
and the color of the nacelle. The variables for these differences are, respectively,
a-rocket-main-img and a-color.
STEP 4
The signature and purpose statement are
STEP 5
The function header is
(define (mk-nacelle-img a-rocket-main-img a-color)
STEP 6
Testing may be satisfied as follows:
STEP 7
The body of the function is
STEP 8
All tests pass.
92 4 Aliens Attack Version 0
The final image constructor needed is the rocket image constructor. This constructor
must place the nacelle over the rocket’s main body.
STEP 1
The rocket image is constructed by placing the nacelle image half the way across
the rocket’s main body and 30% from the bottom of the rocket’s main body.
STEP 2
Definitions for the value of sample expressions are
Notice that 30% from the bottom is the same as 70% from the top of the rocket’s
main body image. Since y values grow downward in the graphic plane, 70% of
the rocket’s main body image is computed.
STEP 3
There are two differences among the sample expressions: the rocket’s main body
image and the rocket’s nacelle image. The variables for these differences are
a-rocket-main-img and a-nacelle-img.
STEP 4
The signature and purpose statement are
;; image image → ci
;; Purpose: Create a rocket ci
Observe that the signature states that this function returns a ci. This is needed
because the returned image is intended for use in Aliens Attack.
STEP 5
The function header is
(define (mk-rocket-ci a-rocket-main-img a-nacelle-img)
STEP 6
Testing may be satisfied as follows:
25 Rocket Image 93
Observe that the output of this function must be a character image and, therefore,
ci? is used to test its properties. Furthermore, observe that there is a single
sample value test and that this test contains a significant amount of repetition.
Both of these are considered poor style. Testing ought to be more thorough. The
repetitions make it difficult to read and understand the test.
STEP 7
The body of the function is
(place-image a-nacelle-img
(/ (image-width a-rocket-main-img) 2)
(* 0.7 (image-height a-rocket-main-img))
a-rocket-main-img)
94 4 Aliens Attack Version 0
STEP 8
All tests pass.
** Ex. 51 — Refine the tests using sample values for mk-rocket-ci. Make
sure to eliminate repeated expressions and include more than one test.
*** Ex. 52 — Personalize your game with cis for the shot, the alien, and the
rocket of your own creation.
26 Drawing Functions
STEP 5
The function header is
(define (draw-ci char-img an-img-x an-img-y scn)
STEP 6
Testing may be satisfied as follows:
IMAGE-HEIGHT/2
STEP 7
The body of the function is
(place-image char-img
(image-x->pix-x an-img-x)
(image-y->pix-y an-img-y)
scn)
STEP 8
All tests pass.
The design now focuses on the two auxiliary functions to map image coordinates
to pixel coordinates. Let us first consider mapping the image coordinate pair (0, 0) to
26 Drawing Functions 97
a pixel coordinate pair. This coordinate pair is the top-left box in Fig. 38b. The center
of the image ought to be placed at the center of the box. The dimensions of the box
are IMAGE-WIDTH × IMAGE-HEIGHT. As illustrated in Fig. 42, this means that the
center of this box is at pixel coordinates (IMAGE-WIDTH/2, IMAGE-HEIGHT/2).
What is the pixel coordinate of image coordinate (3, 2)? This pixel coordinate
(IMAGE-WIDTH/2, IMAGE-HEIGHT/2) must be translated 3 boxes to the right and
2 boxes down. Therefore, the pixel coordinate for the box at the image coordinate (3,
2) is:
(3 * IMAGE-WIDTH + IMAGE-WIDTH/2,
2 * IMAGE-HEIGHT + IMAGE-HEIGHT/2)
In general, the image coordinate (x, y) maps to the pixel coordinate:
(x * IMAGE-WIDTH + IMAGE-WIDTH/2,
y * IMAGE-HEIGHT + IMAGE-HEIGHT/2)
Observe that the mapping works for image coordinate pair (0, 0).
The above insight guides the design of the image-coordinate to pixel-coordinate
functions. The steps of the design recipe to transform an image-x may be satisfied
as follows:
STEP 1
For image-x, ix, the corresponding pixel-x is ix * IMAGE-WIDTH
+ IMAGE-WIDTH/2
STEP 2
Definitions for the value of sample expressions are
STEP 3
The only difference among the sample expressions is the image-x value. The
variable for this difference is ix.
STEP 4
The signature and purpose statement are
;; image-x → pixel-x
;; Purpose: To translate the given image-x to a pixel-x
STEP 5
The function header is
(define (image-x->pix-x ix)
98 4 Aliens Attack Version 0
STEP 6
Observe that the tests using sample values are written for extrema values. That is,
the minimum and maximum possible values of an image-x are used.
STEP 7
The body of the function is
STEP 8
All tests pass.
The design of image-x->pix-x does not reveal the need for any new auxiliary
functions. The only task left is to design a function to translate an image-y to a
pixel-y. The steps of the design recipe may be satisfied as follows:
STEP 1
For image-y, iy, the corresponding pixel-y is iy * IMAGE-HEIGHT +
IMAGE-HEIGHT/2
STEP 2
Definitions for the value of sample expressions are
STEP 3
The only difference among the sample expressions is the image-y value. The
variable for this difference is iy.
STEP 4
The signature and purpose statement are
;; image-y → pixel-y
;; Purpose: To translate the given image-y to a pixel-y
26 Drawing Functions 99
STEP 5
STEP 6
Once again, observe that the tests using sample values are written for extrema
values.
STEP 7
The body of the function is
STEP 8
All tests pass.
The design of image-y->pix-y does not reveal the need for any new auxiliary
functions. This means that all the functions needed to draw a ci have been designed
and implemented.
Making decisions is part of life. For example, while driving on a highway, the driver
monitors the car’s speed. If the speed is too fast, then the driver decreases the speed.
If the speed is too slow, then the driver increases the speed. Otherwise, the driver
does not change the speed. It should not come as a surprise that making decisions is
also part of problem solving. Modern cars are equipped with a cruise control system.
The driver sets the cruise control at a certain speed. If the speed is too fast, then the
cruise control program decreases the speed. If the speed is too slow, then the cruise
control program increases the speed. Otherwise, the cruise control program does not
change the speed.
Regardless of whether it is a driver or a cruise control program, the decision
on how to adjust the speed depends on the car’s current speed. This decision
is may be represented by one of three possible expressions, say, 'increase,
'decrease, or 'steady. Think carefully about what this means. Multiple ex-
pressions for the value of a function means that determining how to adjust the
speed is a compound function. We can, in fact, write a mathematical func-
tion:
⎧
⎪
⎨'decrease if too-fast?(a-speed)
speed-change(a-speed) = 'increase if too-slow?(a-speed)
⎪
⎩
'steady otherwise
Two predicates, too-fast? and too-slow?, are used to evaluate the given speed.
In essence, the above function is stating that there is data variety in the domain of the
function. The input may be in one of three speed intervals indicating that the car is
either traveling too fast, too slow, or neither. In order to determine the speed change
a decision must be made as to which of the 3 expressions needs to be evaluated. This
decision is based on the speed range a-speed belongs to.
It turns out that speed ranges may be used to solve many problems. For example,
a police officer may use the following function to determine a course of action after
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2022 101
M. T. Morazán, Animated Problem Solving, Texts in Computer Science,
[Link]
102 5 Making Decisions
Observe that this function must also decide which expression to use to provide an
answer for a given speed. Furthermore, observe that the two functions have the same
basic structure. This structural similarity stems from the fact that they process the
same type of data and suggests that a function template may be developed to process
a speed. A function template is like a skeleton for functions. It captures the expected
similarities among function that process the same type of data. For each data type a
problem solver defines a function template is developed.
For example, speed may be defined as follows:
A speed is a number such that either:
1. too-fast?(a-speed) is true
2. too-slow?(a-speed) is true
3. Neither too-fast?(a-speed) nor too-slow?(a-speed) is true
the template for functions that process speed is:
⎧
⎪
⎨. . . if too-fast?(a-speed)
f-on-speed(a-speed) = . . . if too-slow?(a-speed)
⎪
⎩
. . . otherwise
This template may be used to design any function that processes a speed. By providing
the missing expressions and a function name both functions above are obtained.
We can surmise from the examples above that compound functions arise from data
having variety. These functions need to decide which expression to evaluate based
on the variety the input belongs to. All functions that process the same data type with
variety have the same structure. This chapter introduces how to design functions that
make decisions. It introduces new BSL syntax to write conditional expressions which
are used to make decisions. A design recipe for compound functions is presented that
includes the development of data definitions for data with variety and of function
templates to capture structural similarities among functions. Several different types
of data with variety are explored.
In BSL, there is syntax for expressions to make decisions. These new expressions are
called conditional expressions. The new expr production rule is:
This rule states that a conditional expression starts with an opening parenthesis
and the keyword cond. Afterwards, there is 1 or more stanzas delimited by square
brackets. Each stanza contains two expressions. The first expression is a condition
and must evaluate to a Boolean. Think of this expression as a question whose
answer is either true or false. The second expression is called the consequence
and is only evaluated if the corresponding condition evaluates to #true. Think of
this expression as the value of the conditional expression when the answer to the
corresponding question is true. The last stanza in a conditional is called the default
stanza and contains in square brackets the keyword else and a single expression. This
expression represents the default value of the conditional. The default expression is
only evaluated if the conditions in all the other stanzas evaluate to #false.
The mathematical function speed-change(a-speed) is translated into BSL
syntax as follows:
(define (speed-change-bsl a-speed)
(cond [(too-fast? a-speed) 'decrease]
[(too-slow? a-speed) 'increase]
[else 'steady]))
The stanzas of a conditional are evaluated from top to bottom. For each stanza the
condition is evaluated first. If the condition evaluates to #true then the value of the
consequence is the value of the conditional. If the condition evaluates to #false
then evaluation continues with the next stanza. If the default stanza is reached, then
the default expression is evaluated to obtain the value of the conditional expression.
For example, for speed-change-bsl, if too-fast? evaluates to #true then the
value of the conditional is 'decrease. If it evaluates to #false then evaluation
continues with the next stanza. If too-slow? evaluates to #true then the value of
the conditional is 'decrease. If it evaluates to #false then evaluation continues
with the next stanza. The next stanza is the else stanza and, therefore, the default
expression is evaluated making the value of the conditional 'steady.
BSL provides shorthand when there are only two varieties of data. That is, short-
hand may be used when only the default stanza and one other stanza is needed. The
shorthand syntax is called an if-expression:
This rule states that an if-expression is formed by an open parenthesis, three ex-
pressions, and a closing parenthesis. The first expression is called the condition and
must evaluate to a Boolean. If the condition is #true then the second expression is
evaluated to obtain the value of the if-expression. Otherwise, the third expression
is evaluated to obtain the value of the if-expression.
Consider, for example, translating the absolute value mathematical function into
BSL. In mathematical syntax the function is defined as follows:
a-num if a-num >= 0
abs-value(a-num) =
-a-num otherwise
104 5 Making Decisions
Observe that there are only two varieties of numbers for this function: nonnegative
and negative. Only two varieties means that an if-expression may be used to translate
it to BSL syntax:
(define (abs-value-bsl a-num)
(if (>= a-num 0)
a-num
(* -1 a-num)))
If the condition, (>= a-num 0), evaluates to #true then the value of the if-
expression is a-num. If the condition evaluates to #false then the value of the
if-expression is (* -1 a-num).
** Ex. 55 — Write a program to decide the speed change for a car if the
maximum speed is 120 kilometers per hour and the minimum speed is 50
kilometers per hour.
When problem analysis reveals that data with variety needs to be processed then a
data definition must be created. Given that there is variety in the data, any function
that processes this type of data requires a conditional expression in its body. A
conditional expression is needed to determine which of the data varieties is being
processed. For each variety an expression for the value of the function needs to be
developed. A problem solver must also develop a function template to capture the
structural similarities among all functions that process the defined data type. Finally,
the number of tests must be greater than or equal to the number of varieties in the
data. In addition to at least one test per variety, it is also good practice to test border
cases that delimit varieties.
Let us examine poem verses as an example. First, we must choose how to rep-
resent a verse. We may, for example, represent a verse as a string. Some poets
feel that it is good practice to limit the length of each verse to 35 characters. A
verse with more than 35 characters is considered too long. A verse with 15 to 35
characters is considered fine. A verse with less than 15 characters is considered
too short. Based on this description, a data definition for a verse is formulated as
29 Designing Functions to Process Data with Variety 105
follows:
;; A verse is either:
;; 1. A string of length greater than 35
;; 2. A string of length 15 to 35
;; 3. A string of length less than 15
The data definition for a verse informs problem solvers that any function that pro-
cesses a verse must have a conditional in its body that determines the variety of the
input verse. In addition, it informs problem solvers that at least 3 tests are needed:
one for each variety. Remember, we say at least because it is always good practice
to test boundary values. In this case, 5 tests are reasonable: one for each variety,
one for length 35, and one for length 15. It is important to note that verse vari-
eties are mutually exclusive. This means that no verse can be part of more than one
variety.
Based on the above data definition, observe that all functions that process a verse
share these features unique to verses:
1. A signature that has at least a verse as an input type.
2. A conditional in the body that determines if the given verse’s length is greater
than 35, between 15 and 34, or less than 15.
3. Sample expressions, if any, for each variety that is used to compute a value.
4. One test for each sample expression
5. Sample value tests including tests for borderline cases of length 35 and 15.
106 5 Making Decisions
This leads to the function template in Fig. 43. Observe that the signature includes a
verse as input and that each stanza in the skeleton’s body has a Boolean expression to
determine the variety of the verse. The default stanza, of course, does not explicitly
state that its Boolean expression is (< (string-length a-verse) 15). This
is unnecessary because verse varieties are mutually exclusive. Mutual exclusion
guarantees that if the first two Boolean expressions are false then the verse’s length
is less than 15. Stated differently:
¬(> (string-length a-verse) 35) ∧
¬(<= 15 (string-length a-verse) 35)
⇒
(< (string-length a-verse) 15)
This logical expression is stating that if the length of a verse is not greater than 35 and
it is not between 15 and 35 then it is less than 15. Finally, observe that the template
suggests 3 sample expressions and sample computation tests (one for each variety)
and 2 sample computation tests (one for each boundary value). The important point
to remember that there must be at least one test for each variety. More tests, of course,
may be added in the interest of clarity.
This function template can be specialized to express the solution to any problem
that requires processing a verse. Consider the problem of computing a string to
describe the length of a verse. To solve the problem, the function must decide
which constant string describing its variety to return: "Too Long", "Fine", or
"Too Short". Given that a verse is not processed to create any of these constant
strings, there are no sample expressions to describe how a verse is used to compute
these strings. If there are no sample expressions, there are also no tests using sample
computations.
The function’s signature, purpose, and header are:
;; verse → string
;; Purpose: Determine if the given verse is too long, fine,
;; or too short
(define (verse-type a-verse)
Observe that the signature uses the type, verse, defined by the data definition above.
Once a type is defined by a data definition it may be used in signatures. Signatures,
therefore, may contain types defined by BSL or by a data definition the programmer
creates. Further observe that the signature only has one input and, therefore, the
function only has one parameter. Finally, observe that the name of the function is
suggestive of its purpose and the name of the parameter is suggestive of the value it
represents.
Given that there are no sample expressions to test (as noted above), the tests using
sample values must be specialized to include a test for each variety of verse and for
each borderline length. Sample tests are:
;; Tests using sample values
(check-expect
(verse-type "Here is a sigh to those who love me,")
"Too Long")
29 Designing Functions to Process Data with Variety 107
(check-expect
(verse-type "And a smile to those who hate")
"Fine")
(check-expect (verse-type "Sorry for fate") "Too Short")
(check-expect
(verse-type "The Battle of Culloden started now,")
"Fine") ;; verse length 35
(check-expect
(verse-type? "Flaming heavens")
"Fine") ;; verse length 15
108 5 Making Decisions
The next step is to specialize the body of the function in the template. For each
verse variety the proper string needs to be returned. The specialized body is:
(cond [(> (string-length a-verse) 35) "Too Long"]
[(<= 15 (string-length a-verse) 35) "Fine"]
[else "Too Short"]))
The complete program is displayed in Fig. 44. To make Fig. 44 easier to read the
comments including the data definition and the function template are displayed using
an italic font.
The development of verse-type suggests a series of steps that guide the de-
velopment of a decision-making function. Figure 45 displays the design recipe
for decision-making functions. This design recipe is a refinement of the general
design recipe for functions in Fig. 28. Therefore, the steps ought to feel familiar.
Step 1 requires the development of data definitions. Of these, at least one must
have varieties that are mutually exclusive. Step 2 requires the development of a
function template. The function’s body must be a conditional that has a stanza for
each variety. The Boolean expressions to determine the variety of the input are
part of the function’s body in the template. In addition, there must be at least one
test outlined for each data variety. Commonly (but not always), sample expression
tests are used to test each variety and sample value tests are used to test boundary
values. These first two steps are part of what is called data analysis and do not
have to be repeated for every problem being solved using the data type defined.
That is, these steps are performed once and the template may be used multiple
times.
The rest of the steps are taken to solve a specific problem. These are the steps
that need to be repeated for each problem solved. These steps specialize the func-
tion template. Step 3 requires an outline of how the function’s value is computed.
Step 4 requires the specialization of constant definitions for the values of sample
expressions. The sample expression illustrates how an answer is computed by pro-
cessing a given variety. The differences are named and serve as parameters to a
function definition. If a variety is not processed to formulate an answer, then no
sample expression is written. Steps 5 and 6 specialize the signature, the purpose
statement, and the function header. Step 7 requires the specialization of tests. There
must be at least one test for each data variety. Tests ought to also be thorough and,
29 Designing Functions to Process Data with Variety 109
therefore, test boundary values between varieties. Write tests that use the constants
defined for sample computations and tests that use sample values. Step 8 requires the
specialization of the function’s body. Fill in the expressions for the answer of each
variety one at a time. Step 9, as before, requires running the tests and redesigning if
necessary.
The design recipe for decision-making functions informs us that data variety plays
a central role in problem solving. For example, the number of sample expressions,
of stanzas in a conditional, and of tests is proportional to the number of varieties in
the data. That is, the shape of the data influences the shape of a solution/program.
This suggests the function templates displayed in Fig. 46. Figure 46a displays the
function template for data with more than two varieties. Figure 46b displays the
function template for data with two varieties. In both, the number of constants for
sample expressions, of tests, and of needed expressions in the body of the function
are equal to the number of varieties. It is, of course, reasonable to increase the
110 5 Making Decisions
number of constants and tests for more thorough testing or to add clarity to the
program.
To illustrate the steps of the design recipe we will explore several different types
of data that have variety. It is impossible to outline all imaginable types of data with
variety. The goal here is to illustrate how to solve problems with several common
data type patterns that have variety.
30 Enumeration Types
A data definition that lists all possible values is called an enumeration type. To a
programmer this means that the conditional in a function that processes this type of
data only needs to distinguish the cases listed in the data definition. This is done by
checking for equality with the values listed in the data definition. Although this data
type represents the simplest form of variety, it may be cumbersome to use as the
number of varieties grows.
To illustrate the design process using an enumerated type consider creating
an animation for of a traffic light. We design our program for a simplified traf-
fic light that only changes colors from green to yellow to red to green and so
on. That is, the possibilities of a flashing red or a flashing yellow are not in-
cluded the design. Based on this there are only three possible traffic light images,
which may be defined as constants. Each image is created by overlaying the three
lights over a background. For example, the three images may be defined as fol-
lows:
(define BACKGROUND (rectangle 60 180 'solid 'black))
Observe that if the traffic light is increased by 1 (remainder 3) the next light
is the correct one. For example, increasing from 0 to 1 (remainder 3) means
that the traffic light changes from green to yellow. Similarly, incrementing
2 (remainder 3) results in 0 meaning that the traffic changes from red to
green.
STEP 2
The function template for tick is:
;; tick ... → ...
;; Purpose: ...
(define (f-on-tick a-tick) ...)
There is no variety in the data definition for tick. Therefore, f-on-tick does not
have a conditional. No decision needs to be made. The number of tests is also
arbitrary as there are no specific values that must be tested.
On the other hand, the data definition for tl has variety. Observe that there
is a conditional in the body of f-on-tl. There is a stanza for each vari-
ety of tl. The tests for each variety are defined here. The template also sug-
gests writing a sample expression and a corresponding test for each variety of
tl.
Now that the steps for data analysis have been completed, we can start solving
the problem of creating functions for the traffic light animation. We know that a
function to process ticks is needed. This function must return an image of a traffic
light. Starting with Step 3, the following the design recipe yields:
STEP 3
Given a tick, two values need to be computed. From a tick a tl value must be
computed. From a tl value an image must be computed. The computation of these
values is deferred to yet unwritten functions. The function tick->tl converts a
tick to a tl. The function image-of-tl converts a tl into a traffic light image.
STEP 4
To specialize the sample expressions, pick three different tick values such that
each maps to a different traffic light image. Specializing tests in this manner
guarantees to illustrate how a clock tick is mapped to each of the possible images.
Here are some sample expressions:
;; Sample expressions for draw-tick
(define TICK12-IMG (image-of-tl (tick->tl 12)))
(define TICK25-IMG (image-of-tl (tick->tl 25)))
(define TICK32-IMG (image-of-tl (tick->tl 32)))
The only difference among the three sample expressions is the value for tick. The
name for this difference is a-tick.
STEP 5
The signature and purpose statement are specialized as:
;; tick → image
;; Purpose: Compute the traffic light image for the
;; given tick
114 5 Making Decisions
STEP 6
The function header is specialized to:
(define (draw-tick a-tick)
STEP 7
The tests are specialized as follows:
The sample value tests illustrate that the function works for more than three
predefined computations.
STEP 8
The body of the function is specialized as follows:
(image-of-tl (tick->tl a-tick))
STEP 9
All tests pass.
The result of Step 9 is expected for now. For the design to be complete all tests
must pass after the auxiliary functions are designed and implemented.
Top-down design has revealed that two auxiliary functions, tick->tl and
image-of-tl, are needed. Each is designed independently of the other. It does
not matter which is designed first. Let us start with tick->tl. Here is an example
of how to satisfy the steps of the design recipe:
STEP 3
Given a tick, the corresponding tl is given by the remainder of the tick and 3.
STEP 4
These are specialized sample expressions to illustrate how each variety of tl is
computed from a tick value:
The only difference among the three sample expressions is the value for tick. The
variable for this difference is a-tick.
STEP 5
The specialized signature and purpose statement are:
;; tick → tl
;; Purpose: Convert the given tick to a tl
STEP 6
The specialized function header is:
(define (tick->tl a-tick)
STEP 7
The specialized tests are:
STEP 8
The body of the function is:
(remainder a-tick 3)
STEP 9
All tests pass.
The design of tick->tl did not reveal the need for any new auxiliary functions.
Therefore, the only remaining task is the design of image-of-tl. For this function,
the presentation of the design is changed. Instead of outlining the result each step of
the design recipe one at a time, the changes made to the template for functions on tl
is directly presented.
This function converts a given tl to an image. We also observe that a tl is only
used to decide which image to return. Since a tl is not used to compute a value there
are no sample expressions or tests using sample expressions to write. Based on these
observations, the initial template specialization looks as follows:
116 5 Making Decisions
;; tl → image
;; Purpose: Return the traffic light image for the given tl
(define (image-of-tl a-tl . . .)
(cond [(= a-tl 0) . . .]
[(= a-tl 1) . . .]
[else ...]))
The final step to complete the traffic light simulation program is to require the
universe teachpack and to write the animate expression. Figure 47 displays the
structure of the program. Elements in angle brackets have been omitted from the
figure due to space limitations. It should be clear, however, what needs to be filled in
from all the design steps illustrated above. The second line in the program requires
the universe teachpack. The last line in the program is telling DrRacket to animate
the program using the draw-tick function. Congratulations! You have written your
first animation. Run the program. What do you think?
;; tick tl
;; Purpose: Convert the given tick to a tl
(define (tick->tl a-tick) (remainder a-tick 3))
;; tl image
;; Purpose: To return the traffic light image for the given tl
(define (image-of-tl a-tl)
(cond [(= a-tl 0) G-ON]
[(= a-tl 1) Y-ON]
[else R-ON]))
;; tick image
;; Purpose: Compute the traffic light image for the given tick
(define (draw-tick a-tick)
(image-of-tl (tick->tl a-tick)))
(animate draw-tick)
31 Interval Types
Running the traffic light simulation reveals that the light changes too fast–28 times
per second. The program is well-designed and works from a certain perspective but
needs to be refined. That is, it needs to be improved. This is our first dive into the
process of iterative refinement. It is fairly clear that we need the light to change at a
slower pace so that the human eye can appreciate the changes. Instead of changing
the image 28 times per second, the animation may change the traffic light image, for
example, twice per second. This means that, instead of changing every clock tick,
the image needs to change every 14 clock ticks.
This refinement means that the data definition of tl must be updated. Instead of
defining tl as 0, 1, or 2, tl could define as follows:
;; A traffic light (tl) is either
;; 1. 0 --means the green light is on
;; 2. 1 --means the green light is on
..
.
;; 13. 13 --means the green light is on
;; 14. 14 --means the yellow light is on
..
.
;; 27. 27 --means the yellow light is on
;; 28. 41 --means the red light is on
..
.
;; 41. 41 --means the red light is on
This data definition leads to a function definition whose conditional has 42 stan-
zas. That is quite large and error-prone during development. Sometimes such data
definitions are unavoidable. Observe, however, that there is something special about
this (long) data definition. There is a lot of repetition. This suggests that there is an
abstraction that needs to be defined.
Many consecutive numbers have the same meaning. For example, the values from
0 through 13 all mean that the green light is on. We can borrow an idea from high
school mathematics called an interval. We can say, for example, that when tl is a
member of [0..13] it means that the green light is on. Equivalently, we can say that tl
is a member of [0..14), (-1..13], or (-1..14). Remember that a square bracket means
included and a parenthesis means not included. This leads to the following data
definition:
;; A traffic light (tl) is a member of either
;; 1. [0..13] --means the green light is on
;; 2. [14..27] --means the yellow light is on
;; 3. [28..41] --means the red light is on
This is called an interval type. An interval type defines orderable data by a set of
categories. For example, tl is defined using three intervals. The intervals must be
31 Interval Types 119
STEP 5:
;; tick --> tl
;; Purpose: Convert the given tick to a tl |#
;; STEPS 6 and 8
(define (tick->tl a-tick)
(remainder a-tick 42))
;; STEP 7: TESTS
;;Tests using sample expressions
(check-expect (tick->tl 11) TL-0)
(check-expect (tick->tl 60) TL-1)
(check-expect (tick->tl 123) TL-2)
mutually exclusive. That is, an instance of the data type must be a member of only
one interval.
Refining a data definition means that the program must also be refined. Any
function that manipulates or creates an instance of the refined data type and its
tests must be updated. To determine which functions to update, a programmer must
look for expressions that manipulate or create an instance of the updated data type.
Functions that are good candidates to be updated are those with signatures that refer
to the refined data type. Examining the signatures in the traffic light simulation
program reveals:
tick->tl: tick → tl Needs to be updated, because it creates an instance of tl.
image-of-tl: tl → image Needs to be updated, because it makes a decision based
on an instance of tl.
draw-tick: tick → image Doest not need to be updated. This function does not
manipulate nor creates an instance of tl.
The refinements necessary involve updating expressions and tests. The tick->tl
function must convert a tick into a tl. Finding the remainder of the given tick by 3
is no longer correct because a tick must be converted to an integer in [0..41].
Modular arithmetic, however, can still be used. Now, the remainder of a tick by 42
must be computed. Figure 48 displays the updated tick->tl function. The sample
120 5 Making Decisions
STEP 5:
;; tl --> image
;; Purpose: To return the traffic light image for the given tl
|#
;; STEPS 6 and 8
(define (image-of-tl a-tl)
(cond [(<= 0 a-tl 13) G-ON]
[(<= 14 a-tl 27) Y-ON]
[else R-ON]))
expressions and the body of the function now use 42 instead of 3. The tests have
been updated in order to have at least one test for each variety of tl using the new
data definition.
Figure 49 displays the refined version of image-of-tl. The image-of-tl func-
tion processes an instance of tl. This means that the conditional expression must
be refined to correspond to the new data definition of tl. Observe that the tests in-
side the conditional now determine what interval contains a-tl. The unit tests for
image-of-tl are also refined to include at least one test for each interval of tl.
An important lesson to absorb is that data in the real or an imaginary world may
be represented in different ways (like tl). Part of the design process is to select a
representation. The representation chosen influences how a problem solver thinks
about a program. Exploring different representation may provide insight into the
problem. It is also important to keep in mind that how data is represented may have
a profound impact on performance.
* Ex. 65 — For the traffic light simulation three images are defined: G-ON,
Y-ON, and R-ON. Rewrite the traffic light simulation using the following data
definition:
;; A traffic light (tl) is either
;; 1. G-ON --means the green light is on
;; 2. Y-ON --means the yellow light is on
;; 3. R-ON --means the red light is on
*** Ex. 67 — Design and implement an animation for a pedestrian traffic light.
A pedestrian traffic light displays one of two messages: Walk or Don’t Walk.
The Walk message should be displayed for 4 s before it changes. The Don’t Walk
message should be displayed for 2 s before it changes.
32 Itemization Types
Enumeration types capture variety by exhaustively listing all possible values. Interval
types capture variety using a set of intervals to classify orderable data. What is
done if both a listing of values and intervals are needed? As it turns out this is
sometimes needed. To define such data an itemization type is used. An instance of
an itemization type may be a specific value or a member of an interval type. Observe
that an itemization type is a generalization of enumeration and interval type. When
all varieties are specific values an itemization type is called an enumeration type.
When all varieties are intervals an itemization type is called an interval type.
To illustrate how to design a program using an itemization type consider the prob-
lem of doubling or rotating an image using single alphanumeric or arrow keystrokes.
The image is rotated 90, 180, or 270◦ using, respectively, the right, down, and left
arrow keys. The image is doubled using the up arrow key. Nothing is done with the
image on an alphanumeric keystroke.
Before proceeding with the design of the solution it is necessary to know how keys
are represented and compared. In the universe teachpack, all keys are represented
with a string. For example, the z key is represented by "z". The right, down, left, and
up keys are represented, respectively, with the strings "right", "down, "left", and
"up". The key=? function is a predicate used to compare keys for equality. Do not
122 5 Making Decisions
mistake key=? as being the same as string=?. The former only compares strings
that represent keys while the latter compares any two strings.
To facilitate the design, assume that there is a constant ROCKET-IMG whose value
is a rocket image created using the functions in the image teachpack. Figure 50
displays Step 1 of the design recipe. An alphanumeric keystroke is an interval type.
There are two intervals: one for letters and one for numbers. Nothing else is an
alphanumeric keystroke. A change image keystroke is an itemization type. It has
four literal values: one for each arrow. It also has one interval type: an alphanumeric
keystroke. Observe that a data definition may refer to another data definition. The
meaning of each variety is also clearly stated for each variety within each data
definition.
Figures 51 and 52 display Step 2 of the design recipe. In Fig. 51, the template for
ciks has a function skeleton with a conditional that distinguishes the variety of the
value received as input. The template also suggests defining a constant and a test for
the value of a sample expression for each variety of ciks. In addition, it suggests the
definition of a sample value test for each variety of ciks. In Fig. 52, the template for
aks has a function skeleton with a conditional that distinguishes between the varieties
of aks. Two sample expressions along with corresponding test are suggested: one
for a letter keystroke and one for a digit keystroke. In the same manner, two sample
value tests are suggested by the template.
Figure 53 displays Steps 3 through 8 of the design recipe for the function that
processes a ciks. Step 3 explains that the rotate and scale functions are used
to compute new images. Step 4 illustrates how these two functions are used to
compute a new image using the ROCKET-IMG constant. Step 5 results in the spe-
cialized signature and purpose statement. The signature states the function takes
two inputs: an image and a ciks. The purpose statement indicates that the given
image is rotated, doubled, or left untouched. The result of step 6 reflects the re-
quirements stated by the signature. The two parameters are named in a manner that
suggest the type of their value. The name of the function is suggestive of its pur-
pose. Step 7 results in tests using sample computations and using sample values.
The tests with sample computations illustrate that the function computes the same
value as the sample expressions. The tests with sample values use images created
32 Itemization Types 123
using functions from the image teachpack. The third test uses flip-vertical.
This test passes because vertically flipping an equilateral triangle results in the
124 5 Making Decisions
same image as rotating the triangle 180◦ . In general, however, this is not true.
For example, vertically flipping a right triangle does not result in the same image
as rotating the triangle 180◦ . Finally, Step 8 specializes the body of the function.
The expressions for the consequence in each stanza of the conditional are based
on the sample expressions and on the last test using a sample value. For the for-
mer, the literal ROCKET-IMG is substituted with, an-img, the image parameter of
the function. For the latter, observe that the input image is the same as the output
image. This means that in the default stanza the value of the parameter must be
returned.
The final noteworthy observation is that the design of change-img did not reveal
the need for auxiliary functions. This means that the template for aks was never used
to design a function. That is fine. Not every problem requires every template to be
specialized. Other problems involving ciks may very well require the specialization
33 What Have We Learned in This Chapter? 125
of the aks function template. Think of the templates developed as different tools in a
toolbox. Sometimes you only need a hammer. Other times you only need a wrench.
Yet other times you need both.
** Ex. 68 — Refine the change image program to flip the given image vertically
for a letter keystroke and to flip the given image horizontally for a number
keystroke. Hint: There is no need to change any data definitions for this problem.
We all know that video games are fun to play. Designing them can also be fun despite
being complex programs. To manage this complexity, video games are developed
using the process of iterative refinement. Adding features to a video game in a
piecemeal manner allows the problem solver to conquer the complexity in small
steps. For example, for Aliens Attack we have already solved the problem of creating
cis (remember, ci is defined as a character image in 4).
Before proceeding, it is a good idea to get a handle on exactly what a video
game is. In many ways, a video game is similar to an animation. For instance,
scenes are flickered fast enough to create the illusion of movement. Video games,
however, allow players to interact with the animation. This interaction may change
the evolution of the game. The evolution of the game changes when a computer
event occurs. A computer event includes any action like pressing a key, the clock
ticking, or clicking the mouse. Players change the evolution of the game through, for
example, key pressing or mouse actions. For instance, in Aliens Attack a player may
use the left arrow and the right arrow keys to move the rocket. Like a simulation, the
evolution of a video game may also change by events not controlled by the player
like a clock tick. In Aliens Attack, for example, the aliens move every time the clock
ticks. It is now clear that functions called event handlers (or simply handlers) are
needed to process computer events.
The ability to design programs that make decisions allows us to implement video
games. Handlers need to make decisions. For example, the handler to process a
keystroke must distinguish what key has been pressed. If the right arrow key is
pressed, then the handler moves the rocket right. If the left arrow key is pressed, then
the handler moves the rocket left. Clearly, data definitions are needed to describe the
keystroke events that change the evolution of the game.
In addition to providing programmers with the ability to write animations, the
universe teachpack provides programmers with the ability to write video games in
BSL. This teachpack is an API. It provides the necessary syntax to associate handlers
with computer events and it defines the signature that these handlers must have.
Using the universe teachpack is an exercise in programming with (the abilities
© The Author(s), under exclusive license to Springer Nature Switzerland AG 2022 127
M. T. Morazán, Animated Problem Solving, Texts in Computer Science,
[Link]
128 6 Aliens Attack Version 1
provided by and the restrictions imposed by) an API. Programming with an API is
a good skill to develop because programming with APIs is standard practice.
The universe teachpack requires the development of a data definition for a world.
A world is all the values that may change during the evolution of the game. Some of
the values may be de displayed when a game image is computed. Other values may
only be needed to advance the evolution of the game. In a fully implemented Aliens
Attack game, for example, the aliens and the direction the aliens travel in are part of
the world. The aliens are displayed in a game image as in Fig. 37. The direction the
aliens travel in, on the other hand, is not displayed in the image.
There is a special expression to run a video game called a big-bang expression
(bb-expr). This expression allows the programmer to provide the initial value of the
world and the handlers needed to advance the evolution of the game. The big-bang
expression syntax is:
bb-expr ::= (big-bang expr bb-clause+)
bb-clause ::= [to-draw function]
[on-key function]
[on-tick function]
[stop-when function]
[name expr]
..
.
A bb-expr has in parenthesis the keyword big-bang followed by an expression
that must evaluate to a world value followed by one or more bb-clauses (big-
bang clauses). A bb-clause associates an event with a handler function to pro-
cess the event. Each bb-clause has in square brackets an event describer (e.g.,
to-draw, on-tick, and stop-when) and a handler for the event. For example,
[to-draw draw-world] states to use draw-world to render the image of the
world, [on-key process-key] states to use process-key to process a key stroke,
[on-tick process-tick] states to use process-tick to process a clock tick,
and [stop-when game-over?] states to use game-over? to detect the end of
the game (or interactive simulation). The only required bb-clause is the to-draw
clause. The only clause that does not follow this scheme is the name clause. This
clause associates a name with the world. The expr in this clause must evaluate to a
string or symbol. The name appears in title of the scene. The vertical dots mean that
there are other varieties of bb-clauses. The big-bang documentation describes all
the possible bb-clauses. An important thing to remember is that a bb-clause may
not be repeated. For example, a bb-expr may only have one to-draw bb-clause.
The universe API also specifies the signature and purpose of the handlers. Each
handler serves a specific purpose like creating a scene, computing the next world
after a clock tick or keystroke, or detecting the end of the game/simulation. For
34 The Universe Teachpack 129
example, the following table displays the signature and purpose for four handlers:
on-draw: world → scene
Purpose: To create the world’s scene
on-key: world key → world
Purpose: To return the world after the given keystroke
on-tick: world → world
Purpose: To return the world after a clock tick
stop-when: world → Boolean
Purpose: To determine if the game/animation has ended
This means that we must write handlers that satisfy the above signatures. To a
programmer this suggest using a top-down design strategy to write a video game. Start
with the functions needed by the big-bang expression. Design auxiliary functions
as their need is revealed.
To illustrate the use of the universe teachpack consider developing an interactive
animation to rotate the image of the fish displayed in Fig. 55. The user may rotate
the image of the fish to point up, down, left, or right using the arrow keys. In Fig. 55
the fish is pointing up. If the user presses the up arrow key, then the image remains
unchanged. If the user presses the right arrow, then the image is rotated to make the
fish point right. A similar action takes place pressing the other arrow keys. If the
user presses any other key, then the fish image remains unchanged regardless of the
direction the fish is pointing in. For example, if the fish is pointing left and the m key
is pressed, then the fish image remains unchanged.
To start, first define the constants that are needed. A scene is needed. A scene is
defined as follows:
A scene is a WIDTH x HEIGHT image
Here WIDTH and HEIGHT are positive integer constants. Presumably, a scene is large
enough to contain the fish image. Otherwise, the fish image is truncated.
The fish image is also a constant. The following code defines all the scene and
fish image constants:
(define WIDTH 200)
(define HEIGHT 200)
(define E-SCENE-COLOR 'black)
(define E-SCENE (empty-scene WIDTH HEIGHT E-SCENE-COLOR))
130 6 Aliens Attack Version 1
The body of run gives us a starting point for the design process. The initial world,
INIT-WORLD, needs to be defined. For this, a world data definition is needed. The
function handlers draw-world and process-key and any needed auxiliary func-
tions need to be designed. Recall that the signatures of the handlers are defined by
the universe API (noted as comments in run). For the key-processing handler, a
data definition is needed for the keys that affect the evolution of the animation.
We start with the data definition for the keys that affect the evolution of the anima-
tion. Remember that every data definition means the development of a corresponding
function template. A fish keystroke is defined as follows:
A fish keystroke (fks) is either:
1. "up" --means fish in up direction
2. "right" --means fish in right direction
3. "down" --means fish in down direction
4. "left" --means fish in left direction
;; fks . . . → . . .
;; Purpose: . . .
(define (f-on-fks a-fks . . .)
(cond [(string=? a-key "up") . . .]
[(string=? a-key "right") . . .]
[(string=? a-key "down") . . .]
[else . . .]))
A key is either:
1. fks
2. not an fks
;; key . . . → . . .
;; Purpose: . . .
(define (f-on-key a-key . . .)
(if (fks? a-key)
...
. . .))
;; world . . . → . . .
;; Purpose: . . .
(define (f-on-world a-world)
. . .(f-on-fks a-world). . .)
and that all possible returned fish images are produced by these. Therefore, there
is no need for tests using sample values. Based on these observations, the final
specialization step yields:
;; fks → image
;; Purpose: Compute the fish image for the given fks
(define (compute-fish-img a-fks)
(cond [(string=? a-key "up") FISH-IMG]
[(string=? a-key "right") (rotate 270 FISH-IMG)]
[(string=? a-key "down") (rotate 180 FISH-IMG)]
[else (flip-vertical (rotate 90 FISH-IMG))]))
Once again, observe that template specialization completes all the steps of the
design recipe except running the tests.
The function process-key, as its name suggests, processes a key. This means
the template for functions on a key must be specialized. This function always takes
as input a world and a key and returns a world according to the universe API. If
the given key is an fks, then the next world is the given key. Otherwise, the world is
unchanged. The initial specialization yields:
;; world key → world
;; Purpose: To return the next world based on the
;; given key
(define (process-key a-world a-key . . .)
(if (fks? a-key)
...
. . .))
;; key → Boolean
;; Purpose: Determine if the given key is an fks
(define (fks? a-key)
(if (or (key=? a-key "right")
(key=? a-key "down")
(key=? a-key "left")
(key=? a-key "up"))
#true
#false))
** Ex. 71 — Redesign the fish interactive simulation using the following data
definition for the world:
A world is an image, either:
1.
2.
3.
4.
This development suggests the design recipe for video game and interactive anima-
tions displayed in Fig. 56. Steps 1–3 are problem and data analysis that are performed
once. Step 1 requires a data definition for the elements that vary in a game. Step 2
140 6 Aliens Attack Version 1
requires the development of a function template and of examples for each data def-
inition. If the defined data has variety, then at least one example for each variety is
needed. Step 3 requires the development of the run function. The body must be a
bb-expr. This function serves as a map to guide the top-down development of the
game. Ask yourself what events may affect the evolution of the game. There must be
a clause in the bb-expr for every event that may change the evolution of the game or
interactive simulation. This function is written without tests because it is only used
to initiate the game or simulation. It is not the solution to a problem. In fact, the run
function’s template may be defined as follows:
; string → world
; Purpose: To run the game
(define (run a-name)
(big-bang INIT-WORLD
[on-draw . . .]
[name a-name]
bb-clause∗))
The final clauses, of course, may not contain a repetition of any clause.
Steps 4–10 are performed for each function that is needed. This is done by
specializing the function template for the data being processed. These steps are the
same as those found in previous design recipes.
The first refinement to Aliens Attack is to add the rocket. Figure 57 displays a scene
of the proposed new version of the video game. Before starting save your Aliens
Attack version 0 to a new file, say Aliens-Attack-v1. It is this new file that is to
be edited with refinements. The version 0 is kept as a safe back-up of work that has
been completed.
36 Adding the Rocket to Aliens Attack 141
Recall that the rocket may move left to right without going off the edges of the
scene. Given that a rocket may exhibit changes as the game evolves, it needs to be
part of the world and, therefore, a data definition for a rocket is needed. Ask yourself
what changes about the rocket as the game advances. The characteristics that change
must be captured in the rocket’s data definition. At this point, it is important to
realize that a rocket is not the same as the image of the rocket. The image of the
rocket does not change as the rocket moves. When the rocket moves left or right the
rocket’s image-x changes. For example, if the image coordinates of the rocket are (5,
ROCKET-Y) and the rocket moves right, then the new image coordinates of the rocket
are (6, ROCKET-Y). That is, the rocket moves to the next image box to the right in
the scene. Observe, as noted in Chap. 6, that the rocket’s image-y is constant. Now
that the rocket’s changing characteristics are identified, the rocket’s data definition,
the template for functions on a rocket, and sample instances (rocket examples) may
be stated as follows:
#| A rocket is an image-x
;; rocket . . . → . . .
;; Purpose: . . .
(define (f-on-rocket a-rocket . . .)
. . .(f-on-image-x a-rocket . . .) . . .)
;; world . . . --> . . .
;; Purpose: . . .
(define (f-on-world a-world . . .)
. . .(f-on-rocket a-world . . .) . . .)
36 Adding the Rocket to Aliens Attack 143
;; key . . . --> . . .
;; Purpose: . . .
(define (f-on-key a-key . . .)
(cond [(key=? a-key "right") . . .]
[(key=? a-key "left") . . .]
[else . . .]))
* Ex. 74 — Develop function templates for ci, image-x, image-y, pixel-x, pixel-
y, and scene.
After completing steps 1 and 2 of the design recipe, we focus on the run template
specialization to complete step 3. The player moves the rocket using the left and
right arrows keys. This means that an on-key clause is also needed to process key
events. The specialization is as follows:
36 Adding the Rocket to Aliens Attack 145
; string → world
; Purpose: To run the game
(define (run a-name)
(big-bang INIT-WORLD
[on-draw draw-world]
[name a-name]
[on-key process-key]))
This states that the handler to render the game’s scene is draw-world and that the
handler to process key events is process-key.
Let us start by designing draw-world. To draw the world a rocket needs to be
drawn in the empty scene. The steps of the design recipe result in the following
specialization of the template for functions on a world:
;; world → scene
;; Purpose: To draw the world in E-SCENE
(define (draw-world a-world)
(draw-rocket a-world E-SCENE))
(check-expect (draw-world 0) )
(check-expect (draw-world (sub1 MAX-CHARS-HORIZONTAL))
)
146 6 Aliens Attack Version 1
)
(check-expect (draw-rocket (sub1 MAX-CHARS-HORIZONTAL)
E-SCENE)
)
36 Adding the Rocket to Aliens Attack 147
The sample expressions use draw-ci to place the rocket’s ci at image coordinates
(3, ROCKET-Y) and (12, ROCKET-Y) in, respectively, E-SCENE and E-SCENE2.
The two differences among the sample expressions are abstracted and become the
parameters to draw-rocket. The tests using sample values, once again, test rocket
extrema values. There are no new auxiliary functions to design. Therefore, this
concludes the design of draw-world and its auxiliary functions.
The design of process-key is done by specializing the f-on-key template. The
universe API requires that the inputs be a world and a key. If the given key is
"right" or "left", then the next world is created by moving the rocket. Otherwise,
the next world is the given world. The specialization of the template yields:
;; world key → world
;; Purpose: Process a key event to return next world
(define (process-key a-world a-key)
(cond [(key=? a-key "right") (move-rckt-right a-world)]
[(key=? a-key "left") (move-rckt-left a-world)]
[else a-world]))
** Ex. 75 — Redesign Aliens Attack version I using the following data defini-
tion for a rocket:
A rocket is an image-x such that it is either:
1. 0
2. (sub1 MAX-CHARS-HORIZONTAL)
3. (0..(sub1 MAX-CHARS-HORIZONTAL))
*** Ex. 77 — Design an interactive traffic light simulation such that if the
player presses:
1."r" the traffic light goes to flashing red.
2."y" the traffic light goes to flashing yellow.
3."n" the traffic light goes to normal operation.
• The universe’s big-bang expression is used to run a video game and allows
a programmer to provide the world’s initial value and the handlers needed to
advance the game’s or animation’s evolution.
• Predicates for data types with varieties sometimes may be simplified by using a
Boolean expression instead of a conditional expression.
• The video game design recipe includes steps to:
– Develop data definitions and sample instances for every element that varies as
the game evolves.
– Develop a function template for every data definition.
– Develop a run function.
• Separation of concerns leads to functions that solve a problem for a single data
type and simplifies the process of iterative refinement.
• Developing video games is intellectually stimulating and exciting.