# More spatial Logic

We want to write a function that returns true if there are three consecutive puppies any direction (left, right, up, down, diagonal).

Let’s imagine we are checking a grid, moving from left to right:

We do *not* need to also check from right to left! Any group of consecutive puppies we find going right would also be found going left — but we don’t need to check both. The same applies to lookup up/down and diagonally.

This limits our searching a bit. We only need to check four directions:

- going right
- going down
- going down and to the left
- going down and to the right

## Using guards

Let’s re-use the `is_puppy()`

function with guards. We wrote this previously.

```
def is_puppy(grid, row, column):
num_rows = len(grid)
num_columns = len(grid[0])
# check if the row is off the grid
if row < 0 or row >= num_rows:
return False
# check if the column is off the grid
if column < 0 or column >= num_columns:
return False
if grid[row][column] == 'p':
return True
return False
```

This will ensure we never check out of bounds on the grid.

## Checking for consecutive puppies

Now we can write functions checking for consecutive puppies without worrying about the edges of the grid. First, a function to look to the right:

```
def has_three_right(grid, row, column):
for c in range(column, column + 3):
if not is_puppy(grid, row, c):
return False
return True
```

We look at three columns in a row, starting with our current column. And if there is *not* a puppy in any of these cells, we can immediately return `False`

. If we get through all three columns and find a puppy everywhere, we return `True`

.

Similarly, we can write a function to check down:

```
def has_three_down(grid, row, column):
for r in range(row, row + 3):
if not is_puppy(grid, r, column):
return False
return True
```

a function to check down and to the left:

```
def has_three_down_left(grid, row, column):
for r, c in zip(range(row, row + 3), range(column, column - 3, -1)):
if not is_puppy(grid, r, c):
return False
return True
```

and a function to check down and to the right:

```
def has_three_down_right(grid, row, column):
for r, c in zip(range(row, row + 3), range(column, column + 3)):
if not is_puppy(grid, r, c):
return False
return True
```

Notice that for these last two we use `zip`

! This lets us look diagonally. For example, to go up and to the right, we are zipping the lists `[row, row + 1, row +2]`

and `[column, column + 1, column + 2]`

to get a list of tuples: `[(row, column), (row + 1, column + 1), (row + 2, column + 2)]`

.

In addition, in all of these functions we use `r`

for ranges counting rows and `c`

for ranges counting columns, so we have to be sure to use `r`

and/or `c`

when checking for puppies.

## Checking for three in a row

Now we can write the primary function:

```
def has_three_in_a_row(grid, value='X'):
num_rows = len(grid)
num_columns = len(grid[0])
for row in range(num_rows):
for column in range(num_columns):
if has_three_right(grid, row, column)
or has_three_down(grid, row, column) \
or has_three_down_left(grid, row, column) \
or has_three_down_right(grid, row, column):
return True
return False
```

We are using

`\`

to continue the`if`

statement on multiple lines since it is really long

## Running the code

We can run this code with:

```
park1 = [
[None, None, 'p', 'p'],
[None, 'p', 'p', 'p'],
[None, None, None, None],
[None, 'p', None, None]
]
park2 = [
[None, None, 'p', 'p'],
[None, None, 'p', None],
[None, 'p', None, 'p'],
[None, 'p', None, None]
]
park3 = [
[None, None, None],
[None, None, None],
[None, None, None]
]
print(has_three_in_a_row(park1))
print(has_three_in_a_row(park2))
print(has_three_in_a_row(park3))
```

We get:

```
True
True
False
```