xref: /openbsd/games/worm/worm.c (revision 73471bf0)
1 /*	$OpenBSD: worm.c,v 1.39 2018/08/24 11:14:49 mestre Exp $	*/
2 
3 /*
4  * Copyright (c) 1980, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 /*
33  * Worm.  Written by Michael Toy
34  * UCSC
35  */
36 
37 #include <ctype.h>
38 #include <curses.h>
39 #include <err.h>
40 #include <poll.h>
41 #include <signal.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 
45 #define HEAD '@'
46 #define BODY 'o'
47 #define LENGTH 7
48 #define RUNLEN 8
49 #define CNTRL(p) (p-'A'+1)
50 
51 WINDOW *tv;
52 WINDOW *stw;
53 struct body {
54 	int x;
55 	int y;
56 	struct body *prev;
57 	struct body *next;
58 } *head, *tail, goody;
59 int growing = 0;
60 int growthscale = 1;
61 int running = 0;
62 int slow = 0;
63 int score = 0;
64 int start_len = LENGTH;
65 int visible_len;
66 int lastch;
67 char outbuf[BUFSIZ];
68 
69 volatile sig_atomic_t wantleave = 0;
70 volatile sig_atomic_t wantsuspend = 0;
71 
72 __dead void	crash(void);
73 void	display(struct body *, char);
74 void	leave(int);
75 void	life(void);
76 void	newpos(struct body *);
77 struct body 	*newlink(void);
78 int	process(int);
79 void	prize(void);
80 int	rnd(int);
81 void	setup(void);
82 void	suspend(int);
83 
84 int
85 main(int argc, char **argv)
86 {
87 	int retval;
88 	struct pollfd pfd[1];
89 	const char *errstr;
90 	struct timespec t, tn, tdiff;
91 
92 	timespecclear(&t);
93 
94 	setvbuf(stdout, outbuf, _IOFBF, sizeof outbuf);
95 	signal(SIGINT, leave);
96 	signal(SIGQUIT, leave);
97 	signal(SIGTSTP, suspend);	/* process control signal */
98 	initscr();
99 
100 	if (pledge("stdio tty", NULL) == -1)
101 		err(1, "pledge");
102 
103 	cbreak();
104 	noecho();
105 	keypad(stdscr, TRUE);
106 	slow = (baudrate() <= 1200);
107 	clear();
108 	if (COLS < 18 || LINES < 5) {
109 		endwin();
110 		errx(1, "screen too small");
111 	}
112 	growthscale = COLS * LINES / 2000;
113 	if (growthscale == 0)
114 		growthscale = 1;
115 	if (argc >= 2) {
116 		start_len = strtonum(argv[1], 1, ((LINES-3) * (COLS-2)) / 3,
117 		    &errstr);
118 		if (errstr) {
119 			endwin();
120 			errx(1, "length argument is %s.", errstr);
121 		}
122 	}
123 	stw = newwin(1, COLS-1, 0, 0);
124 	tv = newwin(LINES-1, COLS-1, 1, 0);
125 	box(tv, '*', '*');
126 	scrollok(tv, FALSE);
127 	scrollok(stw, FALSE);
128 	wmove(stw, 0, 0);
129 	wprintw(stw, " Worm");
130 	refresh();
131 	wrefresh(stw);
132 	wrefresh(tv);
133 	life();			/* Create the worm */
134 	prize();		/* Put up a goal */
135 	wmove(tv, head->y, head->x);    /* Leave cursor on worm */
136 	wrefresh(tv);
137 	while (1) {
138 		if (wantleave) {
139 			endwin();
140 			return 0;
141 		}
142 		if (wantsuspend) {
143 			move(LINES-1, 0);
144 			refresh();
145 			endwin();
146 			fflush(stdout);
147 			kill(getpid(), SIGSTOP);
148 			signal(SIGTSTP, suspend);
149 			cbreak();
150 			noecho();
151 			setup();
152 			wantsuspend = 0;
153 		}
154 
155 		if (running) {
156 			running--;
157 			process(lastch);
158 		} else {
159 			/* Check for timeout. */
160 			clock_gettime(CLOCK_MONOTONIC, &tn);
161 			if (timespeccmp(&t, &tn, <=)) {
162 				t = tn;
163 				t.tv_sec += 1;
164 
165 				process(lastch);
166 				continue;
167 			}
168 
169 			/* Prepare next read */
170 			pfd[0].fd = STDIN_FILENO;
171 			pfd[0].events = POLLIN;
172 			timespecsub(&t, &tn, &tdiff);
173 			retval = ppoll(pfd, 1, &tdiff, NULL);
174 
175 			/* Nothing to do if timed out or signal. */
176 			if (retval <= 0)
177 				continue;
178 
179 			/* Only update timer if valid key was pressed. */
180 			if (process(getch()) == 0)
181 				continue;
182 
183 			/* Update using clock_gettime(), tn is too old now. */
184 			clock_gettime(CLOCK_MONOTONIC, &t);
185 			t.tv_sec += 1;
186 		}
187 	}
188 }
189 
190 void
191 life(void)
192 {
193 	struct body *bp, *np;
194 	int i,j = 1;
195 
196 	head = newlink();
197 	head->x = start_len % (COLS-5) + 2;
198 	head->y = LINES / 2;
199 	head->next = NULL;
200 	display(head, HEAD);
201 	for (i = 0, bp = head; i < start_len; i++, bp = np) {
202 		np = newlink();
203 		np->next = bp;
204 		bp->prev = np;
205 		if (((bp->x <= 2) && (j == 1)) || ((bp->x >= COLS-4) && (j == -1))) {
206 			j *= -1;
207 			np->x = bp->x;
208 			np->y = bp->y + 1;
209 		} else {
210 			np->x = bp->x - j;
211 			np->y = bp->y;
212 		}
213 		display(np, BODY);
214 	}
215 	tail = np;
216 	tail->prev = NULL;
217 	visible_len = start_len + 1;
218 }
219 
220 void
221 display(struct body *pos, char chr)
222 {
223 	wmove(tv, pos->y, pos->x);
224 	waddch(tv, chr);
225 }
226 
227 void
228 leave(int dummy)
229 {
230 	wantleave = 1;
231 }
232 
233 int
234 rnd(int range)
235 {
236 	return arc4random_uniform(range);
237 }
238 
239 void
240 newpos(struct body *bp)
241 {
242 	if (visible_len == (LINES-3) * (COLS-3) - 1) {
243 		endwin();
244 		printf("\nYou won!\nYour final score was %d\n\n", score);
245 		exit(0);
246 	}
247 	do {
248 		bp->y = rnd(LINES-3)+ 1;
249 		bp->x = rnd(COLS-3) + 1;
250 		wmove(tv, bp->y, bp->x);
251 	} while(winch(tv) != ' ');
252 }
253 
254 void
255 prize(void)
256 {
257 	int value;
258 
259 	value = rnd(9) + 1;
260 	newpos(&goody);
261 	waddch(tv, value+'0');
262 	wrefresh(tv);
263 }
264 
265 int
266 process(int ch)
267 {
268 	int x,y;
269 	struct body *nh;
270 
271 	x = head->x;
272 	y = head->y;
273 	switch(ch) {
274 #ifdef KEY_LEFT
275 	case KEY_LEFT:
276 #endif
277 	case 'h':
278 		x--;
279 		break;
280 #ifdef KEY_DOWN
281 	case KEY_DOWN:
282 #endif
283 	case 'j':
284 		y++;
285 		break;
286 #ifdef KEY_UP
287 	case KEY_UP:
288 #endif
289 	case 'k':
290 		y--;
291 		break;
292 #ifdef KEY_RIGHT
293 	case KEY_RIGHT:
294 #endif
295 	case 'l':
296 		x++;
297 		break;
298 	case 'H':
299 		x--;
300 		running = RUNLEN;
301 		ch = tolower(ch);
302 		break;
303 	case 'J':
304 		y++;
305 		running = RUNLEN/2;
306 		ch = tolower(ch);
307 		break;
308 	case 'K':
309 		y--;
310 		running = RUNLEN/2;
311 		ch = tolower(ch);
312 		break;
313 	case 'L':
314 		x++;
315 		running = RUNLEN;
316 		ch = tolower(ch);
317 		break;
318 	case '\f':
319 		setup();
320 		return (0);
321 	case CNTRL('Z'):
322 		suspend(0);
323 		return (0);
324 	case CNTRL('C'):
325 		crash();
326 		return (0);
327 	case CNTRL('D'):
328 		crash();
329 		return (0);
330 	case ERR:
331 		leave(0);
332 		return (0);
333 	default:
334 		return (0);
335 	}
336 	lastch = ch;
337 	if (growing == 0) {
338 		display(tail, ' ');
339 		tail->next->prev = NULL;
340 		nh = tail->next;
341 		free(tail);
342 		tail = nh;
343 		visible_len--;
344 	} else
345 		growing--;
346 	display(head, BODY);
347 	wmove(tv, y, x);
348 	if (isdigit(ch = winch(tv))) {
349 		int amt = ch - '0';
350 		growing += amt * growthscale;
351 		prize();
352 		score += amt;
353 		running = 0;
354 		wmove(stw, 0, COLS - 12);
355 		wprintw(stw, "Score: %3d", score);
356 		wrefresh(stw);
357 	} else if(ch != ' ')
358 		crash();
359 	nh = newlink();
360 	nh->next = NULL;
361 	nh->prev = head;
362 	head->next = nh;
363 	nh->y = y;
364 	nh->x = x;
365 	display(nh, HEAD);
366 	head = nh;
367 	visible_len++;
368 	if (!(slow && running)) {
369 		wmove(tv, head->y, head->x);
370 		wrefresh(tv);
371 	}
372 	return (1);
373 }
374 
375 struct body *
376 newlink(void)
377 {
378 	struct body *tmp;
379 
380 	if ((tmp = malloc(sizeof (struct body))) == NULL) {
381 		endwin();
382 		errx(1, "out of memory");
383 	}
384 	return (tmp);
385 }
386 
387 void
388 crash(void)
389 {
390 	sleep(2);
391 	clear();
392 	endwin();
393 	printf("Well, you ran into something and the game is over.\n");
394 	printf("Your final score was %d\n", score);
395 	exit(0);  /* leave() calls endwin(), which would hose the printf()'s */
396 }
397 
398 void
399 suspend(int dummy)
400 {
401 	wantsuspend = 1;
402 }
403 
404 void
405 setup(void)
406 {
407 	clear();
408 	refresh();
409 	touchwin(stw);
410 	wrefresh(stw);
411 	touchwin(tv);
412 	wrefresh(tv);
413 }
414