\chapter{Case study: Catch}
\label{lecture-catch1}
\section{Graphics}

The Tkinter \pythoninline{canvas} widget can be used to draw shapes to
a window. We are going to spend the next two lectures building a
simple computer game. The game is called catch --- the game tosses the
ball across the screen, and the player has to catch the ball with
their mitt. The game is just for a bit of fun, but it will
make use of several important concepts we've introduced so far in the
course. Let's start by creating a \pythoninline{canvas} in a file
called \pythoninline{circle.py} and drawing a
circle onto it:

\begin{python}
from Tkinter import *

root = Tk()
gameCanvas = Canvas(root, width=800, height=600)
gameCanvas.grid(column=0,row=0)

circle = gameCanvas.create_oval(400, 300, 425, 325, fill="blue")

root.mainloop()
\end{python}

If you run the above program, you should get output similar to Figure
\ref{fig-circle}. The important line in \pythoninline{circle.py} is
the call to \pythoninline{gameCanvas.create\_oval} which can produce an ellipse
shape (circles are a type of ellipse). The
\pythoninline{gameCanvas.create\_oval} function returns a variable
which we can use later to modify various aspects of the display.
\begin{figure}[h]
\centering
\includegraphics[scale=0.5]{illustrations/Tk_Canvas.png}
\caption{Tkinter Canvas Program.\label{fig-circle}}
\end{figure}

\section{Moving the ball}
\label{sec-moving-ball}
To change the coordinates of a shape on the canvas, we can use the
\pythoninline{coords} function. However, if we want to move a shape
whilst the canvas is displayed, then we need to use some form of
callback. In \pythoninline{tk\_move\_circle.py} below, we add a callback
called \pythoninline{move} which is called after 1 second by using the
function \pythoninline{after}:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *

def move():
    global ball
    global gameCanvas
    gameCanvas.coords(ball, 150, 150, 170, 170)
		
root = Tk()
gameCanvas = Canvas(root, width=200, height=200)
gameCanvas.grid(column=0,row=0)

ball = gameCanvas.create_oval(90, 90, 110, 110, fill="blue")

gameCanvas.after(1000, move)

root.mainloop()
\end{python}
\end{minipage}

The \pythoninline{gameCanvas.after(1000, move)} function call does some magic
under the hood of Tkinter and causes the function \pythoninline{move}
to be called after 1000 milliseconds (or 1 second).

That's all well and good, but a little limiting. For a catch game, we
really need the ball to be moving continuously across the screen.
Before having a look at the solution below, it is worthwhile spending
some time to see if you can figure out how to do this yourself.

The solution is quite simple and rather ingenious. All we need to do
is call the \pythoninline{gameCanvas.after} function at the end of every
\pythoninline{move} function. Well, not quite, we also need to do some
housekeeping to keep track of the current coordinates of the ball, and
change them at each call to \pythoninline{move}. Type in the following
code into a file called \pythoninline{tk\_move\_circle2.py} and see what
happens:

\newpage
\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *

def move():
    global ball
    global gameCanvas
    global x, y, dx, dy
    x = x+dx
    y = y+dy
    gameCanvas.coords(ball, x, y, x+20, y+20)
    gameCanvas.after(100, move)

root = Tk()
gameCanvas = Canvas(root, width=200, height=200)
gameCanvas.grid(column=0,row=0)

x=10
y=10
dx=2
dy=1
ball = gameCanvas.create_oval(x, y, x+20, y+20, fill="blue")

gameCanvas.after(100, move)

root.mainloop()
\end{python}
\end{minipage}

When running the program you should see the ball move from the top left of the
canvas towards the bottom right and then off the screen forever. 

\section{Adding randomness}
A game that has the ball starting in the same position is going to be
pretty boring, so let's add some randomness to the start position and
direction. We can do that by using the \pythoninline{random} module.
Here is \pythoninline{tk\_move\_circle2.py} where I've shown only the
modified lines.
\begin{python}
from Tkinter import *
import random
...
x = 10
y = random.randint(10,190)
dx = 2
dy = random.randint(-5,5)
\end{python}

Now we have a ball that's a bit more interesting --- it starts on the
left hand side of the window, and heads off in a random direction.
Unfortunately, it often quickly disappears from the window, never to
return. What we really need is to have the ball bounce off the top and
bottom of the window. This is another situation where it's worth
spending some time thinking about how to make the ball bounce. So
before you look on ahead, see if you can come up with a reasonable
scheme.

The solution is actually quite simple. A perfect bounce that loses no
energy in the collision should leave the bounce at the same angle as
it entered. If the ball is bouncing off a horizontal surface, its
velocity in the $x$ direction will stay the same, and it's velocity in
the $y$ direction will be opposite to what it was before. This can be
achieved fairly easily with an \pythoninline{if-elif} statement. The
\pythoninline{move} function becomes:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *
import random

def move():
    global ball
    global gameCanvas
    global x, y, dx, dy
    
    x = x+dx
    y = y+dy
    if y<0:
        y = 0
        dy = -dy
    elif y>180:
        y = 180
        dy = -dy
    gameCanvas.coords(ball, x, y, x+20, y+20)
    gameCanvas.after(100, move)
		
root = Tk()
gameCanvas = Canvas(root, width=200, height=200)
gameCanvas.grid(column=0,row=0)

x=10
y=random.randint(10,190)
dx=2
dy=random.randint(-5,5)
ball = gameCanvas.create_oval(x, y, x+20, y+20, fill="blue")

gameCanvas.after(100, move)
	
root.mainloop()
\end{python}
\end{minipage}

The ball should now bounce off the top and the bottom of the screen.
I'm a little concerned though that there are a lot of magic numbers
(0, 180, 100, 20 etc) creeping into the code. All these numbers make
the code a bit hard to read, and can make code hard to modify.
Generally, if you find that you use the same number in more than one
place, it is good practice to put it in a constant variable --- that
way you can modify the constants in your program at a later date by
just changing one number and not several. Also, with GUI widgets, we
can often discover their state (such as their width and height), by
using the widget's query method. To discover the width of a widget
in Tkinter, we can use the \pythoninline{winfo\_width()} method. Why do
you think that's preferable than specifying a program constant? If we
make those changes, our bouncing ball program eventually looks like
\pythoninline{bounce.py}:

\begin{minipage}{\textwidth}
\begin{python}
from Tkinter import *
import random

def move():
    global ball
    global gameCanvas
    global x, y, dx, dy
    global BALL_SIZE
    global WAIT_TIME
	
    x = x+dx
    y = y+dy
    if y<0:
        y = 0
        dy = -dy
    elif y>gameCanvas.winfo_height()-BALL_SIZE:
        y = gameCanvas.winfo_height()-BALL_SIZE
        dy = -dy
    gameCanvas.coords(ball, x, y, x+BALL_SIZE, y+BALL_SIZE)
    gameCanvas.after(WAIT_TIME, move)


# constants are usually written all in upper case
BALL_SIZE = 20
WAIT_TIME = 100
root = Tk()
gameCanvas = Canvas(root, width=400, height=400)
gameCanvas.grid(column=0,row=0)
gameCanvas.wait_visibility()

x=0
y=random.randint(0,gameCanvas.winfo_height()-BALL_SIZE)
dx=2
dy=random.randint(-5,5)
ball = gameCanvas.create_oval(x, y, x+BALL_SIZE, y+BALL_SIZE, fill="blue")

gameCanvas.after(WAIT_TIME, move)
	
root.mainloop()
\end{python}
\end{minipage}

I added one extra function call to the above program:
\pythoninline{gameCanvas.wait\_visibility()}. This was because I wanted
to make sure the canvas was created before asking for its size when
generating a random y position.

\section{Glossary}
\begin{description}
\item[random:]Having no specific pattern. Unpredictable. Computers
   are designed to be predictable, and it is not possible to get a
   truly random value from a computer. Certain functions produce
   sequences of values that appear as if they are random, and it is
   these \emph{pseudo-random} values that we get from Python.
 \end{description}

\section{Laboratory exercises}
\begin{enumerate}
\item The following Tkinter script draws a simple house on a Tkinter canvas:
\begin{python}
from Tkinter import *

root = Tk()
houseCanvas = Canvas(root, width=400, height=400)
houseCanvas.grid(row=0, column=0)

houseCanvas.create_rectangle(20, 380, 120, 280) # the house
houseCanvas.create_rectangle(55, 380, 85, 330)  # the door
houseCanvas.create_rectangle(40, 320, 60, 300)  # left window
houseCanvas.create_rectangle(80, 320, 100, 300) # right window
houseCanvas.create_line(20, 280, 70, 240, 120, 280) # the roof

root.mainloop()
\end{python}
\begin{itemize}
\item Create a file called, \pythoninline{house.py}, and type in the
  above code.  Run this script and confirm that you get a window that looks like this: \\
  \includegraphics[scale=0.5]{illustrations/Tk_House.png} \\

\item Wrap the house code in a function named
\pythoninline{draw\_house()}.

\item Run the script now. Do you see a house? Why not?

\item Add a call to \pythoninline{draw\_house()} at the bottom of the script so
    that the house returns to the screen.

\item \emph{Parameterize} the function with \pythoninline{x} and
\pythoninline{y} parameters --- the header should then become
\pythoninline{def~draw\_house(x,~y):}, so that you can pass in the
location of the house on the canvas. 

\item Use \pythoninline{draw\_house} to place five houses on the canvas in
    different locations.
\end{itemize}

\item Modify the code in \pythoninline{bounce.py}, to make the ball go
faster.

\item Modify the code in \pythoninline{bounce.py} so that the bounce
is a \emph{little bit} random. This should make the catch game we
develop in the next lecture a bit more interesting.
\end{enumerate}



