1 /***************************************************************************\
2 |*									   *|
3 |*  game.c:	A version of Tetris to run on ordinary terminals,	   *|
4 |*		(ie., not needing a workstation, so should available	   *|
5 |*		to peasant Newwords+ users.  This is the module that	   *|
6 |*		actually plays the game, (ie. moves things down the	   *|
7 |*		screen, select(2)s on the keyboard, and so on)		   *|
8 |*									   *|
9 |*  Author:	Mike Taylor (mirk@uk.co.ssl)				   *|
10 |*  Started:	Fri May 26 12:26:05 BST 1989				   *|
11 |*									   *|
12 \***************************************************************************/
13 
14 #include <stdio.h>
15 #include <errno.h>
16 #include <sys/types.h>
17 #include <sys/time.h>
18 
19 #include "tt.h"
20 #include "game.h"
21 #include "screen.h"
22 #include "pieces.h"
23 #include "utils.h"
24 
25 /*-------------------------------------------------------------------------*/
26 
27 extern long int random ();
28 
29 /*-------------------------------------------------------------------------*/
30 
31 char left_key	= LEFT_KEY;	/* Move piece left */
32 char right_key	= RIGHT_KEY;	/* Move piece right */
33 char rotate_key = ROTATE_KEY;	/* Rotate piece anticlockwise */
34 char drop_key	= DROP_KEY;	/* Drop piece to bottom of screen */
35 char susp_key	= SUSP_KEY;	/* Suspend.  I'm sorry if its confusing */
36 char quit_key	= QUIT_KEY;	/* Quit.  I'm sorry if its confusing */
37 char cheat_key	= CHEAT_KEY;	/* Do whatever I eventaully make it do */
38 
39 /***************************************************************************\
40 |*									   *|
41 |*  Oh good grief, what on earth is the point of putting a huge, wobbly	   *|
42 |*  box-comment in front of this titchy little self-explanatory function?  *|
43 |*  The name tells the whole story, it's perfectly strightforward, it's	   *|
44 |*  not a proposition from Witgenstein.	 I wouldn't bother commenting it   *|
45 |*  at all if it wasn't for the fact that I know I'd feel guilty in the	   *|
46 |*  morning.  I mean, be fair, you don't really want a program where all   *|
47 |*  the functions *except one* have box-comments explaining them, do you?  *|
48 |*  Ah well, here we go for completion's sake:				   *|
49 |*									   *|
50 |*  The function clear_board() takes no parameters and returns no value.   *|
51 |*  It clears the board.  The end.					   *|
52 |*									   *|
53 \***************************************************************************/
54 
clear_board()55 void clear_board ()
56 {
57   int i, j;
58 
59   for (i = 0; i < GAME_DEPTH+4; i++)
60     for (j = 0; j < GAME_WIDTH; j++)
61       board[i][j] = PI_EMPTY;
62 }
63 
64 /***************************************************************************\
65 |*									   *|
66 |*  The function play_game is the main loop in which the game of Tetris	   *|
67 |*  is implemented.  It takes no parameters, and returns no value.  The	   *|
68 |*  time at which it returns no value is the end of a game.  The main	   *|
69 |*  loop-component is a select(2) which polls the keyboard in real-time.   *|
70 |*  If you use a non-Berkeley UNIX(tm), you're well cheesed.		   *|
71 |*									   *|
72 |*  Actually, I have to admit I'm not proud of this one.  It must be one   *|
73 |*  of the messiest functions I've written in years, in terms of nested	   *|
74 |*  loops with ad-hoc exit conditions, re-used variables, general	   *|
75 |*  obfuscation and so on.  I wanna make it quite clear that I make no	   *|
76 |*  apologies for my use of "goto", which remains a highly desirable	   *|
77 |*  language feature, and is still the most elegant way of coding many	   *|
78 |*  things, but I gotta admit, overall this one is bit of a chicken.	   *|
79 |*									   *|
80 \***************************************************************************/
81 
play_game()82 void play_game ()
83 {
84   int i;			/* Position of origin down board */
85   int j;			/* Position of origin across board */
86   int k;			/* Loop variable when i,j are invariant */
87   int piece_no;			/* Type of piece currently falling */
88   int orient;			/* Which way it is facing */
89   static int next_piece_no=0;	/* Which piece is "next" */
90   static int next_orient=0;	/* And at what orientation is it? */
91   int pause_flag = 0;		/* We won't pause unless told to */
92   int presses_left;		/* Futher moves possible this drop */
93   int free_left = game_level;	/* Number of pieces to drop at start */
94   fd_set read_fds;		/* Select must look at stdin */
95   long int total_time = 200000; /* Allow 1/4 second before falling */
96   struct timeval tmout;		/* Time before piece drops in select(2) */
97   char ch;			/* Keystroke (command) */
98 
99   score = no_levels = no_pieces = 0;
100   update_scores ();
101   clear_board ();
102 
103   next_piece_no = (int) (random () % (NO_PIECES-(2*game_mode)));
104   next_orient = (int) (random () % NO_ORIENTS);
105 
106   while (1) {
107     piece_no = next_piece_no;
108     orient   = next_orient;
109     next_piece_no = (int) (random () % (NO_PIECES-(2*game_mode)));
110     next_orient = (int) (random () % NO_ORIENTS);
111 
112     if (SHOW_NEXT) {
113       draw_raw (piece_no, orient, NEXT_Y, NEXT_X, erase_as);
114       draw_raw (next_piece_no, next_orient, NEXT_Y, NEXT_X,
115 		pieces[next_piece_no].app);
116     } /* if we are showing the next piece */
117 
118 
119     i = -2;			/* Start falling from off-screen */
120     if (free_left > 0) {
121       int leftmost = GAME_WIDTH, rightmost = -GAME_WIDTH, boxlet, x;
122       for (boxlet = 0; boxlet < NO_SQUARES; boxlet++) {
123 	x = (pieces[piece_no].index)[orient][boxlet][1];
124 	if (x < leftmost) leftmost = x;
125 	if (x > rightmost) rightmost = x;
126       }
127       j = (int) (random () % (GAME_WIDTH-(rightmost-leftmost)))-leftmost;
128     } else
129       j = GAME_WIDTH/2;
130 
131     if (!can_place (piece_no, orient, i, j)) {
132       for (k = 0; k < 9; k++) { /* Crude but (hopefully) effective */
133 	draw_piece (piece_no, orient, i, j, PD_ERASE);
134 	myrefresh (); usleep (80000);
135 	draw_piece (piece_no, orient, i, j, PD_DRAW);
136 	myrefresh (); usleep (80000);
137       }
138       break;			/* End of game - piece won't fit */
139     }
140 
141     if (free_left != 0) {	/* If there are pieces to be dropped, */
142       if (free_left > 0)	/* And the number is positiive, */
143 	free_left--;		/* Then decrement it, otherwise */
144       else			/* increment it, in any case, bring */
145 	free_left++;		/* it closer to zero if it gets to zero */
146       if (free_left == 0)	/* set a flag so that the game will */
147 	pause_flag = 1;		/* pause.  Then go to the bit of code */
148       goto DROP_PIECE;		/* that drops it. */
149     }
150 
151     while (1) {
152       presses_left = NO_MOVES;
153       draw_piece (piece_no, orient, i, j, PD_DRAW);
154       update_scores ();
155       myrefresh ();
156 
157       while (1) {
158 	FD_ZERO (&read_fds);
159 	FD_SET (0, &read_fds);
160 	tmout.tv_sec = 0;
161 	tmout.tv_usec = total_time;
162 	switch (select (((presses_left > 0) && (i >= 0)), &read_fds,
163 			(fd_set*) NULL, (fd_set*) NULL, &tmout)) {
164 	case -1:
165 	  if (errno != EINTR)
166 	    die (LE_SELECT, "select(2) failed in play_game()");
167 	  else {		/* otherwise fall through, goto TMOUT */
168 	    print_msg ("Continue");
169 #if 0
170 	    flush_keyboard ();
171 #endif
172 	    (void) read (0, &ch, 1);
173 	    print_msg ("");
174 	  }
175 	case 0:
176 	  goto TMOUT;
177 	default:
178 	  if (read (0, &ch, 1) == -1)
179 	    die (LE_READ, "read(2) failed in play_game()");
180 
181 	  if (ch == REFRESH_KEY)
182 	    hoopy_refresh (piece_no, orient, i, j, next_piece_no, next_orient);
183 
184 	  if ((ch != left_key) && (ch != right_key) && (ch != rotate_key) &&
185 	      (ch != drop_key) && (ch != quit_key) && (ch != susp_key) &&
186 	      (ch != cheat_key))
187 	    break;
188 
189 	  presses_left--;
190 	  if (ch == left_key) {
191 	    if (can_place (piece_no, orient, i, j-1)) {
192 	      draw_piece (piece_no, orient, i, j, PD_ERASE);
193 	      j--;
194 	      draw_piece (piece_no, orient, i, j, PD_DRAW);
195 	      myrefresh ();
196 	    }
197 	  }
198 
199 	  else if (ch == right_key) {
200 	    if (can_place (piece_no, orient, i, j+1)) {
201 	      draw_piece (piece_no, orient, i, j, PD_ERASE);
202 	      j++;
203 	      draw_piece (piece_no, orient, i, j, PD_DRAW);
204 	      myrefresh ();
205 	    }
206 	  }
207 
208 	  else if (ch == rotate_key) {
209 	    int new_or = ((orient+NO_ORIENTS+1-(2*rot_backwards))%
210 			  NO_ORIENTS);
211 	    if (can_place (piece_no, new_or, i, j)) {
212 	      draw_piece (piece_no, orient, i, j, PD_ERASE);
213 	      orient = new_or;
214 	      draw_piece (piece_no, orient, i, j, PD_DRAW);
215 	      myrefresh ();
216 	    }
217 	  }
218 
219 	  else if (ch == drop_key) {
220 	  DROP_PIECE:
221 	    while (can_place (piece_no, orient, i+1, j)) {
222 	      draw_piece (piece_no, orient, i, j, PD_ERASE);
223 	      i++;
224 	      draw_piece (piece_no, orient, i, j, PD_DRAW);
225 	      myrefresh ();
226 	    }
227 	    goto TMOUT;
228 	  }
229 
230 	  else if (ch == quit_key)
231 	    return;
232 
233 	  else if (ch == cheat_key) {
234 	    print_msg ("CHEAT!");
235 	    total_time = 0L;
236 	  }
237 
238 	  else if (ch == susp_key) {
239 	    clear_area ();
240 	    print_msg ("Paused");
241 	    (void) read (0, &ch, 1);
242 	    draw_board ();
243 	    print_msg ("");
244 	  }
245 
246 	  break;
247 	} /* End of "switch(select())" ... ?!? */
248       }
249 
250     TMOUT:
251       if (!can_place (piece_no, orient, i+1, j)) {
252 	place_piece (piece_no, orient, i, j);
253 	score += pieces[piece_no].points;
254 	no_pieces++;
255 	update_scores ();
256 	myrefresh ();
257 
258 	for (i = 0; i < GAME_DEPTH; i++) {
259 	  for (j = 0; j < GAME_WIDTH; j++)
260 	    if (board[i+4][j] == PI_EMPTY)
261 	      break;
262 
263 	  if (j == GAME_WIDTH) {
264 	    no_levels++;
265 	    score += 10;
266 	    update_scores ();
267 	    for (k = i; k > 0; k--)
268 	      for (j = 0; j < GAME_WIDTH; j++)
269 		board[k+4][j] = board[k+3][j];
270 	    for (j = 0; j < GAME_WIDTH; j++)
271 	      board[4][j] = PI_EMPTY;
272 	    draw_board ();
273 	    myrefresh ();
274 	    i--;		/* Check the new row i */
275 	  }
276 	}
277 
278 	if (pause_flag) {	/* If we are pausing after this drop ... */
279 	  pause_flag = 0;	/* Ensure we don't do so next time. */
280 	  print_msg ("Continue");
281 #if 0
282 	  flush_keyboard ();
283 #endif
284 	  (void) read (0, &ch, 1);
285 	  print_msg ("");
286 	}
287 
288 	break;			/* End of fall - piece has hit floor */
289       }
290 
291       draw_piece (piece_no, orient, i, j, PD_ERASE);
292       i++;
293     }
294 
295     myrefresh ();
296     if (total_time != 0)
297       total_time -= 100;
298   }
299 }
300 
301 /*-------------------------------------------------------------------------*/
302