petris-1.0.1/0040755000175000017500000000000010035346566012053 5ustar peterpeterpetris-1.0.1/Makefile0100644000175000017500000000047310033271653013504 0ustar peterpeterpetris : main.o game.o highscore.o gcc -o petris main.o game.o highscore.o -lncurses main.o : main.c game.h petris.h gcc -Wall -c main.c game.o : game.c game.h petris.h config.h gcc -Wall -c game.c highscore.o : highscore.c highscore.h config.h gcc -Wall -c highscore.c clean: rm main.o game.o highscore.o petris-1.0.1/README0100644000175000017500000001110310033272215012707 0ustar peterpeterReadme for Petris v. 1.0.1 Petris is Peter's Tetris "LICENSE" You can do whatever you want with the program, it's Public Domain. (however, it would be nice of you to credit me if you found anything of this useful). The above includes the permission to relicence the program under the MIT/X11 licence reproduced below: --- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. --- BUILDING: To get the highscore facility working, you'll need to set the filename of the highscore file by editing "config.h" (if you want different users to share the same highscore file, please read the INSTALLING section below.) By editing this file you can also change the key bindings. Type "make" to build an executable file named "petris"; "make clean" will remove the created obj. files. INSTALLING: As root, copy the executable file to a place in the normal PATH (/usr/local/bin is the right place). Now everyone can run the game, but what about the highscore file? Different people need access to it but on the other hand, user Joe should not be able to delete it just because user Lisa beat his highscore. I'm not a guru, and thus I'm not sure if the solution outlined below is neither good or secure - but it works (for me at least). SOLUTION: Create a "petris" user by adding this line to the "/etc/passwd" file: petris:*:65533:65533:petris::: Which means a user who cannot log in and doesn't have any home. Then, as root, create the file "/var/local/petris/highscore" and change the ownership to "petris.nogroup". Also, set the per- missions of the file to "600"="rw-------" so only user petris can read/write the file. Now edit "config.h" to point to the new highscore file and compile. Copy the created executable to e.g. "/usr/local/bin". Change the ownership of the file to "petris.nogroup" and do a "chmod ug+s petris". Now, when someone runs the game, they will be running it as user petris and as such be able to read/ write the highscore file. Finally, the manpage "petris.6" can be installed. For local installations put it in /usr/local/man/man6/. You may want to gzip the file. RUNNING: You may invoke the program with a command line argument specifying the size of the well in the form [WIDTH]x[HEIGHT]. Example: petris 12x25 If you make enough points to get on the highscore list, the value of the "USER" environment variable will be used as your name in the new entry. IMPORTANT NOTE: After a CTRL-C abort of the game, sending the game to the background or some similar action, the cursor stays invisible. You can type "setterm -reset" to get it back. Currently, the cursor is only restored when leaving the program in the "normal" way. COMPATIBILITY: The game has been tested and compiled on an x86 / Debian Woody but will probably work fine on any Linux Box. Other Unices may work too. However, this program depends on the select() system call to modify the contents of the timeval struct pointed to by the last argument to indicate the time remaining. Some systems never changes this value! THANKS: Thanks to Andree Leidenfrost for providing the manpage. Thanks to Tom Mortell for fixing a bug concerning the use of the USER environment variable. CONTACT: Feel free to send feedback, bug-reports and other useful things to: Peter Seidler petris-1.0.1/config.h0100644000175000017500000000112210033271556013454 0ustar peterpeter/* config.h */ #ifndef _CONFIG_H_ #define _CONFIG_H_ /* Control keys. These should be something ncurses understands. */ #define CONTROL_LEFT KEY_LEFT /* Move left */ #define CONTROL_RIGHT KEY_RIGHT /* Move right */ #define CONTROL_ROT_CW KEY_UP /* Rotate clock-wise */ #define CONTROL_ROT_CCW ' ' /* Rotate counter-clock-wise */ #define CONTROL_DROP KEY_DOWN /* Drop */ #define CONTROL_REFRESH 'r' /* Refresh screen */ /* Highscore file. */ #define HIGHSCORE_FILE "/var/local/petris/highscores" /* Number of entries in highscore list. */ #define SIZE_HS_LIST 10 #endif /* _CONFIG_H_ */ petris-1.0.1/game.c0100644000175000017500000002570110033271556013124 0ustar peterpeter/* game.c The functions relevant to the game itself. */ #include #include #include #include #include #include #include "petris.h" #include "config.h" #include "game.h" #include "main.h" const DOT block_data[BLOCK_TYPES][BLOCK_ORIENTS][BLOCK_DOTS] = { { {{2,0,1},{2,1,1},{2,2,1},{2,3,1}}, /* */ {{0,1,1},{1,1,1},{2,1,1},{3,1,1}}, /* */ {{2,0,1},{2,1,1},{2,2,1},{2,3,1}}, /* XXXX */ {{0,1,1},{1,1,1},{2,1,1},{3,1,1}} /* */ }, { {{1,1,2},{2,1,2},{1,2,2},{2,2,2}}, /* */ {{1,1,2},{2,1,2},{1,2,2},{2,2,2}}, /* XX */ {{1,1,2},{2,1,2},{1,2,2},{2,2,2}}, /* XX */ {{1,1,2},{2,1,2},{1,2,2},{2,2,2}} /* */ }, { {{1,0,3},{1,1,3},{1,2,3},{2,2,3}}, /* */ {{2,0,3},{0,1,3},{1,1,3},{2,1,3}}, /* XXX */ {{0,0,3},{1,0,3},{1,1,3},{1,2,3}}, /* X */ {{0,1,3},{1,1,3},{2,1,3},{0,2,3}} /* */ }, { {{1,0,4},{2,0,4},{1,1,4},{1,2,4}}, /* */ {{0,0,4},{0,1,4},{1,1,4},{2,1,4}}, /* XXX */ {{1,0,4},{1,1,4},{0,2,4},{1,2,4}}, /* X */ {{0,1,4},{1,1,4},{2,1,4},{2,2,4}} /* */ }, { {{1,0,5},{1,1,5},{2,1,5},{2,2,5}}, /* */ {{1,0,5},{2,0,5},{0,1,5},{1,1,5}}, /* XX */ {{1,0,5},{1,1,5},{2,1,5},{2,2,5}}, /* XX */ {{1,0,5},{2,0,5},{0,1,5},{1,1,5}} /* */ }, { {{2,0,6},{1,1,6},{2,1,6},{1,2,6}}, /* */ {{0,0,6},{1,0,6},{1,1,6},{2,1,6}}, /* XX */ {{2,0,6},{1,1,6},{2,1,6},{1,2,6}}, /* XX */ {{0,0,6},{1,0,6},{1,1,6},{2,1,6}} /* */ }, { {{1,0,7},{1,1,7},{2,1,7},{1,2,7}}, /* X */ {{1,0,7},{0,1,7},{1,1,7},{2,1,7}}, /* XXX */ {{1,0,7},{0,1,7},{1,1,7},{1,2,7}}, /* */ {{0,1,7},{1,1,7},{2,1,7},{1,2,7}} /* */ } }; /* This is a pointer to the well data which will be allocated later. The block of data pointed to is a simple (y,x)-coordinate system. At each point is stored a color (0 means there's space for new blocks). */ unsigned char *well_data; /* Delay before block drop one step down (usec). We start at one sec. and then decreases the delay by 23% at each level. */ int delay[NO_LEVELS] = {1000000, 770000, 593000, 457000, 352000, 271000, 208000, 160000, 124000, 95000}; /* Window in which the action takes place. */ WINDOW *well_win; /* Convert (y, x)-coordinates in well to a pointer. */ unsigned char *yx2pointer(int y, int x) { return well_data + (y * WELL_WIDTH) + x; } /* Used to plot the blocks. Each point is two characters wide - this looks better on my terminal. */ void draw_block(WINDOW *win, int y, int x, int type, int orient, char erase) { int i; DOT dot; /* We have BLOCK_DOTS points to paste on the screen. Read each DOT, get color and draw it relative to y and x. */ for (i = 0; i < BLOCK_DOTS; i++) { dot = block_data[type][orient][i]; wattrset(win, COLOR_PAIR(erase? 0 : dot.color)); mvwprintw(win, y + dot.y, 2*(x + dot.x), " "); } /* An erase operation will most likely (always) be followed by a new drawing. To avoid flickering, we don't update the screem in this case. */ if (0 == erase) wrefresh(win); } /* This redraws the well window with data in well_data. */ void update_well(int start, int lines) { int y, x; for (y = start; y < start + lines; y++) { wmove(well_win, y, 0); for (x = 0; x < WELL_WIDTH; x++) { wattrset(well_win, COLOR_PAIR(*yx2pointer(y, x))); mvwprintw(well_win, y, 2 * x, " "); } } wrefresh(well_win); } /* Check if a position for a block is valid, i.e. it doesn't overlap a point which is already occupied or goes outside the well borders. */ int check_block_pos(int y, int x, int type, int orient) { int i; DOT dot; for (i = 0; i < BLOCK_DOTS; i++) { dot = block_data[type][orient][i]; if ((y + dot.y > WELL_HEIGHT - 1) || (x + dot.x < 0) || (x + dot.x > WELL_WIDTH - 1) || (*yx2pointer(y + dot.y, x + dot.x) > 0) ) return 0; } return 1; } /* Put the points a block occupies into well_data and update. */ void set_block(int y, int x, int type, int orient) { int i; DOT dot; for (i = 0; i < BLOCK_DOTS; i++) { dot = block_data[type][orient][i]; *yx2pointer(y + dot.y, x + dot.x) = dot.color; } update_well(y, BLOCK_DOTS); } /* Helper for check_lines(). Visualize a new line by some "nice effects". */ void visualize_lines(int start, short line_stat) { int i, y; short tmp; /* For comparison with line_stat. */ wattrset(well_win, COLOR_PAIR(0)); for (i = 0; i < 6; i++) { tmp = 0x0001; for (y = BLOCK_DOTS - 1; y >= 0; y--) { if (line_stat & tmp) { wmove(well_win, start + y, 0); whline(well_win, i % 2? ' ' : ':', WELL_WIDTH * 2); } tmp <<= 1; } wrefresh(well_win); usleep(500000 / 6); } } /* Another helper function. Remove lines specified in line_stat (relative to start) from well_data. Then update display. */ void remove_lines(int start, short line_stat) { unsigned char *tmp_well; short tmp; /* Used for comparison with line_stat. */ int y; int lines = 0; /* No. of full lines. */ tmp_well=malloc(WELL_HEIGHT * WELL_WIDTH); memset(tmp_well, 0, BLOCK_DOTS * WELL_WIDTH); /* Copy part below the point where thing may change. */ memcpy(tmp_well + (start + BLOCK_DOTS) * WELL_WIDTH, well_data + (start + BLOCK_DOTS) * WELL_WIDTH, (WELL_HEIGHT - start - BLOCK_DOTS) * WELL_WIDTH); /* Copy the lines that are not full. */ /* We check from bottom and upwards because this makes it easier to take care op moving (not full) lines down. */ tmp = 0x0001; for (y = BLOCK_DOTS - 1; y >= 0; y--) { if (!(line_stat & tmp)) { memcpy(tmp_well + (start + y + lines) * WELL_WIDTH, well_data + (start + y) * WELL_WIDTH, WELL_WIDTH); } else lines++; tmp <<= 1; } /* Copy the rest, i.e. the upper part of the well. */ memcpy(tmp_well + (lines * WELL_WIDTH), well_data, WELL_WIDTH * start); memcpy(well_data, tmp_well, WELL_HEIGHT * WELL_WIDTH); update_well(0, WELL_HEIGHT); } /* Check for complete lines. Update display and well_data. Return number of point obtained. We check from line start to start + BLOCK_DOTS - 1. */ POINTS check_lines(int start) { int y, x; short line; short line_stat = 0; /* Each bit indicates the status for a tested line. If set line is full. Lower bit is lower line. */ POINTS points; points.points = 0; points.lines = 0; /* Avoid reading beyond end of well_data. */ if (start > WELL_HEIGHT - BLOCK_DOTS) start = WELL_HEIGHT - BLOCK_DOTS; for (y = start; y < start + BLOCK_DOTS; y++) { line_stat <<= 1; line = TRUE; for (x = 0; x < WELL_WIDTH; x++) if (*yx2pointer(y, x) == 0) { line = FALSE; break; } if (TRUE == line) { if (0 == line_stat) /* First line. */ points.points = 50; else /* Multiply points by two. */ points.points *= 2; points.lines++; line_stat = line_stat | 0x0001; } } if (line_stat) { visualize_lines(start, line_stat); remove_lines(start, line_stat); } return points; } void update_stat(POINTS points, int block) { static int prev_block = 0; /* This is preserved between calls and make us able to erase last block. */ draw_block(stdscr, STAT_Y + 2, STAT_X / 2 + 2 , prev_block, 0, 1); draw_block(stdscr, STAT_Y + 2, STAT_X / 2 + 2 , block, 0, 0); attrset(COLOR_PAIR(COLOR_POINTS)); mvprintw(STAT_Y + BLOCK_DOTS + 3, STAT_X + 2, "Points: %d", points.points); attrset(COLOR_PAIR(COLOR_LINES)); mvprintw(STAT_Y + BLOCK_DOTS + 5, STAT_X + 2, "Lines: %d", points.lines); attrset(COLOR_PAIR(COLOR_LEVEL)); mvprintw(STAT_Y + BLOCK_DOTS + 7, STAT_X + 2, "Level: %d", points.level); prev_block = block; refresh(); } /* Drop a block in the well. When done return y-cord. of where block ended. If it's not possible to even start with a new block return -1. */ int drop_block(int type, int level) { int defx = WELL_WIDTH / 2 - BLOCK_DOTS / 2; int y = 0; int x = defx; int orient = 0; int ch; fd_set inputs, test_fds; struct timeval timeout; int sel_ret; if (0 == check_block_pos(y, x, type, orient)) return -1; /* Oh no, game over. */ timeout.tv_sec = 0; timeout.tv_usec = delay[level]; FD_ZERO(&inputs); FD_SET(0, &inputs); if (ch != ERR) while (getch() != ERR); draw_block(well_win, y, x, type, orient, 0); while(1) { test_fds = inputs; sel_ret = select(FD_SETSIZE, &test_fds, (fd_set *) 0, (fd_set *) 0, &timeout); ch = getch(); switch (ch) { case CONTROL_LEFT: if (check_block_pos(y, x - 1, type, orient)) { draw_block(well_win, y, x, type, orient, 1); draw_block(well_win, y, --x, type, orient, 0); } break; case CONTROL_RIGHT: if (check_block_pos(y, x + 1, type, orient)) { draw_block(well_win, y, x, type, orient, 1); draw_block(well_win, y, ++x, type, orient, 0); } break; case CONTROL_ROT_CW: if (check_block_pos(y, x, type, orient + 1 == BLOCK_ORIENTS? 0 : orient + 1)) { draw_block(well_win, y, x, type, orient, 1); ++orient == BLOCK_ORIENTS? orient = 0: 0; draw_block(well_win, y, x, type, orient, 0); } break; case CONTROL_ROT_CCW: if (check_block_pos(y, x, type, orient - 1 == -1? BLOCK_ORIENTS -1 : orient - 1)) { draw_block(well_win, y, x, type, orient, 1); --orient == -1? orient = BLOCK_ORIENTS - 1: 0; draw_block(well_win, y, x, type, orient, 0); } break; case CONTROL_DROP: sel_ret = 0; break; case CONTROL_REFRESH: /* Refresh well. */ update_screen(); update_well(0, WELL_HEIGHT); draw_block(well_win, y, x, type, orient, 0); } /* If time has expired we need to move block down. Check if block-pos. is ok if we move it down. If it's not, paste block in well and return. */ if (0 == sel_ret) { if (check_block_pos(y + 1, x, type, orient)) { draw_block(well_win, y, x, type, orient, 1); draw_block(well_win, ++y, x, type, orient, 0); } else { set_block(y, x, type, orient); return y; } timeout.tv_sec = 0; timeout.tv_usec = delay[level]; } } } /* Plays a single game. That is: Drop a block. Test for new lines. Update score. Repeat until game over. */ POINTS play_game(int level) { POINTS points; POINTS tmp; int cur = random() % BLOCK_TYPES; int next = random() % BLOCK_TYPES; int y; /* y-cord. of last-placed block. */ /* Create window used in game. */ well_win = newwin(WELL_HEIGHT, 2 * WELL_WIDTH, WELL_Y, WELL_X); /* Clear and reset. */ well_data = (unsigned char *)malloc(WELL_HEIGHT * WELL_WIDTH); memset(well_data, 0, WELL_HEIGHT * WELL_WIDTH); update_screen(); wclear(well_win); points.points = 0; points.lines = 0; points.level = level; update_stat(points, next); while(1) { y = drop_block(cur, points.level); if (y >= 0) { tmp = check_lines(y); points.points += (tmp.points + points.level); points.lines += tmp.lines; if (points.lines / 10 > level && points.lines / 10 < NO_LEVELS) points.level = points.lines / 10; cur = next; next = random() % BLOCK_TYPES; update_stat(points, next); } else { sleep(2); break; /* Game over. */ } } free(well_data); well_data = 0; delwin(well_win); return points; } petris-1.0.1/game.h0100644000175000017500000000070010033271556013121 0ustar peterpeter/* game.h */ #ifndef _GAME_H_ #define _GAME_H_ /* Extern Functions in game.c */ extern POINTS play_game(int level); /* Change this if you want more blocks or whatever. */ #define BLOCK_TYPES 7 #define BLOCK_ORIENTS 4 #define BLOCK_DOTS 4 /* A dot defines the color at relative (y,x) coordinates. Used to draw blocks. */ typedef struct _dot { unsigned char y; unsigned char x; unsigned char color; } DOT; #endif /* _GAME_H_ */ petris-1.0.1/highscore.c0100644000175000017500000000612210033271556014162 0ustar peterpeter/* highscore.c Highscore functionality. */ #include #include #include #include #include "petris.h" #include "config.h" #include "highscore.h" #include "main.h" /* "Highscore entry" type. */ typedef struct _hs_entry { char name[11]; unsigned int points; } hs_entry; /* Array of highscore entries. */ hs_entry hs_list[SIZE_HS_LIST]; int load_highscores() { FILE *file; int i; size_t items; if ((file = fopen(HIGHSCORE_FILE, "r")) == NULL) { /* Failed opening file, give array sane values. */ message("Failed to open highscore file. Creating empty list."); for (i = 0; i < SIZE_HS_LIST; i++) { hs_list[i].name[0] = (char)0; hs_list[i].points = 0; } return 1; } /* Open ok, read file. */ items = fread(hs_list, sizeof(hs_entry), SIZE_HS_LIST, file); /* Fill rest of array if file was too small. */ for (i = items; i < SIZE_HS_LIST; i++) { hs_list[i].name[0] = '\0'; hs_list[i].points = 0; } fclose(file); return 1; } int save_highscores() { FILE *file; if ((file = fopen(HIGHSCORE_FILE, "w")) == NULL) { message("Error saving highscore file. Your new highscore entry is lost :-("); return 0; } fwrite(hs_list, sizeof(hs_entry), SIZE_HS_LIST, file); fclose(file); return 1; } void insert_hs_entry(int index, char *name, unsigned int points) { int i; /* Move entries one position down starting at index. */ for (i = SIZE_HS_LIST - 1; i > index; i--) { strcpy(hs_list[i].name, hs_list[i-1].name); hs_list[i].points = hs_list[i-1].points; } /* Insert new entry. */ strcpy(hs_list[index].name, name); hs_list[index].points = points; } int check_highscores(unsigned int points) { int i; char *name; name = getenv("USER"); if (NULL == name) name = "nobody"; load_highscores(); /* Check for new highscore. */ for (i = 0; i < SIZE_HS_LIST; i++) if (points >= hs_list[i].points) { insert_hs_entry(i, name, points); save_highscores(); return i + 1; } return 0; } int view_highscores() { WINDOW *win; char *name; int i; int ch; /* Get username */ name = getenv("USER"); if (NULL == name) name = "nobody"; load_highscores(); /* Place window in the middle of the screen. */ win = newwin(21, 24, LINES / 2 - (10 + SIZE_HS_LIST) / 2, COLS / 2 - 12); box(win, 0, 0); wattrset(win, COLOR_PAIR(COLOR_MSG) | A_BOLD); mvwprintw(win, 2, 6, "Highscores:"); wattroff(win, A_BOLD); mvwprintw(win, 4, 2, "%-12sPoints:", "Name:"); /* Print highscore list. */ wattrset(win, COLOR_PAIR(COLOR_POINTS)); for (i = 0; i < SIZE_HS_LIST; i++) { if (0 == strcmp(hs_list[i].name, name)) wattron(win, A_BOLD); else wattroff(win, A_BOLD); mvwprintw(win, 6 + i, 2, "%-12s%7d", hs_list[i].name, hs_list[i].points); } wattrset(win, COLOR_PAIR(COLOR_MSG)); mvwprintw(win, 6 + SIZE_HS_LIST + 1, 2, "Press q to quit, any"); mvwprintw(win, 6 + SIZE_HS_LIST + 2, 2, "other key to cont."); update_screen(); wrefresh(win); nodelay(stdscr, FALSE); ch = getch(); if (ch == 'q' || ch == 'Q') return 0; else return 1; nodelay(stdscr, TRUE); delwin(win); } petris-1.0.1/highscore.h0100644000175000017500000000033710033271556014171 0ustar peterpeter/* highscore.h function prototypes for acces to highscore data */ #ifndef _HIGHSCORE_H_ #define _HIGHSCORE_H_ extern int check_highscores(unsigned int points); extern int view_highscores(); #endif /* _HIGHSCORE_H_ */ petris-1.0.1/CHANGELOG0100644000175000017500000000025410035346476013263 0ustar peterpeterChangelog for Petris 1.0.1: * Manpage included. * Fixed bug concerning use of the USER environment variable in highscore.c. 1.0: * No changelog before release 1.0.1. petris-1.0.1/main.c0100644000175000017500000001734310033272154013135 0ustar peterpeter/* main.c Peter's Tetris (hmm...) By Peter Seidler */ #include #include #include #include #include #include #include "petris.h" #include "main.h" #include "game.h" #include "highscore.h" /* Placement and size of different elements on the screen. */ COORDS coords; /* Initialize struct with coordinates for different elements. The function's argument is a string of the form [width]x[height]. */ void init_coords(char *size) { char *endptr; short totw; /* Total width. */ /* "Constants" */ WELL_Y = 2; STAT_Y = 2; STAT_WIDTH = 18; /* Dependant on constants */ WELL_WIDTH = strtol(size, &endptr, 10); if (endptr == size || *endptr != 'x' || WELL_WIDTH < BLOCK_DOTS) { endwin(); fprintf(stderr, "Petris: Bad [width]x[height] format.\n"); exit(1); } WELL_HEIGHT = strtol(endptr + 1, 0, 10); if (WELL_HEIGHT < BLOCK_DOTS) { endwin(); fprintf(stderr, "Petris: Bad [width]x[height] format.\n"); exit(1); } totw = 2 * WELL_WIDTH + 2 + STAT_WIDTH + 2; /* This is only a basic check of the terminal size. If the status window is taller than the well, or if some of the other windows used during the game are bigger than the space occupied by the well or status display things will go wrong. */ if (totw > COLS || WELL_Y + WELL_HEIGHT >= LINES) { endwin(); fprintf(stderr, "Petris: Terminal too small. Aborting.\n"); exit(1); } /* Place everything in the center of the screen. */ WELL_X = COLS / 2 - totw / 2 + 1; /* +1 because of frame. */ STAT_X = WELL_X + 2 * WELL_WIDTH + 3; } void parse_args(int argc, char **argv) { /* If arg. 2 is there assume it's a well size. If not use default size. */ if (argc >= 2) init_coords(*(argv + 1)); else init_coords("10x20"); } /* Show or hide cursor. */ void cursor_vis(int vis) { char *cmd; if (vis == 1) cmd = tigetstr("cnorm"); else cmd = tigetstr("civis"); if (cmd == (char *)-1) { fprintf(stderr, "Warning! Your termianl does not support cursor on / off\n"); sleep(3); return; } putp(cmd); } void init_colors() { if (!has_colors()) { endwin(); fprintf(stderr, "Petris: Your terminal doesn't support colors. Aborting \n"); cursor_vis(1); exit(1); } /* We assume this goes ok - we should check, really. */ start_color(); /* 1 - 16 is for blocks (we just don't have 16 blocks yet) */ init_pair(1, COLOR_BLACK, COLOR_RED); init_pair(2, COLOR_BLACK, COLOR_YELLOW); init_pair(3, COLOR_BLACK, COLOR_GREEN); init_pair(4, COLOR_BLACK, COLOR_CYAN); init_pair(5, COLOR_BLACK, COLOR_MAGENTA); init_pair(6, COLOR_BLACK, COLOR_BLUE); init_pair(7, COLOR_BLACK, COLOR_WHITE); /* 17 - ? is for other things */ init_pair(COLOR_POINTS, COLOR_RED, COLOR_BLACK); init_pair(COLOR_LINES, COLOR_YELLOW, COLOR_BLACK); init_pair(COLOR_LEVEL, COLOR_GREEN, COLOR_BLACK); init_pair(COLOR_MSG, COLOR_YELLOW, COLOR_BLACK); } /* Redraw static parts of the screen. */ void update_screen() { /* Remember: Each dot in well is two chars wide! */ clear(); /* Draw frame around well window. */ attrset(COLOR_PAIR(0)); move(WELL_Y, WELL_X - 1); vline(ACS_VLINE, WELL_HEIGHT); move(WELL_Y, WELL_X + (2 * WELL_WIDTH)); vline(ACS_VLINE, WELL_HEIGHT); move(WELL_Y + WELL_HEIGHT, WELL_X - 1); addch(ACS_LLCORNER); hline(ACS_HLINE, 2 * WELL_WIDTH); move(WELL_Y + WELL_HEIGHT, WELL_X + (2 * WELL_WIDTH)); addch(ACS_LRCORNER); /* This is for the status stuff. */ move(STAT_Y, STAT_X); addch(ACS_ULCORNER); hline(ACS_HLINE, STAT_WIDTH - 2); move(STAT_Y, STAT_X + STAT_WIDTH - 1); addch(ACS_URCORNER); move(STAT_Y + 1, STAT_X); vline(ACS_VLINE, BLOCK_DOTS + 7); move(STAT_Y + 1, STAT_X + STAT_WIDTH - 1); vline(ACS_VLINE, BLOCK_DOTS + 7); move(STAT_Y + BLOCK_DOTS + 8, STAT_X); addch(ACS_LLCORNER); hline(ACS_HLINE, STAT_WIDTH - 2); move(STAT_Y + BLOCK_DOTS + 8, STAT_X + STAT_WIDTH - 1); addch(ACS_LRCORNER); move(STAT_Y + BLOCK_DOTS + 2, STAT_X); addch(ACS_LTEE); hline(ACS_HLINE, STAT_WIDTH - 2); move(STAT_Y + BLOCK_DOTS + 2, STAT_X + STAT_WIDTH - 1); addch(ACS_RTEE); refresh(); } int get_level(int level) { WINDOW *win; int ch = 0; /* Place window in the middle of the screen. */ win = newwin(12, 16, LINES / 2 - 6, COLS / 2 - 8); box(win, 0, 0); wattrset(win, COLOR_PAIR(COLOR_MSG)); mvwprintw(win, 2, 2, "Choose level"); mvwprintw(win, 3, 2, "(0 - %d)", NO_LEVELS - 1); mvwprintw(win, 5, 2, "Up arrow"); mvwprintw(win, 7, 2, "Down arrow"); mvwprintw(win, 9, 2, "Space for OK"); wattrset(win, COLOR_PAIR(COLOR_LEVEL)); mvwprintw(win, 6, 4, "%d ", level); update_screen(); wrefresh(win); nodelay(stdscr, FALSE); while(ch != ' ' && ch != KEY_ENTER) { ch = getch(); switch (ch) { case KEY_UP: level = (level + 1 > NO_LEVELS - 1)? 0 : level + 1; mvwprintw(win, 6, 4, "%d ", level); wrefresh(win); break; case KEY_DOWN: level = (level - 1 < 0)? NO_LEVELS - 1: level - 1; mvwprintw(win, 6, 4, "%d ", level); wrefresh(win); break; } } nodelay(stdscr, TRUE); delwin(win); return level; } /* If use_hs != 0 check for new highscore. */ int show_score(POINTS points, int use_hs) { WINDOW *win; int ranking; /* Highscore ranking. */ int ch; int ret = 1; /* Place window in the middle of the screen. */ win = newwin(20, 28, LINES / 2 - 10, COLS / 2 - 14); box(win, 0, 0); wattrset(win, COLOR_PAIR(COLOR_MSG) | A_BOLD); mvwprintw(win, 2, 6, "*** GAME OVER ***"); wattrset(win, COLOR_PAIR(COLOR_MSG)); mvwprintw(win, 4, 2, "You scored:"); wattrset(win, COLOR_PAIR(COLOR_POINTS)); mvwprintw(win, 6, 5, "Points: %d", points.points); wattrset(win, COLOR_PAIR(COLOR_LINES)); mvwprintw(win, 7, 5, "Lines: %d", points.lines); wattrset(win, COLOR_PAIR(COLOR_LEVEL)); mvwprintw(win, 8, 5, "Level: %d", points.level); wattrset(win, COLOR_PAIR(COLOR_POINTS) | A_BOLD); if (use_hs) { ranking = check_highscores(points.points); if (ranking) { mvwprintw(win, 10, 2, "Congratulations!"); wattrset(win, COLOR_PAIR(COLOR_MSG)); mvwprintw(win, 12, 5, "You're on the high-"); mvwprintw(win, 13, 5, "score list as no. %d", ranking); } else mvwprintw(win, 11, 5, "Practice some more!"); } else { mvwprintw(win, 10, 2, "Highscore disabled!"); } wattrset(win, COLOR_PAIR(COLOR_MSG)); mvwprintw(win, 15, 2, "Press q to quit, h to"); mvwprintw(win, 16, 2, "view highscores or any"); mvwprintw(win, 17, 2, "other key to play again."); update_screen(); wrefresh(win); if (ch != ERR) while (getch() != ERR); nodelay(stdscr, FALSE); ch = getch(); if (ch == 'q' || ch == 'Q') ret = 0; else if (ch == 'h' || ch == 'H') ret = view_highscores(); nodelay(stdscr, TRUE); delwin(win); return ret; } /* Displays a message in the top left corner for a couple * of seconds. Used for displaying "global" messages like * errors */ void message(char *msg) { attrset(COLOR_PAIR(COLOR_MSG) | A_BLINK); mvprintw(0,0, msg); refresh(); sleep(3); } int main(int argc, char **argv) { int play; int level = 0; POINTS points; int use_highscore = 1; /* Initialize screen */ if (0 == initscr()) { fprintf(stderr, "Petris: Error initializing ncurses.\n"); exit(1); } parse_args(argc, argv); /* We only use highscore list if the size of the well is standard 10x20. */ if (WELL_WIDTH != 10 || WELL_HEIGHT != 20) use_highscore = 0; /* Turn off cursor. */ cursor_vis(0); /* Setup keyboard. We'd like to get each and every character, but not to display them on the terminal. */ keypad(stdscr, TRUE); nodelay(stdscr, TRUE); noecho(); cbreak(); init_colors(); /* Set random seed. */ srandom(time( (time_t *) 0 )); /* This is where the game begins! */ while (play) { level = get_level(level); points = play_game(level); play = show_score(points, use_highscore); } cursor_vis(1); endwin(); exit(0); } petris-1.0.1/main.h0100644000175000017500000000026010033271556013135 0ustar peterpeter#ifndef _MAIN_H_ #define _MAIN_H_ /* Extern functions. */ extern void update_screen(); extern void cursor_vis(int vis); extern void message(char *msg); #endif /* _MAIN_H_ */ petris-1.0.1/petris.60100755000175000017500000000216610033271556013447 0ustar peterpeter.TH "PETRIS" "6" "February 22, 2004" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME petris \- Petris is Peter's Tetris(TM) .SH SYNOPSIS .B petris .RI [ size ] .SH DESCRIPTION This manual page documents briefly the .B petris command. .PP \fBpetris\fP is a clone of Tetris(TM) for the command line. It supports colour and a highscore file (see /usr/share/doc/petris/README for details). .SH OPTIONS This program does not have any normal command line options. The only option available is: .TP .B [WIDTHxHEIGHT] Sets the size of the terminal window. \fBExample:\fP petris 12x25 .SH AUTHOR This manual page was written by Andree Leidenfrost , for the Debian project (but may be used by others). petris-1.0.1/petris.h0100644000175000017500000000330210033271556013517 0ustar peterpeter/* petris.h #defines and data types that are (or can be) usefull in most places. */ #ifndef _PETRIS_H_ #define _PETRIS_H_ /* In term.h is a "#define lines ...". We don't want that one * since we use a variable called lines in the POINTS struct. * Also, the number of terminal line may be accessed as LINES. */ #ifdef lines #undef lines #endif /* Different colors for different things. */ #define COLOR_POINTS 17 #define COLOR_LINES 18 #define COLOR_LEVEL 19 #define COLOR_MSG 20 /* We will set the coordinates / sizes of different elements dynamically. To avoid a big mess we put all this in a struct. NOTE: There should only be _one_ instance of this struct: coords, which is defined and initialized in main.c */ typedef struct _coords { int well_x; int well_y; int well_width; int well_height; int stat_x; int stat_y; int stat_width; } COORDS; /* Make the one and only instance, coords, extern so everyone can use and love it. */ extern COORDS coords; /* Macros for easy reference to the members of the coords struct. */ #define WELL_X coords.well_x #define WELL_Y coords.well_y #define WELL_HEIGHT coords.well_height #define WELL_WIDTH coords.well_width #define STAT_Y coords.stat_y #define STAT_X coords.stat_x #define STAT_WIDTH coords.stat_width #define NO_LEVELS 10 /* Level 0 - 9 */ /* Make it easy to pass points, lines and level around as a whole. */ typedef struct _points { unsigned int points; unsigned char lines; unsigned char level; } POINTS; #endif /* _PETRIS_H_ */