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 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 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 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 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 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 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 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 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 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 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 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 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