xref: /netbsd-src/games/cribbage/crib.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: crib.c,v 1.25 2012/10/13 20:36:06 dholland 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 __COPYRIGHT("@(#) Copyright (c) 1980, 1993\
35  The Regents of the University of California.  All rights reserved.");
36 #endif /* not lint */
37 
38 #ifndef lint
39 #if 0
40 static char sccsid[] = "@(#)crib.c	8.1 (Berkeley) 5/31/93";
41 #else
42 __RCSID("$NetBSD: crib.c,v 1.25 2012/10/13 20:36:06 dholland Exp $");
43 #endif
44 #endif /* not lint */
45 
46 #include <curses.h>
47 #include <err.h>
48 #include <fcntl.h>
49 #include <signal.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 
54 #include "deck.h"
55 #include "cribbage.h"
56 #include "cribcur.h"
57 #include "pathnames.h"
58 
59 static void makeboard(void);
60 static void gamescore(void);
61 static void game(void);
62 static int playhand(BOOLEAN);
63 static int deal(BOOLEAN);
64 static void discard(BOOLEAN);
65 static int cut(BOOLEAN, int);
66 static void prcrib(BOOLEAN, BOOLEAN);
67 static int peg(BOOLEAN);
68 static void prtable(int);
69 static int score(BOOLEAN);
70 
71 int
72 main(int argc, char *argv[])
73 {
74 	BOOLEAN playing;
75 	FILE *f;
76 	int ch;
77 	int fd;
78 	int flags;
79 
80 	f = fopen(_PATH_LOG, "a");
81 	if (f == NULL)
82 		warn("fopen %s", _PATH_LOG);
83 	if (f != NULL && fileno(f) < 3)
84 		exit(1);
85 
86 	/* Revoke setgid privileges */
87 	setgid(getgid());
88 
89 	/* Set close-on-exec flag on log file */
90 	if (f != NULL) {
91 		fd = fileno(f);
92 		flags = fcntl(fd, F_GETFD);
93 		if (flags < 0)
94 			err(1, "fcntl F_GETFD");
95 		flags |= FD_CLOEXEC;
96 		if (fcntl(fd, F_SETFD, flags) == -1)
97 			err(1, "fcntl F_SETFD");
98 	}
99 
100 	while ((ch = getopt(argc, argv, "eqr")) != -1)
101 		switch (ch) {
102 		case 'e':
103 			explain = TRUE;
104 			break;
105 		case 'q':
106 			quiet = TRUE;
107 			break;
108 		case 'r':
109 			rflag = TRUE;
110 			break;
111 		case '?':
112 		default:
113 			(void) fprintf(stderr, "usage: cribbage [-eqr]\n");
114 			exit(1);
115 		}
116 
117 	if (!initscr())
118 		errx(0, "couldn't initialize screen");
119 	(void)signal(SIGINT, receive_intr);
120 	cbreak();
121 	noecho();
122 
123 	Playwin = subwin(stdscr, PLAY_Y, PLAY_X, 0, 0);
124 	Tablewin = subwin(stdscr, TABLE_Y, TABLE_X, 0, PLAY_X);
125 	Compwin = subwin(stdscr, COMP_Y, COMP_X, 0, TABLE_X + PLAY_X);
126 	Msgwin = subwin(stdscr, MSG_Y, MSG_X, Y_MSG_START, SCORE_X + 1);
127 	leaveok(Playwin, TRUE);
128 	leaveok(Tablewin, TRUE);
129 	leaveok(Compwin, TRUE);
130 	clearok(stdscr, FALSE);
131 
132 	if (!quiet) {
133 		msg("Do you need instructions for cribbage? ");
134 		if (getuchar() == 'Y') {
135 			endwin();
136 			clear();
137 			mvcur(0, COLS - 1, LINES - 1, 0);
138 			fflush(stdout);
139 			instructions();
140 			cbreak();
141 			noecho();
142 			clear();
143 			refresh();
144 			msg("For cribbage rules, use \"man cribbage\"");
145 		}
146 	}
147 	playing = TRUE;
148 	do {
149 		wclrtobot(Msgwin);
150 		msg(quiet ? "L or S? " : "Long (to 121) or Short (to 61)? ");
151 		if (glimit == SGAME)
152 			glimit = (getuchar() == 'L' ? LGAME : SGAME);
153 		else
154 			glimit = (getuchar() == 'S' ? SGAME : LGAME);
155 		game();
156 		msg("Another game? ");
157 		playing = (getuchar() == 'Y');
158 	} while (playing);
159 
160 	if (f != NULL) {
161 		(void)fprintf(f, "%s: won %5.5d, lost %5.5d\n",
162 		    getlogin(), cgames, pgames);
163 		(void) fclose(f);
164 	}
165 	bye();
166 	exit(0);
167 }
168 
169 /*
170  * makeboard:
171  *	Print out the initial board on the screen
172  */
173 static void
174 makeboard(void)
175 {
176 	mvaddstr(SCORE_Y + 0, SCORE_X,
177 	    "+---------------------------------------+");
178 	mvaddstr(SCORE_Y + 1, SCORE_X,
179 	    "|  Score:   0     YOU                   |");
180 	mvaddstr(SCORE_Y + 2, SCORE_X,
181 	    "| *.....:.....:.....:.....:.....:.....  |");
182 	mvaddstr(SCORE_Y + 3, SCORE_X,
183 	    "| *.....:.....:.....:.....:.....:.....  |");
184 	mvaddstr(SCORE_Y + 4, SCORE_X,
185 	    "|                                       |");
186 	mvaddstr(SCORE_Y + 5, SCORE_X,
187 	    "| *.....:.....:.....:.....:.....:.....  |");
188 	mvaddstr(SCORE_Y + 6, SCORE_X,
189 	    "| *.....:.....:.....:.....:.....:.....  |");
190 	mvaddstr(SCORE_Y + 7, SCORE_X,
191 	    "|  Score:   0      ME                   |");
192 	mvaddstr(SCORE_Y + 8, SCORE_X,
193 	    "+---------------------------------------+");
194 	gamescore();
195 }
196 
197 /*
198  * gamescore:
199  *	Print out the current game score
200  */
201 static void
202 gamescore(void)
203 {
204 	if (pgames || cgames) {
205 		mvprintw(SCORE_Y + 1, SCORE_X + 28, "Games: %3d", pgames);
206 		mvprintw(SCORE_Y + 7, SCORE_X + 28, "Games: %3d", cgames);
207 	}
208 	Lastscore[0] = -1;
209 	Lastscore[1] = -1;
210 }
211 
212 /*
213  * game:
214  *	Play one game up to glimit points.  Actually, we only ASK the
215  *	player what card to turn.  We do a random one, anyway.
216  */
217 static void
218 game(void)
219 {
220 	int i, j;
221 	BOOLEAN flag;
222 	BOOLEAN compcrib;
223 
224 	compcrib = FALSE;
225 	makedeck(deck);
226 	shuffle(deck);
227 	if (gamecount == 0) {
228 		flag = TRUE;
229 		do {
230 			if (!rflag) {			/* player cuts deck */
231 				msg(quiet ? "Cut for crib? " :
232 			    "Cut to see whose crib it is -- low card wins? ");
233 				get_line();
234 			}
235 			i = (rand() >> 4) % CARDS;	/* random cut */
236 			do {	/* comp cuts deck */
237 				j = (rand() >> 4) % CARDS;
238 			} while (j == i);
239 			addmsg(quiet ? "You cut " : "You cut the ");
240 			msgcard(deck[i], FALSE);
241 			endmsg();
242 			addmsg(quiet ? "I cut " : "I cut the ");
243 			msgcard(deck[j], FALSE);
244 			endmsg();
245 			flag = (deck[i].rank == deck[j].rank);
246 			if (flag) {
247 				msg(quiet ? "We tied..." :
248 				    "We tied and have to try again...");
249 				shuffle(deck);
250 				continue;
251 			} else
252 				compcrib = (deck[i].rank > deck[j].rank);
253 		} while (flag);
254 		do_wait();
255 		clear();
256 		makeboard();
257 		refresh();
258 	} else {
259 		makeboard();
260 		refresh();
261 		werase(Tablewin);
262 		wrefresh(Tablewin);
263 		werase(Compwin);
264 		wrefresh(Compwin);
265 		msg("Loser (%s) gets first crib", (iwon ? "you" : "me"));
266 		compcrib = !iwon;
267 	}
268 
269 	pscore = cscore = 0;
270 	flag = TRUE;
271 	do {
272 		shuffle(deck);
273 		flag = !playhand(compcrib);
274 		compcrib = !compcrib;
275 	} while (flag);
276 	++gamecount;
277 	if (cscore < pscore) {
278 		if (glimit - cscore > 60) {
279 			msg("YOU DOUBLE SKUNKED ME!");
280 			pgames += 4;
281 		} else
282 			if (glimit - cscore > 30) {
283 				msg("YOU SKUNKED ME!");
284 				pgames += 2;
285 			} else {
286 				msg("YOU WON!");
287 				++pgames;
288 			}
289 		iwon = FALSE;
290 	} else {
291 		if (glimit - pscore > 60) {
292 			msg("I DOUBLE SKUNKED YOU!");
293 			cgames += 4;
294 		} else
295 			if (glimit - pscore > 30) {
296 				msg("I SKUNKED YOU!");
297 				cgames += 2;
298 			} else {
299 				msg("I WON!");
300 				++cgames;
301 			}
302 		iwon = TRUE;
303 	}
304 	gamescore();
305 }
306 
307 /*
308  * playhand:
309  *	Do up one hand of the game
310  */
311 static int
312 playhand(BOOLEAN mycrib)
313 {
314 	int deckpos;
315 
316 	werase(Compwin);
317 	wrefresh(Compwin);
318 	werase(Tablewin);
319 	wrefresh(Tablewin);
320 
321 	knownum = 0;
322 	deckpos = deal(mycrib);
323 	sorthand(chand, FULLHAND);
324 	sorthand(phand, FULLHAND);
325 	makeknown(chand, FULLHAND);
326 	prhand(phand, FULLHAND, Playwin, FALSE);
327 	discard(mycrib);
328 	if (cut(mycrib, deckpos))
329 		return TRUE;
330 	if (peg(mycrib))
331 		return TRUE;
332 	werase(Tablewin);
333 	wrefresh(Tablewin);
334 	if (score(mycrib))
335 		return TRUE;
336 	return FALSE;
337 }
338 
339 /*
340  * deal cards to both players from deck
341  */
342 static int
343 deal(BOOLEAN mycrib)
344 {
345 	int i, j;
346 
347 	for (i = j = 0; i < FULLHAND; i++) {
348 		if (mycrib) {
349 			phand[i] = deck[j++];
350 			chand[i] = deck[j++];
351 		} else {
352 			chand[i] = deck[j++];
353 			phand[i] = deck[j++];
354 		}
355 	}
356 	return (j);
357 }
358 
359 /*
360  * discard:
361  *	Handle players discarding into the crib...
362  * Note: we call cdiscard() after prining first message so player doesn't wait
363  */
364 static void
365 discard(BOOLEAN mycrib)
366 {
367 	const char *prompt;
368 	CARD crd;
369 
370 	prcrib(mycrib, TRUE);
371 	prompt = (quiet ? "Discard --> " : "Discard a card --> ");
372 	cdiscard(mycrib);	/* puts best discard at end */
373 	crd = phand[infrom(phand, FULLHAND, prompt)];
374 	cremove(crd, phand, FULLHAND);
375 	prhand(phand, FULLHAND, Playwin, FALSE);
376 	crib[0] = crd;
377 
378 	/* Next four lines same as last four except for cdiscard(). */
379 	crd = phand[infrom(phand, FULLHAND - 1, prompt)];
380 	cremove(crd, phand, FULLHAND - 1);
381 	prhand(phand, FULLHAND, Playwin, FALSE);
382 	crib[1] = crd;
383 	crib[2] = chand[4];
384 	crib[3] = chand[5];
385 	chand[4].rank = chand[4].suit = chand[5].rank = chand[5].suit = EMPTY;
386 }
387 
388 /*
389  * cut:
390  *	Cut the deck and set turnover.  Actually, we only ASK the
391  *	player what card to turn.  We do a random one, anyway.
392  */
393 static int
394 cut(BOOLEAN mycrib, int  pos)
395 {
396 	int i;
397 	BOOLEAN win;
398 
399 	win = FALSE;
400 	if (mycrib) {
401 		if (!rflag) {	/* random cut */
402 			msg(quiet ? "Cut the deck? " :
403 		    "How many cards down do you wish to cut the deck? ");
404 			get_line();
405 		}
406 		i = (rand() >> 4) % (CARDS - pos);
407 		turnover = deck[i + pos];
408 		addmsg(quiet ? "You cut " : "You cut the ");
409 		msgcard(turnover, FALSE);
410 		endmsg();
411 		if (turnover.rank == JACK) {
412 			msg("I get two for his heels");
413 			win = chkscr(&cscore, 2);
414 		}
415 	} else {
416 		i = (rand() >> 4) % (CARDS - pos) + pos;
417 		turnover = deck[i];
418 		addmsg(quiet ? "I cut " : "I cut the ");
419 		msgcard(turnover, FALSE);
420 		endmsg();
421 		if (turnover.rank == JACK) {
422 			msg("You get two for his heels");
423 			win = chkscr(&pscore, 2);
424 		}
425 	}
426 	makeknown(&turnover, 1);
427 	prcrib(mycrib, FALSE);
428 	return (win);
429 }
430 
431 /*
432  * prcrib:
433  *	Print out the turnover card with crib indicator
434  */
435 static void
436 prcrib(BOOLEAN mycrib, BOOLEAN blank)
437 {
438 	int y, cardx;
439 
440 	if (mycrib)
441 		cardx = CRIB_X;
442 	else
443 		cardx = 0;
444 
445 	mvaddstr(CRIB_Y, cardx + 1, "CRIB");
446 	prcard(stdscr, CRIB_Y + 1, cardx, turnover, blank);
447 
448 	if (mycrib)
449 		cardx = 0;
450 	else
451 		cardx = CRIB_X;
452 
453 	for (y = CRIB_Y; y <= CRIB_Y + 5; y++)
454 		mvaddstr(y, cardx, "       ");
455 	refresh();
456 }
457 
458 /*
459  * peg:
460  *	Handle all the pegging...
461  */
462 static CARD Table[14];
463 static unsigned Tcnt;
464 
465 static int
466 peg(BOOLEAN mycrib)
467 {
468 	static CARD ch[CINHAND], ph[CINHAND];
469 	int i, j, k;
470 	int l;
471 	int cnum, pnum, sum;
472 	BOOLEAN myturn, mego, ugo, last, played;
473 	CARD crd;
474 
475 	played = FALSE;
476 	cnum = pnum = CINHAND;
477 	for (i = 0; i < CINHAND; i++) {	/* make copies of hands */
478 		ch[i] = chand[i];
479 		ph[i] = phand[i];
480 	}
481 	Tcnt = 0;		/* index to table of cards played */
482 	sum = 0;		/* sum of cards played */
483 	mego = ugo = FALSE;
484 	myturn = !mycrib;
485 	for (;;) {
486 		last = TRUE;	/* enable last flag */
487 		prhand(ph, pnum, Playwin, FALSE);
488 		prhand(ch, cnum, Compwin, TRUE);
489 		prtable(sum);
490 		if (myturn) {	/* my tyrn to play */
491 			if (!anymove(ch, cnum, sum)) {	/* if no card to play */
492 				if (!mego && cnum) {	/* go for comp? */
493 					msg("GO");
494 					mego = TRUE;
495 				}
496 							/* can player move? */
497 				if (anymove(ph, pnum, sum))
498 					myturn = !myturn;
499 				else {			/* give him his point */
500 					msg(quiet ? "You get one" :
501 					    "You get one point");
502 					do_wait();
503 					if (chkscr(&pscore, 1))
504 						return TRUE;
505 					sum = 0;
506 					mego = ugo = FALSE;
507 					Tcnt = 0;
508 				}
509 			} else {
510 				played = TRUE;
511 				j = -1;
512 				k = 0;
513 							/* maximize score */
514 				for (i = 0; i < cnum; i++) {
515 					l = pegscore(ch[i], Table, Tcnt, sum);
516 					if (l > k) {
517 						k = l;
518 						j = i;
519 					}
520 				}
521 				if (j < 0)		/* if nothing scores */
522 					j = cchose(ch, cnum, sum);
523 				crd = ch[j];
524 				cremove(crd, ch, cnum--);
525 				sum += VAL(crd.rank);
526 				Table[Tcnt++] = crd;
527 				if (k > 0) {
528 					addmsg(quiet ? "I get %d playing " :
529 					    "I get %d points playing ", k);
530 					msgcard(crd, FALSE);
531 					endmsg();
532 					if (chkscr(&cscore, k))
533 						return TRUE;
534 				}
535 				myturn = !myturn;
536 			}
537 		} else {
538 			if (!anymove(ph, pnum, sum)) {	/* can player move? */
539 				if (!ugo && pnum) {	/* go for player */
540 					msg("You have a GO");
541 					ugo = TRUE;
542 				}
543 							/* can computer play? */
544 				if (anymove(ch, cnum, sum))
545 					myturn = !myturn;
546 				else {
547 					msg(quiet ? "I get one" :
548 					    "I get one point");
549 					do_wait();
550 					if (chkscr(&cscore, 1))
551 						return TRUE;
552 					sum = 0;
553 					mego = ugo = FALSE;
554 					Tcnt = 0;
555 				}
556 			} else {			/* player plays */
557 				played = FALSE;
558 				if (pnum == 1) {
559 					crd = ph[0];
560 					msg("You play your last card");
561 				} else
562 					for (;;) {
563 						prhand(ph,
564 						    pnum, Playwin, FALSE);
565 						crd = ph[infrom(ph,
566 						    pnum, "Your play: ")];
567 						if (sum + VAL(crd.rank) <= 31)
568 							break;
569 						else
570 					msg("Total > 31 -- try again");
571 					}
572 				makeknown(&crd, 1);
573 				cremove(crd, ph, pnum--);
574 				i = pegscore(crd, Table, Tcnt, sum);
575 				sum += VAL(crd.rank);
576 				Table[Tcnt++] = crd;
577 				if (i > 0) {
578 					msg(quiet ? "You got %d" :
579 					    "You got %d points", i);
580 					if (pnum == 0)
581 						do_wait();
582 					if (chkscr(&pscore, i))
583 						return TRUE;
584 				}
585 				myturn = !myturn;
586 			}
587 		}
588 		if (sum >= 31) {
589 			if (!myturn)
590 				do_wait();
591 			sum = 0;
592 			mego = ugo = FALSE;
593 			Tcnt = 0;
594 			last = FALSE;			/* disable last flag */
595 		}
596 		if (!pnum && !cnum)
597 			break;				/* both done */
598 	}
599 	prhand(ph, pnum, Playwin, FALSE);
600 	prhand(ch, cnum, Compwin, TRUE);
601 	prtable(sum);
602 	if (last) {
603 		if (played) {
604 			msg(quiet ? "I get one for last" :
605 			    "I get one point for last");
606 			do_wait();
607 			if (chkscr(&cscore, 1))
608 				return TRUE;
609 		} else {
610 			msg(quiet ? "You get one for last" :
611 			    "You get one point for last");
612 			do_wait();
613 			if (chkscr(&pscore, 1))
614 				return TRUE;
615 		}
616 	}
617 	return (FALSE);
618 }
619 
620 /*
621  * prtable:
622  *	Print out the table with the current score
623  */
624 static void
625 prtable(int curscore)
626 {
627 	prhand(Table, Tcnt, Tablewin, FALSE);
628 	mvwprintw(Tablewin, (Tcnt + 2) * 2, Tcnt + 1, "%2d", curscore);
629 	wrefresh(Tablewin);
630 }
631 
632 /*
633  * score:
634  *	Handle the scoring of the hands
635  */
636 static int
637 score(BOOLEAN mycrib)
638 {
639 	sorthand(crib, CINHAND);
640 	if (mycrib) {
641 		if (plyrhand(phand, "hand"))
642 			return (TRUE);
643 		if (comphand(chand, "hand"))
644 			return (TRUE);
645 		do_wait();
646 		if (comphand(crib, "crib"))
647 			return (TRUE);
648 		do_wait();
649 	} else {
650 		if (comphand(chand, "hand"))
651 			return (TRUE);
652 		if (plyrhand(phand, "hand"))
653 			return (TRUE);
654 		if (plyrhand(crib, "crib"))
655 			return (TRUE);
656 	}
657 	return (FALSE);
658 }
659