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