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