Due dates:

Design artifacts due on Thursday, April 27th

Code due on Friday, May 5th by 11:59 PM

Update 4/24: Added section on how to represent movement direction

Update 4/25: Added some suggestions for how to define struct Scene and struct Point

Update 4/28: Indicate that extra credit features must be individual effort (not collaborative.)

Getting Started

Start by downloading CS101_Assign04.zip, saving it in the CS101 directory within your home directory.

Start a Cygwin Bash Shell (or Linux terminal, or MacOS terminal) and run the following commands:

cd h:
cd CS101
unzip CS101_Assign04.zip
cd CS101_Assign04

(Note that you should omit the cd h: step on Linux and MacOS.)

Using a text editor (e.g., Notepad++), open the file

CS101/CS101_Assign04/Snake.cpp

You will add your code to this file.

Your Task

Your task is to implement a Snake game.

The play controls a snake. The snake consists of some number of segments. At each time step, the snake’s head segment moves up, right, down, or left, and the other segments of the snake follow. The player can control the direction of the snake’s head using the arrow keys. At all times, there is a piece of fruit on the playing field. If the snake eats the fruit (i.e., the snake’s head segment reaches the fruit), the snake grows by one segment. When the fruit is eaten, a new piece of fruit appears in a random location. If the snake’s head goes out of bounds, or if the snake’s head collides with one of the snake’s body segments, the game is over.

Here is an animation showing gameplay:

snake game

Here is an animation showing the player losing because the snake’s head went out of bounds:

snake goes out of bounds

Here is an animation showing the player losing because the snake’s head collided with its body:

snake collides with its body

You will use the terminal graphics functions: see Lab 18, Lab 19, Lab 21, and especially Lab 23 and Lab 24.

Approach

This is a substantial assignment. Here is a suggested approach to getting all of the features of the program working:

  1. Define struct Point and struct Snake data types.
  2. Add fields to struct Scene to represent the snake.
  3. Add code to scene_init to initialize the snake.
  4. Add code to scene_render to draw the segments of the snake. The snake should be visible, but will not move.
  5. Add code to scene_update to move the snake by adding a new head segment and removing the current tail segment. The snake will now move in one direction.
  6. Add code to scene_update to check for keypresses, changing the direction of the snake as appropriate when an arrow key is pressed. Now you should be able to control the snake.
  7. Add code to scene_update to detect if the snake’s head has moved out of bounds, and if so, set a “game over” flag in the struct Scene. Also, add code to scene_render to print a game over message when the game over flag is set.
  8. Add code to scene_update to detect if the snake’s head has collided with any of its body segments, and if so, set a game over flag in struct Scene.
  9. Add code to check whether the snake’s head is in the same place as the piece of fruit, and if so, increase the player’s score, increase the snake’s number of segments by 1, and place a new piece of fruit in a random location. (Make sure that the new fruit location isn’t in the same place as any of the snake’s segments.)

Note that only items 1–8 are required for full credit on the assignment.

Hints, specifications, and requirements

Playing field, rendering

The playing field should be 80 characters wide by 23 characters high. (This leaves one row in the terminal available for displaying the current score and number of segments.)

The scene_render function should use the terminal graphics functions to render a visual representation of the game state, including:

The score and number of segments should be rendered in the last row of the terminal.

struct Scene, main function

The starting code has a struct Scene data type and an example main function. You should add fields to struct Scene to represent the current game state. Important: you may not modify the main function. This means that you must define scene_init, scene_render, and scene_update functions that can work with the code in the main function.

Here is a possible definition for the struct Scene data type:

struct Scene {
    struct Snake snake;
};

Note that as you add additional features (fruit, score) you may need to add additional fields to this data type.

Defining struct types and functions

In addition to the struct Scene data type, you should define data types struct Point and struct Snake.

The struct Point data type might be defined as follows:

struct Point {
    int x, y;
};

You should define the following functions to operate on instances of struct Point and struct Snake:

void point_init(struct Point *p, int x, int y);
void snake_init(struct Snake *snake);
void snake_append_head(struct Snake *snake, int x, int y);
void snake_remove_tail(struct Snake *snake);
struct Point snake_get_head(const struct Snake *snake);
struct Point snake_get_segment(const struct Snake *snake, int index);

point_init should initialize an instance of struct Point by storing the specified x and y coordinate values.

snake_init should initialize an instance of struct Snake. The snake should have 8 segments initially. The snake’s initial position, configuration, and direction is up to you. See the “Implementing the snake” section for more details.

snake_append_head should add an additional segment to the snake at the position specified by the x and y parameters. The new segment will become the new “head” segment. All of the existing segments should remain in place.

snake_remove_tail should remove the last segment of the snake.

snake_get_head should return the struct Point corresponding to the “head” of the snake.

Implementing the snake

A good way to represent the snake is using an array of segments, such that each segment is an x/y coordinate pair (e.g., a struct Point). Because the number of segments varies (increasing each time the snake eats a piece of fruit), the snake struct type should use a counter field to keep track of how many segments there are. The snake struct type should also keep track of which direction the snake is moving in. Here is a recommendation for how to define the struct Snake data type:

struct Snake {
    struct Point segments[MAX_SEGMENTS];
    int num_segments;  // how many segments the snake has
    int dir;           // which direction the snake is moving in
};

When the game starts, the snake should have 8 segments.

The game should allow the snake to have up to 100 segments, so the MAX_SEGMENTS constant should be defined as 100.

Moving the snake is conceptually quite simple: a new head segment is added, based on the location of the current head segment and the snake’s direction of motion. If the length of the snake isn’t changing (meaning that the snake did not eat a piece of fruit, or the snake is already at the maximum length), then the “tail” segment should be removed by calling the snake_remove_tail function. Adding a new head segment should be done by calling the snake_append_head function.

Moving the snake is conceptually quite simple: just remove the current tail segment by calling snake_remove_tail and append a new head segment by calling snake_append_head. The position of the new head can be determined from the position of the current head segment and the snake’s direction.

Here is a suggested approach to implementing the snake_append_head and snake_remove_tail functions:

If you follow this approach, the tail segment is element 0 in the array of segments, and head segment is the one whose index is one less than the total number of segments.

The scene_update function should move the snake each time it is called. It should also check to see if the snake’s head has gone out of bounds or has collided with the snake’s body, and should check to see if the piece of fruit was eaten.

Growing the snake (when a piece of fruit is eaten) is easy: just add a new head segment without removing the current tail segment.

Representing movement direction

The head of the snake is always moving up, down, right, or left.

If you use the suggested definition for the struct Snake data type, then you can use the dir field to keep track of which direction the snake is moving in.

It will be helpful to define constants for the different possible directions: e.g.:

#define UP    0
#define DOWN  1
#define RIGHT 2
#define LEFT  3

Note that there is nothing special about the specific integer values used to represent directions. The only requirement is that each direction can be distinguished from the other directions.

Handling user input

In the scene_update function, call the cons_get_keypress() function. If it returns one of the arrow keys (UP_ARROW, RIGHT_ARROW, DOWN_ARROW, or LEFT_ARROW), then change the direction of the snake as appropriate. Note that you should not allow the snake to reverse its direction: for example, if the snake’s head is currently moving up, then scene_update should do nothing if DOWN_ARROW is pressed.

If the q key is pressed, then scene_update should return 0, causing the main function to finish (and the program to exit.) Otherwise, scene_update should return 1 (causing main to keep running.)

Note that the cons_get_keypress() function will return -1 if no key has been pressed.

Scoring

Each time the snake eats a piece of fruit, the player earns a number of points equal to 10 times the snake’s current number of segments.

Design artifacts

The design artifacts are due in class on Tuesday, April 25th for the following functions:

The design artifacts are due in class on Thursday, April 27th for the following functions:

So, make sure that you have two design artifacts for each day - four design artifacts in total.

Grading

Your grade will be determined according to the following criteria:

Standard features (complete these for full credit on the assignment):

Extra credit features (complete these for up to 40 points of extra credit):

Important: Include a comment at the top of your program explaining which extra credit features you implemented.

Important: Any work on extra credit features must be done individually.

Submitting

To submit your work, make sure your Snake.cpp file is saved, and in the Cygwin window type the command

make submit

Enter your Marmoset username and password (which you should have received by email.) Note that your password will not be echoed to the screen. Make sure that after you enter your username and password, you see a message indicating that the submission was successful.

Important: Make sure that you check the file(s) you submitted to ensure that they are correct. Log into the server using the following URL (also linked from the course homepage):

https://cs.ycp.edu/marmoset

You should see a list of labs and assignments. In the row for assign04, click the link labeled view. You will see a list of your submissions. Download the most recent one (which should be listed first). Verify that it contains the correct files.

You are responsible for making sure that your submission contains the correct file(s).