summaryrefslogtreecommitdiff
path: root/docs/random_avoid.tex
diff options
context:
space:
mode:
Diffstat (limited to 'docs/random_avoid.tex')
-rw-r--r--docs/random_avoid.tex167
1 files changed, 167 insertions, 0 deletions
diff --git a/docs/random_avoid.tex b/docs/random_avoid.tex
new file mode 100644
index 0000000..90ac183
--- /dev/null
+++ b/docs/random_avoid.tex
@@ -0,0 +1,167 @@
+%%- from "macros.tex" import make_board -%%
+
+\section{Random Avoid Bot}
+\fasttrack{Choose a direction at random, but not one which will lead to immediate death.}
+
+The last bot we wrote had a big problem, it ran into its own tail.
+We don’t want our next bot to be that stupid, so we need to teach it how to not
+do that!
+
+But before we can do that, we need to know few more things about our bots.
+You might have noticed that our functions have two parameters,
+\texttt{board} and \texttt{position}.
+We haven’t had to use them so far, but we will now, so we need to know what they
+are.
+But rather than me just telling you what they are,
+why not have a look yourself?
+
+\pythonfile{print_bot.py}
+
+You should see something like this (on a 4x3 board):
+\begin{minted}{pytb}
+(1, 2)
+[['.', '.', '*', '.'], ['.', '.', '*', '.'], ['.', 'A', '.', '.']]
+Exception in bot A (<'<'>function print_bot at 0x7f61165f2e60<'>'>):
+------------------------------------------------------------
+Traceback (most recent call last):
+ File "…/snakegame/engine.py", line 132, in update_snakes
+ "Return value should be a string."
+AssertionError: Return value should be a string.
+------------------------------------------------------------
+\end{minted}
+
+Ignore all the Exception stuff, that’s just because we didn’t return one of
+\py|'L'|, \py|'U'|, \py|'D'| or \py|'R'|.
+The first line is our position: it’s a \py|tuple| of the x and y
+coordinates of our snake’s head.
+The second line is the board: it’s a list of each row in the board,
+and each row is a list of the cells in that row.
+
+Notice that if we index the board first by the y coordinate and then by the x
+coordinate, we can get the character in the board where our snake is:
+\py|board[y][x] == board[2][1] == 'A'|.
+The head of our snake is always an uppercase character in the board,
+and the rest of our body (the tail) are always lowercase characters.
+
+This is all very well, but how do we stop our bot from eating its tail?
+Well, the answer is that we need to look at each of the squares surrounding our
+snake’s head, to see if we’ll die if we move into them or not.
+
+Let’s have a look at the square to the right of our snake’s head.
+First, we need to know its coordinates: looking at
+Board~\ref{brd:right-square:normal},
+we see that if our snake is at position $(x, y)$,
+then the square on the right will be at position $(x + 1, y)$.
+
+But this isn’t the whole story: Board~\ref{brd:right-square:wrapping}
+shows that if the snake is on the rightmost column, the square on the right
+is going to wrap around to be on the leftmost column.
+
+\begin{board}
+\begin{subfigure}{.45\linewidth}
+ \begin{tabular}{l|l|l|l|l}
+ … & $x-1$ & $x$ & $x + 1$ & … \\\hline
+ $y-1$ & & & & \\\hline
+ $y$ & & $\mathbf{(x,y)}$ & $\mathbf{(x+1,y)}$ & \\\hline
+ $y+1$ & & & & \\\hline
+ … & & & & … \\
+ \end{tabular}
+\caption{Coordinate of the square to the right (ignoring wrapping).}
+\label{brd:right-square:normal}
+\end{subfigure}
+\hfill
+%
+\begin{subfigure}{.45\linewidth}
+< make_board([
+ '.....',
+ '*...A',
+ '.....',
+]) >
+\caption{%
+ The board wraps around,
+ so the square to the right of our snake $(4, 1)$
+ is the apple $(0, 1)$.
+}
+\label{brd:right-square:wrapping}
+\end{subfigure}
+
+\caption{Finding the square to the right.}
+\label{brd:right-square}
+\end{board}
+
+Fortunately for us, there’s an easy way of ‘wrapping’ around in Python,
+which is the modulo operator (\%). The modulo operator returns the
+\emph{remainder} when you divide two numbers.
+\begin{minted}{pycon}
+>>> 3 % 8
+3
+>>> 7 % 8
+7
+>>> 8 % 8
+0
+>>> 9 % 8
+1
+>>> for i in range(20):
+... print i % 8,
+...
+0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3
+\end{minted}
+
+\newcommand\mod{\,\%\,}
+
+% TODO: how do we get the width and height of the board?
+
+Looking back at Board~\ref{brd:right-square:wrapping}, we need to wrap the x
+coordinate back to $0$ when $x + 1 = width$,
+so we need $(x + 1) \mod width$.
+Taking this to a more general level, imagine we need to get the cell where
+the x coordinate is shifted by $dx$
+and the y coordinate is shifted by $dy$.
+For example, we might want to get the cell diagonally adjacent on the bottom
+left: it’s one square to the left, $dx = -1$ and one square down $dy = 1$.
+Don’t forget that moving right or down means adding
+and moving left or upwards means subtracting!
+Back to our general case, our new cell is going to be at the position
+$((x + dx) \mod width, (y + dy) \mod height)$.
+
+Don’t worry if you didn’t follow the general case there, you just need to
+remember that the cell to the right is at $((x + 1) \mod width, y)$.
+We then need to look \emph{in the board} at that position to see what’s in that
+cell.
+Remember that our board is a list of rows (stacked vertically),
+and each row is a list of cells (stacked horizontally).
+So we need to first find the right row, which we will do by using the y
+coordinate: \py|board[y]|.
+Then we need to find the right cell in the row, using the x coordinate:
+\py|board[y][(x + 1) % width]|.
+
+We’re almost at the end: all we need to do is build up a list of each cell we
+can move into. We know that we can move into cells which are
+empty (represented by a full stop)
+or have an apple (represented by an asterisk) in them,
+so we’ll test for that.
+Take a moment to write out the code we’ve managed to build so far, hopefully
+you’ll end up with something very close to what I’ve got below.
+Then you just need to add the other directions (left, up and down), and you’re
+done.
+
+\begin{pythoncode}
+from random import choice
+
+def bot(board, position):
+ x, y = position
+ height = len(board)
+ width = len(board[0])
+
+ valid_moves = []
+
+ right = board[y][(x + 1) % width]
+ if right == '.' or right == '*':
+ valid_moves.append('R')
+
+ return choice(valid_moves)
+\end{pythoncode}
+
+If you’re really stuck, or want to check your solution, here’s my solution:
+\pythonfile{random_avoid.py}
+