1/*	$NetBSD: io.c,v 1.25 2011/05/23 22:47:22 joerg 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#include <sys/cdefs.h>
33#ifndef lint
34#if 0
35static char sccsid[] = "@(#)io.c	8.1 (Berkeley) 5/31/93";
36#else
37__RCSID("$NetBSD: io.c,v 1.25 2011/05/23 22:47:22 joerg Exp $");
38#endif
39#endif /* not lint */
40
41#include <ctype.h>
42#include <curses.h>
43#include <signal.h>
44#include <stdarg.h>
45#include <stdlib.h>
46#include <string.h>
47#include <termios.h>
48#include <unistd.h>
49
50#include "deck.h"
51#include "cribbage.h"
52#include "cribcur.h"
53
54#define	LINESIZE		128
55
56#ifdef CTRL
57#undef CTRL
58#endif
59#define	CTRL(X)			(X - 'A' + 1)
60
61static int msgcrd(CARD, BOOLEAN, const char *, BOOLEAN);
62static void printcard(WINDOW *, int, CARD, BOOLEAN);
63static int incard(CARD *);
64static void wait_for(int);
65static int readchar(void);
66
67static char linebuf[LINESIZE];
68
69static const char *const rankname[RANKS] = {
70	"ACE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN",
71	"EIGHT", "NINE", "TEN", "JACK", "QUEEN", "KING"
72};
73
74static const char *const rankchar[RANKS] = {
75	"A", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K"
76};
77
78static const char *const suitname[SUITS] = {
79	"SPADES", "HEARTS", "DIAMONDS", "CLUBS"
80};
81
82static const char *const suitchar[SUITS] = {"S", "H", "D", "C"};
83
84/*
85 * msgcard:
86 *	Call msgcrd in one of two forms
87 */
88int
89msgcard(CARD c, BOOLEAN brief)
90{
91	if (brief)
92		return (msgcrd(c, TRUE, NULL, TRUE));
93	else
94		return (msgcrd(c, FALSE, " of ", FALSE));
95}
96
97/*
98 * msgcrd:
99 *	Print the value of a card in ascii
100 */
101static int
102msgcrd(CARD c, BOOLEAN brfrank, const char *mid, BOOLEAN brfsuit)
103{
104	if (c.rank == EMPTY || c.suit == EMPTY)
105		return (FALSE);
106	if (brfrank)
107		addmsg("%1.1s", rankchar[c.rank]);
108	else
109		addmsg("%s", rankname[c.rank]);
110	if (mid != NULL)
111		addmsg("%s", mid);
112	if (brfsuit)
113		addmsg("%1.1s", suitchar[c.suit]);
114	else
115		addmsg("%s", suitname[c.suit]);
116	return (TRUE);
117}
118
119/*
120 * printcard:
121 *	Print out a card.
122 */
123static void
124printcard(WINDOW *win, int cardno, CARD c, BOOLEAN blank)
125{
126	prcard(win, cardno * 2, cardno, c, blank);
127}
128
129/*
130 * prcard:
131 *	Print out a card on the window at the specified location
132 */
133void
134prcard(WINDOW *win, int y, int x, CARD c, BOOLEAN blank)
135{
136	if (c.rank == EMPTY)
137		return;
138
139	mvwaddstr(win, y + 0, x, "+-----+");
140	mvwaddstr(win, y + 1, x, "|     |");
141	mvwaddstr(win, y + 2, x, "|     |");
142	mvwaddstr(win, y + 3, x, "|     |");
143	mvwaddstr(win, y + 4, x, "+-----+");
144	if (!blank) {
145		mvwaddch(win, y + 1, x + 1, rankchar[c.rank][0]);
146		waddch(win, suitchar[c.suit][0]);
147		mvwaddch(win, y + 3, x + 4, rankchar[c.rank][0]);
148		waddch(win, suitchar[c.suit][0]);
149	}
150}
151
152/*
153 * prhand:
154 *	Print a hand of n cards
155 */
156void
157prhand(const CARD h[], int n, WINDOW *win, BOOLEAN blank)
158{
159	int i;
160
161	werase(win);
162	for (i = 0; i < n; i++)
163		printcard(win, i, *h++, blank);
164	wrefresh(win);
165}
166
167/*
168 * infrom:
169 *	reads a card, supposedly in hand, accepting unambigous brief
170 *	input, returns the index of the card found...
171 */
172int
173infrom(const CARD hand[], int n, const char *prompt)
174{
175	int i, j;
176	CARD crd;
177
178	if (n < 1) {
179		printf("\nINFROM: %d = n < 1!!\n", n);
180		exit(74);
181	}
182	for (;;) {
183		msg("%s", prompt);
184		if (incard(&crd)) {	/* if card is full card */
185			if (!is_one(crd, hand, n))
186				msg("That's not in your hand");
187			else {
188				for (i = 0; i < n; i++)
189					if (hand[i].rank == crd.rank &&
190					    hand[i].suit == crd.suit)
191						break;
192				if (i >= n) {
193			printf("\nINFROM: is_one or something messed up\n");
194					exit(77);
195				}
196				return (i);
197			}
198		} else			/* if not full card... */
199			if (crd.rank != EMPTY) {
200				for (i = 0; i < n; i++)
201					if (hand[i].rank == crd.rank)
202						break;
203				if (i >= n)
204					msg("No such rank in your hand");
205				else {
206					for (j = i + 1; j < n; j++)
207						if (hand[j].rank == crd.rank)
208							break;
209					if (j < n)
210						msg("Ambiguous rank");
211					else
212						return (i);
213				}
214			} else
215				msg("Sorry, I missed that");
216	}
217	/* NOTREACHED */
218}
219
220/*
221 * incard:
222 *	Inputs a card in any format.  It reads a line ending with a CR
223 *	and then parses it.
224 */
225static int
226incard(CARD *crd)
227{
228	int i;
229	int rnk, sut;
230	char *line, *p, *p1;
231	BOOLEAN retval;
232
233	retval = FALSE;
234	rnk = sut = EMPTY;
235	if (!(line = get_line()))
236		goto gotit;
237	p = p1 = line;
238	while (*p1 != ' ' && *p1 != '\0')
239		++p1;
240	*p1++ = '\0';
241	if (*p == '\0')
242		goto gotit;
243
244	/* IMPORTANT: no real card has 2 char first name */
245	if (strlen(p) == 2) {	/* check for short form */
246		rnk = EMPTY;
247		for (i = 0; i < RANKS; i++) {
248			if (*p == *rankchar[i]) {
249				rnk = i;
250				break;
251			}
252		}
253		if (rnk == EMPTY)
254			goto gotit;	/* it's nothing... */
255		++p;		/* advance to next char */
256		sut = EMPTY;
257		for (i = 0; i < SUITS; i++) {
258			if (*p == *suitchar[i]) {
259				sut = i;
260				break;
261			}
262		}
263		if (sut != EMPTY)
264			retval = TRUE;
265		goto gotit;
266	}
267	rnk = EMPTY;
268	for (i = 0; i < RANKS; i++) {
269		if (!strcmp(p, rankname[i]) || !strcmp(p, rankchar[i])) {
270			rnk = i;
271			break;
272		}
273	}
274	if (rnk == EMPTY)
275		goto gotit;
276	p = p1;
277	while (*p1 != ' ' && *p1 != '\0')
278		++p1;
279	*p1++ = '\0';
280	if (*p == '\0')
281		goto gotit;
282	if (!strcmp("OF", p)) {
283		p = p1;
284		while (*p1 != ' ' && *p1 != '\0')
285			++p1;
286		*p1++ = '\0';
287		if (*p == '\0')
288			goto gotit;
289	}
290	sut = EMPTY;
291	for (i = 0; i < SUITS; i++) {
292		if (!strcmp(p, suitname[i]) || !strcmp(p, suitchar[i])) {
293			sut = i;
294			break;
295		}
296	}
297	if (sut != EMPTY)
298		retval = TRUE;
299gotit:
300	(*crd).rank = rnk;
301	(*crd).suit = sut;
302	return (retval);
303}
304
305/*
306 * getuchar:
307 *	Reads and converts to upper case
308 */
309int
310getuchar(void)
311{
312	int c;
313
314	c = readchar();
315	if (islower(c))
316		c = toupper(c);
317	waddch(Msgwin, c);
318	return (c);
319}
320
321/*
322 * number:
323 *	Reads in a decimal number and makes sure it is between "lo" and
324 *	"hi" inclusive.
325 */
326int
327number(int lo, int hi, const char *prompt)
328{
329	char *p;
330	int sum;
331
332	for (sum = 0;;) {
333		msg("%s", prompt);
334		if (!(p = get_line()) || *p == '\0') {
335			msg(quiet ? "Not a number" :
336			    "That doesn't look like a number");
337			continue;
338		}
339		sum = 0;
340
341		if (!isdigit((unsigned char)*p))
342			sum = lo - 1;
343		else
344			while (isdigit((unsigned char)*p)) {
345				sum = 10 * sum + (*p - '0');
346				++p;
347			}
348
349		if (*p != ' ' && *p != '\t' && *p != '\0')
350			sum = lo - 1;
351		if (sum >= lo && sum <= hi)
352			break;
353		if (sum == lo - 1)
354			msg("that doesn't look like a number, try again --> ");
355		else
356		msg("%d is not between %d and %d inclusive, try again --> ",
357			    sum, lo, hi);
358	}
359	return (sum);
360}
361
362/*
363 * msg:
364 *	Display a message at the top of the screen.
365 */
366static char Msgbuf[BUFSIZ] = {'\0'};
367static int Mpos = 0;
368static int Newpos = 0;
369
370void
371msg(const char *fmt, ...)
372{
373	va_list ap;
374
375	va_start(ap, fmt);
376	(void)vsnprintf(&Msgbuf[Newpos], sizeof(Msgbuf)-Newpos, fmt, ap);
377	Newpos = strlen(Msgbuf);
378	va_end(ap);
379	endmsg();
380}
381
382/*
383 * addmsg:
384 *	Add things to the current message
385 */
386void
387addmsg(const char *fmt, ...)
388{
389	va_list ap;
390
391	va_start(ap, fmt);
392	(void)vsnprintf(&Msgbuf[Newpos], sizeof(Msgbuf)-Newpos, fmt, ap);
393	Newpos = strlen(Msgbuf);
394	va_end(ap);
395}
396
397/*
398 * endmsg:
399 *	Display a new msg.
400 */
401static int Lineno = 0;
402
403void
404endmsg(void)
405{
406	static int lastline = 0;
407	int len;
408	char *mp, *omp;
409
410	/* All messages should start with uppercase */
411	mvaddch(lastline + Y_MSG_START, SCORE_X, ' ');
412	if (islower((unsigned char)Msgbuf[0]) && Msgbuf[1] != ')')
413		Msgbuf[0] = toupper((unsigned char)Msgbuf[0]);
414	mp = Msgbuf;
415	len = strlen(mp);
416	if (len / MSG_X + Lineno >= MSG_Y) {
417		while (Lineno < MSG_Y) {
418			wmove(Msgwin, Lineno++, 0);
419			wclrtoeol(Msgwin);
420		}
421		Lineno = 0;
422	}
423	mvaddch(Lineno + Y_MSG_START, SCORE_X, '*');
424	lastline = Lineno;
425	do {
426		mvwaddstr(Msgwin, Lineno, 0, mp);
427		if ((len = strlen(mp)) > MSG_X) {
428			omp = mp;
429			for (mp = &mp[MSG_X - 1]; *mp != ' '; mp--)
430				continue;
431			while (*mp == ' ')
432				mp--;
433			mp++;
434			wmove(Msgwin, Lineno, mp - omp);
435			wclrtoeol(Msgwin);
436		}
437		if (++Lineno >= MSG_Y)
438			Lineno = 0;
439	} while (len > MSG_X);
440	wclrtoeol(Msgwin);
441	Mpos = len;
442	Newpos = 0;
443	wrefresh(Msgwin);
444	refresh();
445	wrefresh(Msgwin);
446}
447
448/*
449 * do_wait:
450 *	Wait for the user to type ' ' before doing anything else
451 */
452void
453do_wait(void)
454{
455	static const char prompt[] = {'-', '-', 'M', 'o', 'r', 'e', '-', '-', '\0'};
456
457	if ((int)(Mpos + sizeof prompt) < MSG_X)
458		wmove(Msgwin, Lineno > 0 ? Lineno - 1 : MSG_Y - 1, Mpos);
459	else {
460		mvwaddch(Msgwin, Lineno, 0, ' ');
461		wclrtoeol(Msgwin);
462		if (++Lineno >= MSG_Y)
463			Lineno = 0;
464	}
465	waddstr(Msgwin, prompt);
466	wrefresh(Msgwin);
467	wait_for(' ');
468}
469
470/*
471 * wait_for
472 *	Sit around until the guy types the right key
473 */
474static void
475wait_for(int ch)
476{
477	int c;
478
479	if (ch == '\n')
480		while ((c = readchar()) != '\n')
481			continue;
482	else
483		while (readchar() != ch)
484			continue;
485}
486
487/*
488 * readchar:
489 *	Reads and returns a character, checking for gross input errors
490 */
491static int
492readchar(void)
493{
494	int cnt;
495	unsigned char c;
496
497over:
498	cnt = 0;
499	while (read(STDIN_FILENO, &c, sizeof(unsigned char)) <= 0)
500		if (cnt++ > 100) {	/* if we are getting infinite EOFs */
501			bye();		/* quit the game */
502			exit(1);
503		}
504	if (c == CTRL('L')) {
505		wrefresh(curscr);
506		goto over;
507	}
508	if (c == '\r')
509		return ('\n');
510	else
511		return (c);
512}
513
514/*
515 * get_line:
516 *      Reads the next line up to '\n' or EOF.  Multiple spaces are
517 *	compressed to one space; a space is inserted before a ','
518 */
519char *
520get_line(void)
521{
522	char *sp;
523	int c, oy, ox;
524	WINDOW *oscr;
525
526	oscr = stdscr;
527	stdscr = Msgwin;
528	getyx(stdscr, oy, ox);
529	refresh();
530	/* loop reading in the string, and put it in a temporary buffer */
531	for (sp = linebuf; (c = readchar()) != '\n'; clrtoeol(), refresh()) {
532			if (c == erasechar()) {	/* process erase character */
533				if (sp > linebuf) {
534					int i;
535
536					sp--;
537					for (i = strlen(unctrl(*sp)); i; i--)
538						addch('\b');
539				}
540				continue;
541			} else
542				if (c == killchar()) {	/* process kill
543							 * character */
544					sp = linebuf;
545					move(oy, ox);
546					continue;
547				} else
548					if (sp == linebuf && c == ' ')
549						continue;
550		if (sp >= &linebuf[LINESIZE - 1] || !(isprint(c) || c == ' '))
551			putchar(CTRL('G'));
552		else {
553			if (islower(c))
554				c = toupper(c);
555			*sp++ = c;
556			addstr(unctrl(c));
557			Mpos++;
558		}
559	}
560	*sp = '\0';
561	stdscr = oscr;
562	return (linebuf);
563}
564
565void
566receive_intr(int signo __unused)
567{
568	bye();
569	exit(1);
570}
571
572/*
573 * bye:
574 *	Leave the program, cleaning things up as we go.
575 */
576void
577bye(void)
578{
579	signal(SIGINT, SIG_IGN);
580	mvcur(0, COLS - 1, LINES - 1, 0);
581	fflush(stdout);
582	endwin();
583	putchar('\n');
584}
585