From decwrl!ucbvax!tut.cis.ohio-state.edu!cs.utexas.edu!csd4.milw.wisc.edu!lll-winken!uunet!zephyr!tektronix!tekgen!tekred!saab!billr Sat Jun 24 15:53:26 PDT 1989 Article 589 of comp.sources.games: Path: decwrl!ucbvax!tut.cis.ohio-state.edu!cs.utexas.edu!csd4.milw.wisc.edu!lll-winken!uunet!zephyr!tektronix!tekgen!tekred!saab!billr From: billr@saab.CNA.TEK.COM (Bill Randle) Newsgroups: comp.sources.games Subject: v06i080: tt - Tetris for ordinary terminals, Part01/02 Message-ID: <4124@tekred.CNA.TEK.COM> Date: 21 Jun 89 14:32:27 GMT Sender: news@tekred.CNA.TEK.COM Lines: 1615 Approved: billr@saab.CNA.TEK.COM Submitted-by: Mike Taylor Posting-number: Volume 6, Issue 80 Archive-name: tt/Part01 [Although the author states that this should compile on any BSD-based machine, he really means any 4.3BSD based machine. The game uses the FD_SET and FD_ZERO macros for select(2) that are part of in 4.3, but are missing in my 4.2 based Sun OS 3.5. One should be able to work around this, but I didn't take the time. -br] [From the author...] [[Here is my implementation of the game "Tetris"! We've had at least two versions of this come across the net recently, but both of them only work for Sun workstations, (ie. not on the ordinary textual terminals that most of us mortals use). So I wrote this one. It should compile on pretty well any Berkeley-based machine, the only non-standard thing I use is select(2), which SYSV'ers will have to replace with poll(2) ... If anyone converts this to SYSV, incidentally, please mail me a shar of your new version. I've included our current high-score file, just in case you want something to aim for. This might work for you, it might not. It should do on a Sun3 or 4 running SunOS 4.0.1, other than that, it is is very machine-dependent, 'cos it writes out structs directly. If it doesn't work, don't worry about it, just generate your own high-score table. The best score here so far is ... +------+-------------------------+---------+-------+--------+--------+------+ | 1 | Ruth 'Vashti' Glassboro | cstvrgj | 4422 | 629 | 241 | 0 | +------+-------------------------+---------+-------+--------+--------+------+ A special mention also goes to the follwing titanic score, since it was achieved of a pretty difficult level! +------+-------------------------+---------+-------+--------+--------+------+ | 3 | SAM | csttrgm | 4235 | 611 | 229 | -7 | +------+-------------------------+---------+-------+--------+--------+------+ Anyway, enough of this tedious mucking about in hyperspace, here's the shar you've all been waiting for.]] #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'README' <<'END_OF_FILE' XTetris for Terminals - "tt" - Written by Mike Taylor X==================================================== X X(1) Specification X================== X X This is a game written in C for Berkeley UNIX machines. It X was written on a Sun4 and has been extensively played and X tested on both this machine and a Sun3, both running SunOS X 4.0.1, a Berkeley 4.3bsd-derived UNIX, but it should port with X minimum difficulties to any Berkeley UNIX. One potential X difficulty to look out for is incompatibility of the high- X score file between different architectures, (eg. sun3 and X sun4). I've done all I can to make it work, but there are no X guarantees, and you might be better compiling multiple- X architecture "tt"s with different high-score tables. X X(2) Compilation X================ X X The Makefile as included should be pretty much applicable X anywhere. Simply edit the file "tt.h" to give the pathname of X the file you want to use for the high-score table, and unless X you are using the LOCKF #definition (also in tt.h), the file X you want to use as a lock for this table (which should be in a X publicly writeable directory). Then type "make". The program X will be compiled, producing a binary called "tt". This can X then be moved to a bin directory if required. X X(3) Acknowledgements X==================== X X The game Tetris was apparently designed by "a Russian X Researcher". Sadly, history, or at least the version of it X that I heard, does not record his name, and it seems that the X poor frog-head not only made no money from his brilliant idea, X but also didn't get any fame. 'S'sad. Still, I credit him X with the original idea, whoever he may be. X X The inspiration for this program came from two recently-posted X versions of Tetris, both of which run on Sun workstations X only. There are a few of these around here, but many people X have no access to them. I wrote this version so that everyone X could play. It's the communist in me :-) X X The program design, planning (hah!), coding, and so on for X this version was all done entirely by me, (Mike Taylor), and X very smug I am about it, too. The only exception to this is X that the game-levels were accidentally co-invented by James X "root@weed" Beckett, by pressing the space-bar too many times. X X Play-testing, which has been extensive even now, less than a X week after I started writing, has been by many people, but X special mention goes to Paul Metcalfe, Kenton Oatley, Harvey X "Max" Thomson and Paul "Freddy" Capper. No mention *at all*, X not even this one, goes to Mike "Sunny" Lessacher, who claims X not to like Tetris. ("Not like Tetris? What does that X mean?" :-) X X All documentation is also by me. X X(4) The Legal Position X====================== X X Look, I'm sorry, I *know* this bit is dull, but it has to be X done, and it saves time and trouble for everyone if we just X get on with it. here we go: X X The program "tt", its visual appearance, its code, its X documentation, etc., are the intellectual property of X Mike Taylor. The program may be freely distributed, X copied, modified, re-posted or whatever PROVIDED that X the authorship and ownership remains clear, and that X no-one makes any money from it without me knowing (and X taking a hefty cut!) Whoops, that's spoilt the X official feel to it. Never mind, you get the idea. X Do what you want provided it isn't sneaky. X X(5) The Game X============ X X Tetris is one of those simple-but-compulsive games that you X persistently find yourself wanting to play "just once more". X I know, I know what you're thinking, "We've all heard that X before!", but it just happens to be true on this occasion. X The object of the game is simply to prevent a stack of blocks X from building up to the top of the play-area, (to the left of X the screen). You do this by rotating and shifting the blocks X as they fall, in such a way as to make them fit together as X well as possible at the bottom. If you manage to complete a X whole line, from right to left, then that row will disappear, X and all the rows above will fall down into its place. it is X possible to get more than one row at once, and it is a X wonderful feeling to get four at once! X X There are five types of block, (seven if you count mirror X images), each of which is made up of four squares stuck X together, (hence "Tetris", I assume). These pieces score X different numbers of points based on how difficult or X otherwise it is to fit them into the place required. The X pieces are: X X ###### "T-shape" 1 point. X ## X X <><><><> "Long one" 2 points. X X () {} X () {} "L-shapes" 3 points. X ()() {}{} X X [][] "Square" 4 points. X [][] X X %% @@ "S-shapes" 5 points. X %%%% @@@@ X %% @@ X X Once a piece has been positioned where you want it, it can be X dropped into place immediately. There are no extra points for X this, 'cos I think it would spoil the purity of the scoring X system; it just speeds up the game. X X Hehehe, that reminds me, as the game progresses, it very very X gradually speeds up, until, at scores of around 3000, it gets X very difficult indeed ... I mean *very* difficult! X X The keys that control the pieces, and other special keys X (those used to Quit or Pause the game, or Refresh the screen) X are shown on the screen. They can be re-defined (except the X Refresh key), as explained in the manual page. X X(6) Basic Hints X================ X X Obviously, the aim is to keep the stack low, so at all times, X you should be on the lookout for ways to fit the currently- X falling piece onto those that have already fallen in such a X way as to complete rows. Removing rows is the key to success. X X However, if a lower section of the screen gets hopelessly X hole-ridden, it is sometimes best to forget about it, and X concentrate on building complete rows higher up, as this can X often be the best way to make the holes lower down become X available again. A good player can take over from a game 2/3 X full of badly-packed pieces, and eventually wrestle it right X back down to ground level again. X X Finally, there is a tendency among beginners to leave long, X thin gaps down the sides of the screen, praying for a "Long X one" to slide down it -- it is, of course, at precisely these X times that a succession of S-shapes, all of the same X handedness, comes pouring down the screen. The moral is X two-fold: (1) Beware of allowing such a situation to build up X in the first place, it often isn't necessary, and (2), learn X how to remove layers from higher up the screen with non-long X pieces, so your screen doesn't fill up while you wait for that X magical "Long one". X X(7) What to do if you don't like it X==================================== X X Email me at the following address: mirk@uk.ac.warwick.cs X In fact, email me if you *do* like it. Email me with X bouquets, brickbats, bugs, baboons, bachelors, blueberries, X boathouses, er ... um, sorry, I seem to have got a bit carried X away. Anyway, the point is, let me know what you think of X "tt", and it might just influence the next version. Also, I X am pretty good at replying to mail! X X That's it -- have fun! X______________________________________________________________________________ XMike Taylor - {Christ,M{athemat,us}ic}ian ... Email to: mirk@uk.ac.warwick.cs X "Imagine the universe perfect and whole and beautiful. Then be sure X of one thing: God has imagined it quite a bit better than you" END_OF_FILE if test 7370 -ne `wc -c <'README'`; then echo shar: \"'README'\" unpacked with wrong size! fi # end of 'README' fi if test -f 'MANIFEST' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'MANIFEST'\" else echo shar: Extracting \"'MANIFEST'\" \(1150 characters\) sed "s/^X//" >'MANIFEST' <<'END_OF_FILE' X File Name Archive # Description X----------------------------------------------------------- X Logo 2 Do with this what you will - advertise, maybe? X MANIFEST 1 This file -- a shipping list. X Makefile 2 See make(1) -- this file directs compilation. X README 1 General notes and instructions. X game.c 1 Module containing the main game loop. X game.h 2 Header file for above. X pieces.c 1 Module containing definitions of pieces. X pieces.h 2 Header file for above. X screen.c 1 Module containing all curses(3x)-based bits. X screen.h 2 Header file for above. X tt.6 1 Manual entry (install in /usr/man/man6) X tt.c 1 Module contaning main function and initialisation. X tt.h 2 Header file for above. X ttscores 2 Sample high-score table (might not work). X utils.c 2 Module containing re-usable general utilities. X utils.h 2 Header file for above. END_OF_FILE if test 1150 -ne `wc -c <'MANIFEST'`; then echo shar: \"'MANIFEST'\" unpacked with wrong size! fi # end of 'MANIFEST' fi if test -f 'game.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'game.c'\" else echo shar: Extracting \"'game.c'\" \(8962 characters\) sed "s/^X//" >'game.c' <<'END_OF_FILE' X/***************************************************************************\ X|* *| X|* game.c: A version of Tetris to run on ordinary terminals, *| X|* (ie., not needing a workstation, so should available *| X|* to peasant Newwords+ users. This is the module that *| X|* actually plays the game, (ie. moves things down the *| X|* screen, select(2)s on the keyboard, and so on) *| X|* *| X|* Author: Mike Taylor (mirk@uk.ac.warwick.cs) *| X|* Started: Fri May 26 12:26:05 BST 1989 *| X|* *| X\***************************************************************************/ X X#include X#include X#include X#include X X#include "tt.h" X#include "game.h" X#include "screen.h" X#include "pieces.h" X#include "utils.h" X X/*-------------------------------------------------------------------------*/ X Xextern long int random (); X X/*-------------------------------------------------------------------------*/ X Xchar left_key = LEFT_KEY; /* Move piece left */ Xchar right_key = RIGHT_KEY; /* Move piece right */ Xchar rotate_key = ROTATE_KEY; /* Rotate piece anticlockwise */ Xchar drop_key = DROP_KEY; /* Drop piece to bottom of screen */ Xchar susp_key = SUSP_KEY; /* Suspend. I'm sorry if its confusing */ Xchar quit_key = QUIT_KEY; /* Quit. I'm sorry if its confusing */ Xchar cheat_key = CHEAT_KEY; /* Do whatever I eventaully make it do */ X X/***************************************************************************\ X|* *| X|* Oh good grief, what on earth is the point of putting a huge, wobbly *| X|* box-comment in front of this titchy little self-explanatory function? *| X|* The name tells the whole story, it's perfectly strightforward, it's *| X|* not a proposition from Witgenstein. I wouldn't bother commenting it *| X|* at all if it wasn't for the fact that I know I'd feel guilty in the *| X|* morning. I mean, be fair, you don't really want a program where all *| X|* the functions *except one* have box-comments explaining them, do you? *| X|* Ah well, here we go for completion's sake: *| X|* *| X|* The function clear_board() takes no parameters and returns no value. *| X|* It clears the board. The end. *| X|* *| X\***************************************************************************/ X Xvoid clear_board () X{ X int i, j; X X for (i = 0; i < GAME_DEPTH+4; i++) X for (j = 0; j < GAME_WIDTH; j++) X board[i][j] = PI_EMPTY; X} X X/***************************************************************************\ X|* *| X|* The function play_game is the main loop in which the game of Tetris *| X|* is implemented. It takes no parameters, and returns no value. The *| X|* time at which it returns no value is the end of a game. The main *| X|* loop-component is a select(2) which polls the keyboard in real-time. *| X|* If you use a non-Berkeley UNIX(tm), you're well cheesed. *| X|* *| X|* Actually, I have to admit I'm not proud of this one. It must be one *| X|* of the messiest functions I've written in years, in terms of nested *| X|* loops with ad-hoc exit conditions, re-used variables, general *| X|* obfuscation and so on. I wanna make it quite clear that I make no *| X|* apologies for my use of "goto", which remains a highly desirable *| X|* language feature, and is still the most elegant way of coding many *| X|* things, but I gotta admit, overall this one is bit of a chicken. *| X|* *| X\***************************************************************************/ X Xvoid play_game () X{ X int i; /* Position of origin down board */ X int j; /* Position of origin across board */ X int k; /* Loop variable when i,j are invariant */ X int piece_no; /* Type of piece currently falling */ X int orient; /* Which way it is facing */ X int pause_flag = 0; /* We won't pause unless told to */ X int presses_left; /* Futher moves possible this drop */ X int free_left = game_level; /* Number of pieces to drop at start */ X fd_set read_fds; /* Select must look at stdin */ X long int total_time = 200000; /* Allow 1/4 second before falling */ X struct timeval timeout; /* Time before piece drops in select(2) */ X char ch; /* Keystroke (command) */ X X score = no_levels = no_pieces = 0; X update_scores (); X clear_board (); X X while (1) { X piece_no = (int) (random () % NO_PIECES); X orient = (int) (random () % NO_ORIENTS); X i = -2; /* Start falling from off-screen */ X if (free_left > 0) /* If we have random starting-pieces */ X j = 1 + (int) (random () % (GAME_WIDTH-2)); X else /* places them randomly, otherwise */ X j = GAME_WIDTH/2; /* put the piece in the middle */ X X if (!can_place (piece_no, orient, i, j)) { X for (k = 0; k < 9; k++) { /* Crude but (hopefully) effective */ X draw_piece (piece_no, orient, i, j, PD_ERASE); X myrefresh (); usleep (80000); X draw_piece (piece_no, orient, i, j, PD_DRAW); X myrefresh (); usleep (80000); X } X break; /* End of game - piece won't fit */ X } X X if (free_left != 0) { /* If there are pieces to be dropped, */ X if (free_left > 0) /* And the number is positiive, */ X free_left--; /* Then decrement it, otherwise */ X else /* increment it, in any case, bring */ X free_left++; /* it closer to zero if it gets to zero */ X if (free_left == 0) /* set a flag so that the game will */ X pause_flag = 1; /* pause. Then go to the bit of code */ X goto DROP_PIECE; /* that drops it. */ X } X X while (1) { X presses_left = NO_MOVES; X draw_piece (piece_no, orient, i, j, PD_DRAW); X update_scores (); X myrefresh (); X X while (1) { X FD_ZERO (&read_fds); X FD_SET (0, &read_fds); X timeout.tv_sec = 0; X timeout.tv_usec = total_time; X switch (select (((presses_left > 0) && (i >= 0)), &read_fds, X (fd_set*) NULL, (fd_set*) NULL, &timeout)) { X case -1: X if (errno != EINTR) X die (LE_SELECT, "select(2) failed in play_game()"); X /* Otherwise fall through, goto TIMEOUT */ X case 0: X goto TIMEOUT; X default: X if (read (0, &ch, 1) == -1) X die (LE_READ, "read(2) failed in play_game()"); X X if (ch == REFRESH_KEY) X hoopy_refresh (); X X if ((ch != left_key) && (ch != right_key) && (ch != rotate_key) && X (ch != drop_key) && (ch != quit_key) && (ch != susp_key) && X (ch != cheat_key)) X break; X X presses_left--; X if (ch == left_key) { X if (can_place (piece_no, orient, i, j-1)) { X draw_piece (piece_no, orient, i, j, PD_ERASE); X j--; X draw_piece (piece_no, orient, i, j, PD_DRAW); X myrefresh (); X } X } X X else if (ch == right_key) { X if (can_place (piece_no, orient, i, j+1)) { X draw_piece (piece_no, orient, i, j, PD_ERASE); X j++; X draw_piece (piece_no, orient, i, j, PD_DRAW); X myrefresh (); X } X } X X else if (ch == rotate_key) { X int new_or = ((rotate_backwards == 0) ? ((orient+1)%NO_ORIENTS) : X ((orient-1)%NO_ORIENTS+NO_ORIENTS*(orient == 0))); X if (can_place (piece_no, new_or, i, j)) { X draw_piece (piece_no, orient, i, j, PD_ERASE); X orient = new_or; X draw_piece (piece_no, orient, i, j, PD_DRAW); X myrefresh (); X } X } X X else if (ch == drop_key) { X DROP_PIECE: X while (can_place (piece_no, orient, i+1, j)) { X draw_piece (piece_no, orient, i, j, PD_ERASE); X i++; X draw_piece (piece_no, orient, i, j, PD_DRAW); X myrefresh (); X } X goto TIMEOUT; X } X X else if (ch == quit_key) X return; X X else if (ch == cheat_key) { X print_msg ("CHEAT!"); X total_time = 0L; X } X X else if (ch == susp_key) { X print_msg ("Paused"); X (void) read (0, &ch, 1); X print_msg (""); X } X X break; X } X } X X TIMEOUT: X if (!can_place (piece_no, orient, i+1, j)) { X place_piece (piece_no, orient, i, j); X score += pieces[piece_no].points; X no_pieces++; X update_scores (); X myrefresh (); X X for (i = 0; i < GAME_DEPTH; i++) { X for (j = 0; j < GAME_WIDTH; j++) X if (board[i+4][j] == PI_EMPTY) X break; X X if (j == GAME_WIDTH) { X no_levels++; X score += 10; X update_scores (); X for (k = i; k > 0; k--) X for (j = 0; j < GAME_WIDTH; j++) X board[k+4][j] = board[k+3][j]; X for (j = 0; j < GAME_WIDTH; j++) X board[4][j] = PI_EMPTY; X draw_board (); X myrefresh (); X i--; /* Check the new row i */ X } X } X X if (pause_flag) { /* If we are pausing after this drop ... */ X pause_flag = 0; /* Ensure we don't do so next time. */ X flush_keyboard (); X print_msg ("Continue"); X (void) read (0, &ch, 1); X print_msg (""); X } X X break; /* End of fall - piece has hit floor */ X } X X draw_piece (piece_no, orient, i, j, PD_ERASE); X i++; X } X X myrefresh (); X if (total_time != 0) X total_time -= 100; X } X} X X/*-------------------------------------------------------------------------*/ END_OF_FILE if test 8962 -ne `wc -c <'game.c'`; then echo shar: \"'game.c'\" unpacked with wrong size! fi # end of 'game.c' fi if test -f 'pieces.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'pieces.c'\" else echo shar: Extracting \"'pieces.c'\" \(2102 characters\) sed "s/^X//" >'pieces.c' <<'END_OF_FILE' X/***************************************************************************\ X|* *| X|* pieces.c: A version of Tetris to run on ordinary terminals, *| X|* (ie., not needing a workstation, so should available *| X|* to peasant Newwords+ users. This module contains the *| X|* definitions of the pieces. *| X|* *| X|* Author: Mike Taylor (mirk@uk.ac.warwick.cs) *| X|* Started: Fri May 26 12:26:05 BST 1989 *| X|* *| X\***************************************************************************/ X X#include "tt.h" X#include "pieces.h" X X/*-------------------------------------------------------------------------*/ X Xstruct piece pieces[NO_PIECES] = { X { "[]", 4, /* Square piece */ X { X {{0,0}, {0,1}, {1,0}, {1,1}}, X {{0,0}, {0,1}, {1,0}, {1,1}}, X {{0,0}, {0,1}, {1,0}, {1,1}}, X {{0,0}, {0,1}, {1,0}, {1,1}} X } X }, X X { "<>", 2, /* Long piece */ X { X {{0,0}, {1,0}, {2,0}, {3,0}}, X {{1,-1}, {1,0}, {1,1}, {1,2}}, X {{0,0}, {1,0}, {2,0}, {3,0}}, X {{1,-1}, {1,0}, {1,1}, {1,2}} X } X }, X X { "()", 3, /* L-shaped piece */ X { X {{0,0}, {1,0}, {2,0}, {2,1}}, X {{0,1}, {1,-1}, {1,0}, {1,1}}, X {{0,-1}, {0,0}, {1,0}, {2,0}}, X {{1,-1}, {1,0}, {1,1}, {2,-1}} X } X }, X X { "{}", 3, /* Backwards L-shaped piece */ X { X {{0,0}, {1,0}, {2,-1}, {2,0}}, X {{1,-1}, {1,0}, {1,1}, {2,1}}, X {{0,0}, {0,1}, {1,0}, {2,0}}, X {{0,-1}, {1,-1}, {1,0}, {1,1}} X } X }, X X { "##", 1, /* T-shaped piece */ X { X {{1,-1}, {1,0}, {1,1}, {2,0}}, X {{0,0}, {1,0}, {1,1}, {2,0}}, X {{0,0}, {1,-1}, {1,0}, {1,1}}, X {{0,0}, {1,-1}, {1,0}, {2,0}} X } X }, X X { "%%", 5, /* S-shaped piece */ X { X {{0,0}, {0,1}, {1,-1}, {1,0}}, X {{0,-1}, {1,-1}, {1,0}, {2,0}}, X {{0,0}, {0,1}, {1,-1}, {1,0}}, X {{0,-1}, {1,-1}, {1,0}, {2,0}} X } X }, X X { "@@", 5, /* Backwards S-shaped piece */ X { X {{0,-1}, {0,0}, {1,0}, {1,1}}, X {{0,0}, {1,-1}, {1,0}, {2,-1}}, X {{0,-1}, {0,0}, {1,0}, {1,1}}, X {{0,0}, {1,-1}, {1,0}, {2,-1}} X } X }, X}; X X/*-------------------------------------------------------------------------*/ END_OF_FILE if test 2102 -ne `wc -c <'pieces.c'`; then echo shar: \"'pieces.c'\" unpacked with wrong size! fi # end of 'pieces.c' fi if test -f 'screen.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'screen.c'\" else echo shar: Extracting \"'screen.c'\" \(11377 characters\) sed "s/^X//" >'screen.c' <<'END_OF_FILE' X/***************************************************************************\ X|* *| X|* screen.c: A version of Tetris to run on ordinary terminals, *| X|* (ie., not needing a workstation, so should available *| X|* to peasant Newwords+ users. This module handles all *| X|* the icky curses(3x) bits. *| X|* *| X|* Author: Mike Taylor (mirk@uk.ac.warwick.cs) *| X|* Started: Fri May 26 12:26:05 BST 1989 *| X|* *| X\***************************************************************************/ X X#include X#include X#include X#include X X#include "screen.h" X#include "tt.h" X#include "pieces.h" X#include "utils.h" X#include "game.h" X X/***************************************************************************\ X|* *| X|* The function myrefresh() calls the curses(3x) function refresh() *| X|* after first moving the cursor out of harm's way at the bottom of *| X|* the screen. *| X|* *| X\***************************************************************************/ X Xvoid myrefresh () X{ X int x; X X if ((x = screen_depth-2) < GAME_DEPTH+1) X x = GAME_DEPTH+1; X X move (x, 0); X refresh (); X} X X/***************************************************************************\ X|* *| X|* The function hoopy_refresh() touches the screen, then refreshes it, *| X|* so curses(3X) doesn't get any chance to phlegm about with only doing *| X|* the bits that it thinks are OK. So this should fix up screens that *| X|* have been interfered with by biff(1), mesg(1) etc. *| X|* *| X\***************************************************************************/ X Xvoid hoopy_refresh () X{ X clear (); X setup_screen (); X draw_board (); X update_scores (); X myrefresh (); X} X X/***************************************************************************\ X|* *| X|* The function print_msg() prints a short message centered on the *| X|* floor of the playing area, with a space before and after it. *| X|* If the message is NULL (ie. (char*)0), or null, (ie. ""), then *| X|* no message is printed. *| X|* *| X\***************************************************************************/ X Xvoid print_msg (line) X char *line; X{ X int i; X X move (GAME_DEPTH, 2); X for (i = 0; i < 2*GAME_WIDTH; i++) X addch (FLOOR_CHAR); X X if ((line != NULL) && (*line != '\0')) X mvaddstr (GAME_DEPTH, GAME_WIDTH+1-(strlen (line))/2, X form (" %s ", line)); X myrefresh (); X} X X/***************************************************************************\ X|* *| X|* The function clear_area() fills the playing area with BLANK_CHARs. *| X|* It is used to clear the screen before each game, and also clearing *| X|* the screen while displaying pieces one at a time when in debug mode. *| X|* *| X\***************************************************************************/ X Xvoid clear_area () X{ X int i, j, status, fastflag = 0; X fd_set fds; X struct timeval timeout; X static char buffer[LINELEN]; X X for (i = 0; i < GAME_DEPTH; i++) { X move (i, 2); X for (j = 0; j < 2*GAME_WIDTH; j++) X addch (BLANK_CHAR); X X if (fastflag == 0) { X move (i+1, 2); X for (j = 0; j < 2*GAME_WIDTH; j++) X addch (FLOOR_CHAR); X myrefresh (); X X FD_ZERO (&fds); X FD_SET (0, &fds); X timeout.tv_sec = 0L; /* Future implementations of select(2) */ X timeout.tv_usec = 50000L; /* might change this value on return */ X if ((status = select (1, &fds, (fd_set*) NULL, (fd_set*) NULL, X &timeout)) == -1) X if (errno != EINTR) X die (LE_SELECT, "select(2) failed in clear_area()"); X X if (status != 0) { X fastflag = 1; X (void) read (0, buffer, LINELEN); X } X } X } X X move (GAME_DEPTH, 2); X for (j = 0; j < 2*GAME_WIDTH; j++) X addch (FLOOR_CHAR); X X mvaddch (GAME_DEPTH, 0, CORNER_CHAR); X addch (CORNER_CHAR); X mvaddch (GAME_DEPTH, 2*GAME_WIDTH+2, CORNER_CHAR); X addch (CORNER_CHAR); X X myrefresh (); X} X X/***************************************************************************\ X|* *| X|* The function setup_screen should be called exactly once, near the *| X|* beginning of execution. It initialises curses(3x), and prints the *| X|* game titles, the walls and the floor of the game area, and clears *| X|* this area using clear_area() (perhaps unsurprisingly) *| X|* *| X|* STOP PRESS: It no longer calls clear_area(), since play_game() *| X|* does that fairly immediately after this function returns. *| X|* *| X|* STOP PRESS^2: It now does do it again, since play_game() does the *| X|* sneaky-but-slow clear that it fine at times, but cruddy for *| X|* initialisation. *| X|* *| X\***************************************************************************/ X Xvoid setup_screen () X{ X int i; X X for (i = 0; i < GAME_DEPTH; i++) { X mvaddch (i, 0, WALL_CHAR); X addch (WALL_CHAR); X mvaddch (i, 2*GAME_WIDTH+2, WALL_CHAR); X addch (WALL_CHAR); X } X X move (GAME_DEPTH, 2); X for (i = 0; i < 2*GAME_WIDTH; i++) X addch (FLOOR_CHAR); X X mvaddch (GAME_DEPTH, 0, CORNER_CHAR); X addch (CORNER_CHAR); X mvaddch (GAME_DEPTH, 2*GAME_WIDTH+2, CORNER_CHAR); X addch (CORNER_CHAR); X X mvaddstr (0, 2*GAME_WIDTH+6, form ("%sTETRIS FOR TERMINALS%*s%s", X so_str, so_gunk, "", se_str)); X mvaddstr (2, 2*GAME_WIDTH+6, "Written by Mike Taylor"); X mvaddstr (3, 2*GAME_WIDTH+6, "Email: mirk@uk.ac.warwick.cs"); X mvaddstr (4, 2*GAME_WIDTH+6, "Started: Fri May 26 12:26:05 BST 1989"); X mvaddstr (6, 2*GAME_WIDTH+6, form ("Game level: %d", game_level)); X mvaddstr (8, 2*GAME_WIDTH+6, "Score:"); X mvaddstr (9, 2*GAME_WIDTH+6, "Pieces:"); X mvaddstr (10, 2*GAME_WIDTH+6, "Levels:"); X X mvaddstr (12, 2*GAME_WIDTH+8, "Use keys:"); X mvaddstr (13, 2*GAME_WIDTH+8, "========="); X mvaddstr (14, 2*GAME_WIDTH+8, form ("Move left: '%c'", left_key)); X mvaddstr (15, 2*GAME_WIDTH+8, form ("Move right: '%c'", right_key)); X mvaddstr (16, 2*GAME_WIDTH+8, form ("Rotate: '%c'", rotate_key)); X mvaddstr (17, 2*GAME_WIDTH+8, form ("Drop: '%c'", drop_key)); X mvaddstr (18, 2*GAME_WIDTH+8, form ("Pause: '%c'", susp_key)); X mvaddstr (19, 2*GAME_WIDTH+8, form ("Quit: '%c'", quit_key)); X mvaddstr (20, 2*GAME_WIDTH+8, "Refresh: '^L'"); X} X X/***************************************************************************\ X|* *| X|* The function setup_curses should be called exactly once, near the *| X|* beginning of execution. It initialises curses(3x), and notes that *| X|* this has been done, by setting the global variable in_curses. *| X|* *| X\***************************************************************************/ X Xvoid setup_curses () X{ X initscr (); X clear (); X#ifndef LINT X noecho (); X cbreak (); X#endif /* LINT */ X in_curses = 1; X} X X/***************************************************************************\ X|* *| X|* The function update_scores() puts the sepecified values of score, *| X|* no_pieces and no_levels on the screen in the specified positions. *| X|* *| X\***************************************************************************/ X Xvoid update_scores () X{ X move (8, 34); X clrtoeol (); X addstr (form ("%d", score)); X X move (9, 34); X clrtoeol (); X addstr (form ("%d", no_pieces)); X X move (10, 34); X clrtoeol (); X addstr (form ("%d", no_levels)); X} X X/***************************************************************************\ X|* *| X|* The function draw_board() puts the current state of the global array *| X|* board[] ontop the curses(3x) screen, then refresh()es it. *| X|* *| X\***************************************************************************/ X Xvoid draw_board () X{ X int i, j; X X for (i = 0; i < GAME_DEPTH; i++) X for (j = 0; j < GAME_WIDTH; j++) X if (board[i+4][j] == PI_EMPTY) { X mvaddch (i, 2*j+2, BLANK_CHAR); X addch (BLANK_CHAR); X } X else X mvaddstr (i, 2*j+2, pieces[board[i+4][j]].app); X} X X/***************************************************************************\ X|* *| X|* The function draw_piece draws or erases one of the tetris pieces on *| X|* the screen in a specified orientation and position. The form of the *| X|* function is: *| X|* *| X|* draw_piece (piece_no, orientation, y, x, flag) *| X|* *| X|* All the arrguments are integers. Flag is either PD_DRAW or *| X|* PD_ERASE, specifying the effect of the function. Piece_no is *| X|* between 0 and 6 inclusive, and specifies what sort of piece is *| X|* required. Orientation is between 0 and 3 inclusive, and states *| X|* which way up the piece is to be drawn, and y and x express the *| X|* position as an index into the GAME_DEPTH by GAME_WIDTH array *| X|* that is the board. *| X|* *| X\***************************************************************************/ X Xvoid draw_piece (piece_no, orient, y, x, flag) X int piece_no; X int orient; X int y; X int x; X int flag; X{ X int i; X extern WINDOW *stdscr; X X for (i = 0; i < NO_SQUARES; i++) X if (y+pieces[piece_no].index[orient][i][0] >= 0) X if (flag == PD_ERASE) { X mvaddch (y+pieces[piece_no].index[orient][i][0], X (2*(x+pieces[piece_no].index[orient][i][1]))+2, X BLANK_CHAR); X addch (BLANK_CHAR); X } X else X mvaddstr (y+pieces[piece_no].index[orient][i][0], X (2*(x+pieces[piece_no].index[orient][i][1]))+2, X pieces[piece_no].app); X} X X/***************************************************************************\ X|* *| X|* The function place_piece takes the same parameters as draw_piece, *| X|* except for the flag, and does not draw the piece, but places it *| X|* on the board. No checking is done to see if it will fit, since *| X|* should already have been done by can_place(). *| X|* *| X\***************************************************************************/ X Xvoid place_piece (piece_no, orient, y, x) X int piece_no; X int orient; X int y; X int x; X{ X int i; X X for (i = 0; i < NO_SQUARES; i++) X board[y+4+pieces[piece_no].index[orient][i][0]] X [x+pieces[piece_no].index[orient][i][1]] = piece_no; X} X X/***************************************************************************\ X|* *| X|* The function can_place takes the same parameters as place_piece, *| X|* It does not draw the piece, nor place it on the board, but returns *| X|* an integer value -- 0 if the piece will not fit on the board, 1 if *| X|* it will (with the specified orientation, position, etc.) *| X|* *| X\***************************************************************************/ X X#define HERE(x) pieces[piece_no].index[orient][i][x] X Xint can_place (piece_no, orient, y, x) X int piece_no; X int orient; X int y; X int x; X{ X int i; X X for (i = 0; i < NO_SQUARES; i++) X if (((x+HERE(1) >= GAME_WIDTH) || /* Off right of screen or */ X (x+HERE(1) < 0)) || /* Off left of screen */ X (y+HERE(0) >= GAME_DEPTH) || /* Off bottom of screen */ X (board[y+4+HERE(0)][x+HERE(1)] != PI_EMPTY)) X /* Board position not empty */ X return (0); X X return (1); X} X X/*-------------------------------------------------------------------------*/ END_OF_FILE if test 11377 -ne `wc -c <'screen.c'`; then echo shar: \"'screen.c'\" unpacked with wrong size! fi # end of 'screen.c' fi if test -f 'tt.6' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'tt.6'\" else echo shar: Extracting \"'tt.6'\" \(3838 characters\) sed "s/^X//" >'tt.6' <<'END_OF_FILE' X.\" @(#)tt.6 1.7 89/06/05 SMI; from Mirksoft X.TH TT 6 "1 February 1983" X.SH NAME Xtt \- a hoopy real-time puzzle-game. X.SH SYNOPSIS X.B tt X[ X.B -s X| X.B -s# X] [ X.B -b X] [ X.B -l# X] X.SH DESCRIPTION XThe program X.B tt Xis an implementation of the well-known game X.B Tetris. XQuadominoes (groups of four squares joined orthogonally together) fall Xslowly down the screen, accumulating at the bottom, and when the pile Xreaches to top of the screen the game is over. The pieces may be Xmoved to the left or right, and rotated as they fall, with the aim of Xmaking them tessellate with the pieces already at the bottom of the Xgame area. The height of the stack of pieces can be reduced by Xfilling a complete row of 10 squares, at which point that row will Xdisappear, and those above will fall down into its place. It is Xpossible (and desirable) to destroy multiple rows at once. X.LP XThe keys with which these operations can be accomplished are displayed Xon the screen during play. They can be redefined if necessary, (see Xthe section below on the environment variable TTKEYS). The game also Xrecognises a suspend key and a quit key, with which the game can be Xsuspended or quit. Incredible, huh? I mean, who would have thought Xit? X.LP XIn between games, when the program is waiting for a keypress before Xrestarting, pressing the "n" or "q" key will end the session, and Xpressing the "s" key will list the top ten entries of the high-score Xtable. X.LP XThe high-score table stores only a single score for each user at any Xgame-level. Thus a user exceeding his own level-0 high-score would Xhave his old entry in the high-score table (if any) replaced with the Xnew score. However, a single user may have multiple high-score table Xentries for different game-levels. X.LP XThe author recommends that the optimal game-levels are 0, 10 and -6 X.SH FLAGS X.IP -s XIf the X.B -s Xflag is set, then X.B tt Xwill print the top 10 entries in the high-score table. X.IP -s# XIf a number is specified, then X.B tt Xwill print that many high-score entries, up to a pre-defined maximum. X.IP -b XIf the X.B -b Xflag is set, then X.B tt Xwill rotate pieces backwards (ie. clockwise), for compatibility with Xthe grotty versions of Tetris available on the BBC micro and other such Xmachines. (The default anticlockwise rotation is compatible with the X.B tetris Xand X.B mex Xprogram mentioned below) X.IP -l# XIf the X.B -l Xflag is set, then X.B tt Xwill play on the level specified, which must be between -10 and 20. XEach level of play starts at the same speed, and increases in speed at Xthe same rate. They are differentiated only by the fact that non-zero Xlevels drop a number of pieces, equal to the absolute value of the Xlevel, onto the screen before the game starts. Negative levels drop Xpieces down the middle of the screen, positive levels place them Xrandomly. X.SH ENVIRONMENT XThe environment variable X.B TTKEYS Xcontains, if set, the keys which will be used respectively for the Xoperations move left, move right, rotate piece, drop piece, suspend Xgame, and quit game. The keys ^L (redraw screen) and s (print the Xhigh-score table, when pressed between games) cannot be rebound. X.LP XThe environment variable X.B TTNAME Xcontains, if set, the name which will be used in the high-score table, Xif a good enough score is obtained to merit inclusion. If this Xvariable is not set, X.B tt Xwill use the environment variable X.B NAME Xand if this is also not set, the user-code will be used. X.SH FILES X/poppy/ma/ujf/open/lib/ttscores \-- high-score table. X.br X/poppy/ma/ujf/open/lib/junk/ttlock \-- lock file for high-score table. X.SH "SEE ALSO" Xmundi(6), tetris(6), mex(6) X.SH AUTHOR XThe program X.B tt Xwas written by Mike Taylor (mirk@uk.ac.warwick.cs), based on the Xoriginal Tetris idea, by a frustratingly anonymous "Russian XResearcher". X.SH BUGS XNone known \-- Please report any bugs to the author. END_OF_FILE if test 3838 -ne `wc -c <'tt.6'`; then echo shar: \"'tt.6'\" unpacked with wrong size! fi # end of 'tt.6' fi if test -f 'tt.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'tt.c'\" else echo shar: Extracting \"'tt.c'\" \(15896 characters\) sed "s/^X//" >'tt.c' <<'END_OF_FILE' X/***************************************************************************\ X|*+-----------------------------------------------------------------------+*| X|*| |*| X|*| tt.c: A version of Tetris to run on ordinary terminals, |*| X|*| (ie., not needing a workstation, so should available |*| X|*| to peasant Newwords+ users. |*| X|*| |*| X|*| Author: Mike Taylor (mirk@uk.ac.warwick.cs) |*| X|*| Started: Fri May 26 12:26:05 BST 1989 |*| X|*| |*| X|*| Oooh look, I've just invented a new, chunkier kind of |*| X|*| comment-box. I can't decide yet whether I like it. |*| X|*| |*| X|*+-----------------------------------------------------------------------+*| X\***************************************************************************/ X X#include X#include X#include X#include X#include X#include X#include X#include X#include X X#include "tt.h" X#include "utils.h" X#include "screen.h" X#include "game.h" X X/*-------------------------------------------------------------------------*/ X Xextern time_t time (); Xextern char *ctime (); Xextern char *malloc (); Xextern char *getenv (); Xextern char *getlogin (); Xextern char *sprintf (); X X/*-------------------------------------------------------------------------*/ X Xint screen_depth; /* To be calculated by termcap(3) */ Xint screen_width; /* To be calculated by termcap(3) */ Xint so_gunk; /* To be calculated by termcap(3) */ Xint in_curses = 0; /* Set to 1 after initialisation */ Xint rotate_backwards = 0; /* If set non-zero, rotate clockwise */ Xint no_hiscores = 0; /* Number of hi-scores in the table */ Xint no_shown = NO_SHOWN; /* Number of hi-scores to list */ Xint game_level = 0; /* Number of free pieces */ Xint score; /* Accumulated game score */ Xint no_pieces; /* Number of pieces dropped so far */ Xint no_levels; /* Number of levels filled & deleted */ Xchar prog_name[LINELEN]; /* Will be the basename of argv[0] */ Xchar user_name[NAMELEN]; /* From environment: TTNAME or NAME */ Xchar user_code[CODELEN]; /* From getpwuid(getuid())->pw_name */ Xint board[GAME_DEPTH+4][GAME_WIDTH]; Xstruct score_ent hi_scores[NO_HISCORES]; X Xchar tc_string[LINELEN]; /* Needed as static storage for the awful */ Xchar *so_str; /* ... tgetstr() function. so_str and ... */ Xchar *se_str; /* ... se_str point into it. */ X X/***************************************************************************\ X|* *| X|* This function is called if a SIGHUP, SIGINT or SIGTERM is caught, *| X|* and merely returns the user to standard terminal modes, (ie. exits *| X|* from curses(3X) before die()ing. *| X|* *| X\***************************************************************************/ X Xvoid signal_end () X{ X if (in_curses) X print_msg ("Aborted!"); X X die (LE_OK, ""); X} X X/***************************************************************************\ X|* *| X|* The function get_scores() reads the contents of the global array *| X|* hi_scores from the file named in the #definition of SCORE_FILE. *| X|* It also sets no_hiscores to the number of scores in the table. *| X|* *| X\***************************************************************************/ X Xvoid get_scores () X{ X int fd; X struct stat stat_buf; X X if ((fd = open (SCORE_FILE, O_RDONLY)) == -1) { X if (errno != ENOENT) X die (LE_OPEN, "couldn't open(2) high-score file for reading"); X else { X no_hiscores = 0; X return; X } X } X X if (fstat (fd, &stat_buf) == -1) X die (LE_STAT, "couldn't stat(2) high-score file"); X no_hiscores = stat_buf.st_size/sizeof (struct score_ent); X X if (read (fd, (char*) hi_scores, (int) stat_buf.st_size) == -1) { X (void) perror ("read()"); X die (LE_READ, "couldn't read(2) high-score file"); X } X X (void) close (fd); X} X X/***************************************************************************\ X|* *| X|* The function print_scores() gets the table in from the disk_file, *| X|* and prints it in a nice, human-readable format. *| X|* *| X\***************************************************************************/ X Xvoid print_scores () X{ X int i; X X get_scores (); X if (no_hiscores == 0) X (void) puts ("There are no high-scores (yet!)"); X else { X (void) puts ("+------+-------------------------+---------+-------+--------+--------+------+"); X (void) puts ("| Rank | Name | Code | Score | Pieces | Levels | Type |"); X (void) puts ("+------+-------------------------+---------+-------+--------+--------+------+"); X for (i = 0; i < ((no_hiscores < no_shown) ? no_hiscores : no_shown); i++) X (void) printf ("|%5d | %-*.*s| %-8.8s|%6d |%7d |%7d |%5d |\n", X i+1, NAMELEN, NAMELEN, hi_scores[i].name, X hi_scores[i].code, hi_scores[i].score, X hi_scores[i].no_pieces, hi_scores[i].no_levels, X hi_scores[i].game_level); X (void) puts ("+------+-------------------------+---------+-------+--------+--------+------+"); X } X} X X/***************************************************************************\ X|* *| X|* The function update_file puts a lock on the high-score-file, and *| X|* then (if successful), reads the high-scores, inserts the current *| X|* score in the table if it's good enough, and writes it back if it *| X|* has changed, finally removing the lock. It returns the player's *| X|* position in the table, or 0 if he is unplaced. If we were unable *| X|* to get the lock-file, it returns -1. *| X|* *| X|* I'm about to do some mods allowing compilation to be done with *| X|* the flag -DLOCKF to do the mutual exclusion using lockf(3) instead *| X|* a lock-file. This will be unneccesary on most systems, but here on *| X|* the Warwick systems, some users have titchy quotas that cause them *| X|* to be unable to create a lock-file, and thus to use the high-score *| X|* table. I'm using lockf(3) instead of flock(2) since it works across *| X|* machines. *| X|* *| X|* Apologies for the cruddy way I've written this function. It has had *| X|* bits added onto it in a very ad-hoc way, including the #ifdef'd bits *| X|* that determine what locking mechanism is used, and the result is, *| X|* shall we say, sub-optimal elegance. Particularly nasty is that way *| X|* that when the lockf(3)ing locking mechanism is used, we maintain *| X|* two open file-descriptors at once on the same file, but it's the *| X|* quickest and easiest way to use the existing get_scores() function, *| X|* and it does at least work. Since I don't anticipate anything else *| X|* significant being added to the function, I'm going to leave it as it *| X|* is, and not tidy it up unless I feel *really* guilty in the morning. *| X|* *| X\***************************************************************************/ X Xint update_file () X{ X int i = 0, j = 0, k = 0; /* Sundry re-usable loop-indices */ X int score_fd; /* The fd through which we write new file */ X int lock_fd; /* If LOCKF is defined, we use this as */ X /* an auxiliary fd on the SCORE_FILE, one */ X /* that stays open all the time, so we can */ X /* use lockf(). Otherwise, it is the fd */ X /* that we open to the LOCK_FILE */ X X (void) umask (0000); /* 000 octal, just to make the point */ X X#ifdef LOCKF X if ((lock_fd = open (SCORE_FILE, O_RDWR | O_CREAT, 0666)) == -1) X die (LE_OPEN, "Couldn't open(2) score-file for lockf()"); X X while (i++ < 5) { /* Make up to five attempts */ X if (lockf (lock_fd, F_TLOCK, 0) != -1) X break; /* If we succeed, then carry on */ X /* Otherwise, if not due to exclusivity */ X if (errno != EAGAIN) { /* then die with a system error */ X print_msg ("lockf(3) error!"); X (void) get_key (); X return (-1); X } X X print_msg ("Hi-score access:"); X sleep (1); /* Back off and wait ... */ X print_msg (""); /* Then try again */ X } X#else /* LOCKF */ X while (i++ < 5) { /* Make up to five attempts */ X if ((lock_fd = open (LOCK_FILE, O_CREAT | O_EXCL, 0666)) != -1) X break; /* If we succeed, then carry on */ X /* Otherwise, if not due to exclusivity */ X if (errno != EEXIST) { /* then die with a system error */ X print_msg ("open(2) error!"); X (void) get_key (); X return (-1); X } X X print_msg ("Hi-score access:"); X sleep (1); /* Back off and wait ... */ X print_msg (""); /* Then try again */ X } X#endif /* LOCKF */ X X if (i > 5) /* If we tried 5 times unsuccessfully, */ X return (-1); /* Then give up and return -1 instead */ X X get_scores (); X for (i = 0; i < no_hiscores; i++) { X if (((!strcmp (user_code, hi_scores[i].code)) && X (game_level == hi_scores[i].game_level)) && X ((score < hi_scores[i].score) || X ((score == hi_scores[i].score) && X ((no_pieces < hi_scores[i].no_pieces) || X ((no_pieces == hi_scores[i].no_pieces) && X (no_levels < hi_scores[i].no_levels)))))) { X i = NO_HISCORES; /* If the same user has a better score */ X break; /* on the same level, then drop this one */ X } X X if ((score > hi_scores[i].score) || X ((score == hi_scores[i].score) && X ((no_pieces > hi_scores[i].no_pieces) || X ((no_pieces == hi_scores[i].no_pieces) && X ((no_levels > hi_scores[i].no_levels) || X ((no_levels == hi_scores[i].no_levels) && X ((game_level >= hi_scores[i].game_level)))))))) /* Lisp :-) */ X break; /* i is the new position of the player */ X } X X if (i == NO_HISCORES) { /* If we looped off the end of the array */ X (void) close (lock_fd); /* then the score isn't good enough: */ X#ifndef LOCKF /* Automagically removes advisory lockf() */ X (void) unlink (LOCK_FILE); X#endif /* LOCKF */ X return (0); X } X /* If there is a matching score lower down */ X /* the file, set j to it, (otherwise to i) */ X for (j = NO_HISCORES-1; j >= i; j--) X if ((!strcmp (user_code, hi_scores[j].code)) && X (game_level == hi_scores[j].game_level)) X break; X X /* No duplicate score found, so just */ X if (j < i) { /* shunt up all other scores. */ X for (j = NO_HISCORES-1; j > i; j--) X bcopy ((char*) &hi_scores[j-1], (char*) &hi_scores[j], X sizeof (struct score_ent)); X if (no_hiscores < NO_HISCORES) X no_hiscores++; X } X else { /* j points at a duplicate score of the */ X for (k = j; k > i; k--) { /* new one, so shift bits between them */ X bcopy ((char*) &hi_scores[k-1], (char*) &hi_scores[k], X sizeof (struct score_ent)); X } X } X X (void) strcpy (hi_scores[i].name, user_name); X (void) strcpy (hi_scores[i].code, user_code); X hi_scores[i].score = score; X hi_scores[i].no_pieces = no_pieces; X hi_scores[i].no_levels = no_levels; X hi_scores[i].game_level = game_level; X X if ((score_fd = open (SCORE_FILE, O_WRONLY | O_CREAT, 0666)) == -1) { X perror ("open"); X die (LE_OPEN, "couldn't open(2) score-file for writing"); X } X X if (write (score_fd, (char*) hi_scores, no_hiscores*sizeof X (struct score_ent)) == -1) { X perror ("write"); X die (LE_WRITE, "couldn't write(2) to score-file"); X } X X (void) close (score_fd); X (void) close (lock_fd); X#ifndef LOCKF X (void) unlink (LOCK_FILE); X#endif /* LOCKF */ X return (i+1); X} X X/***************************************************************************\ X|* *| X|* The function get_key() reads a character from the keyboard, and *| X|* performs some simple processing on it. If it's an 's', it lists *| X|* the high-score table. Otherwise, it returns 1 for a 'q' or 'n', *| X|* and 0 for anything else. *| X|* *| X\***************************************************************************/ X Xint get_key () X{ X char ch; X X (void) read (0, &ch, 1); X if ((ch == 's') || (ch == 'S')) { X print_scores (); X (void) read (0, &ch, 1); X hoopy_refresh (); X print_msg ("Press a key:"); X (void) read (0, &ch, 1); X } X X print_msg (""); X return ((ch == 'n') || (ch == 'N') || (ch == 'q') || (ch == 'Q')); X} X X/***************************************************************************\ X|* *| X|* The main() function handles initialisation, gets keys and names from *| X|* the environent, parses command-line arguments and so on. It then *| X|* goes into the main loop of calling play_game(), and asking if the *| X|* player wants another game, and so on. *| X|* *| X\***************************************************************************/ X Xmain (argc, argv) X int argc; X char **argv; X{ X int i; X char *cp; /* Temporary pointer for getenv() */ X time_t ignore_me; /* Storage for time for random seed. */ X struct passwd *pw_ptr; /* Used with getuid() to find usercode */ X X (void) srandom ((int) time (&ignore_me)); X (void) strcpy (prog_name, basename (argv[0])); X X if ((i = getuid ()) == -1) X die (LE_GETUID, "couldn't getuid(2)"); X if ((pw_ptr = getpwuid (i)) == NULL) X die (LE_GETPW, "couldn't get password entry"); X (void) strncpy (user_code, pw_ptr->pw_name, CODELEN-1); X user_code[CODELEN-1] = '\0'; X X if ((cp = getenv ("TTNAME")) == NULL) X if ((cp = getenv ("NAME")) == NULL) X cp = user_code; X (void) strncpy (user_name, cp, NAMELEN-1); X user_name[NAMELEN-1] = '\0'; X X if ((cp = getenv ("TTKEYS")) != NULL) { X if (*cp != '\0') X left_key = *(cp++); X if (*cp != '\0') X right_key = *(cp++); X if (*cp != '\0') X rotate_key = *(cp++); X if (*cp != '\0') X drop_key = *(cp++); X if (*cp != '\0') X susp_key = *(cp++); X if (*cp != '\0') X quit_key = *(cp++); X if (*cp != '\0') X cheat_key = *(cp++); X } X X for (i = 1; i < argc; i++) X switch (argv[i][0]) { X case '-': X switch (argv[i][1]) { X case 's': X if (argv[i][2] != '\0') X if (((no_shown = atoi (argv[i]+2)) < 1) || X (no_shown > NO_HISCORES)) { X static char tmp[LINELEN]; /* To stop recursive form() */ X (void) sprintf (tmp, "Number of scores must be between 1 and %d", X NO_HISCORES); X die (LE_LEVEL, tmp); X } X print_scores (); /* Flag '-s': print scores, then exit. */ X exit (LE_OK); /* Not in curses => No need to call die() */ X case 'b': X rotate_backwards = 1; X break; X case 'l': X if (argv[i][2] == '\0') X goto USAGE_ERROR; X else X if (((game_level = atoi (argv[i]+2)) < -10) || (game_level > 20)) X die (LE_LEVEL, "Game-level must be between -10 and 20"); X break; X default: X goto USAGE_ERROR; X } X break; X default: X USAGE_ERROR: X die (LE_USAGE, form ("Usage: %s [ -s ] [ -b ] [ -l# ]", prog_name)); X } X X get_termcap (); X if (screen_depth < GAME_DEPTH+1) X die (LE_SCREEN, "screen is not deep enough"); X if (screen_width < (2*GAME_WIDTH)+6+STAT_WIDTH) X die (LE_SCREEN, "screen is not wide enough"); X X if ((signal (SIGHUP, signal_end) == BADSIG) || X (signal (SIGINT, signal_end) == BADSIG) || X /* (signal (SIGQUIT, signal_end) == BADSIG) || */ X (signal (SIGTERM, signal_end) == BADSIG)) X die (LE_SIGNAL, "couldn't set up signal-handling"); X X setup_curses (); X setup_screen (); X clear_board (); X print_msg ("Press a key:"); X if (get_key ()) X die (LE_OK, ""); X X while (1) { X play_game (); X X if ((i = update_file ()) > 0) { X static char tmp[LINELEN]; /* To stop recursive form() phelgming */ X (void) sprintf (tmp, "Score ranks #%d", i); X print_msg (tmp); X } X if (i < 0) X print_msg ("Save-score failed!"); X if (i != 0) { X flush_keyboard (); X if (get_key ()) X break; X } X X print_msg ("Again?"); X flush_keyboard (); X if (get_key ()) X break; X X clear_area (); X } X X die (LE_OK, ""); X} X X/*-------------------------------------------------------------------------*/ END_OF_FILE if test 15896 -ne `wc -c <'tt.c'`; then echo shar: \"'tt.c'\" unpacked with wrong size! fi # end of 'tt.c' fi echo shar: End of archive 1 \(of 2\). cp /dev/null ark1isdone MISSING="" for I in 1 2 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked both archives. rm -f ark[1-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0