/* Copyright (c) 1982 Regents of the University of California */
static char sccsid
[] = "@(#)canfield.c 4.9 %G%";
* Originally written: Steve Levine
* Converted to use curses and debugged: Steve Feldman
* Card counting: Kirk McKusick and Mikey Olson
* User interface cleanups: Eric Allman and Kirk McKusick
* Betting by Kirk McKusick
#define INCRHAND(row, col) {\
#define DECRHAND(row, col) {\
#define NIL ((struct cardtype *) -1)
struct cardtype
*deck
[decksize
];
struct cardtype cards
[decksize
];
struct cardtype
*bottom
[4], *found
[4], *tableau
[4];
struct cardtype
*talon
, *hand
, *stock
, *basecard
;
int cardsoff
, base
, cinhand
, taloncnt
, stockcnt
, timesthru
;
char suitmap
[4] = {spades
, clubs
, hearts
, diamonds
};
char colormap
[4] = {black
, black
, red
, red
};
char pilemap
[4] = {atabcol
, btabcol
, ctabcol
, dtabcol
};
int coldcol
, cnewcol
, coldrow
, cnewrow
;
bool mtfdone
, Cflag
= FALSE
;
int status
= INSTRUCTIONBOX
;
#define costofinspection 13
#define costofrunthroughhand 5
#define costofinformation 1
#define secondsperdollar 60
* Variables associated with betting
long hand
; /* cost of dealing hand */
long inspection
; /* cost of inspecting hand */
long game
; /* cost of buying game */
long runs
; /* cost of running through hands */
long information
; /* cost of information */
long thinktime
; /* cost of thinking time */
long wins
; /* total winnings */
long worth
; /* net worth after costs */
struct betinfo
this, total
;
bool startedgame
= FALSE
, infullgame
= FALSE
;
* The following procedures print the board onto the screen using the
* addressible cursor. The end of these procedures will also be
* separated from the rest of the program.
* procedure to set the move command box
printbottominstructions();
* print directions above move box
printw("*--------------------------*");
move(tboxrow
+ 1, boxcol
);
move(tboxrow
+ 2, boxcol
);
printw("|s# = stock to tableau |");
move(tboxrow
+ 3, boxcol
);
printw("|sf = stock to foundation |");
move(tboxrow
+ 4, boxcol
);
printw("|t# = talon to tableau |");
move(tboxrow
+ 5, boxcol
);
printw("|tf = talon to foundation |");
move(tboxrow
+ 6, boxcol
);
printw("|## = tableau to tableau |");
move(tboxrow
+ 7, boxcol
);
printw("|#f = tableau to foundation|");
move(tboxrow
+ 8, boxcol
);
printw("|ht = hand to talon |");
move(tboxrow
+ 9, boxcol
);
printw("|c = toggle card counting |");
move(tboxrow
+ 10, boxcol
);
printw("|b = present betting info |");
move(tboxrow
+ 11, boxcol
);
printw("|q = quit to end the game |");
move(tboxrow
+ 12, boxcol
);
printw("|==========================|");
move(tboxrow
+ 1, boxcol
);
printw("*--------------------------*");
move(tboxrow
+ 2, boxcol
);
printw("|Costs Hand Total |");
move(tboxrow
+ 3, boxcol
);
move(tboxrow
+ 4, boxcol
);
printw("| Inspections |");
move(tboxrow
+ 5, boxcol
);
move(tboxrow
+ 6, boxcol
);
move(tboxrow
+ 7, boxcol
);
printw("| Information |");
move(tboxrow
+ 8, boxcol
);
printw("| Think time |");
move(tboxrow
+ 9, boxcol
);
printw("|Total Costs |");
move(tboxrow
+ 10, boxcol
);
move(tboxrow
+ 11, boxcol
);
move(tboxrow
+ 12, boxcol
);
printw("|==========================|");
* clear info above move box
for (i
= 0; i
<= 11; i
++) {
move(tboxrow
+ i
, boxcol
);
move(tboxrow
+ 12, boxcol
);
printw("*--------------------------*");
* print instructions below move box
printbottominstructions()
printw("|Replace # with the number |");
move(bboxrow
+ 1, boxcol
);
printw("|of the tableau you want. |");
move(bboxrow
+ 2, boxcol
);
printw("*--------------------------*");
* print betting information below move box
printw("|x = toggle information box|");
move(bboxrow
+ 1, boxcol
);
printw("|i = list instructions |");
move(bboxrow
+ 2, boxcol
);
printw("*--------------------------*");
* clear info below move box
printw("*--------------------------*");
for (i
= 1; i
<= 2; i
++) {
move(bboxrow
+ i
, boxcol
);
* procedure to put the board on the screen using addressable cursor
move(titlerow
, titlecol
);
printw("=-> CANFIELD <-=");
move(foundrow
- 1, fttlcol
);
printw("=---= =---= =---= =---=");
printw("| | | | | | | |");
move(foundrow
+ 1, fttlcol
);
printw("=---= =---= =---= =---=");
move(stockrow
- 1, sidecol
);
move(stockrow
+ 1, sidecol
);
move(talonrow
- 2, sidecol
);
move(talonrow
- 1, sidecol
);
move(talonrow
+ 1, sidecol
);
move(tabrow
- 1, atabcol
);
printw("-1- -2- -3- -4-");
* clean up the board for another game
for(ptr
= stock
, row
= stockrow
;
ptr
= ptr
->next
, row
++) {
move(stockrow
+ 1, sidecol
);
move(talonrow
- 2, sidecol
);
move(talonrow
- 1, sidecol
);
move(talonrow
+ 1, sidecol
);
printw("| | | | | | | |");
for (cnt
= 0; cnt
< 4; cnt
++) {
for(ptr
= tableau
[cnt
], row
= tabrow
;
* procedure to create a deck of cards
for (scnt
=0; scnt
<4; scnt
++) {
for (r
=Ace
; r
<=King
; r
++) {
cards
[i
].color
= colormap
[scnt
];
* procedure to shuffle the deck
for (i
=0; i
<decksize
; i
++) {
deck
[i
]->visible
= FALSE
;
for (i
= decksize
-1; i
>=0; i
--) {
* procedure to remove the card from the board
* procedure to print the cards on the board
printrank(a
, b
, cp
, inverse
)
case 2: case 3: case 4: case 5: case 6: case 7:
* procedure to print out a card
else if (cp
->visible
== FALSE
) {
bool inverse
= (cp
->suit
== 'd' || cp
->suit
== 'h');
printrank(a
, b
, cp
, inverse
);
* procedure to move the top card from one location to the top
* of another location. The pointers always point to the top
struct cardtype
**source
, **dest
;
*source
= (*source
)->next
;
* Procedure to set the cards on the foundation base when available.
* Note that it is only called on a foundation pile at the beginning of
* the game, so the pile will have exactly one card in it.
if ((*cp
)->rank
== basecard
->rank
) {
printcard(pilemap
[base
], foundrow
, *cp
);
length
[0] = length
[0] - 1;
length
[1] = length
[1] - 1;
length
[2] = length
[2] - 1;
length
[3] = length
[3] - 1;
transit(cp
, &found
[base
]);
printcard(column
, row
, *cp
);
this.wins
+= valuepercardup
;
total
.wins
+= valuepercardup
;
} while (nomore
== FALSE
);
* procedure to initialize the things necessary for the game
deck
[i
]->next
= deck
[i
- 1];
tableau
[i
- 14] = deck
[i
];
for (i
=18; i
<decksize
-1; i
++)
deck
[i
]->next
= deck
[i
+ 1];
deck
[decksize
-1]->next
= NIL
;
cnewcol
= cinitcol
+ cwidthcol
;
* procedure to print the beginning cards and to start each game
total
.hand
+= costofhand
;
printcard(foundcol
, foundrow
, found
[0]);
printcard(stockcol
, stockrow
, stock
);
printcard(atabcol
, tabrow
, tableau
[0]);
printcard(btabcol
, tabrow
, tableau
[1]);
printcard(ctabcol
, tabrow
, tableau
[2]);
printcard(dtabcol
, tabrow
, tableau
[3]);
printcard(taloncol
, talonrow
, talon
);
move(foundrow
- 2, basecol
);
move(foundrow
- 1, basecol
);
printrank(basecol
, foundrow
, found
[0], 0);
fndbase(&tableau
[j
], pilemap
[j
], tabrow
);
fndbase(&stock
, stockcol
, stockrow
);
showstat(); /* show card counting info to cheaters */
* procedure to clear the message printed from an error
* procedure to print an error message if the move is not listed
printw("Not a proper move ");
* procedure to print an error message if the move is not possible
printw("Error: Can't move there");
* function to see if the source has cards in it
printw("Error: no cards to move");
* function to see if the rank of one card is less than another
struct cardtype
*cp1
, *cp2
;
else if (cp1
->rank
+ 1 == cp2
->rank
)
* function to check the cardcolor for moving to a tableau
struct cardtype
*cp1
, *cp2
;
if (cp1
->color
== cp2
->color
)
* function to see if the card can move to the tableau
if ((cp
== stock
) && (tableau
[des
] == NIL
))
else if (tableau
[des
] == NIL
)
else if (ranklower(cp
, tableau
[des
]) && diffcolor(cp
, tableau
[des
]))
* procedure to turn the cards onto the talon from the deck
printw("Talon is now the new hand");
this.runs
+= costofrunthroughhand
;
total
.runs
+= costofrunthroughhand
;
cnewcol
= cinitcol
+ cwidthcol
;
printw("I believe you have lost");
printw("Talon and hand are empty");
INCRHAND(cnewrow
, cnewcol
);
INCRHAND(coldrow
, coldcol
);
removecard(cnewcol
, cnewrow
);
if (talon
->paid
== FALSE
&& talon
->visible
== TRUE
) {
this.information
+= costofinformation
;
total
.information
+= costofinformation
;
printcard(coldcol
, coldrow
, talon
);
printcard(taloncol
, talonrow
, talon
);
move(handstatrow
, handstatcol
);
move(talonstatrow
, talonstatcol
);
fndbase(&talon
, taloncol
, talonrow
);
* procedure to print card counting info on screen
register struct cardtype
*ptr
;
move(talonstatrow
, talonstatcol
- 7);
printw("Talon: %3d", taloncnt
);
move(handstatrow
, handstatcol
- 7);
printw("Hand: %3d", cinhand
);
move(stockstatrow
, stockstatcol
- 7);
printw("Stock: %3d", stockcnt
);
for ( row
= coldrow
, col
= coldcol
, ptr
= talon
;
if (ptr
->paid
== FALSE
&& ptr
->visible
== TRUE
) {
this.information
+= costofinformation
;
total
.information
+= costofinformation
;
printcard(col
, row
, ptr
);
for ( row
= cnewrow
, col
= cnewcol
, ptr
= hand
;
if (ptr
->paid
== FALSE
&& ptr
->visible
== TRUE
) {
this.information
+= costofinformation
;
total
.information
+= costofinformation
;
printcard(col
, row
, ptr
);
* procedure to clear card counting info from screen
move(talonstatrow
, talonstatcol
- 7);
move(handstatrow
, handstatcol
- 7);
move(stockstatrow
, stockstatcol
- 7);
for ( row
= ctoprow
; row
<= cbotrow
; row
++ ) {
* procedure to update card counting base
removecard(coldcol
, coldrow
);
DECRHAND(coldrow
, coldcol
);
if (talon
!= NIL
&& (talon
->visible
== FALSE
)) {
this.information
+= costofinformation
;
total
.information
+= costofinformation
;
printcard(coldcol
, coldrow
, talon
);
move(talonstatrow
, talonstatcol
);
* procedure to update stock card counting base
move(stockstatrow
, stockstatcol
);
* let 'em know how they lost!
register struct cardtype
*ptr
;
for (ptr
= talon
; ptr
!= NIL
; ptr
= ptr
->next
) {
for (ptr
= hand
; ptr
!= NIL
; ptr
= ptr
->next
) {
move(stockrow
+ 1, sidecol
);
move(talonrow
- 2, sidecol
);
move(talonrow
- 1, sidecol
);
move(talonrow
+ 1, sidecol
);
for (ptr
= stock
, row
= stockrow
; ptr
!= NIL
; ptr
= ptr
->next
, row
++) {
printcard(stockcol
, row
, ptr
);
move(handstatrow
, handstatcol
- 7);
getcmd(moverow
, movecol
, "Hit return to exit");
* procedure to update the betting values
long thiscosts
, totalcosts
;
dollars
= (now
- acctstart
) / secondsperdollar
;
this.thinktime
+= dollars
;
total
.thinktime
+= dollars
;
acctstart
+= dollars
* secondsperdollar
;
thiscosts
= this.hand
+ this.inspection
+ this.game
+
this.runs
+ this.information
+ this.thinktime
;
totalcosts
= total
.hand
+ total
.inspection
+ total
.game
+
total
.runs
+ total
.information
+ total
.thinktime
;
this.worth
= this.wins
- thiscosts
;
total
.worth
= total
.wins
- totalcosts
;
if (status
!= BETTINGBOX
)
move(tboxrow
+ 3, boxcol
+ 13);
printw("%4d%9d", this.hand
, total
.hand
);
move(tboxrow
+ 4, boxcol
+ 13);
printw("%4d%9d", this.inspection
, total
.inspection
);
move(tboxrow
+ 5, boxcol
+ 13);
printw("%4d%9d", this.game
, total
.game
);
move(tboxrow
+ 6, boxcol
+ 13);
printw("%4d%9d", this.runs
, total
.runs
);
move(tboxrow
+ 7, boxcol
+ 13);
printw("%4d%9d", this.information
, total
.information
);
move(tboxrow
+ 8, boxcol
+ 13);
printw("%4d%9d", this.thinktime
, total
.thinktime
);
move(tboxrow
+ 9, boxcol
+ 13);
printw("%4d%9d", thiscosts
, totalcosts
);
move(tboxrow
+ 10, boxcol
+ 13);
printw("%4d%9d", this.wins
, total
.wins
);
move(tboxrow
+ 11, boxcol
+ 13);
printw("%4d%9d", this.worth
, total
.worth
);
* procedure to move a card from the stock or talon to the tableau
transit(cp
, &tableau
[des
]);
printcard(pilemap
[des
], length
[des
], tableau
[des
]);
printcard(stockcol
, stockrow
, stock
);
printcard(taloncol
, talonrow
, talon
);
struct cardtype
*tempcard
;
for (i
=tabrow
; i
<=length
[sour
]; i
++)
removecard(pilemap
[sour
], i
);
dlength
= length
[des
] + 1;
printcard(pilemap
[des
], dlength
, tableau
[sour
]);
while (slength
!= tabrow
- 1) {
tempcard
= tableau
[sour
];
for (i
=1; i
<=slength
-tabrow
; i
++)
tempcard
= tempcard
->next
;
printcard(pilemap
[des
], dlength
, tempcard
);
* procedure to move from the tableau to the tableau
if (notempty(tableau
[sour
])) {
if (tabok(bottom
[sour
], des
)) {
temp
->next
= tableau
[des
];
tableau
[des
] = tableau
[sour
];
length
[des
] = length
[des
] + (length
[sour
] - (tabrow
- 1));
length
[sour
] = tabrow
- 1;
* functions to see if the card can go onto the foundation
if (found
[let
]->rank
== King
)
else if (cp
->rank
- 1 == found
[let
]->rank
)
* function to determine if two cards are the same suit
if (cp
->suit
== found
[let
]->suit
)
* procedure to move a card to the correct foundation pile
if (found
[tempbase
] != NIL
)
if (rankhigher(*cp
, tempbase
)
&& samesuit(*cp
, tempbase
)) {
transit(cp
, &found
[tempbase
]);
printcard(pilemap
[tempbase
],
foundrow
, found
[tempbase
]);
printcard(stockcol
, stockrow
, stock
);
} else if (mtforigin
== tal
) {
printcard(taloncol
, talonrow
, talon
);
removecard(pilemap
[source
], length
[source
]);
this.wins
+= valuepercardup
;
total
.wins
+= valuepercardup
;
} while ((tempbase
!= 4) && !mtfdone
);
* procedure to get a command
if (ch
>= 'A' && ch
<= 'Z')
} else if (i
>= 2 && ch
!= _tty
.sg_erase
&& ch
!= _tty
.sg_kill
) {
if (ch
!= '\n' && ch
!= '\r' && ch
!= ' ')
} else if (ch
== _tty
.sg_erase
&& i
> 0) {
} else if (ch
== _tty
.sg_kill
&& i
> 0) {
} else if (ch
== '\032') { /* Control-Z */
} else if (isprint(ch
)) {
} while (ch
!= '\n' && ch
!= '\r' && ch
!= ' ');
* Suspend the game (shell escape if no process control on system)
* procedure to evaluate and make the specific moves
char osrcpile
, odestpile
;
if (talon
== NIL
&& hand
!= NIL
)
getcmd(moverow
, movecol
, "Move:");
if (srcpile
>= '1' && srcpile
<= '4')
source
= (int) (srcpile
- '1');
if (destpile
>= '1' && destpile
<= '4')
dest
= (int) (destpile
- '1');
(srcpile
== 't' || srcpile
== 's' || srcpile
== 'h' ||
srcpile
== '1' || srcpile
== '2' || srcpile
== '3' ||
if (status
!= BETTINGBOX
)
getcmd(moverow
, movecol
, "Inspect game?");
} while (srcpile
!= 'y' && srcpile
!= 'n');
this.inspection
+= costofinspection
;
total
.inspection
+= costofinspection
;
if (destpile
== 'f' || destpile
== 'F')
movetofound(&talon
, source
);
else if (destpile
>= '1' && destpile
<= '4')
simpletableau(&talon
, dest
);
if (destpile
== 'f' || destpile
== 'F')
movetofound(&stock
, source
);
else if (destpile
>= '1' && destpile
<= '4')
simpletableau(&stock
, dest
);
if (destpile
!= 't' && destpile
!= 'T') {
if (status
== BETTINGBOX
) {
} while (srcpile
!= 'y' &&
this.wins
+= valuepercardup
* cardsoff
;
total
.wins
+= valuepercardup
* cardsoff
;
total
.game
+= costofgame
;
printbottominstructions();
case '1': case '2': case '3': case '4':
if (destpile
== 'f' || destpile
== 'F')
movetofound(&tableau
[source
], source
);
else if (destpile
>= '1' && destpile
<= '4')
fndbase(&stock
, stockcol
, stockrow
);
fndbase(&talon
, taloncol
, talonrow
);
char *basicinstructions
[] = {
"Here are brief instuctions to the game of Canfield:\n\n",
" If you have never played solitaire before, it is recom-\n",
"mended that you consult a solitaire instruction book. In\n",
"Canfield, tableau cards may be built on each other downward\n",
"in alternate colors. An entire pile must be moved as a unit\n",
"in building. Top cards of the piles are available to be able\n",
"to be played on foundations, but never into empty spaces.\n\n",
" Spaces must be filled from the stock. The top card of\n",
"the stock also is available to be played on foundations or\n",
"built on tableau piles. After the stock is exhausted, ta-\n",
"bleau spaces may be filled from the talon and the player may\n",
"keep them open until he wishes to use them.\n\n",
" Cards are dealt from the hand to the talon by threes\n",
"and this repeats until there are no more cards in the hand\n",
"or the player quits. To have cards dealt onto the talon the\n",
"player types 'ht' for his move. Foundation base cards are\n",
"also automatically moved to the foundation when they become\n",
"push any key when you are finished: ",
char *bettinginstructions
[] = {
" The rules for betting are somewhat less strict than\n",
"those used in the official version of the game. The initial\n",
"deal costs $13. You may quit at this point or inspect the\n",
"game. Inspection costs $13 and allows you to make as many\n",
"moves as is possible without moving any cards from your hand\n",
"to the talon. (the initial deal places three cards on the\n",
"talon; if all these cards are used, three more are made\n",
"available) Finally, if the game seems interesting, you must\n",
"pay the final installment of $26. At this point you are\n",
"credited at the rate of $5 for each card on the foundation;\n",
"as the game progresses you are credited with $5 for each\n",
"card that is moved to the foundation. Each run through the\n",
"hand after the first costs $5. The card counting feature\n",
"costs $1 for each unknown card that is identified. If the\n",
"information is toggled on, you are only charged for cards\n",
"that became visible since it was last turned on. Thus the\n",
"maximum cost of information is $34. Playing time is charged\n",
"at a rate of $1 per minute.\n\n",
"push any key when you are finished: ",
* procedure to printout instructions
move(originrow
, origincol
);
printw("This is the game of solitaire called Canfield. Do\n");
printw("you want instructions for the game?");
getcmd(originrow
+ 3, origincol
, "y or n?");
} while (srcpile
!= 'y' && srcpile
!= 'n');
for (cp
= basicinstructions
; *cp
!= 0; cp
++)
move(originrow
, origincol
);
printw("Do you want instructions for betting?");
getcmd(originrow
+ 2, origincol
, "y or n?");
} while (srcpile
!= 'y' && srcpile
!= 'n');
for (cp
= bettinginstructions
; *cp
!= 0; cp
++)
* procedure to initialize the game
dbfd
= open("/usr/games/lib/cfscores", 2);
i
= lseek(dbfd
, uid
* sizeof(struct betinfo
), 0);
i
= read(dbfd
, (char *)&total
, sizeof(total
));
lseek(dbfd
, uid
* sizeof(struct betinfo
), 0);
* procedure to end the game
move(originrow
, origincol
);
printw("CONGRATULATIONS!\n");
printw("You won the game. That is a feat to be proud of.\n");
move(originrow
+ 4, origincol
);
printw("Wish to play again? ");
printw("You got %d card", cardsoff
);
getcmd(moverow
, movecol
, "Hit return to continue");
printw("Wish to play again? ");
getcmd(row
, col
, "y or n?");
} while (srcpile
!= 'y' && srcpile
!= 'n');
printw("Really wish to quit? ");
getcmd(moverow
, movecol
, "y or n?");
} while (srcpile
!= 'y' && srcpile
!= 'n');
* procedure to clean up and exit
write(dbfd
, (char *)&total
, sizeof(total
));
* Can you tell that this used to be a Pascal program?
puts("The system load is too high. Try again later.");
signal(SIGTERM
, cleanup
);