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