Assignment 1

Extra information

  • game.c: It includes an additional state named GAME_OVER. This is where you should perform operations like reporting the score or clearning the screen. Additionally, the game() now take a highscore structure as input and returns an updated structure.

highscore_t *game(highscore_t *highscores);

  • main.c: Setup to accept a command line argument for the highscore file. This functionality is not part of milestone 3 and is commented out. However, it is available as an extra credit opportunity in milestone 4.
  • tetris.h: Changed the default well width and height. Tetris is intended to be played on a well that is 6-7 spaces wide and 12-18 spaces deep. With the previous well size, it takes a very long time to complete a row.
  • well.c[h]: New functionality has been added to remove completed rows from the well. This function is called prune_well. It returns the number of completed lines.
  • score.c[h]: New functionality to convert the number of completed lines into a score. It also handles the case where multiple 4 line clears (called a tetris) occur.
  • highscore.c[h]: New functionality used to read and write previous high scores to a file. When the previous high scores are read from the file, a linked list is generated to store the data. If the current players score qualifies, then it is inserted into the linked list and saved back into the highcscore file.
  • Makefile: Added new files to the build and added an all target. This allows the project to be cleaned and built in one step.

make clean all

  • tetromino.c[h], key.c[h]: Unchanged

Overview:

This milestone is focused on scoring and determining when the game is over. In tetris, a player scores by completing horizontal line. When a line is completed it is removed. If multiple lines are completed simultaneously, then the player is rewarded with additional points beyond what the individual lines are worth.

The game is over, when a new tetromino cannot be creating without colliding with previously created tetromino pieces.

Removing Completed Lines.

Functionality has been added to well.c[h] that will search for completed lines and remove them. It accomplishes this by reading back the screen in the area of the well and looking for completed lines.

The name of the function is prune_well.

intprune_well(well_t * well);

This function take a well structure as input. Note that the well is created in the INIT state of the game function. You should call this function:

  • after the current tetromino has hit the bottom
  • before a new tetromino is created

The number of lines returned should be captured and used for scoring.

Scoring

Functionality to perform the scoring has been added to score.c[h]. The function is called compute score.

intcompute_score(int previous_score, int lines_cleared);

You pass in the current score and the number of lines cleared. It returns the new score.

Ending the Game

The game is over when a new tetromino cannot be added to the well without colliding. In this case, the game state should be set to GAME_OVER.

The game over state will clear the screen and display a message. Your code should display the current players score on this screen.

Requirements

You should implement the following additional functionality to your milestone 2 code.

  • Remove completed lines from the well.
  • Compute and display the running score.
  • Display the timer for the current game and maintain a timed game.
  • End the game when a tetromino will immediately collide if it is added to the well.

game.c

#include <unistd.h>

#include <ncurses.h>

#include <time.h>

#include “highscore.h”

#include “game.h”

#include “well.h”

#include “tetris.h”

#include “tetromino.h”

#include “key.h”

void init_game(void) {

int x,y;

}

highscore_t *game(highscore_t *highscores) {

static int state = INIT;

tetromino_t *next = NULL;

tetromino_t *current = NULL;

well_t *w;

int x,y;

int c;

int arrow;

struct timespec tim = {0,1000000};

struct timespec tim_ret;

int move_counter = 0;

int move_timeout = 500;

int status;

int counter = 0;

int lines_cleared = 0;

int score = 0;

char str[80];

while(1) {

switch(state) {

case INIT:               // Initialize the game, only run one time

initscr();

nodelay(stdscr,TRUE);  // Do not wait for characters using getch.

noecho();              // Do not echo input characters

getmaxyx(stdscr,y,x);  // Get the screen dimensions

w = init_well(((x/2)-(WELL_WIDTH/2)),3,WELL_WIDTH,WELL_HEIGHT);

draw_well(w);

srand(time(NULL));     // Seed the random number generator with the time. Used in create tet.

display_score(score, w->upper_left_x-15,w->upper_left_y);

state = ADD_PIECE;

break;

case ADD_PIECE:          // Add a new piece to the game

if (next) {

current = next;

next = create_tetromino ((w->upper_left_x+(w->width/2)), w->upper_left_y);

}

else {

current = create_tetromino ((w->upper_left_x+(w->width/2)), w->upper_left_y);

next = create_tetromino ((w->upper_left_x+(w->width/2)), w->upper_left_y);

}

display_tetromino(current);

state = MOVE_PIECE;

break;

case MOVE_PIECE:         // Move the current piece

if ((arrow = read_escape(&c)) != NOCHAR) {

if (arrow == UP) {

undisplay_tetromino(current);

rotate_ccw(current);

display_tetromino(current);

}

else if (arrow == DOWN) {

undisplay_tetromino(current);

rotate_cw(current);

display_tetromino(current);

}

else if (arrow == LEFT) {

undisplay_tetromino(current);

move_tet(current,current->upper_left_x-1,current->upper_left_y);

display_tetromino(current);

}

else if (arrow == RIGHT) {

undisplay_tetromino(current);

move_tet(current,current->upper_left_x+1,current->upper_left_y);

display_tetromino(current);

}

else if (arrow == REGCHAR) {

if (c == ‘ ‘) {

move_timeout = DROP_RATE;

}

if (c == ‘q’) {

state = GAME_OVER;

}

}

}

if (move_counter++ >= move_timeout) {

counter++;

undisplay_tetromino(current);

status = move_tet(current,current->upper_left_x,current->upper_left_y+1);

display_tetromino(current);

if (status == MOVE_FAILED) {

state = ADD_PIECE;

move_timeout = BASE_FALL_RATE;

}

move_counter = 0;

}

break;

case GAME_OVER:

nodelay(stdscr,FALSE);

clear();

getmaxyx(stdscr,y,x);

mvprintw(1,x/2-5,”  GAME_OVER  “);

mvprintw(2,x/2-5,”#############”);

mvprintw(16,x/2-5,”Hit q to exit”);

getch(); // Wait for a key to be pressed.

state = EXIT;

break;

case EXIT:

return(highscores);  // Return the highscore structure back to main to be stored on disk.

break;

}

refresh();

nanosleep(&tim,&tim_ret);

}

}

 highscore.c

#include <stdio.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

#include “highscore.h”

highscore_t *parse_line(char *line) {

highscore_t *sptr;

char *p;

char *p_end;

const char delim = ‘,’;

enum {NAME, SCORE, EXIT};

int state = NAME;

// Make sure that the line ptr is not NULL

if (!line) {

return (NULL);

}

// Make Space for a new structure

sptr = malloc(sizeof(highscore_t));

// Assumnig that the new space is available, break the line down into parts and load the struct.

if (sptr) {

p = strtok(line,&delim); // initialize strtok to break line into tokens delimited by ‘,’

while (p) {

switch (state) {

case NAME:

if (strlen(p)<NAME_SIZE) {

strcpy(sptr->initials,p);

state = SCORE;

}

else {

return (NULL);

}

break;

case SCORE:

errno = 0;

sptr->score = strtol(p,&p_end,10);

if (errno) {

return (NULL);

}

return (sptr);

break;

default:

return(NULL);

}

p = strtok(NULL,&delim); // Read the next token

}

}

return (NULL);

}

highscore_t *load_scores(char *filename) {

char linebuf[MAX_LINE];

FILE *fp;

char *lp;

int status;

highscore_t *current = NULL;

highscore_t *next = NULL;

highscore_t *head = NULL;

// If file exists, open it for reading and writing.

errno = 0;

fp = fopen(filename,”r”);

if (errno) {

fp = fopen(filename,”w”);

sprintf(linebuf,”END,0″);

head = parse_line(linebuf);

close(fp);

return (head);

}

// Read the file, line-by-line

while (lp = fgets(linebuf,MAX_LINE-1,fp)) {

next = parse_line(linebuf);

if (next) {

if (current) {

current->next = next;

next->next = NULL;

}

else {

head = next;

next->next = NULL;

}

current = next;

}

}

close(fp);

return (head);

}

highscore_t *insert_score(highscore_t *list, char * initials, int score) {

highscore_t *current = NULL;

highscore_t *next = NULL;

highscore_t *new_item = NULL;

highscore_t *last = NULL;

new_item = malloc(sizeof(highscore_t));

if (new_item) {

strncpy(new_item->initials,initials,NAME_SIZE-1);

new_item->score = score;

}

if (!list) {

// the list does not exist, need to create it and add this as the first element

// Return the list.

new_item->next = NULL;

return (new_item);

}

else {

current = list;

while (current) {

if (score > current->score) {

if (last) {

last->next = new_item;

new_item->next=current;

return (list);

}

else {

// New first item in the list

new_item->next = current;

return (new_item);

}

}

last = current;

current = current->next;

}

// Lowest score in the list, add it to the end.

last->next = new_item;

new_item->next = NULL;

}

return(list);

}

int store_scores(char *filename, highscore_t *list) {

int counter = 0;

FILE *fp;

highscore_t *current = NULL;

// Open the high score file for writing.

fp = fopen(filename,”w”);

current = list;

while (current) {

fprintf(fp,”%s,%d\n”,current->initials,current->score);

current = current->next;

}

close(fp);

return (0);

}

int free_score_list (highscore_t *list) {

highscore_t *current = list;

highscore_t *next = NULL;

 

while (current) {

next = current->next;

free(current);

current = next;

}

return(0);

}

int print_score_list (highscore_t *list, int x_start, int y_start, int numscores) {

/* Prints the scores to the screen. If -1 or 0 is passed for numscores,

then the function prints all of the scores. Otherwise, it prints only numscores

intials/scores to the screen.

*/

int i = 0;

while ((list) && (i<numscores)){

if (strcmp(list->initials,”END”)) {

mvprintw(y_start+i,x_start,”%s %d”,list->initials, list->score);

if (numscores > 0) {

i++;

}

}

list = list->next;

}

return(0);

}

int compare_highscore(highscore_t *list, int score, int numscores) {

int i = 0;

// Compares the passed score with the scores in the list. If

// -1 or 0 is passed for numscores, then the function compares against all scores in the list.

while ((list) && (i<numscores)) {

if (score >= list->score) {

return (1);

}

list = list->next;

if (numscores > 0) {

i++;

}

}

return(0);

}

 key.c

#include <ncurses.h>

#include “key.h”

int read_escape(int *read_char) {

int c;

if ((c = getch()) == ERR) {

return (NOCHAR);

}

else if (c==0x1b) {

if ((c = getch()) == ‘[‘) {

c=getch();

switch (c) {

case ‘A’:

return (UP);

break;

case ‘B’:

return (DOWN);

break;

case ‘C’:

return (RIGHT);

break;

case ‘D’:

return (LEFT);

break;

default:

return (BADESC);

}

}

}

else {

*read_char = c;

return (REGCHAR);

}

}

 main.c

#include <stdio.h>

#include <ncurses.h>

#include “tetromino.h”

#include “highscore.h”

#include “game.h”

int main(int argc, char *argv[]) {

int status = 1;

highscore_t * highscores;

/* if (argc!=2) { */

/*   printf(“Please specify a high score file\n”); */

/*   return (-1); */

/* } */

highscores = game(highscores);

endwin();

return (0);

}

 score.c

int compute_score(int previous_score, int lines_cleared) {

enum {NORMAL, TETRIS};

int new_score;

static int state = NORMAL;

switch (lines_cleared) {

case 0:

new_score = 0;

break;

case 1:

new_score = 100;

break;

case 2:

new_score = 250;

break;

case 3:

new_score = 500;

break;

case 4:

new_score = 800;

state = TETRIS;

break;

}

switch (state) {

case NORMAL:

return (previous_score + new_score);

break;

case TETRIS:

if (lines_cleared == 4) {

return (previous_score + 1200);

}

else {

state = NORMAL;

return (previous_score + new_score);

}

break;

default:

state = NORMAL;

return (previous_score + new_score);

break;

}

}

void display_score(int score, int x, int y) {

mvprintw(y,x,”*** SCORE ***”,score);

mvprintw(y+1,x,”%8d”,score);

}

 tetromino.c

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <time.h>

#include <ncurses.h>

#include “tetromino.h”

const tetromino_t tetromino_types[7] = {

{“block”,

{{0,0,0,0},

{0,1,1,0},

{0,1,1,0},

{0,0,0,0}},

0,

0,

‘%’,

{0,0,0}},

{“tee”,

{{0,0,0,0},

{1,1,1,0},

{0,1,0,0},

{0,0,0,0}},

0,

0,

‘%’,

{0,0,0}},

{“zigzag_l”,

{{0,0,1,0},

{0,1,1,0},

{0,1,0,0},

{0,0,0,0}},

0,

0,

‘%’,

{0,0,0}},

{“zigzag_r”,

{{0,1,0,0},

{0,1,1,0},

{0,0,1,0},

{0,0,0,0}},

0,

0,

‘%’,

{0,0,0}},

{“lform_l”,

{{0,0,1,0},

{0,0,1,0},

{0,1,1,0},

{0,0,0,0}},

0,

0,

‘%’,

{0,0,0}},

{“lform_r”,

{{0,1,0,0},

{0,1,0,0},

{0,1,1,0},

{0,0,0,0}},

0,

0,

‘%’,

{0,0,0}},

{“pipe”,

{{0,1,0,0},

{0,1,0,0},

{0,1,0,0},

{0,1,0,0}},

0,

0,

‘%’,

{0,0,0}}

};

int check_collision (tetromino_t *tet) {

int x,y;

chtype row_buf[5];

int num_chars;

int i;

for (y=0;y<+4;y++) {

num_chars = mvinchnstr(tet->upper_left_y+y, tet->upper_left_x, row_buf, 4);

for (x=0;x<4;x++) {

if (tet->piece[x][y] && row_buf[x]!=’ ‘) {

return COLLIDE;

}

}

}

return SAFE;

}

int move_tet (tetromino_t *tet, int new_x, int new_y) {

int old_x = tet->upper_left_x;

int old_y = tet->upper_left_y;

tet->upper_left_x = new_x;

tet->upper_left_y = new_y;

if (check_collision(tet) == COLLIDE) {

tet->upper_left_x = old_x;

tet->upper_left_y = old_y;

return MOVE_FAILED;

}

else {

return MOVE_OK;

}

}

int rotate_cw(tetromino_t *tet) {

char temp[4][4];

int x,y;

tetromino_t temp_tet;

memcpy(&temp_tet,tet,sizeof(tetromino_t));

for (x=0;x<4;x++) {

for (y=0;y<4;y++) {

temp[x][y] = tet->piece[y][3-x];

}

}

memcpy(tet->piece,&temp,sizeof(tet->piece));

if (check_collision(tet) == COLLIDE) {

memcpy(tet,&temp_tet,sizeof(tetromino_t));

return MOVE_FAILED;

}

else {

return MOVE_OK;

}

}

int rotate_ccw(tetromino_t *tet) {

char temp[4][4];

int x,y;

tetromino_t temp_tet;

memcpy(&temp_tet,tet,sizeof(tetromino_t));

for (x=0;x<4;x++) {

for (y=0;y<4;y++) {

temp[x][y] = tet->piece[3-y][x];

}

}

memcpy(tet->piece,&temp,sizeof(tet->piece));

if (check_collision(tet) == COLLIDE) {

memcpy(tet,&temp_tet,sizeof(tetromino_t));

return MOVE_FAILED;

}

else {

return MOVE_OK;

}

}

tetromino_t *create_tetromino (int initial_x, int initial_y) {

int type;

tetromino_t *tet = malloc(sizeof(tetromino_t));

type = rand()%7;

memcpy(tet, &tetromino_types[type], sizeof(tetromino_t));

tet->upper_left_x = initial_x;

tet->upper_left_y = initial_y;

srand(time(NULL));

int randColor = rand()%7 +1;

tet->color[0] = randColor;

return(tet);

}

display_tetromino(tetromino_t *tet) {

int x,y;

initscr();

start_color();

init_pair(tet->color[0],tet->color[0],tet->color[0]);

attron(COLOR_PAIR(tet->color[0]));

for (x=0;x<4;x++) {

for (y=0;y<+4;y++) {

if (tet->piece[x][y]) {

mvprintw(tet->upper_left_y+y,tet->upper_left_x+x,”%c”,tet->draw_char);

}

}

}

attroff(COLOR_PAIR(tet->color[0]));

endwin();

refresh();

}

undisplay_tetromino(tetromino_t *tet) {

int x,y;

for (x=0;x<4;x++) {

for (y=0;y<+4;y++) {

if (tet->piece[x][y]) {

mvprintw(tet->upper_left_y+y,tet->upper_left_x+x,” “,tet->draw_char);

}

}

}

}

int destroy_tetromino(tetromino_t *tet) {

free(tet);

}

 well.c

 #include <stdlib.h>

#include <ncurses.h>

#include “well.h”

well_t *init_well(int upper_left_x, int upper_left_y, int width, int height) {

well_t *w;

w = malloc(sizeof(well_t));

w->upper_left_x = upper_left_x;

w->upper_left_y = upper_left_y;

w->width = width;

w->height = height;

w->draw_char = ‘#’;

w->color[0] = 0;

w->color[1] = 0;

w->color[2] = 0;

return (w);

}

void draw_well(well_t *w) {

int row_counter, column_counter;

// Draw left side of well

for (column_counter=w->upper_left_y;column_counter<=(w->upper_left_y + w->height);column_counter++) {

mvprintw(column_counter,w->upper_left_x,”%c”,w->draw_char);

}

// Draw right side of well

for (column_counter=w->upper_left_y;column_counter<=(w->upper_left_y + w->height);column_counter++) {

mvprintw(column_counter,(w->upper_left_x + w->width),”%c”,w->draw_char);

}

// Draw Bottom of well

for (row_counter=w->upper_left_x;row_counter<=(w->upper_left_x + w->width);row_counter++) {

mvprintw(w->upper_left_y + w->height,row_counter,”%c”,w->draw_char);

}

}

int prune_well(well_t * well) {

// returns the number of lines cleared. Assumes that a new tet has not been started yet.

int row; // indicator of the row in the well. The row furthest from the top is row 0.

chtype *well_original;  // pointer to an array of characters representing the state of the well.

chtype *well_modified;

chtype *col_ptr;

int current_column_position, current_row_position, output_row;

int num_chars;

int i, keep_row;

int cleared_rows = 0;

// Setup memory to receive the status of the well

current_column_position =  well->upper_left_x + 1; // left most well x, not including the wall

current_row_position = well->upper_left_y + well->height – 1; // bottom of the well.

well_original = malloc(sizeof(chtype) * well->width * well->height);

well_modified = well_original;

// Read in the well

while (current_row_position > well->upper_left_y) {

num_chars = mvinchnstr(current_row_position, current_column_position, well_modified, well->width-1);

well_modified += well->width-1;

current_row_position–;

}

well_modified = well_original; // Reset to the beginning of the memory.

current_row_position = well->upper_left_y + well->height – 1; // bottom of the well.

output_row = current_row_position;

while (current_row_position > well->upper_left_y) {

col_ptr = well_modified; // Capture a pointer to the beginning of the row

keep_row = 0;

// Test the row to see if it is complete

for (i = 0; i < well->width-1; i++) {

if (*well_modified == ‘ ‘) {

keep_row = 1;

}

well_modified++;

}

// If it is not complete, write it back. Otherwise, skip this row.

if (keep_row) {

well_modified = col_ptr; // restore pointer back to the beginning of the row.

for (i = 0; i < well->width-1; i++) {

mvprintw(output_row,current_column_position+i,”%c”,*well_modified);

//mvprintw(output_row,i+2,”%c”,*well_modified);

well_modified++;

}

output_row–;

}

else {

cleared_rows++;

}

current_row_position–;

}

free(well_original);

return(cleared_rows);

} 

Solution 

game.c

#include <unistd.h>

#include <ncurses.h>

#include <time.h>

#include “highscore.h”

#include “game.h”

#include “well.h”

#include “tetris.h”

#include “tetromino.h”

#include “key.h”

void init_game(void) {

int x, y;

}

void display_timer(time_t timer, int x, int y) {

// Calculate difference time from start with now.

time_t now = time(NULL);

double second = difftime(now, timer);

mvprintw(y, x, “***TIME(s)***”, second);

mvprintw(y + 1, x, “%8.f (s)”, (long) second);

mvprintw(y + 2, x, “***CONF(s)***”);

mvprintw(y + 3, x, “%8ld (s)”, (long) MAX_TIMER);

}

highscore_t *game(highscore_t *highscores) {

static int state = INIT;

tetromino_t *next = NULL;

tetromino_t *current = NULL;

well_t *w;

int x, y;

int c;

int arrow;

struct timespec tim = { 0, 1000000 };

struct timespec tim_ret;

int move_counter = 0;

int move_timeout = 500;

int status;

int counter = 0;

int lines_cleared = 0;

int score = 0;

char str[80];

time_t starttime = 0;

while (1) {

switch (state) {

case INIT:               // Initialize the game, only run one time

initscr();

nodelay(stdscr, TRUE);  // Do not wait for characters using getch.

noecho();              // Do not echo input characters

getmaxyx(stdscr, y, x);  // Get the screen dimensions

w = init_well(((x / 2) – (WELL_WIDTH / 2)), 3, WELL_WIDTH,

WELL_HEIGHT);

draw_well(w);

time(&starttime);   // Save time start

srand(time(NULL)); // Seed the random number generator with the time. Used in create tet.

display_score(score, w->upper_left_x – 15, w->upper_left_y);

display_timer(starttime, w->upper_left_x – 15, w->upper_left_y + 2);

state = ADD_PIECE;

break;

case ADD_PIECE:          // Add a new piece to the game

if (next) {

current = next;

next = create_tetromino((w->upper_left_x + (w->width / 2)),

w->upper_left_y);

} else {

current = create_tetromino((w->upper_left_x + (w->width / 2)),

w->upper_left_y);

next = create_tetromino((w->upper_left_x + (w->width / 2)),

w->upper_left_y);

}

if (check_collision(current) == COLLIDE) {

state = GAME_OVER;

} else {

display_tetromino(current);

display_next(next);

state = MOVE_PIECE;

}

break;

case MOVE_PIECE:         // Move the current piece

if ((arrow = read_escape(&c)) != NOCHAR) {

if (arrow == UP) {

undisplay_tetromino(current);

rotate_ccw(current);

display_tetromino(current);

} else if (arrow == DOWN) {

undisplay_tetromino(current);

rotate_cw(current);

display_tetromino(current);

} else if (arrow == LEFT) {

undisplay_tetromino(current);

move_tet(current, current->upper_left_x – 1,

current->upper_left_y);

display_tetromino(current);

} else if (arrow == RIGHT) {

undisplay_tetromino(current);

move_tet(current, current->upper_left_x + 1,

current->upper_left_y);

display_tetromino(current);

} else if (arrow == REGCHAR) {

if (c == ‘ ‘) {

move_timeout = DROP_RATE;

}

if (c == ‘q’) {

state = GAME_OVER;

}

}

}

if (move_counter++ >= move_timeout) {

counter++;

undisplay_tetromino(current);

status = move_tet(current, current->upper_left_x,

current->upper_left_y + 1);

display_tetromino(current);

if (status == MOVE_FAILED) {

lines_cleared = prune_well(w);

score = compute_score(score, lines_cleared);

display_score(score, w->upper_left_x – 15, w->upper_left_y);

display_timer(starttime, w->upper_left_x – 15, w->upper_left_y + 2);

state = ADD_PIECE;

move_timeout = BASE_FALL_RATE;

}

move_counter = 0;

}

break;

case GAME_OVER:

nodelay(stdscr, FALSE);

clear();

getmaxyx(stdscr, y, x);

mvprintw(1, x / 2 – 5, ”  GAME_OVER  “);

mvprintw(2, x / 2 – 5, “#############”);

mvprintw(9, x / 2 – 5, “Score: %d”, score);

mvprintw(11, x / 2 – 5, “Time: %8.f’s”,

difftime(time(NULL), starttime));

mvprintw(13, x / 2 – 5, “Time configure: %d’s”, MAX_TIMER);

mvprintw(16, x / 2 – 5, “Hit q to exit”);

getch(); // Wait for a key to be pressed.

state = EXIT;

break;

case EXIT:

return (highscores); // Return the highscore structure back to main to be stored on disk.

break;

}

refresh();

nanosleep(&tim, &tim_ret);

display_timer(starttime, w->upper_left_x – 15, w->upper_left_y + 2);

if(MAX_TIMER <= difftime(time(NULL), starttime) && state != EXIT)

{

state = GAME_OVER;

}

}

}

 game.h 

/*

* game.h

*

*  Created on: Oct 7, 2017

*/

#ifndef GAME_H_

#define GAME_H_

typedef struct terminal_dimensions {

int maxx;

int maxy;

} terminal_dimensions_t;

// Delay timers for the main game loop.

#ifndef DELAY_US

#define DELAY_US 100000

#endif

// Game States

enum {INIT, ADD_PIECE, MOVE_PIECE, ADJUST_WELL, GAME_OVER, EXIT};

void init_game(void);

highscore_t *game(highscore_t *);

#endif /* GAME_H_ */

highscore.c 

#include <stdio.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

#include “highscore.h”

highscore_t *parse_line(char *line) {

highscore_t *sptr;

char *p;

char *p_end;

const char delim = ‘,’;

enum {NAME, SCORE, EXIT};

int state = NAME;

// Make sure that the line ptr is not NULL

if (!line) {

return (NULL);

}

// Make Space for a new structure

sptr = malloc(sizeof(highscore_t));

// Assumnig that the new space is available, break the line down into parts and load the struct.

if (sptr) {

p = strtok(line,&delim); // initialize strtok to break line into tokens delimited by ‘,’

while (p) {

switch (state) {

case NAME:

if (strlen(p)<NAME_SIZE) {

strcpy(sptr->initials,p);

state = SCORE;

}

else {

return (NULL);

}

break;

case SCORE:

errno = 0;

sptr->score = strtol(p,&p_end,10);

if (errno) {

return (NULL);

}

return (sptr);

break;

default:

return(NULL);

}

p = strtok(NULL,&delim); // Read the next token

}

}

return (NULL);

}

highscore_t *load_scores(char *filename) {

char linebuf[MAX_LINE];

FILE *fp;

char *lp;

int status;

highscore_t *current = NULL;

highscore_t *next = NULL;

highscore_t *head = NULL;

// If file exists, open it for reading and writing.

errno = 0;

fp = fopen(filename,”r”);

if (errno) {

fp = fopen(filename,”w”);

sprintf(linebuf,”END,0″);

head = parse_line(linebuf);

close(fp);

return (head);

}

// Read the file, line-by-line

while (lp = fgets(linebuf,MAX_LINE-1,fp)) {

next = parse_line(linebuf);

if (next) {

if (current) {

current->next = next;

next->next = NULL;

}

else {

head = next;

next->next = NULL;

}

current = next;

}

}

close(fp);

return (head);

}

highscore_t *insert_score(highscore_t *list, char * initials, int score) {

highscore_t *current = NULL;

highscore_t *next = NULL;

highscore_t *new_item = NULL;

highscore_t *last = NULL;

new_item = malloc(sizeof(highscore_t));

if (new_item) {

strncpy(new_item->initials,initials,NAME_SIZE-1);

new_item->score = score;

}

if (!list) {

// the list does not exist, need to create it and add this as the first element

// Return the list.

new_item->next = NULL;

return (new_item);

}

else {

current = list;

while (current) {

if (score > current->score) {

if (last) {

last->next = new_item;

new_item->next=current;

return (list);

}

else {

// New first item in the list

new_item->next = current;

return (new_item);

}

}

last = current;

current = current->next;

}

// Lowest score in the list, add it to the end.

last->next = new_item;

new_item->next = NULL;

}

return(list);

}

int store_scores(char *filename, highscore_t *list) {

int counter = 0;

FILE *fp;

highscore_t *current = NULL;

// Open the high score file for writing.

fp = fopen(filename,”w”);

current = list;

while (current) {

fprintf(fp,”%s,%d\n”,current->initials,current->score);

current = current->next;

}

close(fp);

return (0);

}

int free_score_list (highscore_t *list) {

highscore_t *current = list;

highscore_t *next = NULL;

while (current) {

next = current->next;

free(current);

current = next;

}

return(0);

}

int print_score_list (highscore_t *list, int x_start, int y_start, int numscores) {

/* Prints the scores to the screen. If -1 or 0 is passed for numscores,

then the function prints all of the scores. Otherwise, it prints only numscores

intials/scores to the screen.

*/

int i = 0;

while ((list) && (i<numscores)){

if (strcmp(list->initials,”END”)) {

mvprintw(y_start+i,x_start,”%s %d”,list->initials, list->score);

if (numscores > 0) {

i++;

}

}

list = list->next;

}

return(0);

}

int compare_highscore(highscore_t *list, int score, int numscores) {

int i = 0;

// Compares the passed score with the scores in the list. If

// -1 or 0 is passed for numscores, then the function compares against all scores in the list.

while ((list) && (i<numscores)) {

if (score >= list->score) {

return (1);

}

list = list->next;

if (numscores > 0) {

i++;

}

}

return(0);

}

 highscore.h

/*

* highscore.h

*

*  Created on: Oct 7, 2017

*/

#ifndef HIGHSCORE_H_

#define HIGHSCORE_H_

#define MAX_LINE 100

#define NAME_SIZE 4

#define NUM_SCORES_STORED 10

typedef struct highscore {

char initials[NAME_SIZE+1];

int score;

struct highscore *next;

} highscore_t;

highscore_t *parse_line(char *);

int compare_highscore(highscore_t *, int, int);

highscore_t *load_scores(char *);

highscore_t *insert_score(highscore_t *, char *, int);

int store_scores(char *, highscore_t *);

int compare_highscore(highscore_t *, int, int);

int print_score_list (highscore_t *, int, int, int);

#endif /* HIGHSCORE_H_ */

key.c

#include <ncurses.h>

#include “key.h”

int read_escape(int *read_char) {

int c;

if ((c = getch()) == ERR) {

return (NOCHAR);

}

else if (c==0x1b) {

if ((c = getch()) == ‘[‘) {

c=getch();

switch (c) {

case ‘A’:

return (UP);

break;

case ‘B’:

return (DOWN);

break;

case ‘C’:

return (RIGHT);

break;

case ‘D’:

return (LEFT);

break;

default:

return (BADESC);

}

}

}

else {

*read_char = c;

return (REGCHAR);

}

}

 key.h

/*

* key.h

*

*  Created on: Oct 7, 2017

*/

#ifndef KEY_H_

#define KEY_H_

enum {NOCHAR, REGCHAR, UP, DOWN, LEFT, RIGHT, BADESC};

int read_escape(int *);

#endif /* KEY_H_ */

 main.c

 #include <stdio.h>

#include <ncurses.h>

#include “tetromino.h”

#include “highscore.h”

#include “game.h”

int main(int argc, char *argv[]) {

int status = 1;

highscore_t * highscores;

/* if (argc!=2) { */

/*   printf(“Please specify a high score file\n”); */

/*   return (-1); */

/* } */

highscores = game(highscores);

endwin();

return (0);

}

 score.c

int compute_score(int previous_score, int lines_cleared) {

enum {NORMAL, TETRIS};

int new_score;

static int state = NORMAL;

switch (lines_cleared) {

case 0:

new_score = 0;

break;

case 1:

new_score = 100;

break;

case 2:

new_score = 250;

break;

case 3:

new_score = 500;

break;

case 4:

new_score = 800;

state = TETRIS;

break;

}

switch (state) {

case NORMAL:

return (previous_score + new_score);

break;

case TETRIS:

if (lines_cleared == 4) {

return (previous_score + 1200);

}

else {

state = NORMAL;

return (previous_score + new_score);

}

break;

default:

state = NORMAL;

return (previous_score + new_score);

break;

}

}

void display_score(int score, int x, int y) {

mvprintw(y,x,”*** SCORE ***”,score);

mvprintw(y+1,x,”%8d”,score);

}

 score.h

/*

* score.h

*

*  Created on: Oct 7, 2017

*/

#ifndef SCORE_H_

#define SCORE_H_

int compute_score(int previous_score, int lines_cleared);

void display_score(int score, int x, int y);

#endif /* SCORE_H_ */

tetris.h

 /*

* tetris.h

*

*  Created on: Oct 7, 2017

*      Author: thanh

*/

#ifndef TETRIS_H_

#define TETRIS_H_

#define WELL_WIDTH 8

#define WELL_HEIGHT 16

#define BASE_FALL_RATE 500

#define DROP_RATE 10

// 5 minute = 5 * 60 seconds

#define MAX_TIMER 300

 

#endif /* TETRIS_H_ */

tetromino.c

#include <string.h>

#include <stdlib.h>

#include <stdio.h>

#include <time.h>

#include <ncurses.h>

#include “tetromino.h”

const tetromino_t tetromino_types[7] = { { “block”, { { 0, 0, 0, 0 }, { 0, 1, 1,

0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, 0, 0, ‘%’, { 0, 0, 0 } }, {

“tee”,

{ { 0, 0, 0, 0 }, { 1, 1, 1, 0 }, { 0, 1, 0, 0 }, { 0, 0, 0, 0 } }, 0,

0, ‘%’, { 0, 0, 0 } }, { “zigzag_l”, { { 0, 0, 1, 0 }, { 0, 1, 1, 0 }, {

0, 1, 0, 0 }, { 0, 0, 0, 0 } }, 0, 0, ‘%’, { 0, 0, 0 } }, { “zigzag_r”,

{ { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 1, 0 }, { 0, 0, 0, 0 } }, 0,

0, ‘%’, { 0, 0, 0 } }, { “lform_l”, { { 0, 0, 1, 0 }, { 0, 0, 1, 0 }, {

0, 1, 1, 0 }, { 0, 0, 0, 0 } }, 0, 0, ‘%’, { 0, 0, 0 } }, { “lform_r”, {

{ 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1, 1, 0 }, { 0, 0, 0, 0 } }, 0, 0,

‘%’, { 0, 0, 0 } }, { “pipe”, { { 0, 1, 0, 0 }, { 0, 1, 0, 0 }, { 0, 1,

0, 0 }, { 0, 1, 0, 0 } }, 0, 0, ‘%’, { 0, 0, 0 } } };

int check_collision(tetromino_t *tet) {

int x, y;

chtype row_buf[5];

int num_chars;

int i;

for (y = 0; y < +4; y++) {

num_chars = mvinchnstr(tet->upper_left_y + y, tet->upper_left_x,

row_buf, 4);

for (x = 0; x < 4; x++) {

if (tet->piece[x][y] && row_buf[x] != ‘ ‘) {

return COLLIDE;

}

}

}

return SAFE;

}

int move_tet(tetromino_t *tet, int new_x, int new_y) {

int old_x = tet->upper_left_x;

int old_y = tet->upper_left_y;

tet->upper_left_x = new_x;

tet->upper_left_y = new_y;

if (check_collision(tet) == COLLIDE) {

tet->upper_left_x = old_x;

tet->upper_left_y = old_y;

return MOVE_FAILED;

} else {

return MOVE_OK;

}

}

int rotate_cw(tetromino_t *tet) {

char temp[4][4];

int x, y;

tetromino_t temp_tet;

memcpy(&temp_tet, tet, sizeof(tetromino_t));

for (x = 0; x < 4; x++) {

for (y = 0; y < 4; y++) {

temp[x][y] = tet->piece[y][3 – x];

}

}

memcpy(tet->piece, &temp, sizeof(tet->piece));

if (check_collision(tet) == COLLIDE) {

memcpy(tet, &temp_tet, sizeof(tetromino_t));

return MOVE_FAILED;

} else {

return MOVE_OK;

}

}

int rotate_ccw(tetromino_t *tet) {

char temp[4][4];

int x, y;

tetromino_t temp_tet;

memcpy(&temp_tet, tet, sizeof(tetromino_t));

for (x = 0; x < 4; x++) {

for (y = 0; y < 4; y++) {

temp[x][y] = tet->piece[3 – y][x];

}

}

memcpy(tet->piece, &temp, sizeof(tet->piece));

if (check_collision(tet) == COLLIDE) {

memcpy(tet, &temp_tet, sizeof(tetromino_t));

return MOVE_FAILED;

} else {

return MOVE_OK;

}

}

tetromino_t *create_tetromino(int initial_x, int initial_y) {

int type;

tetromino_t *tet = malloc(sizeof(tetromino_t));

type = rand() % 7;

memcpy(tet, &tetromino_types[type], sizeof(tetromino_t));

tet->upper_left_x = initial_x;

tet->upper_left_y = initial_y;

srand(time(NULL));

int randColor = rand() % 7 + 1;

tet->color[0] = randColor;

return (tet);

}

display_tetromino(tetromino_t *tet) {

int x, y;

initscr();

start_color();

init_pair(tet->color[0], tet->color[0], tet->color[0]);

attron(COLOR_PAIR(tet->color[0]));

for (x = 0; x < 4; x++) {

for (y = 0; y < +4; y++) {

if (tet->piece[x][y]) {

mvprintw(tet->upper_left_y + y, tet->upper_left_x + x, “%c”,

tet->draw_char);

}

}

}

attroff(COLOR_PAIR(tet->color[0]));

endwin();

refresh();

}

void display_next(tetromino_t *tet) {

int x, y;

for (x = 0; x < 4; x++) {

for (y = 0; y < +4; y++) {

if (tet->piece[x][y]) {

mvprintw(tet->upper_left_y + 10, tet->upper_left_x + 4, “%c”,

tet->draw_char);

}

}

}

}

undisplay_tetromino(tetromino_t *tet) {

int x, y;

for (x = 0; x < 4; x++) {

for (y = 0; y < +4; y++) {

if (tet->piece[x][y]) {

mvprintw(tet->upper_left_y + y, tet->upper_left_x + x, ” “,

tet->draw_char);

}

}

}

}

int destroy_tetromino(tetromino_t *tet) {

free(tet);

} 

tetromino.h 

/*

* tetromino.h

*

*  Created on: Oct 7, 2017

*/

#ifndef TETROMINO_H_

#define TETROMINO_H_

typedef struct tetromino {

char type_str[20];

char piece[4][4];             // 4×4 grid of characters, piece[x][y]

int upper_left_x;

int upper_left_y;

char draw_char;

char color[3];

} tetromino_t;

extern const tetromino_t tetromino_types[7];

enum {SAFE, COLLIDE}; // Return status for check_collision

int check_collision (tetromino_t *);

enum {MOVE_OK, MOVE_FAILED}; // Return status for move

int move_tet (tetromino_t *, int, int);

int rotate_cw(tetromino_t *);

int rotate_ccw(tetromino_t *);

tetromino_t *create_tetromino (int, int);

int destroy_tetromino(tetromino_t *);

void print_tetromino(tetromino_t *tet);

void test_tetromino(void);

void display_next(tetromino_t *tet);

/* tetromino.h ends here */

#endif /* TETROMINO_H_ */

well.c

 #include <stdlib.h>

#include <ncurses.h>

#include “well.h”

well_t *init_well(int upper_left_x, int upper_left_y, int width, int height) {

well_t *w;

w = malloc(sizeof(well_t));

w->upper_left_x = upper_left_x;

w->upper_left_y = upper_left_y;

w->width = width;

w->height = height;

w->draw_char = ‘#’;

w->color[0] = 0;

w->color[1] = 0;

w->color[2] = 0;

return (w);

}

void draw_well(well_t *w) {

int row_counter, column_counter;

// Draw left side of well

for (column_counter=w->upper_left_y;column_counter<=(w->upper_left_y + w->height);column_counter++) {

mvprintw(column_counter,w->upper_left_x,”%c”,w->draw_char);

}

// Draw right side of well

for (column_counter=w->upper_left_y;column_counter<=(w->upper_left_y + w->height);column_counter++) {

mvprintw(column_counter,(w->upper_left_x + w->width),”%c”,w->draw_char);

}

// Draw Bottom of well

for (row_counter=w->upper_left_x;row_counter<=(w->upper_left_x + w->width);row_counter++) {

mvprintw(w->upper_left_y + w->height,row_counter,”%c”,w->draw_char);

}

}

int prune_wll(well_t * well) {

// returns the number of lines cleared. Assumes that a new tet has not been started yet.

int row; // indicator of the row in the well. The row furthest from the top is row 0.

chtype *well_original;  // pointer to an array of characters representing the state of the well.

chtype *well_modified;

chtype *col_ptr;

int current_column_position, current_row_position, output_row;

int num_chars;

int i, keep_row;

int cleared_rows = 0;

// Setup memory to receive the status of the well

current_column_position =  well->upper_left_x + 1; // left most well x, not including the wall

current_row_position = well->upper_left_y + well->height – 1; // bottom of the well.

well_original = malloc(sizeof(chtype) * well->width * well->height);

well_modified = well_original;

// Read in the well

while (current_row_position > well->upper_left_y) {

num_chars = mvinchnstr(current_row_position, current_column_position, well_modified, well->width-1);

well_modified += well->width-1;

current_row_position–;

}

well_modified = well_original; // Reset to the beginning of the memory.

current_row_position = well->upper_left_y + well->height – 1; // bottom of the well.

output_row = current_row_position;

while (current_row_position > well->upper_left_y) {

col_ptr = well_modified; // Capture a pointer to the beginning of the row

keep_row = 0;

// Test the row to see if it is complete

for (i = 0; i < well->width-1; i++) {

if (*well_modified == ‘ ‘) {

keep_row = 1;

}

well_modified++;

}

// If it is not complete, write it back. Otherwise, skip this row.

if (keep_row) {

well_modified = col_ptr; // restore pointer back to the beginning of the row.

for (i = 0; i < well->width-1; i++) {

mvprintw(output_row,current_column_position+i,”%c”,*well_modified);

//mvprintw(output_row,i+2,”%c”,*well_modified);

well_modified++;

}

output_row–;

}

else {

cleared_rows++;

}

current_row_position–;

}

free(well_original);

return(cleared_rows);

} 

well.h 

/*

* well.h

*

*  Created on: Oct 7, 2017

*      Author: thanh

*/

#ifndef WELL_H_

#define WELL_H_

typedef struct well {

int upper_left_x;

int upper_left_y;

int width;

int height;

char draw_char;

char color[3];

} well_t;

well_t *init_well(int, int, int, int);

void draw_well(well_t *);

int prune_well(well_t * well);

#endif /* WELL_H_ */