/* * Copyright (c) Abraham vd Merwe * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of other contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include "typedefs.h" #include "utils.h" #include "io.h" #include "config.h" #include "engine.h" /* * Macros */ /* Upper left corner of board */ #define XTOP ((out_width () - NUMROWS - 3) >> 1) #define YTOP ((out_height () - NUMCOLS - 9) >> 1) /* Maximum digits in a number (i.e. number of digits in score, */ /* number of blocks, etc. should not exceed this value */ #define MAXDIGITS 11 /* Number of levels in the game */ #define MINLEVEL 1 #define MAXLEVEL 9 /* This calculates the time allowed to move a shape, before it is moved a row down */ #define DELAY (1000000 / (level + 2)) /* The score is multiplied by this to avoid losing precision */ #define SCOREFACTOR 2 /* This calculates the stored score value */ #define SCOREVAL(x) (SCOREFACTOR * (x)) /* This calculates the real (displayed) value of the score */ #define GETSCORE(score) ((score) / SCOREFACTOR) /* Remove extra input after fgets if needed */ #define ENDFGETS(buffer) \ if (!strchr(buffer, '\n')) { \ int ch; \ while (((ch = getchar()) != EOF) && (ch != '\n')); \ } static bool shownext; static bool dottedlines; static int level = MINLEVEL - 1,shapecount[NUMSHAPES]; static char blockchar = ' '; /* * Functions */ /* This function is responsible for increasing the score appropriately whenever * a block collides at the bottom of the screen (or the top of the heap */ static void score_function (engine_t *engine) { int score = SCOREVAL (level * (engine->status.dropcount + 1)); if (shownext) score /= 2; if (dottedlines) score /= 2; engine->score += score; } /* Draw the board on the screen */ static void drawboard (board_t board) { int x,y; out_setattr (ATTR_OFF); for (y = 1; y < NUMROWS - 1; y++) for (x = 0; x < NUMCOLS - 1; x++) { out_gotoxy (XTOP + x * 2,YTOP + y); switch (board[x][y]) { /* Wall */ case WALL: out_setattr (ATTR_BOLD); out_setcolor (COLOR_BLUE,COLOR_BLACK); out_putch ('<'); out_putch ('>'); out_setattr (ATTR_OFF); break; /* Background */ case 0: if (dottedlines) { out_setcolor (COLOR_BLUE,COLOR_BLACK); out_putch ('.'); out_putch (' '); } else { out_setcolor (COLOR_BLACK,COLOR_BLACK); out_putch (' '); out_putch (' '); } break; /* Block */ default: out_setcolor (COLOR_BLACK,board[x][y]); out_putch (blockchar); out_putch (blockchar); } } out_setattr (ATTR_OFF); } /* Show the next piece on the screen */ static void drawnext (int shapenum,int x,int y) { int i; block_t ofs[NUMSHAPES] = { { 1, 0 }, { 1, 0 }, { 1, -1 }, { 2, 0 }, { 1, -1 }, { 1, -1 }, { 0, -1 } }; out_setcolor (COLOR_BLACK,COLOR_BLACK); for (i = y - 2; i < y + 2; i++) { out_gotoxy (x - 2,i); out_printf (" "); } out_setcolor (COLOR_BLACK,SHAPES[shapenum].color); for (i = 0; i < NUMBLOCKS; i++) { out_gotoxy (x + SHAPES[shapenum].block[i].x * 2 + ofs[shapenum].x, y + SHAPES[shapenum].block[i].y + ofs[shapenum].y); out_putch (' '); out_putch (' '); } } /* Draw the background */ static void drawbackground () { out_setattr (ATTR_OFF); out_setcolor (COLOR_WHITE,COLOR_BLACK); out_gotoxy (4,YTOP + 7); out_printf ("H E L P"); out_gotoxy (1,YTOP + 9); out_printf ("p: Pause"); out_gotoxy (1,YTOP + 10); out_printf ("j: Left"); out_gotoxy (1,YTOP + 11); out_printf ("l: Right"); out_gotoxy (1,YTOP + 12); out_printf ("k: Rotate"); out_gotoxy (1,YTOP + 13); out_printf ("s: Draw next"); out_gotoxy (1,YTOP + 14); out_printf ("d: Toggle lines"); out_gotoxy (1,YTOP + 15); out_printf ("a: Speed up"); out_gotoxy (1,YTOP + 16); out_printf ("q: Quit"); out_gotoxy (2,YTOP + 17); out_printf ("SPACE: Drop"); out_gotoxy (3,YTOP + 19); out_printf ("Next:"); } static int getsum () { int i,sum = 0; for (i = 0; i < NUMSHAPES; i++) sum += shapecount[i]; return (sum); } /* This show the current status of the game */ static void showstatus (engine_t *engine) { static const int shapenum[NUMSHAPES] = { 4, 6, 5, 1, 0, 3, 2 }; char tmp[MAXDIGITS + 1]; int i,sum = getsum (); out_setattr (ATTR_OFF); out_setcolor (COLOR_WHITE,COLOR_BLACK); out_gotoxy (1,YTOP + 1); out_printf ("Your level: %d",level); out_gotoxy (1,YTOP + 2); out_printf ("Full lines: %d",engine->status.droppedlines); out_gotoxy (2,YTOP + 4); out_printf ("Score"); out_setattr (ATTR_BOLD); out_setcolor (COLOR_YELLOW,COLOR_BLACK); out_printf (" %d",GETSCORE (engine->score)); if (shownext) drawnext (engine->nextshape,3,YTOP + 22); out_setattr (ATTR_OFF); out_setcolor (COLOR_WHITE,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 12,YTOP + 1); out_printf ("STATISTICS"); out_setcolor (COLOR_BLACK,COLOR_MAGENTA); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 3); out_printf (" "); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 4); out_printf (" "); out_setcolor (COLOR_MAGENTA,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 3); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[0]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 3); out_printf ("%s",tmp); out_setcolor (COLOR_BLACK,COLOR_RED); out_gotoxy (out_width () - MAXDIGITS - 13,YTOP + 5); out_printf (" "); out_setcolor (COLOR_RED,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 5); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[1]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 5); out_printf ("%s",tmp); out_setcolor (COLOR_BLACK,COLOR_WHITE); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 7); out_printf (" "); out_gotoxy (out_width () - MAXDIGITS - 13,YTOP + 8); out_printf (" "); out_setcolor (COLOR_WHITE,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 7); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[2]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 7); out_printf ("%s",tmp); out_setcolor (COLOR_BLACK,COLOR_GREEN); out_gotoxy (out_width () - MAXDIGITS - 9,YTOP + 9); out_printf (" "); out_gotoxy (out_width () - MAXDIGITS - 11,YTOP + 10); out_printf (" "); out_setcolor (COLOR_GREEN,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 9); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[3]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 9); out_printf ("%s",tmp); out_setcolor (COLOR_BLACK,COLOR_CYAN); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 11); out_printf (" "); out_gotoxy (out_width () - MAXDIGITS - 15,YTOP + 12); out_printf (" "); out_setcolor (COLOR_CYAN,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 11); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[4]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 11); out_printf ("%s",tmp); out_setcolor (COLOR_BLACK,COLOR_BLUE); out_gotoxy (out_width () - MAXDIGITS - 9,YTOP + 13); out_printf (" "); out_gotoxy (out_width () - MAXDIGITS - 9,YTOP + 14); out_printf (" "); out_setcolor (COLOR_BLUE,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 13); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[5]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 13); out_printf ("%s",tmp); out_setattr (ATTR_OFF); out_setcolor (COLOR_BLACK,COLOR_YELLOW); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 15); out_printf (" "); out_gotoxy (out_width () - MAXDIGITS - 15,YTOP + 16); out_printf (" "); out_setcolor (COLOR_YELLOW,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 3,YTOP + 15); out_putch ('-'); snprintf (tmp,MAXDIGITS + 1,"%d",shapecount[shapenum[6]]); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 15); out_printf ("%s",tmp); out_setcolor (COLOR_WHITE,COLOR_BLACK); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 17); for (i = 0; i < MAXDIGITS + 16; i++) out_putch ('-'); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 18); out_printf ("Sum :"); snprintf (tmp,MAXDIGITS + 1,"%d",sum); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 18); out_printf ("%s",tmp); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 20); for (i = 0; i < MAXDIGITS + 16; i++) out_putch (' '); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 20); out_printf ("Score ratio :"); snprintf (tmp,MAXDIGITS + 1,"%d",GETSCORE (engine->score) / sum); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 20); out_printf ("%s",tmp); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 21); for (i = 0; i < MAXDIGITS + 16; i++) out_putch (' '); out_gotoxy (out_width () - MAXDIGITS - 17,YTOP + 21); out_printf ("Efficiency :"); snprintf (tmp,MAXDIGITS + 1,"%d",engine->status.efficiency); out_gotoxy (out_width () - strlen (tmp) - 1,YTOP + 21); out_printf ("%s",tmp); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ /* Header for scorefile */ #define SCORE_HEADER "Tint 0.02b (c) Abraham vd Merwe - Scores" /* Header for score title */ static const char scoretitle[] = "\n\t TINT HIGH SCORES\n\n\tRank Score Name\n\n"; /* Length of a player's name */ #define NAMELEN 20 /* Number of scores allowed in highscore list */ #define NUMSCORES 10 typedef struct { char name[NAMELEN]; int score; time_t timestamp; } score_t; static void getname (char *name) { struct passwd *pw = getpwuid (geteuid ()); fprintf (stderr,"Congratulations! You have a new high score.\n"); fprintf (stderr,"Enter your name [%s]: ",pw != NULL ? pw->pw_name : ""); memset(name, 0, NAMELEN); fgets(name, NAMELEN - 1, stdin); ENDFGETS(name) name[strlen (name) - 1] = '\0'; if (!strlen (name) && pw != NULL) { strncpy (name,pw->pw_name,NAMELEN); name[NAMELEN - 1] = '\0'; } } static void err1 () { fprintf (stderr,"Error creating %s\n",scorefile); exit (EXIT_FAILURE); } static void err2 () { fprintf (stderr,"Error writing to %s\n",scorefile); exit (EXIT_FAILURE); } void showplayerstats (engine_t *engine) { fprintf (stderr, "\n\t PLAYER STATISTICS\n\n\t" "Score %11d\n\t" "Efficiency %11d\n\t" "Score ratio %11d\n", GETSCORE (engine->score),engine->status.efficiency,GETSCORE (engine->score) / getsum ()); } static void createscores (int score) { FILE *handle; int i,j; score_t scores[NUMSCORES]; char header[strlen (SCORE_HEADER)+1]; if (score == 0) return; /* No need saving this */ for (i = 1; i < NUMSCORES; i++) { strcpy (scores[i].name,"None"); scores[i].score = -1; scores[i].timestamp = 0; } getname (scores[0].name); scores[0].score = score; scores[0].timestamp = time (NULL); if ((handle = fopen (scorefile,"w")) == NULL) err1 (); strcpy (header,SCORE_HEADER); i = fwrite (header,strlen (SCORE_HEADER),1,handle); if (i != 1) err2 (); for (i = 0; i < NUMSCORES; i++) { j = fwrite (scores[i].name,strlen (scores[i].name) + 1,1,handle); if (j != 1) err2 (); j = fwrite (&(scores[i].score),sizeof (int),1,handle); if (j != 1) err2 (); j = fwrite (&(scores[i].timestamp),sizeof (time_t),1,handle); if (j != 1) err2 (); } fclose (handle); fprintf (stderr,"%s",scoretitle); fprintf (stderr,"\t 1* %7d %s\n\n",score,scores[0].name); } static int cmpscores (const void *a,const void *b) { int result; result = (int) ((score_t *) a)->score - (int) ((score_t *) b)->score; /* a < b */ if (result < 0) return 1; /* a > b */ if (result > 0) return -1; /* a = b */ result = (time_t) ((score_t *) a)->timestamp - (time_t) ((score_t *) b)->timestamp; /* a is older */ if (result < 0) return -1; /* b is older */ if (result > 0) return 1; /* timestamps is equal */ return 0; } static void savescores (int score) { FILE *handle; int i,j,ch; score_t scores[NUMSCORES]; char header[strlen (SCORE_HEADER)+1]; time_t tmp = 0; if ((handle = fopen (scorefile,"r")) == NULL) { createscores (score); return; } i = fread (header,strlen (SCORE_HEADER),1,handle); if ((i != 1) || (strncmp (SCORE_HEADER,header,strlen (SCORE_HEADER)) != 0)) { createscores (score); return; } for (i = 0; i < NUMSCORES; i++) { j = 0; while ((ch = fgetc (handle)) != '\0') { if ((ch == EOF) || (j >= NAMELEN - 2)) { createscores (score); return; } scores[i].name[j++] = (char) ch; } scores[i].name[j] = '\0'; j = fread (&(scores[i].score),sizeof (int),1,handle); if (j != 1) { createscores (score); return; } j = fread (&(scores[i].timestamp),sizeof (time_t),1,handle); if (j != 1) { createscores (score); return; } } fclose (handle); if (score > scores[NUMSCORES - 1].score) { getname (scores[NUMSCORES - 1].name); scores[NUMSCORES - 1].score = score; scores[NUMSCORES - 1].timestamp = tmp = time (NULL); } qsort (scores,NUMSCORES,sizeof (score_t),cmpscores); if ((handle = fopen (scorefile,"w")) == NULL) err2 (); strcpy (header,SCORE_HEADER); i = fwrite (header,strlen (SCORE_HEADER),1,handle); if (i != 1) err2 (); for (i = 0; i < NUMSCORES; i++) { j = fwrite (scores[i].name,strlen (scores[i].name) + 1,1,handle); if (j != 1) err2 (); j = fwrite (&(scores[i].score),sizeof (int),1,handle); if (j != 1) err2 (); j = fwrite (&(scores[i].timestamp),sizeof (time_t),1,handle); if (j != 1) err2 (); } fclose (handle); fprintf (stderr,"%s",scoretitle); i = 0; while ((i < NUMSCORES) && (scores[i].score != -1)) { j = scores[i].timestamp == tmp ? '*' : ' '; fprintf (stderr,"\t %2d%c %7d %s\n",i + 1,j,scores[i].score,scores[i].name); i++; } fprintf (stderr,"\n"); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ static void showhelp () { fprintf (stderr,"USAGE: tint [-h] [-l level] [-n] [-d] [-b char]\n"); fprintf (stderr," -h Show this help message\n"); fprintf (stderr," -l Specify the starting level (%d-%d)\n",MINLEVEL,MAXLEVEL); fprintf (stderr," -n Draw next shape\n"); fprintf (stderr," -d Draw vertical dotted lines\n"); fprintf (stderr," -b Use this character to draw blocks instead of spaces\n"); exit (EXIT_FAILURE); } static void parse_options (int argc,char *argv[]) { int i = 1; while (i < argc) { /* Help? */ if (strcmp (argv[i],"-h") == 0) showhelp (); /* Level? */ else if (strcmp (argv[i],"-l") == 0) { i++; if (i >= argc || !str2int (&level,argv[i])) showhelp (); if ((level < MINLEVEL) || (level > MAXLEVEL)) { fprintf (stderr,"You must specify a level between %d and %d\n",MINLEVEL,MAXLEVEL); exit (EXIT_FAILURE); } } /* Show next? */ else if (strcmp (argv[i],"-n") == 0) shownext = TRUE; else if(strcmp(argv[i],"-d")==0) dottedlines = TRUE; else if(strcmp(argv[i], "-b")==0) { i++; if (i >= argc || strlen(argv[i]) < 1) showhelp(); blockchar = argv[i][0]; } else { fprintf (stderr,"Invalid option -- %s\n",argv[i]); showhelp (); } i++; } } static void choose_level () { char buf[NAMELEN]; do { fprintf (stderr,"Choose a level to start [%d-%d]: ",MINLEVEL,MAXLEVEL); if (NULL == fgets(buf, NAMELEN - 1, stdin)) { fprintf(stderr, "\nGoodbye!\n"); level = -1; break; } ENDFGETS(buf) buf[strlen (buf) - 1] = '\0'; } while (!str2int (&level,buf) || level < MINLEVEL || level > MAXLEVEL); } /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ int main (int argc,char *argv[]) { bool finished; int ch; engine_t engine; /* Initialize */ rand_init (); /* must be called before engine_init () */ engine_init (&engine,score_function); /* must be called before using engine.curshape */ finished = shownext = FALSE; memset (shapecount,0,NUMSHAPES * sizeof (int)); shapecount[engine.curshape]++; parse_options (argc,argv); /* must be called after initializing variables */ if (level < MINLEVEL) choose_level (); if (level < 0) exit (EXIT_SUCCESS); io_init (); drawbackground (); in_timeout (DELAY); /* Main loop */ do { /* draw shape */ showstatus (&engine); drawboard (engine.board); out_refresh (); /* Check if user pressed a key */ if ((ch = in_getch ()) != ERR) { switch (ch) { case 'j': case KEY_LEFT: engine_move (&engine,ACTION_LEFT); break; case 'k': case '\n': engine_move (&engine,ACTION_ROTATE); break; case 'l': case KEY_RIGHT: engine_move (&engine,ACTION_RIGHT); break; case ' ': case KEY_DOWN: engine_move (&engine,ACTION_DROP); break; /* show next piece */ case 's': shownext = TRUE; break; /* toggle dotted lines */ case 'd': dottedlines = !dottedlines; break; /* next level */ case 'a': case KEY_UP: if (level < MAXLEVEL) { level++; in_timeout (DELAY); } else out_beep (); break; /* quit */ case 'q': finished = TRUE; break; /* pause */ case 'p': out_setcolor (COLOR_WHITE,COLOR_BLACK); out_gotoxy ((out_width () - 34) / 2,out_height () - 2); out_printf ("Paused - Press any key to continue"); while ((ch = in_getch ()) == ERR) ; /* Wait for a key to be pressed */ in_flush (); /* Clear keyboard buffer */ out_gotoxy ((out_width () - 34) / 2,out_height () - 2); out_printf (" "); break; /* unknown keypress */ default: out_beep (); } in_flush (); } else { switch (engine_evaluate (&engine)) { /* game over (board full) */ case -1: if ((level < MAXLEVEL) && ((engine.status.droppedlines / 10) > level)) level++; finished = TRUE; break; /* shape at bottom, next one released */ case 0: if ((level < MAXLEVEL) && ((engine.status.droppedlines / 10) > level)) { level++; in_timeout (DELAY); } shapecount[engine.curshape]++; break; /* shape moved down one line */ case 1: break; } } } while (!finished); /* Restore console settings and exit */ io_close (); /* Don't bother the player if he want's to quit */ if (ch != 'q') { showplayerstats (&engine); savescores (GETSCORE (engine.score)); } exit (EXIT_SUCCESS); }