Skip to content

Commit

Permalink
Added short snake instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
ben18785 committed Jul 2, 2021
1 parent a90e77b commit a71dcc1
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 45 deletions.
6 changes: 4 additions & 2 deletions example_snake/snake.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ def grow(self):
self.body.append(head)

def hits_side(self, head_pos, screen_width, screen_height):
if head_pos[0] == screen_width or head_pos[0] == 0:
# needs negative here for latter since head_pos[0] is left side
if head_pos[0] == screen_width or head_pos[0] == -self.size:
return True
if head_pos[1] == screen_height or head_pos[1] == 0:
# needs negative here for latter since head_pos[1] is top side
if head_pos[1] == screen_height or head_pos[1] == -self.size:
return True
return False

Expand Down
86 changes: 86 additions & 0 deletions short_snake/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import pygame
import time

from snake import Snake, Food, losing_message, score_message

from pygame.locals import (
K_UP,
K_DOWN,
K_LEFT,
K_RIGHT,
K_ESCAPE,
KEYDOWN,
QUIT,
)

BLACK = (0, 0, 0)

pygame.init()
clock = pygame.time.Clock()

# Create screen
SCREEN_HEIGHT = 400
SCREEN_WIDTH = 600
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT])

# Create an initially stationary snake
snake = Snake((SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2))
food = Food(SCREEN_WIDTH, SCREEN_HEIGHT)
direction = [0, 0]

# Instantiate score
score = 0

# Starting speed
speed = 10

# Run until the user asks to quit
running = True
is_survived = True
while running:

for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
elif event.type == QUIT:
running = False

# Check whether snake changes direction by key pressing
keys_pressed = pygame.key.get_pressed()
if keys_pressed[K_UP]:
direction = [0, -1]
elif keys_pressed[K_DOWN]:
direction = [0, 1]
elif keys_pressed[K_LEFT]:
direction = [-1, 0]
elif keys_pressed[K_RIGHT]:
direction = [1, 0]
snake.change_direction(direction)

# Check whether snake eats, move the snake and see if they survive
if snake.get_pos() == food.get_pos():
score += 1
speed += 1
del food
food = Food(SCREEN_WIDTH, SCREEN_HEIGHT)
else:
is_survived = snake.move_and_survive(SCREEN_WIDTH, SCREEN_HEIGHT)
if not is_survived:
running = False

# Draw things on screen
screen.fill(BLACK)
snake.draw(screen)
food.draw(screen)
score_message(score, screen, SCREEN_WIDTH, SCREEN_HEIGHT)
pygame.display.update()

# Dictates snake speed
clock.tick(speed)

# Done! Time to quit.
losing_message("You lost", screen, SCREEN_WIDTH, SCREEN_HEIGHT)
pygame.display.update()
time.sleep(2)
pygame.quit()
83 changes: 83 additions & 0 deletions short_snake/snake.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import pygame
import random

WHITE = (255, 255, 255)


def losing_message(msg, screen, screen_width, screen_height):
font_style = pygame.font.SysFont(None, 50)
mesg = font_style.render(msg, True, WHITE)
screen.blit(mesg, [screen_width/2, screen_height/2])


def score_message(score, screen, screen_width, screen_height):
font_style = pygame.font.SysFont(None, 50)
value = font_style.render("Your Score: " + str(score), True, WHITE)
screen.blit(value, [0, 0])


class Snake:
def __init__(self, position, size=20):
bone = Bone(position, size)
self.body = bone
self.direction = [0, 0]
self.size = size

def change_direction(self, direction):
self.direction = direction

def draw(self, screen):
self.body.draw(screen)

def get_new_pos(self):
current_pos = self.get_pos()
new_pos = (
current_pos[0] + self.direction[0] * self.size,
current_pos[1] + self.direction[1] * self.size,
)
return new_pos

def get_pos(self):
return self.body.pos

def hits_side(self, pos, screen_width, screen_height):
# needs negative here for latter since pos[0] is left side
if pos[0] == screen_width or pos[0] == -self.size:
return True
# needs negative here for latter since pos[0] is topside
if pos[1] == screen_height or pos[1] == -self.size:
return True
return False

def move_and_survive(self, screen_width, screen_height):
new_pos = self.get_new_pos()
has_hit_side = self.hits_side(new_pos, screen_width, screen_height)
if has_hit_side:
return False
moved_bone = Bone(new_pos, self.size)
self.body = moved_bone
return True


class Bone:
def __init__(self, position, size):
self.pos = position
self.size = size

def draw(self, screen):
pygame.draw.rect(
screen, WHITE, [self.pos[0], self.pos[1], self.size, self.size]
)


class Food:
def __init__(self, screen_width, screen_height, size=20):
x = round(random.randrange(0, screen_width - size) / size) * size
y = round(random.randrange(0, screen_height - size) / size) * size
self.bone = Bone((x, y), size)

def draw(self, screen):
self.bone.draw(screen)

def get_pos(self):
return self.bone.pos
79 changes: 79 additions & 0 deletions steps/coding_up_short_snake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Steps for coding up snake using object oriented programming

In this guide, we provide a list of ideas for coding up snake in using concepts from object oriented programming (OOP). Note, this is just one possible way to code up the game and many others are possible and may well be better!

Note also that, we provide a fully working version of the game using OOP in [this](./short_snake/) folder. We expect that it may be useful to consult our approach throughout the task to give you an idea as to how to proceed.

We first discuss the three Python classes that we are going to create:

* Bone

* Food
* Snake

These classes are going to be kept in the file `snake.py` and will be imported by the main script.

## Bone

Our snake and our food are going to be composed of bones. Computational bones are simpler than real bones. They have two properties:

* a `pos`: a list `[x,y]` of the bone's coordinates
* a `size`: the width or height of the bone (it's a square)

Bones also have a method that draws them at their given position on the canvas. Here, we would just use the `pyjama.draw.rect` method size the bones are squares. Note this function will need to take the screen as an input to be able to draw on it.

##Food

The snake eats prey that we call "Food". The food only has one property: the single bone that comprises it.

Food is somewhat miraculous: each time a bit of food is eaten, another bit of food appears on the screen at a random location.

We want the locations at which the food appears to occur on grid values that will overlap with the position of our snake, so we ensure that this is the case using the following instantiation:

```python
class Food:
def __init__(self, screen_width, screen_height, size=20):
x = round(random.randrange(0, screen_width - size) / size) * size
y = round(random.randrange(0, screen_height - size) / size) * size
self.bone = Bone((x, y), size)
```

The food also has two methods:

* `draw()`: which draws the food on the screen by drawing the underlying bone
* `get_pos()`: which returns its position (this is going to be used by the `Snake` to check whether the food has been eaten)

## Snake

The snake is the most complex of beats in the game. It has the following properties:

* a `body` which comprises a single bone
* a `direction` in which it is currently travelling: this is a two-element list indicating the direction vector
* a `size`, which is the height / width of our square snake

It also has a number of methods:

* `change_direction` which updates the snake's direction according to a specified direction vector
* `draw` which draws the snake's bone
* `get_pos` which returns the current `[x,y]` position of the snake
* `get_new_pos` which updates the snake's position according to the direction in which it travels. Each time a snake moves, it moves by a distance equal to its `size`.
* `hits_side` which determines if the snake has hit any of the sides of the canvas
* `move_and_survive`: this moves the snake and then determines whether the snake is still surived after the move. This first uses `get_new_pos` to move the snake, then uses `hits_side` to determine if the new snake position has hit the side of the domain.
* If the snake has hit the side of the domain the function return `False`.
* If the snake has not hit the sides, it updates the snake's body to account for the new position and returns `True`.

## The main script file

Many of the things that are needed to code up the main script are contained within the various vignettes. We make, however, the following suggestions that may help you to get up and running:

* Start with a `screen_height=400` and a `screen_width=600`
* Start the snake in the middle of the canvas and make it stationary
* Initialise the game with `speed=10`
* In the game while loop:
1. Check whether the game has been quit by the user clicking the box in the upper left corner of the canvas
2. Determine which (if any) of the arrow keys have been pressed and use this to change the snake's direction
3. Check whether the snake's position matches that of the food. If so, increment the speed by 1 unit
4. If the snake hasn't fed, move the snake and check whether it has survived.
5. If the snake hasn't survived terminate the game
6. Otherwise draw the canvas, the snake and the food

43 changes: 0 additions & 43 deletions steps/coding_up_snake.md

This file was deleted.

0 comments on commit a71dcc1

Please sign in to comment.