home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The C Users' Group Library 1994 August
/
wc-cdrom-cusersgrouplibrary-1994-08.iso
/
vol_300
/
332_02
/
yahtzee.c
< prev
Wrap
C/C++ Source or Header
|
1990-03-29
|
19KB
|
920 lines
/*- -*- Fundamental -*-
*
* Facility: yahtzee
*
* File: yahtzee.c
*
* Associated files: yahtzee.hlp -- man page
*
* Description: Dice game yahtzee
*
* Portability: Conforms to X/Open Portability Guide, ed. 3,
* I hope ...
*
* Author: Steve Ward
*
* Editor: Anders Thulin
* Rydsvagen 288
* S-582 50 Linkoping
* SWEDEN
*
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Edit history :
*
* Vers Ed Date By Comments
* ---- --- ---------- ---------------- -------------------------------
* 1.0 0 19xx-xx-xx Steve Ward
* 1.1 1 1988-10-25 Anders Thulin Original for H89 computer -
* changed to curses. Cleaned
* up user interface a bit.
*
*/
/* - - Configuration options: - - - - - - - - - - - - - - - - - - - - - - */
/*
* Compile-time environment:
*
* ANSI ANSI C
* BSD BSD Unix, SunOS 3.5
* SV2 AT&T UNIX System V.2
* XPG3 X/Open Portability Guide, ed. 3
* ZTC205 Zortech C 2.05
*
* If you have an ANSI C conformant compiler, use ANSI. Otherwise choose
* the environment that best matches yours.
*
*/
#define ANSI 0
#define BSD 0
#define SV2 0
#define XPG3 0
#define ZTC205 1
/* - - end of configuration - - - - - - - - - - - - - - - - - - - - - - - - */
/* --- Known problems: --------------------------------------------------
curses
Some older implementation of curses dows not have any of the
functions beep(), nodelay() and delay_output().
It may be possible to emulate beep() through calls similar to
fputc(0x07, stderr).
It will, in general, not be possible to emulate nodelay(), as
it changes the behaviour of getch() from blocking to non-blocking(),
that is, if no character is immediately available, return ERR.
delay_output() just waits for a number of seconds. It is only
used in self-play mode, so it can safely be ignored.
------------------------------------------------------------------------*/
#if ANSI
# include <ctype.h>
# include <curses.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
#endif
#if BSD
# include <ctype.h>
# include <curses.h>
# include <string.h>
typedef long time_t;
#endif
#if SV2
# include <ctype.h>
# include <curses.h>
# include <string.h>
typedef long time_t;
#endif
#if XPG3
# include <ctype.h>
# include <curses.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
#endif
#if ZTC205
# include <ctype.h>
# include <curses.h>
# include <string.h>
# include <time.h>
#endif
/* Command characters used only by auto player: */
#define PASSC -1 /* Accept roll */
#define ROLLC -2
/* Coordinates for screen output - assumes 24 x 80 screen */
#define GOX 45 /* Coordinates for prompt. */
#define GOY 22
#define GAMEY 20
#define SCOREX 30 /* leftmost column for scores */
#define DIEY 22
#define PLAYERS 10 /* Max number of players */
#define NONE (-1) /* illegal value. */
#define TOP 0
#define MID 8
#define BOT 16
/* Various variables: */
int AutoPar[10]; /* heuristic parameters -HA#, -HB#, etc. */
int Dice[5]; /* current roll */
int DTime; /* delay time between moves (in seconds) */
int nplayers; /* number of players */
unsigned rand_seed; /* random number seed */
int ShowTot;
char Potent[13]; /* Potential score... */
char PotKind, PotSum, PotStr; /* Filled by Pot */
char PotMax, PotCnt;
int *ShowSc;
char ShowX, ShowY;
struct Player {
int Scores[13]; /* Scores, or 127 iff none yet. */
int Select; /* Currently selected category. */
int Col; /* Column number, for scores. */
char Name[40]; /* Name of player. */
int Total; /* total score. */
int Games;
} Who[PLAYERS];
/* Local routines: */
#if __STDC__
int Auto(int pl, int rolls);
void Bd(int f);
void Board(void);
void Center(char *text, int centx, int y, int xsize);
int Cycle(int pl, int delta);
void Delay(int sec);
void Die(int number, int count);
int Go(int rolls, int player);
void iplayer(char *name, struct Player *pl);
int Kind(int die);
void Play(int pl);
void Pot(int pl);
void Roll(void);
void Show(int pl);
void Show1(int n, int flag);
int Straight(int die);
#else
int Auto();
void Bd();
void Board();
void Center();
int Cycle();
void Delay();
void Die();
int Go();
void iplayer();
int Kind();
void Play();
void Pot();
void Roll();
void Show();
void Show1();
int Straight();
#endif
/*
* Routine: Auto
*
* Description: Make move for a computer player.
*
* Note that the routine handles selection of dice
* to reroll internally, without bothering to
* go through 'Go()'.
*
* Strategy is somewhat unclear.
*
*/
int Auto(pl, rolls)
int pl;
char rolls;
{
int choice;
int i;
int *sc;
char gofor, j;
int value[13], target[6], best, n;
sc = Who[pl].Scores;
Pot(pl);
mvaddstr(GOY+1, GOX, "Hmmm ... lemme think."); clrtoeol();
Delay(DTime);
for (i=0; i<13; i++) {
if (sc[i] != NONE) value[i] = i-99;
else if (i<6) value[i] = 2*(Potent[i]-3*(i+1));/* face counts */
else if (i==6) value[i] = Potent[i]-20; /* 3 of a kind */
else if (i==7) value[i] = Potent[i]-15; /* 4 of a kind */
else if (i==9) value[i] = Potent[i]-12; /* 4 straight */
else if (i==10) value[i] = Potent[i]-10; /* 5 straight */
else if (i==12) value[i] = Potent[i]-21-AutoPar[5]; /* CHANCE */
else value[i] = Potent[i]-10;
}
for (i=0; i<6; i++) {
if (sc[12] == NONE) target[i] = i+i; else target[i] = 0;
if (sc[i] == NONE) target[i] += i<<2; else target[i] -= 15;
if ((sc[8] == NONE) && (Kind(i+1) == 3)) target[i] += 6;
if (sc[11] == NONE) target[i] += 1<<(Kind(i+1));
target[i] += Kind(i+1)<<3;
}
best = -99; gofor = PotCnt; choice = Who[pl].Select;
for (i=0; i<13; i++) {
if (value[i] > best) {
best = value[i]; choice = i;
}
}
Who[pl].Select = choice;
Show(pl);
if (rolls && /* Convert a straight?? */
(Potent[9] > 0) &&
(Potent[10] == 0) &&
(sc[10] == NONE)) {
for (i=0; i<5; i++) { /* Find the useless die. */
j = Dice[i];
Dice[i] = 0;
Pot(pl);
Dice[i] = j;
if (Potent[9] > 0) {
Die(i, 0);
goto rollit;
}
}
}
Pot(pl);
if ((choice > 7) && (choice < 12)) goto hit; /* End the inning. */
if (rolls) {
for (i=0; i<6; i++) {
if ((n=(target[i]-27-AutoPar[0])) > best) {
gofor = i;
choice = 255;
best=n;
}
}
}
if ((choice == 255) || (choice < 8))
goto maxim; /* See if we can help. */
if (!rolls) goto hit; /* Least of evils... */
for (i=0; i<5; i++) Die(i,0); /* Re-roll entire hand. */
goto rollit;
maxim:
if (!rolls) goto hit; /* Maximize number of chosen dice. */
for (i=0; i<5; i++)
if (Dice[i] && (Dice[i] != (gofor+1)))
Die(i, 0);
rollit:
move(GOY+1, GOX);
printw("Baby needs shoes ... %d/%d", choice, best);
clrtoeol();
Delay(DTime/2);
return ROLLC;
hit:
move(GOY+1, GOX);
printw("I've decided ... %d/%d", choice, best);
clrtoeol();
Delay(DTime);
return PASSC;
}
/*
* Routine: Bd
*
* Description: Display empty board line. Type of line depends
* on argument.
*
*/
void Bd(f)
int f;
{
char *cc;
int i;
cc = " . ";
if (f == 1) {
cc = "===";
} else if (!f) {
#if 0
puts("\033q"); /* normal mode */
#endif
}
for (i=((COLS-SCOREX)/3); i--;) {
addstr(cc);
}
#if 0
puts("\033p\033G"); /* inverse mode, no graphics */
#endif
}
/*
* Routine: Board
*
* Description: Print empty score-sheet
*
*/
void Board()
{
int i;
clear();
addstr(" YAHTZEE 1.1 ");
for (i=48; i--;) {
addch('=');
}
move( 1, 0); addstr(" ACES ADD 1's "); Bd(0);
move( 2, 0); addstr(" TWOS ADD 2's "); Bd(0);
move( 3, 0); addstr(" THREES ADD 3's "); Bd(0);
move( 4, 0); addstr(" FOURS ADD 4's "); Bd(0);
move( 5, 0); addstr(" FIVES ADD 5's "); Bd(0);
move( 6, 0); addstr(" SIXES ADD 6's "); Bd(0);
move( 7, 0); addstr(" Subtotal . . . . . "); Bd(2);
move( 8, 0); addstr(" Bonus iff >= 63 35 "); Bd(2);
move( 9, 0); addstr("TOTAL ABOVE . . . . . "); Bd(2);
move(11, 0); addstr(" 3 of a kind SUM "); Bd(0);
move(12, 0); addstr(" 4 of a kind SUM "); Bd(0);
move(13, 0); addstr(" Full House 25 "); Bd(0);
move(14, 0); addstr(" 4 Straight 30 "); Bd(0);
move(15, 0); addstr(" 5 Straight 40 "); Bd(0);
move(16, 0); addstr(" YAHTZEE 50 "); Bd(0);
move(17, 0); addstr(" Chance SUM "); Bd(0);
move(18, 0); addstr("TOTAL SCORE . . . . . "); Bd(2);
move(GAMEY, 0); addstr("GAMES . . . . . . . "); Bd(1);
}
/*
* Routine: Center
*
* Description: Write a string centered about the x coordinate.
*
*/
void Center(text, centx, y, xsize)
char *text;
int centx, y, xsize;
{
int l;
l = strlen(text);
if (l > xsize) l = xsize;
move(y, centx - l/2);
addch(' ');
while (*text && l--) addch(*text++);
addch(' ');
}
/*
* Routine: Cycle
*
* Description: Alter the screen selection of a player. The
* delta parameter is 1 for moving downwards, or
* -1 for upwards.
*
*/
int Cycle(pl, delta)
int pl;
int delta;
{
int tries; /* Remaining nr of tries to find empty selection */
int s; /* Player's current/new selection */
s = Who[pl].Select;
tries = 14;
do {
s += delta;
if (s < 0) {
s = 12;
} else if (s > 12) {
s = 0;
}
} while (--tries > 0 && (Who[pl].Scores[s] != NONE));
Who[pl].Select = s;
return tries; /* 0 if no more selections available */
}
/*
* Routine: Delay
*
* Description: Wait for a number of seconds
*
*/
void Delay(s)
int s;
{
#ifndef __POWERC
delay_output(1000*s);
#endif
}
/*
* Routine: Die
*
* Description: Print a die face with COUNT dots in the NUMBER
* place.
*
*/
void Die(number, count)
int number;
int count;
{
int i;
if (count != 0) {
attron(A_REVERSE);
}
for (i=0; i<3; i++) { /* Once for each row on the die */
move(DIEY+i, 4+(7*number));
switch (i*8 + count) {
case TOP+0: /* count is 0 (empty) for re-rolled */
case MID+0:
case BOT+0:
case TOP+1:
case BOT+1:
case MID+2:
case MID+4:
addstr(" ");
break;
case TOP+2:
case TOP+3:
addstr("* ");
break;
case MID+1:
case MID+3:
case MID+5:
addstr(" * ");
break;
case BOT+2:
case BOT+3:
addstr(" *");
break;
case TOP+4:
case BOT+4:
case TOP+5:
case BOT+5:
case TOP+6:
case MID+6:
case BOT+6:
addstr("* *");
break;
}
}
Dice[number] = count;
if (count != 0) {
attroff(A_REVERSE);
}
}
/*
* Routine: Go
*
* Description: Command interpreter. Accepts commands either
* from player or auto-player and performs them.
*
* Note: Auto-player does some internal command handling
*
*/
int Go(rolls, player)
int rolls, player;
{
int ch; /* command */
int i;
int save[5]; /* copy of latest roll */
int reroll; /* true if user has selected dice to reroll */
Pot(player);
for (i=0; i<5; i++) {
Dice[i] = 0;
}
Roll();
Who[player].Select = 12;
if (!Cycle(player, 1)) return 0;
Top:
for (i=0; i<5; i++) save[i] = Dice[i];
Show(player);
reroll = FALSE;
for (;;) {
move(GOY, GOX);
printw("%s's move: %d rolls left. ", Who[player].Name, rolls);
clrtoeol();
esc:
/* n. Get and execute command: */
refresh();
if (Who[player].Name[0] == '=') {
ch = Auto(player, rolls);
} else {
ch = toupper(getch());
}
switch (ch) {
case 033:
goto esc;
case ' ': /* Go 'down' */
Cycle(player, 1);
Show(player);
continue;
case '1': /* select die to be rerolled */
case '2':
case '3':
case '4':
case '5':
if (rolls == 0) { /* sorry - no rolls left */
beep();
} else {
Die(ch - '1', 0);
reroll = TRUE;
}
continue;
case 0x08: /* Backspace */
case 0x7F: /* Delete == undo selection */
for (i=0; i<5; i++) Die(i, save[i]);
continue;
case ROLLC: /* make new roll - from Auto() */
if (rolls>0) { Roll(); rolls--; }
else beep();
goto Top;
case '\r':
case '\n':
case PASSC:
if (reroll) { /* Make new roll */
if (rolls>0) { Roll(); rolls--; }
else beep();
goto Top;
} else { /* Accept latest roll & selection */
Play(player);
Who[player].Select = NONE;
Show(player);
return ch;
}
default: /* unknown input */
beep();
continue;
}
}
}
/*
* Routine: iplayer
*
* Description: Initialize a player
*
*/
void iplayer(name, pl)
char *name;
struct Player *pl;
{
char i, *fake;
fake = "==0==";
if (!name[1] && *name == '=') {
name = fake; name[3]++;
}
for (i=0; i<13; i++) {
pl->Scores[i] = NONE;
}
pl->Select = NONE;
strcpy(pl->Name, name);
}
/*
* Routine: Kind
*
* Description: Gives number of dice of count die
*
*/
int Kind(die)
int die;
{
int count, i;
count = 0;
for (i=0; i<5; i++) {
if (die == Dice[i]) {
count++;
}
}
return count;
}
/*
* Routine: main
*
* Description: ...
*
*/
int main(argc, argv)
int argc;
char *argv[];
{
int arg;
int i;
char j,*carg;
int n;
/* 0. Initialize: */
for (i=0; i<PLAYERS; i++) Who[i].Games = 0;
for (i=0; i<10; i++) AutoPar[i] = 0;
DTime = 2;
initscr();
cbreak(); /* return keypresses immediately */
nodelay(stdscr, FALSE); /* read blocks */
rand_seed = time((time_t *)0);
for (arg=1; (arg<argc) && (*(carg = argv[arg]) == '-'); arg++) {
switch (*++carg) {
case 'R':
rand_seed = atoi(++carg);
continue;
case 'H':
i = (*++carg)-'A';
AutoPar[i] = atoi(++carg);
continue;
case 'D':
DTime = atoi(++carg)|1;
continue;
default:
continue;
}
}
srand(rand_seed);
Again:
nplayers = 0;
for (i=arg; i<argc; i++) {
iplayer(argv[i], &Who[nplayers++]);
}
if (nplayers == 0) {
iplayer("You", &Who[nplayers++]);
}
n = (80-SCOREX)/nplayers;
for (i=SCOREX+(n/2), j=0; j<nplayers; j++, i += n) Who[j].Col = i;
Board();
for (j=0; j<nplayers; j++) Center(Who[j].Name, Who[j].Col, 0, n-1);
for (i=0; i<nplayers; i++) Show(i);
for (;;) {
for (i=0; i<nplayers; i++) {
if (!Go(2, i)) {
for (i=n=0; i<nplayers; i++)
if (Who[i].Total > n)
n = Who[i].Total;
for (i=0; i<nplayers; i++)
if (Who[i].Total >= n) {
move(GAMEY, Who[i].Col);
standout();
printw("%3d", ++(Who[i].Games));
}
move(GOY+1, GOX);
for (i=0; i<nplayers; i++)
if (Who[i].Total >= n)
printw("%s ", Who[i].Name);
beep();
addstr("WINS!");
standend();
addstr(" Play again?"); clrtoeol();
if (toupper(getch()) == 'Y') goto Again;
endwin();
exit(0);
}
}
}
}
/*
* Routine: Play
*
* Description: Make a player's move.
*
*/
void Play(pl)
int pl;
{
Pot(pl);
Who[pl].Scores[Who[pl].Select] = Potent[Who[pl].Select];
}
/*
* Routine: Pot
*
* Description: Compute potential scores for player.
*
* Notes: A. Thulin:
* Odd that the parameter isn't used ...
*/
void Pot(pl)
int pl;
{
int i,j,k;
for (i=0; i<13; i++) Potent[i] = 0;
for (i=0; i<5; i++) if (((j = Dice[i])>0) && (j<7)) Potent[j-1] += j;
for (PotMax=PotSum=i=PotKind=0; i<5; i++)
{ PotKind |= (1<<(j=Kind(k=Dice[i]))); PotSum += Dice[i];
if (j>PotMax) { PotMax=j; PotCnt=k; }}
Potent[12] = PotSum;
if ((PotKind & 0xC) == 0xC) Potent[8] = 25;
if (PotKind & 0x38) Potent[6] = PotSum;
if (PotKind & 0x30) Potent[7] = PotSum;
if (PotKind & 0x20) Potent[11] = 50;
for (i=PotStr=0; i<5; i++) if ((j = Straight(Dice[i])) > PotStr)
PotStr=j;
if (PotStr >= 4) Potent[9] = 30;
if (PotStr == 5) Potent[10] = 40;
}
/*
* Routine: Roll
*
* Description: Roll the dice ...
*
*/
void Roll()
{
int i;
for (i=0; i<5; i++) {
if (Dice[i] == 0) {
Die(i, 1+rand()%6);
}
}
}
/*
* Routine: Show
*
* Description: Show the scores of a player
*
*/
void Show(pl)
char pl;
{
int f;
struct Player *p;
int cc;
char tot, j;
Pot(pl);
p = &Who[pl];
ShowX = p->Col-1; ShowY = 1; ShowTot = 0;
ShowSc = p->Scores; cc = 0; f = p->Select;
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
tot = ShowTot;
Show1(255,44);
ShowTot = j = tot>=63? 35:0;
Show1(255,44);
tot = ShowTot = tot+j;
Show1(255,44);
ShowTot = tot;
ShowY++;
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
Show1(cc++,f--);
p->Total = ShowTot;
Show1(255,44);
}
/*
* Routine: Show1
*
* Description: ...
*
*/
void Show1(n, flag)
int n, flag;
{
int val;
move(ShowY++, ShowX);
if (n == 255) {
val = ShowTot;
} else {
val = ShowSc[n];
}
if (!flag) {
standout();
val = Potent[n];
} else if (flag == 44) {
standout();
}
if (val == NONE) {
addstr(" ? ");
} else {
printw(" %2d", val);
ShowTot += val;
}
if (!flag || (flag == 44)) {
standend();
}
}
/*
* Routine: Straight
*
* Description: Gives longest straight starting at die
*
*/
int Straight(die)
int die;
{
int i, j;
for (i=1; i<6; i++) {
die++;
for (j=0; j<5; j++) {
if (Dice[j] == die) goto hit;
}
break;
hit:
continue;
}
return i;
}