1
0
vt100-games/Backgammon/Original/backgmmn.c
2020-07-01 15:28:08 +02:00

2278 lines
58 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* backgmmn.c */
/***************************************************************************
GAMMON IV, Version 2
Author: David C. Oshel
1219 Harding Avenue
Ames, Iowa 50010
Date: March 26, 1986
Gammon IV is probably the best backgammon game currently available for
CP/M. I wrote it because I was disgusted with the price and dullness
of all other programs which allegedly play backgammon on CP/M systems.
This program has THREE DIFFERENT PLAYING STYLES, any one of which can
consistently beat a novice player, and occasionally beat a good player.
In all three levels, the computer's strategy can even seem brilliant;
there is nothing routine about it.
This version incorporates a few minor changes and bug fixes which make it
different from previous editions which have appeared on various bulletin
boards and club offerings around the country. This is a public domain
program. Feel free to distribute or improve it. Credit to the original
author (me) will be appreciated, but is not strictly required since the
copyright owners are abnormally mild-mannered (and extremely distant;
see Acknowledgments, below.)
Version 2:
a) Fixed the bug in which the doubling cube might revert to its
default value if the computer took back a move, and if the cube
had been doubled one or more times during the opening roll-off
but not yet offered.
b) Fixed an odd bug in the Arrange command which occasionally caused
stones to switch allegiance to the opponent's color, or else to
mysteriously multiply to more than 15 on a side.
c) An improved error handler now hints that BAR and HOME are words
used in the game; also, slows down the incorrect entry routine so
that it is less mysterious than before.
d) Added a Graphic toggle to main command line. Same as Control-K,
as in previous versions; turns on/off the Kaypro '84 video graphics;
uses the alternate token set defined in the CRT module below.
e) Changed the break character back to Control-C. The proximity of
the ESCAPE key to the 1 digit on the Kaypro keyboard sometimes
caused a game to be inadvertently paused during play.
f) There is no longer a sanction against cheating; if you type
Control-C during play and re-Arrange the stones, your opponent
does not change.
g) As before, the computer will not always take (or find) a forced
move. The rule is that the player must take both dice if possible,
or the larger if both are possible singly but not together.
The playing algorithm attempts to find the legally required move,
but will sometimes fail. It is sometimes advantageous to avoid a
forced move but the computer cannot make that distinction, in this
version; the program allows the same lenience for the human player.
The cube is doubled when doubles are thrown during the opening
rolloff to decide who goes first. However, if the cube reaches 8,
further pairs on the dice during rolloff have no effect.
Gammon IV knows all the other OFFICIAL rules of the game;
U.S. Navy and/or Saloon rules don't count.
h) Improved coding in some routines, especially putstone(), which was
far more baroque than necessary. There is also some occasional
re-coding, where I could not prevent myself. The game strategy
has not been changed, mostly because I don't fully understand it
anymore, even though the documentation there is better than usual.
It was written months ago in a white heat of inspiration; but I
feel reluctant to submit myself to that strain again, without the
prospect of remuneration.
Acknowledgments:
The opening dialogue, in which you chose your computer opponent,
is freely adapted from Alexei Panshin's long out-of-print, and
now classic, "Star Well" travelogues. Scholars of computer history
will recognize many allusions to this same opus in various passages
of Mike Goetz's 550-point Adventure (especially in the Ice Caverns!).
The Xochitl Sodality, which owns the copyright on Gammon IV, is a
philanthropic society first publicized by Panshin. Persons who wish
to obtain information on site licensing for Gammon IV should contact
the Monist Association imaginary properties secretary at the following
address:
Monist Association I.P.S.
c/o Xochitl Sodality
Semichastny House
Delbalso, Nash. Emp.
Special Instructions:
Terminal must be Lear-Siegler ADM-3A compatible, or else have
the same graphics capability as a Kaypro 10, 2X or 4'84. This
requirement is fully configurable in the video section below,
however.
This program requires the Software Toolworks' C/80 v3.1 compiler
for CP/M 2.2. C/80 has a configuration program. C/80 must be
configured as follows, or else this program WILL NOT compile:
Symbol table size: 512
String constant table: 3200
Dump constants after each routine: YES
Macro table size: 500
Switch table size: 128
Structure table size: 200
Merge duplicate string constants: YES
NB: Assembler: C/80's AS
Initialize arrays to zero: < 256 BYES ONLY
Generate ROMable code in Macro-80: YES
Screen size: 24 (doesn't matter)
Generate slightly larger, faster code: NO
Sign extension on char to int conversion: YES
Device for library files: A: (your choice)
Compilation:
NB: Microsoft's MACRO-80 assembler and LINK-80 linker are required,
and are specified in the (-m) compiler switch! This is a moderately
complex compile, so the procedure is directed by batch SUBMIT files.
Distribute files as follows:
On Drive A:
BACKGMMN.C, BACKGMMN.SUB, CLIBRARY.REL, GAMEPLAN.C,
GAMEPLAN.HDR, MYLIB2.C, PRINTF.C, STDLIB.REL, SUBMIT.COM
On Drive B:
C.COM, L80.COM, M80.COM, WS.COM, WSMSGS.OVR, WSOVLY1.OVR
Then, SUBMIT BACKGMMN to compile, assemble & link the game. You will
need about 180k of free space on Drive A.
You must pay strict attention to the Special Instructions above,
regarding C/80 configuration.
Absent Files:
M80.COM and L80.COM are from Microsoft, and are not part
of this distribution. C.COM, STDLIB.REL, CLIBRARY.REL and
PRINTF.C are from Software Toolworks, and are not part of
this distribution either. SUBMIT.COM is a CP/M transient
command; it came with your computer when you bought it.
Notes:
Gammon IV is impossible to implement in any C which does not allow
functions to be passed as parameters to another function -- K & R must
be followed on this point!
THIS CODE ASSUMES INTEL 8080 CPU. Inline code simulates an old,
archaic version of SetJump() and LongJump(), which are not features
of C/80 3.1. Use of inline code means you need 8080 compatibility.
By isolating the game-playing algorithm into a separately compiled
module, I have allowed for the possibility that someone else may come
up with significant improvements in strategy, AND BE GIVEN CREDIT FOR
THEM, without having to re-design primitive parts of the program.
The single exception to this rule is the code which decides when the
computer will offer, accept or reject the doubling cube; that is
considered primitive, even though the cube is a major factor in human
strategies when playing for blood or money. Gammon IV always plays
for the simple fun of aggravating humans, so cube tactics are neither
daring nor profound. Gammon IV does not bluff with the cube; if it
offers the cube, it is almost sure to win.
*****************************************************************************/
#define TRUE 1
#define FALSE 0
#define ME 1
#define YU 2
#define YRBAR 0
#define MYBAR 25
#define YRHOME 26
#define MYHOME 27
#define ERROR 999 /* anything well out of range */
#define MYLEVEL 2
/* put CRT stuff first so version differences won't affect it */
#asm
;
; Note: Assumes assembly by M80.COM
;
; *========================= CRT Module =========================*
; * *
; * User Patch Area: ALL DB STRINGS MUST TERMINATE WITH A NULL *
; * Use contiguous data area for all console functions, so user *
; * may configure the program for some terminal not a Kaypro 10 *
; * *
; * CLEAR SCREEN and GOTOXY are Required Minimum Functions *
; * CRTNIT, CRTXIT, CURSON, CURSOF are Optional and Recommended *
; * *
; * The tokens TK1..TK4 comprise two sets each of playing tokens *
; * for the computer & human player. 24 bytes are reserved for *
; * EACH token, so that users may turn on video enhancements -- *
; * see the Kaypro '84 recommended settings for examples. *
; * *
; * The tokens TK5..TK8 and the KRTNIT, etc. strings implement *
; * the Control-K command to toggle the Kaypro '84 display. *
; * These should only be patched if you are configuring for two *
; * levels of terminal characteristics, as vis-as-vis the "old" *
; * and "new" Kaypro terminals. These two levels must use the *
; * same basic protocols for cursor address and clear screen. *
; * *
; * Command Strings must terminate with 0, and the 0 byte CANNOT *
; * be sent to the console. 0 is INCLUDED in the reserved area. *
; * *
; * GAMMON IV no longer uses any console command function not *
; * specifically mentioned here. In particular, the clear-to- *
; * end-of-line function is now handled in a more general way. *
; *==============================================================*
;
; Gammon IV.09 (Universal Version) User Patch Area, D.C.OSHEL, 6/15/85
;----------------------------------------------------------------------------
;
; * REQUIRED *
; reserve 24 bytes apiece for the players' tokens (must terminate with 0)
; these settings are recommended for Lear Siegler ADM-3A (old Kaypros)
;
DB 'USER PATCH AREA '
DB 'ALL STRINGS MUST HAVE ZERO TERMINATOR->'
DB 'TOKEN1:'
TK1: DB '(',')',0,0,0,0,0,0 ; computer's token, ()
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 'TOKEN2:'
TK2: DB '[',']',0,0,0,0,0,0 ; player's token, []
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 'TOKEN3:'
TK3: DB 'q','b',0,0,0,0,0,0 ; computer's token (alternate, qb )
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 'TOKEN4:'
TK4: DB 'C','3',0,0,0,0,0,0 ; player's token (alternate, C3 )
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;----------------------------------------------------------------------------
;
; * Optional *
; reserve 8 bytes apiece for the OPTIONAL console function strings
; patch the first byte to 0 for each function NOT implemented
;
DB 'CRTNIT:'
CRTNIT: DB 0,0,0,0,0,0,0,0 ; crt init, e.g., video mode on
DB 'CRTXIT:'
CRTXIT: DB 0,0,0,0,0,0,0,0 ; crt exit, e.g., video mode off
DB 'CURSON:'
CURSON: DB 0,0,0,0,0,0,0,0 ; cursor ON (show cursor)
DB 'CURSOF:'
CURSOF: DB 0,0,0,0,0,0,0,0 ; cursor OFF (hide cursor)
;----------------------------------------------------------------------------
;
; * REQUIRED *
; clear screen and gotoxy (0,0 is top left of screen) are NOT optional
; default settings are for the Lear Siegler ADM-3A terminal (old Kaypros)
;
DB 'CLS->'
CLS: DB 26,0,0,0,0,0,0,0 ; clear screen command
DB 'GOTOXY->'
GOXY: DB 27,'=',0,0,0,0,0,0 ; cursor address prefix
DB 'YB4X BYTE:'
YB4X: DB 1 ;BYTE 1 = YX: Send Row, Then Col (ADM-3A); 0 = XY: Col, Row
DB 'XOFS BYTE:'
XOFS: DB 32 ;BYTE offset to add to x in gotoxy sequence (ADM-3A = 20H)
DB 'YOFS BYTE:'
YOFS: DB 32 ;BYTE offset to add to y in gotoxy sequence (ADM-3A = 20H)
;
; these strings must terminate with 0, and 8 bytes are reserved for each
;
;
;----------------------------------------------------------------------------
;: The following strings implement the Control-K command for Kaypro '84
;: video able terminals.
;----------------------------------------------------------------------------
;:
;: * Kaypro '84 *
;: recommended tokens for the video able Kaypro '84s (10s, 4'84s, etc)
;: the second set requires that "video mode" be set in the CRTNIT string
;: the first set uses Kaypro underline, inverse and low intensity commands
;: to form the players' stones (24 bytes are reserved for each token)
;:
DB 'ENHANCED ALTERNATES->'
DB 'TOKEN5:'
TK5: DB 27,'B1' ; computer's token
DB 27,'B0'
DB 27,'B3'
DB '><'
DB 27,'C3'
DB 27,'C0'
DB 27,'C1'
DB 0,0,0,0
DB 'TOKEN6:'
TK6: DB 27,'B3' ; player's token
DB 27,'B0'
DB '[]'
DB 27,'C0'
DB 27,'C3'
DB 0,0,0,0,0,0,0,0,0,0
DB 'TOKEN7:'
TK7: DB 128,233,129,150,0,0,0,0 ; computer's "black chiclet"
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DB 'TOKEN8:'
TK8: DB 128,253,129,190,0,0,0,0 ; player's "white chiclet"
DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
;----------------------------------------------------------------------------
;:
;: * Kaypro '84 *
;: recommended settings for Kaypro '84s (strings must terminate with 0)
;:
DB 'KRTNIT:'
KRTNIT: DB 27,'B5',0,0,0,0,0 ; crt init, e.g., video mode on
DB 'KRTXIT:'
KRTXIT: DB 27,'C5',0,0,0,0,0 ; crt exit, e.g., video mode off
DB 'KURSON:'
KURSON: DB 27,'B4',0,0,0,0,0 ; cursor ON (show cursor)
DB 'KURSOF:'
KURSOF: DB 27,'C4',0,0,0,0,0 ; cursor OFF (hide cursor)
DB '<-END USER PATCH AREA'
;----------------------------------------------------------------------------
; LOF LG
#endasm
static int kaypro;
get1tkn() {
#asm
LXI H,TK1 ;load string pointer for token 1
#endasm
}
get2tkn() {
#asm
LXI H,TK2 ;load string pointer for token 2
#endasm
}
get3tkn() {
#asm
LXI H,TK3 ;load string pointer for token 3
#endasm
}
get4tkn() {
#asm
LXI H,TK4 ;load string pointer for token 4
#endasm
}
get5tkn() {
#asm
LXI H,TK5 ;load string pointer for token 1
#endasm
}
get6tkn() {
#asm
LXI H,TK6 ;load string pointer for token 2
#endasm
}
get7tkn() {
#asm
LXI H,TK7 ;load string pointer for token 3
#endasm
}
get8tkn() {
#asm
LXI H,TK8 ;load string pointer for token 4
#endasm
}
getcls() {
#asm
LXI H,CLS
#endasm
}
getcini() {
#asm
LXI H,CRTNIT
#endasm
}
getcxit() {
#asm
LXI H,CRTXIT
#endasm
}
getcof() {
#asm
LXI H,CURSOF
#endasm
}
getcon() {
#asm
LXI H,CURSON
#endasm
}
getkini() {
#asm
LXI H,KRTNIT
#endasm
}
getkxit() {
#asm
LXI H,KRTXIT
#endasm
}
getkof() {
#asm
LXI H,KURSOF
#endasm
}
getkon() {
#asm
LXI H,KURSON
#endasm
}
gotoxy(x,y) int x,y; { /* this is solid gold! */
#asm
POP H ; get return address
POP D ; get Y
POP B ; get X
PUSH B ; restore all to keep C happy
PUSH D ; ditto
PUSH H ; ditto
;
LXI H,GOXY ; blast out the prefix
CALL STOUT
;
LDA YB4X ; sending row, i.e. Y, first?
ORA A
JZ @X1 ; no, do it the other way
;
LDA YOFS ; yes indeed, here's Y
ADD E
CALL COUT
;
LDA XOFS ; and here's X
ADD C
CALL COUT
RET ; thank you very much, we're done
@X1: LDA XOFS ; your weird terminal wants X first? ok
ADD C
CALL COUT
;
LDA YOFS ; now Y
ADD E
CALL COUT
RET ; tyvm, we done
;
STOUT: MOV A,M ; string pointer in HL on entry, trash A, HL
ORA A
RZ
CALL COUT
INX H
JMP STOUT
;
COUT: PUSH H ; char in A on entry, disturb nothing
PUSH D
PUSH B
PUSH PSW
MVI C,6
MOV E,A
CALL 5
POP PSW
POP B
POP D
POP H
RET
#endasm
/* using this C code instead of inline will slow things down perceptibly */
/* puts( getprfx() );
if ( ybefore() ) {
putc(y + getyofs(),0); putc(x + getxofs(),0);
}
else {
putc(x + getxofs(),0); putc(y + getyofs(),0);
}
*/
} /* end: gotoxy */
crtinit() {
if (kaypro) puts( getkini() );
else puts ( getcini() );
} /* here, turn 2 byte graph chars on */
crtexit() {
if (kaypro) puts( getkxit() );
else puts ( getcxit() );
} /* and off again on exit... */
clr_screen() { puts ( getcls() ); } /* standard */
on_cursor() {
if (kaypro) puts( getkon() );
else puts ( getcon() );
} /* optional "hide cursor" command */
off_cursor() {
if (kaypro) puts( getkof() );
else puts ( getcof() );
} /* optional "show cursor" command */
#include "printf.c"
extern char *bgversion;
char *backtalk[] = {
"VILLIERS: At your service!",
"LOUISA: Delighted!",
"TORVE: Is interesting line of occurrence. Thurb!",
"Copyright (c) 1985 by The Xochitl Sodality Wonders & Marvels Committee",
};
int list[2][28]; /* two dice, two lists */
struct board {
int stones, /* number of stones on that point */
owner, /* and whose they are */
x,y, /* x and y coordinates of point base */
lastx,lasty, /* last location drawn on this point */
cx,cy; /* coordinates for column numbers */
}
point[28], bdsave[28]; /* 24 points, plus 2 bars, 2 homes */
struct { int cube, whosecube; } doubles;
struct { int fr,to,flag; } pending;
int level, dice[2], myscore, yrscore, player, movesleft, cantuse, myturns,
swapped, tswap, deciding, expert, tone, show, moremsgline,
firstmove, helpdisabled, yrdice, lookforit, startcubevalue;
char *token1, *token2, chatter[80], buzzard[8];
/*======================================================================
OPPONENT -- A little scenario, in which to select level of play
========================================================================*/
char *chooseplayer() {
int ch; char *q;
dissemble();
loo: ch = getkey();
switch (ch) {
case 'A':
case 'V': { level = 0; break; }
case 'L':
case 'P': { level = 1; break; }
case 'T': { level = 2; break; }
default: goto loo;
}
q = backtalk[ level ];
clr_screen(); draw_board(q);
return(q);
} /* end: chooseplayer */
/*====================================================================
MAIN
======================================================================*/
main() {
static int ch;
static char *p1 = "P(lay, R(everse, S(wap, A(rrange, N(ew, G(raphic, Q(uit ",
*p2 = "U(se %s dice, O(pponent, X(pert, B(eep, C(ount, Z(ero ",
*myline;
level = MYLEVEL + 1; /* fetch copyright notice */
setup();
hint();
while (TRUE) {
moremsgline = FALSE; /* show first command line on entry */
debug(""); /* erase messages */
myline = backtalk[ level ]; /* did level change? */
msg( myline );
firstmove = TRUE;
newboard(); /* note, sets starting cube value to 1 */
deciding = TRUE;
while (deciding) {
/* display command line */
off_cursor();
if (show) { mytotal(); yrtotal(); }
else { gotoxy(0,3); puts(" "); gotoxy(0,19); puts(" "); }
if (tone) beep();
if (expert) msg("Your pleasure? ");
else {
msg("Select: ");
if (moremsgline) printf(p2,(yrdice? "my": "your"));
else printf(p1);
}
/* get response and do it */
ch = getkey();
switch (ch) {
case 'Q': { /* quit play, exit to CP/M */
finishup();
break;
}
case 'A': { /* arrange stones (or cheat?) */
/* play is suspended, so don't use the long */
/* messages that assist game play */
helpdisabled = TRUE;
arrange();
update();
debug("");
break;
}
case 'R': { /* mirror board image */
reverse();
update();
break;
}
case 'C': { /* show mytotal, yrtotal counts */
show ^= TRUE;
break;
}
case 'S': { /* SWAP Command - exchange stones */
swaptokens();
update();
break;
}
case 'B': { /* kill the beep */
tone ^= TRUE; break;
}
case 'X': /* expert mode toggle(s) */
case 27 : {
expert ^= TRUE;
break;
}
case 'D': { /* use my dice or your dice? */
case 'U':
yrdice ^= TRUE;
break;
}
case 'O': { /* change opponents and skill level */
myline = chooseplayer();
deciding = FALSE;
break;
}
case 'P': { /* play the game as board is arranged */
helpdisabled = FALSE;
if (level > MYLEVEL ) {
myline = chooseplayer();
update();
}
play();
break;
}
case 'G': /* graphic screen switch, same as: */
case 11: { /* control-K, toggle Kaypro '84 display */
crtexit();
kaypro ^= TRUE;
crtinit();
swaptokens(); swaptokens();
swaptokens(); swaptokens();
update();
break;
}
case 'N': { /* abandon game without quitting */
deciding = FALSE; player = 0;
break;
}
case 'Z': { /* zero the score */
myscore = yrscore = 0; putscore();
break;
}
default: { moremsgline ^= TRUE; break; }
}}
}
} /* end: main */
/*=======================================================================
PLAY Command - this is the command that initiates the 2-player game
=========================================================================*/
play() {
/* any vars here MUST be STATIC */
#asm
MOV B,H ;set up to exit this function by saving caller's
LXI H,0 ;stack pointer for use in deeply-nested scope
DAD SP ;WARNING: this function must not use dynamic variables
SHLD _fool ;CAUTION: risky business if called with parameters??
MOV H,B ;HL is restored on general principles, BC is trashed
#endasm
whofirst();
taketurns();
} /* end: play */
whofirst() {
int ch, myval, yrval;
if (yrdice || (player < 0)) { /* board has been re-arranged */
msg("Is it my "); puts(token1);
puts(" turn or your "); puts(token2);
puts(" turn? ");
loo: ch = toupper(getc(0));
if (!(ch == 'M' || ch == 'Y')) goto loo;
if (ch == 'M') player = YU; /* player says Me, of course! */
else player = ME;
rolldice(player);
}
else if (player == 0) { /* fresh start, roll the dice */
barcube();
zoo: debug("Tossing for first turn...");
wipedice();
rolldice(ME);
myval = dice[0];
rolldice(YU);
yrval = dice[0];
if (myval == yrval) {
if (tone) beep();
off_cursor();
gotoxy(37,11); puts("[___]"); sleep(3);
startcubevalue *= 2;
if (startcubevalue > 8) startcubevalue = 8;
doubles.cube = startcubevalue;
gotoxy(37,11);
if (doubles.cube < 16) printf("[ %d ]",doubles.cube);
else printf("[%03d]",doubles.cube);
if (startcubevalue < 9) {
msg("Double the cube!");
sleep(20);
}
goto zoo;
}
else if (myval < yrval) player = YU;
else player = ME;
dice[0] = max(myval,yrval); dice[1] = min(myval,yrval);
}
/* otherwise, continue with last dice rolled as play is resumed */
} /* end: whofirst */
getmove() {
static int i, ch, temp, happy;
cantuse = ERROR; /* important for human player in tellmove */
movesleft = 2;
if (dice[0] == dice[1]) movesleft += 2;
temp = movesleft;
getlist(); saveboard(); lookforit = TRUE;
if ( nomove() ) {
debug("All ");
if (player == ME) puts("my"); else puts("your");
puts(" moves are blocked!");
if (player == YU) sleep(20);
return;
}
if (player == ME) {
/* handle doubles as two consecutive, independent moves */
setchat("I move"); debug(chatter);
if (movesleft == 4) myturns = 2; else myturns = 1;
clrpend();
while (myturns > 0) {
cantuse = ERROR;
movesleft = 2;
while (movesleft > 0) { getlist(); mymove(); }
myturns--;
}
strcat(chatter,"\008."); debug(chatter);
}
else { /* allow the human to take back a bad board position */
happy = FALSE;
while (!happy) {
while (movesleft > 0) { getlist(); yrmove(); }
msg("All ok? Y/N ");
do {
ch = getkey();
} while (ch != 'N' && (ch != 'Y' && ch != '\n'));
if ( ch == 'N' ) {
msg("Ok, as it was...");
restoreboard();
update();
movesleft = temp;
cantuse = ERROR;
}
else happy = TRUE;
debug("");
}
}
} /* end: getmove */
taketurns() {
while (TRUE) { /* NO EXIT! Only a Win or player ESC can exit */
getmove();
player = other(player);
if ( player == ME ) {
if ( endgame() ) {
if (topstone(ME) < 6 && cubeval()) idouble();
else if ( mytotal() < (yrtotal() - 8) ) {
idouble();
}
}
else if ( bearoff() ) idouble();
}
rolldice (player);
}
} /* end: taketurns */
/*=============================================*/
/* Y O U R M O V E */
/*=============================================*/
tellmove() { /* show what the player's current dice are */
int k,n;
n = movesleft;
debug("You ");
if (!expert) { puts(token2); puts(" "); }
puts("have ");
while (n--) {
if (dice[0] == dice[1]) k = dice[0]; /* doubles? */
else if (cantuse == 1) k = dice[0];
else if (cantuse == 0) k = dice[1];
else if (n == 1) k = dice[1]; /* 2 of 2? */
else k = dice[0]; /* 1 of 2? */
printf("[%d] ",k);
}
puts("left");
if (expert) puts("."); else puts(", moving from high to low.");
} /* end: tellmove */
yrmove() {
if ( nomove() ) {
debug("You have no more moves in this line of play.");
movesleft = 0;
return;
}
else {
loo: tellmove();
if (!getyrmove()) {
hint();
goto loo;
}
debug("");
}
} /* end: yrmove */
hint() {
if (!expert) {
debug("HELP, BAR and HOME are useful words in this game.");
sleep(10);
}
} /* end: hint */
nomove() {
int i,j;
for (i = 0; i < 2; i++)
for (j = 0; j < 28; j++)
if (list[i][j] != ERROR) return (FALSE);
return (TRUE);
} /* end: nomove */
getyrmove() {
int fpoint, tpoint;
firstmove = FALSE; /* I got it, I got it! */
msg("Move from? ");
fpoint = getpt(YRBAR,YRHOME);
if ( fpoint == ERROR ||
(list[0][fpoint] == ERROR && list[1][fpoint] == ERROR)) {
return (FALSE);
}
puts(" To? ");
tpoint = getpt(YRBAR,YRHOME);
if ( tpoint == ERROR ||
(list[0][fpoint] != tpoint && list[1][fpoint] != tpoint)) {
return (FALSE);
}
movestone( fpoint, tpoint );
if (movesleft < 2) {
if (list[0][fpoint] == tpoint) cantuse = 0;
else cantuse = 1;
}
return (TRUE);
} /* end: getyrmove */
/*====== Functions That Make The Selected Move ======*/
setchat( p ) char *p; {
strcpy(chatter,p);
} /* end: setchat */
putdice( f,t) int f,t; {
static char q[15];
if (t == MYHOME) sprintf(q," %d to Home,",25 - f);
else if (f == MYBAR) sprintf(q," Bar to %d,",25 - t);
else sprintf(q," %d to %d,",25 - f, 25 - t);
strcat(chatter,q);
debug(chatter); /* avoid using save_cursor() */
} /* end: putdice */
lurch( f, t, zlist ) int f, t, zlist; {
movestone( f, t ); /* move the stone */
putdice(f,t); /* tell user, the action is a bit fast */
if (movesleft < 2) cantuse = zlist;
} /* end: lurch */
/*****************************/
/* must LINK to GAMEPLAN.REL */
/*****************************/
/*==========================================================================
GETLIST -- Find the possible moves for any particular throw of the dice
===========================================================================*/
checkpips( whichlist, ptimon, pips, tops )
int whichlist, ptimon, pips, tops; {
static int j,k;
if ( !ptimon ) {
j = whosebar(player); /* 0 if you, 25 if me */
k = abs(j - pips);
}
else if (player == ME) {
j = ptimon;
k = j - pips;
if (k < 1) k = MYHOME;
}
else {
j = 25 - ptimon;
k = j + pips;
if (k > 24) k = YRHOME;
}
if (point[j].stones > 0 && point[j].owner == player) {
/* no move to a blocked point */
if (point[k].owner != player && point[k].stones > 1)
return;
/* no move home if i can't bear off yet */
if (k == whosehome(player) && cantbearoff(j,pips,tops))
return;
/* no other move is allowed if i'm on the bar */
if (tops == 25 && j != whosebar(player))
return;
/* the move is legal (but maybe not optimal) */
list [whichlist] [j] = k;
}
} /* end: checkpips */
build( whichlist, pips ) int whichlist, pips; {
int i, tops;
if (whichlist == cantuse) return;
tops = topstone(player);
for (i = 0; i < 25; i++) checkpips( whichlist, i, pips, tops );
} /* end: build */
getlist() { /* find all legal moves using these dice */
static int i, j;
for (i = 0; i < 2; i++) /* initialize the lists */
for (j = 0; j < 28; j++)
list [i] [j] = ERROR;
build( 0, dice[0] ); /* usually the low die */
build( 1, dice[1] ); /* usually the high die */
} /* end: getlist */
/*==========================================================================
EVALUATE UTILITIES -- Functions for legal and/or best play, telling who's
who, who's ahead, who won, etc. etc.
============================================================================*/
mytotal() {
int i, cnt;
cnt = 0;
for (i = 0; i < 26; i++) {
if (point[i].owner == ME) cnt += point[i].stones * i;
}
if (show) {
gotoxy(0,3); printf("%03d",cnt);
}
return (cnt);
} /* end: mytotal */
yrtotal() {
int i, cnt;
cnt = 0;
for (i = 0; i < 26; i++) {
if (point[i].owner == YU) cnt += point[i].stones * (25 - i);
}
if (show) {
gotoxy(0,19); printf("%03d",cnt);
}
return (cnt);
} /* end: yrtotal */
topstone( who ) int who; {
static int i,j;
if (point[ whosebar( who ) ].stones > 0) i = 25;
else {
i = 24;
while (i > 0) {
if (who == ME) j = i; else j = 25 - i;
if (point[j].stones > 0 &&
point[j].owner == who) return (i);
--i;
}
}
return (i); /* return normalized value, 1 - 25, 0 is home */
} /* end: topstone */
cantbearoff( mypt, pips, tops ) int mypt, pips, tops; {
/* My destination is Home, but can I do it??? */
if (mypt > 6) mypt = 25 - mypt; /* normalize inner table */
/* I can't bear off if there's anybody still not in my inner table */
if (tops > 6) return (TRUE);
/* If I'm the highest blot in my own table, I CAN bear off */
if (tops == mypt) return (FALSE);
/* If I'm NOT high, I have to have an exact roll to get away with it */
return ( (mypt != pips) );
} /* end: cantbearoff */
whosebar( who ) int who; {
return ( (who == ME? MYBAR: YRBAR) );
}
whosehome( who ) int who; {
return ( (who == YU? YRHOME: MYHOME) );
}
checkwin() {
if (mytotal() == 0) winner(ME, topstone(YU));
if (yrtotal() == 0) winner(YU, topstone(ME));
} /* end: checkwin */
other( color ) int color; {
if (color == ME) return (YU); else return (ME);
} /* end: otherplayer */
winner( who, high ) int who, high; {
int gammon; char ch;
if (tone) beep();
if (point[ whosehome( other(who) )].stones > 0) gammon = 1;
else gammon = 2; /* nothing off is a gammon! */
if (high == 0) gammon = 1; /* someone doubled */
else if (high > 18) gammon = 3; /* backgammon! */
debug("");
if (who == ME) puts("I"); else puts("You"); puts(" win");
switch (gammon) {
case 1: { puts("!"); break; }
case 2: { puts(" a Gammon!"); break; }
case 3: { puts(" a Backgammon!"); break; }
}
gammon *= doubles.cube;
switch (who) {
case ME: { myscore += gammon; break; }
case YU: { yrscore += gammon; break; }
}
putscore();
msg("Hit ESC to resume play");
loo: ch = getc(0); if (ch != 27) goto loo;
player = 0;
reverse(); deciding = FALSE; jumpjack();
} /* end: winner */
putscore() {
gotoxy(39,0); blanks(39); gotoxy(53,0);
printf("SCORE: You %d, Me %d",yrscore,myscore);
} /* end: putscore */
/*========================================================================
REVERSE Command - allow player to take the opponent's viewpoint of the
board layout, mirror the board. Returns board layout
to the arranged position as seen from opposite side.
==========================================================================*/
reverse() {
int cnt1, cnt2, cnt3, cnt4;
off_cursor();
cnt1 = point[MYHOME].stones; /* save counts for erase */
cnt2 = point[YRHOME].stones;
cnt3 = point[MYBAR].stones;
cnt4 = point[YRBAR].stones;
putstone( MYHOME, 0, 0); /* erase old trays before update */
putstone( YRHOME, 0, 0);
halfswap(1); halfswap(13);
gotoxy(0,11); puts(" "); /* erase HOME message */
gotoxy(75,11); puts(" ");
if (point[1].x < 40) {
point[MYHOME].x = point[YRHOME].x = 0;
}
else {
point[MYHOME].x = point[YRHOME].x = 75;
}
point[MYHOME].owner = point[MYBAR].owner = ME; /* restore counts */
point[YRHOME].owner = point[YRBAR].owner = YU;
point[MYHOME].stones = cnt1;
point[YRHOME].stones = cnt2;
point[MYBAR].stones = cnt3;
point[YRBAR].stones = cnt4;
} /* end: reverse */
#asm
DB 'Gammon IV concept & text graphic rendition by David C. Oshel',0
DB 'MidSummer''s Day, June 21, 1985',0
DB 'To Whomever Destroys This Notice -- Nothing Shall Happen, Forever',0
#endasm
halfswap( n ) int n; {
static int i, j, k, o;
o = n + 6;
for (i = n; i < o; i++) {
j = ((o * 2) - 1) - i;
k = point[i].x;
point[i].cx = point[i].x = point[j].x;
point[j].cx = point[j].x = k;
}
} /* end: halfswap */
getkey() {
int ch;
ch = getc(0); /* keybounce? */
while ( (ch = toupper(getc(0))) == 0 ) acg(); return (ch);
} /* end: getkey */
wipedice() {
static char *s = " ";
off_cursor();
gotoxy(47,11); puts(s); /* erase dice roll messages */
gotoxy(12,11); puts(s);
} /* end: wipedice */
update() {
int i, x, c;
for (i = 1; i < 25; i++) {
gotoxy(point[i].cx,point[i].cy);
printf("%2d",25 - i);
}
if (doubles.whosecube == YU) yrcube(doubles.cube);
else if (doubles.whosecube == ME) mycube(doubles.cube);
else barcube();
for (i = 0; i < 28; i++) {
x = point[i].stones;
c = point[i].owner;
putstone(i,x,c);
}
if (point[1].x < 40) x = 0; else x = 75;
gotoxy(x,11); puts("HOME");
putscore(); mytotal(); yrtotal();
} /* end: update */
dissemble() {
clr_screen();
puts("You are a passenger in the \"Orion\" bound for STAR WELL, a slightly disreputable\n");
puts("planetoid in the Flammarion Rift, where you have a scheduled layover of several\n");
puts("hours. You enter the Casino there, desperately bored. Your attention is \n");
puts("immediately drawn to an unusual trio. Your instinct for good company (they \n");
puts("are playing Backgammon) leads you easily into a round of introductions:\n");
puts("\n");
puts("LOUISA PARINI -- A young woman who gives you the uncomfortable feeling that she\n");
puts("is even younger than she looks. In fact, she is the offspring of a clan of\n");
puts("noted interstellar jewel thieves and con artists, on her way to a famous\n");
puts("girl's finishing school on Nashua. She has larceny in her soul, but she is on\n");
puts("holiday. Do not underestimate her. (But you will, of course. You must.)\n");
puts("\n");
puts("ANTHONY VILLIERS -- A mysterious young fop with impeccable manners, and (you\n");
puts("notice) an even more impeccable dueling saber at his side. There is something\n");
puts("between Louisa and him. His conversation is light, witty and just slightly\n");
puts("cynical, but you are not wrong to conclude that this is someone you can trust.\n");
puts("\n");
puts("TORVE THE TROG -- This blue-eyed, golden-furred entity is a member of the most\n");
puts("dangerous and unpredictable race in the galaxy (aside from humans). You are \n");
puts("amazed that this one is allowed to travel. (In fact, Torve's papers were forged\n");
puts("by a member of Louisa's family and procured for him by Villiers). Torve is\n");
puts("lost in some inner rapture, emitting soft \"Thurb\"-like noises.\n");
puts("\n(Hit any key to continue)");
getkey();
puts("\015You suggest a friendly game of backgammon, at small stakes, and your\n");
puts("companions agree instantly. Who will be your opponent? (L, V, or T) ");
} /* end: dissemble */
/*======================================================================
ARRANGE Command: Move stones around in the playing area. Play will
commence with this final arrangement. Notice, this
command allows for cheating because the line input
function traps Ctrl-C and executes jumpjack(). Player
returns to the command line with the game frozen, may
re-arrange as desired, then resume play. The Ctrl-C
trap will increment play level by 1. The sufficiently
stupid player will not notice, and so may lose anyway.
Trap implemented in MYLIB2.C, not here.
========================================================================*/
arrange() { /* whoever calls arrange() must also call update() next */
/* any vars here MUST be STATIC */
#asm
MOV B,H ;set up to exit this function by saving caller's
LXI H,0 ;stack pointer for use in deeply-nested scope
DAD SP ;WARNING: this function must not use dynamic variables
SHLD _fool ;CAUTION: risky business if called with parameters??
MOV H,B ;HL is restored on general principles, BC is trashed
#endasm
moveabout();
} /* end: arrange */
moveabout() {
player = -1; /* flag to ask who moves first */
if (!expert)
debug("Type BAR or HOME, or the Number of a Point.");
while ( TRUE ) { /* exit via jumpjack() by typing Control-C */
mytotal();
yrtotal();
revise();
}
} /* end: moveabout */
getpt(b,h) int b,h; {
static char ans[6], *p; int x, d1, d2, look, try;
on_cursor();
gets(ans,5);
p = ans;
while (*p) *p = toupper(*p++); /* capitalize string */
off_cursor();
if (!helpdisabled && ((index(ans,"HEL") != -1) ||
(index(ans,"?") != -1))) {
x = ERROR;
msg("");
if (point[ whosebar(player) ].stones > 0) {
puts("You're on the Bar, so let's move that one! BAR ");
x = b;
}
else {
puts("Are you ");
if (cantuse != 0 && cantuse != 1) puts("REALLY ");
puts("blocked? Try moving From ");
look = 24;
while (look > 0) {
try = list[0][look];
if (try == ERROR) try = list[1][look];
if ( try != ERROR ) {
printf("%d To ",25-look);
if (try == YRHOME) printf("HOME");
else printf("%d",25-try);
look = 0;
}
look--;
}
sleep(40);
}
}
else if (index(ans,"B") != -1) x = b;
else if (index(ans,"H") != -1) x = h;
else {
x = atoi( ans );
if (x < 1 || x > 24) x = ERROR;
else x = 25 - x; /* translate human to computer view */
}
return (x);
} /* end: getpt */
whoseit(p,a,b) char *p; int a,b; {
int ch;
if (point[a].stones > 0 && point[b].stones > 0) {
debug("Whose "); puts(p); puts("? 1 = ");
puts(token1);
puts(" 2 = "); puts(token2); puts(" ");
loo: ch = getkey();
if (!(ch == '1' || ch == '2')) goto loo;
debug("From ");
if (ch == '1') { ch = a; puts(token1); }
else { puts(token2); ch = b; }
puts("'s "); puts(p);
return ( ch );
}
else if (point[a].stones > 0) return (a);
else if (point[b].stones > 0) return (b);
else return (ERROR);
} /* end: whoseit */
revise() {
do {
setchat("Move a Stone From? ");
msg(chatter);
}
while ( !delightful() );
} /* end: revise */
delightful() {
static int from, to, fcnt, tcnt, fcolor, tcolor;
from = getpt(MYBAR,MYHOME);
sprintf(buzzard,"%d",abs(25 - from));
if (from == MYBAR) {
from = whoseit("bar",MYBAR,YRBAR);
strcpy(buzzard,"BAR");
}
if (from == MYHOME) {
from = whoseit("home",MYHOME,YRHOME);
strcpy(buzzard,"HOME");
}
if (from == ERROR) {
debug("Type Control-C to quit.");
return( FALSE );
}
/*-----------------------------------------------------*/
/* establish the color of the stones on the from point */
/*-----------------------------------------------------*/
fcolor = point[from].owner;
/*--------------------------------------------------*/
/* establish the number of stones on the from point */
/*--------------------------------------------------*/
fcnt = point[from].stones;
if (fcnt == 0) {
debug("What's the point?");
return(FALSE);
}
strcat(chatter,buzzard); /* this avoids use of save_cursor() */
strcat(chatter," To? ");
msg(chatter);
to = getpt(MYBAR,MYHOME);
sprintf(buzzard,"%d",abs(25 - to));
if (to == MYBAR) {
if (fcolor == YU) to = YRBAR;
strcpy(buzzard,"BAR");
}
if (to == MYHOME) {
if (fcolor == YU) to = YRHOME;
strcpy(buzzard,"HOME");
}
if (to == ERROR) {
debug("Type Control-C to quit.");
return(FALSE);
}
if (from == to) {
debug("Quite easily done!");
return(FALSE);
}
strcat(chatter,buzzard);
/*---------------------------------------------------*/
/* establish the color of the stones on the to point */
/*---------------------------------------------------*/
tcolor = point[to].owner;
/*------------------------------------------------*/
/* establish the number of stones on the to point */
/*------------------------------------------------*/
tcnt = point[to].stones;
if (fcolor == tcolor || tcolor == 0) {
msg(chatter);
--fcnt;
++tcnt;
putstone(from, fcnt, fcolor); /* one less */
putstone(to, tcnt, fcolor); /* one more */
debug("");
return (TRUE);
}
else {
debug("Evict the other stone");
if (tcnt > 1) puts("s");
puts(" first!");
return (FALSE);
}
} /* end: delightful */
/*==========================================================================
INITIALIZATION and NEWBOARD commands -- start of a new game, or cold
===========================================================================*/
wipeout() {
static int i;
player = 0;
barcube();
for (i = 0; i < 28; i++) {
point[i].stones = point[i].owner = 0;
}
update();
} /* end: wipeout */
setup() {
static int i, j, k, copyright;
myscore = yrscore = player = dice[0] = dice[1] = 0;
kaypro = swapped = tswap = expert = helpdisabled = yrdice = FALSE;
show = moremsgline = tone = TRUE;
init_lib();
OFFinterrupt(); /* enable jumpjack() on ctl-c */
crtinit(); /* Kaypro video mode on (2-byte graphics) */
off_cursor();
token1 = getAtkn();
token2 = getBtkn();
copyright = backtalk[ MYLEVEL + 1 ];
draw_board( copyright );
for (i = 0; i < 28; i++) {
point[i].stones = point[i].owner = 0;
point[i].x = point[i].y = point[i].lastx = point[i].lasty = 0;
point[i].cx = point[i].cy = 0;
}
k = 68;
for (i = 1; i < 13; i++ ) { /* establish xy coords for the points */
j = 25 - i;
point[i].cx = point[j].cx = point[i].x = point[j].x = k;
k -= 5;
point[i].y = 4;
point[j].y = 18;
point[i].cy = 2;
point[j].cy = 20;
if (k == 38) k -= 5; /* skip over bar */
}
point[MYBAR].x = point[YRBAR].x = 38;
point[MYHOME].x = point[YRHOME].x = 75;
point[MYBAR].y = point[MYHOME].y = 5;
point[YRBAR].y = point[YRHOME].y = 17;
} /* end: setup */
newboard() {
static int i;
startcubevalue = 1;
wipedice(); wipeout();
putstone( MYHOME, 15, ME );
putstone( YRHOME, 15, YU );
putstone( YRHOME, 13, YU );
putstone( 1, 2, YU );
putstone( YRHOME, 8, YU );
putstone( 12, 5, YU );
putstone( YRHOME, 5, YU );
putstone( 17, 3, YU );
putstone( YRHOME, 0, 0 );
putstone( 19, 5, YU );
putstone( MYHOME, 10, ME );
putstone( 6, 5, ME );
putstone( MYHOME, 7, ME );
putstone( 8, 3, ME );
putstone( MYHOME, 2, ME );
putstone( 13, 5, ME );
putstone( MYHOME, 0, 0 );
putstone( 24, 2, ME );
} /* end: newboard */
draw_board( c ) char *c; {
static int line;
static char *m = " ", *picture[] = {
"The Peelgrunt Game of GAMMON IV%s\n\n\n",
":=================================o=================================:\n",
":: .. \\/ .. \\/ .. \\/ ||| .. \\/ .. \\/ .. \\/ ::\n",
":: ||| ::\n",
":: /\\ .. /\\ .. /\\ .. ||| /\\ .. /\\ .. /\\ .. ::\n"
};
clr_screen();
off_cursor();
msg( c );
off_cursor();
gotoxy(0,0);
puts(m); printf(picture[0],bgversion);
puts(m); puts(picture[1]);
for (line = 0; line < 6; line++) {
puts(m); puts(picture[2]);
}
puts(m); puts(picture[3]);
puts(m); puts(picture[3]);
puts(m); puts(picture[3]);
for (line = 0; line < 6; line++) {
puts(m); puts(picture[4]);
}
puts(m); puts(picture[1]);
} /* end: draw_board */
/*=========================================================================
DICE Commands: How to roll the dice
===========================================================================*/
peek() {
if (yrdice) return; /* you know your own dice, probably...? */
if (expert) debug("");
else debug("The Dice will Rattle until you Roll. Now on ");
getdice();
printf("[%d] [%d] ...",dice[0],dice[1]);
} /* end: peek */
getonedie() {
return ( (abs(acg()) % 6) + 1 );
} /* end: getonedie */
fixup() { /* ensure that the low die is in dice[0] */
int d,e;
if (player == 0) return; /* whofirst? don't mess with the odds */
d = min(dice[0],dice[1]);
e = max(dice[0],dice[1]);
dice[0] = d;
dice[1] = e;
} /* end: fixup */
getdice() {
int ch;
/* if it's MY dice we're using, generate random dice... */
if (!yrdice) {
dice[0] = getonedie();
acg(); /* bounce a little for luck */
dice[1] = getonedie();
fixup();
return;
}
/* but if it's YOUR dice, then ask about the roll... */
zoo: msg("<> ");
if (player == ME) puts("My"); else puts("Your");
puts(" roll: ");
if (!expert) puts("\008\008, using your dice: ");
puts(" First? ");
loo: while ( (ch = getc(0)) == 0 ); /* don't bother acg */
if (ch == 3) haltgame();
ch -= '0';
if (ch < 1 || ch > 6) goto loo;
putc( ch + '0',0);
dice[0] = ch;
puts(" Second? ");
while ( (ch = getc(0)) == 0 );
if (ch == 3) haltgame();
ch -= '0';
if (ch < 1 || ch > 6) goto zoo;
putc( ch + '0',0);
dice[1] = ch;
puts(" All Ok? ");
roo: while ( (ch = toupper(getc(0))) == 0 ); /* don't bother acg */
if (ch == 3) haltgame();
if (ch != 'N' && ch != 'Y') goto zoo;
fixup();
} /* end: getdice */
haltgame() {
player = -1; /* if play resumes, ask whose roll it is */
hint();
jumpjack();
} /* end: haltgame */
rolldice( who ) int who; {
int waiting;
setchat("Your Turn: ");
if (!expert) {
if (!yrdice) strcat(chatter," P(eek,");
strcat(chatter," D(ouble, Q(uit, or <AnyKey> to Roll ");
}
if (player == YU) { /* not executed if player == 0, i.e., whofirst */
waiting = TRUE;
while (waiting) {
msg(chatter);
switch ( getkey() ) {
/* bailout is Ctrl-C instead of ESCape */
case 3 : { haltgame(); break; }
case 'P': { peek(); break; }
case 'D': { udouble(); break; }
case 'Q': { winner(ME,topstone(YU)); break; }
default : waiting = FALSE;
} }
off_cursor(); debug(""); msg("");
}
highroller(who);
} /* end: rolldice */
highroller( who ) int who; { /* parameter is not redundant */
static int y = 11;
static int xme, xyu;
/* get the values for two dice, either yours or mine */
getdice();
/* display the values of the dice in the board area */
off_cursor();
xme = 47; xyu = 12; /* decide which half to show the values in */
if (point[1].x > 40) {
xme = 12;
xyu = 47;
}
if (player) {
gotoxy(xyu,y); blanks(18); /* erase, if not whofirst */
gotoxy(xme,y); blanks(18);
}
if (who == ME) {
gotoxy(xme,y); puts("My");
}
else {
gotoxy(xyu,y); puts("Your");
}
printf(" Roll> [%d] ",dice[0]);
if (player) printf("[%d] ",dice[1]); /* whofirst doesn't show this */
} /* end: highroller */
/*========================================================================
MAJOR UTILITIES -- Miscellaneous functions sans which the game will not
proceed so well as otherwise..........
========================================================================*/
jumpjack() { /* Much too simple-minded LONGJUMP. (But it IS simple!)
CAUTION: C/80 has no idea what's going on here!
The in-line assembly which sets _fool MUST NOT be in
a function which uses dynamic variables, or the stack
will be disrupted. See arrange() and play() herein. */
#asm
LHLD _fool ;Retrieve old stack status...
SPHL ;...diddle stack pointer
RET ;...and execute the ad hoc jump back to outer loop
_fool: DS 2 ;stack pointer is saved by doit(), read and used here
#endasm
} /* end: jumpjack */
acg() { /* additive congruential generator for pseudo-random numbers */
static int arg[] = {
4292, 60, 4947, 3972, 4489,
1917, 3916, 7579, 3048, 6856,
1832, 7589, 1798, 4954, 2880,
5142, 5187, 3045, 1529, 3110,
4333, 167, 5556, 7237, 5906,
5419, 6632, 5833, 3760, 1081,
1434, 80, 6212, 344, 7303,
3044, 7675, 5420, 457, 3434,
2657, 700, 6777, 4436, 620,
2129, 629, 3550, 1639, 4546,
1220, 6469, 862, 3280, 4664
};
static int rp1 = 0, rp2 = 32;
rp1++;
rp2++;
rp1 %= 55;
rp2 %= 55;
arg[rp1] ^= arg[rp2];
return ( arg[rp1] );
} /* end: acg */
finishup() {
crtexit(); on_cursor(); /* restore for user */
exit();
}
saveboard() {
int i;
for (i = 0; i < 28; i++) {
bdsave[i].stones = point[i].stones;
bdsave[i].owner = point[i].owner;
}
} /* end: saveboard */
restoreboard() {
int i;
for (i = 0; i < 28; i++) {
point[i].stones = bdsave[i].stones;
point[i].owner = bdsave[i].owner;
}
} /* end: restoreboard */
getAtkn() {
if (kaypro) return( get5tkn() );
else return( get1tkn() );
}
getBtkn() {
if (kaypro) return( get6tkn() );
else return( get2tkn() );
}
getCtkn() {
if (kaypro) return( get7tkn() );
else return( get3tkn() );
}
getDtkn() {
if (kaypro) return( get8tkn() );
else return( get4tkn() );
}
swaptokens() {
char *temp;
swapped ^= TRUE;
if (swapped) {
temp = token1;
token1 = token2;
token2 = temp;
}
else {
tswap ^= TRUE;
if (tswap) {
token1 = getCtkn();
token2 = getDtkn();
}
else {
token1 = getAtkn();
token2 = getBtkn();
}
}
} /* end: swaptokens */
blanks( n ) int n; {
while (n--) putc(' ',0);
} /* end: blanks */
msg(p) char *p; {
on_cursor();
gotoxy(5,23); blanks(74);
gotoxy(5,23); puts(p);
}
debug(p) char *p; {
on_cursor();
gotoxy(5,22); blanks(74);
gotoxy(5,22); puts(p); return(FALSE);
}
nxtyp(i) int i; {
if (i > 9) return ( i - 1 ); else return ( i + 1 );
}
isbar(p) int p; {
return (( p == MYBAR ) || ( p == YRBAR ));
}
ishome(p) int p; {
return (( p == MYHOME ) || ( p == YRHOME ));
}
putstone( pt, cnt, color ) int pt, cnt, color; {
int i, xp, yp, slack;
char *background, *token;
if (cnt < 1) { /* empty point has neither stones nor owner */
cnt = 0;
color = 0;
}
point[pt].stones = cnt; /* number of stones on this point */
point[pt].owner = color; /* and whose they are */
/* stack stones 5 high in the home tray, 6 high on the points */
if (isbar(pt) || ishome(pt)) slack = 5; else slack = 6;
/* locate the base address of the point for animation */
xp = point[pt].x;
yp = point[pt].y;
/* decide on the background pattern to be used for empty places */
if (pt > 12) background = "/\\ ";
else background = "\\/ ";
if (point[1].x < 40) {
if ((pt % 2) == 1) background = ".. ";
}
else if ((pt % 2) == 0) background = ".. ";
if (ishome(pt)) background = " ";
if (isbar(pt)) background = "||| ";
/* get the token pattern to be used */
if (color == ME) {
token = token1;
}
else {
token = token2;
}
/* draw the entire point with token and background patterns */
off_cursor();
/* first erase all blots from this point (draw the background) */
for (i = 0; i < slack; i++) {
gotoxy(xp,yp);
puts(background); /* string has point's width */
point[pt].lastx = 0; /* future, not implemented */
point[pt].lasty = 0;
yp = nxtyp(yp);
}
/* now draw all the blots there are on this point onto the point */
for (i = 0; i < cnt; i++) {
xp = point[pt].x + (i / slack);
if ((i % slack) == 0) yp = point[pt].y;
gotoxy(xp, yp);
puts(token);
point[pt].lastx = xp; /* future, not implemented */
point[pt].lasty = yp;
yp = nxtyp(yp);
}
} /* end: putstone */
hitblot( from, color ) int from, color; {
static int barpt, addone;
if (tone) beep();
barpt = whosebar( color );
putstone(from, 0, 0);
addone = point[barpt].stones + 1;
putstone( barpt, addone, color );
} /* end: hitblot */
movestone( from, to) int from, to; {
static int opponent, subone, addone;
opponent = other( player );
if (point[to].owner == opponent) hitblot(to, opponent);
subone = point[from].stones - 1;
addone = point[to].stones + 1;
putstone(from, subone, player);
putstone(to, addone, player);
--movesleft;
checkwin(); /* never but NEVER let a win go unnoticed! */
} /* end: movestone */
/*========================================================================
CUBE Commands -- commands related to the cube, doubling, etc.
=========================================================================*/
notyrcube() {
gotoxy(75,19); blanks(5);
gotoxy(75,20); blanks(5);
} /* end: notyrcube */
notmycube() {
gotoxy(75,2); blanks(5);
gotoxy(75,3); blanks(5);
} /* end: notmycube */
barcube() {
/* startcubevalue is normally 1, but it may have doubled */
/* if the opening rolloff for first turn came up doubles */
doubles.cube = startcubevalue;
doubles.whosecube = 0;
notmycube(); notyrcube();
gotoxy(37,11);
if (startcubevalue == 1) puts("[BAR]");
else if (doubles.cube < 16) printf("[ %d ]",doubles.cube);
else printf("[%03d]",doubles.cube);
} /* end: barcube */
notbarcube() {
gotoxy(37,11); puts(" BAR ");
} /* end: notbarcube */
mycube(value) int value; {
notbarcube(); gotoxy(75,2); puts("CUBE");
gotoxy(75,3);
sprintf(buzzard,"[%d]",value);
printf("%-5s",buzzard);
doubles.whosecube = ME;
} /* end: mycube */
yrcube(value) int value; {
notbarcube; gotoxy(75,19); puts("CUBE");
gotoxy(75,20); sprintf(buzzard,"[%d]",value);
printf("%-5s",buzzard);
doubles.whosecube = YU;
} /* end: yrcube */
idouble() {
static int ch;
if (doubles.whosecube == YU) return; /* not mine, can't double! */
if (doubles.cube > 256) return; /* maximum, don't consider it */
notbarcube();
if (tone) beep();
debug("I double. Will you accept the cube ");
printf("at %d points? ",doubles.cube * 2);
loo: while ((ch = getc(0)) == 0);
if (toupper(ch) == 'Y') {
notmycube();
doubles.cube *= 2;
yrcube(doubles.cube);
off_cursor();
}
else if (toupper(ch) == 'N') winner(ME,0);
else goto loo;
} /* end: idouble() */
backgame() {
int max, barred, count, i;
if (topstone(YU) < 12 && topstone(ME) > 18) {
if (mytotal() < yrtotal() + 4) return (TRUE);
max = barred = count = 0;
i = 24;
while (i > 18) {
if (point[i].owner == ME) {
max = i;
if (point[i].stones > 1) barred++;
count += point[i].stones;
}
i--;
}
return ((max < topstone(YU) + 1) && (barred > 1 && count < 7));
}
else return ( mytotal() < yrtotal() + 24 );
} /* end: backgame */
cubeval() {
int ineed, yuneed, yrtop, mytop;
if (endgame()) {
/* calculate the number of dice that are required to end */
/* the game, with appropriate fudge factors for position */
yrtop = topstone(YU);
mytop = topstone(ME);
ineed = 15 - point[MYHOME].stones;
yuneed = 15 - point[YRHOME].stones;
if (yrtop < 4 && yuneed < 3) return (FALSE); /* obvious */
/* topstones still running? use a different method */
if (mytop > 6 || yrtop > 6) {
ineed = 2 * (mytotal() / 8) + 1; /* number of dice */
yuneed = 2 * (yrtotal() / 8) + 1;
}
/* count the stones on point 6 twice, they're losers */
yuneed += point[6].stones;
ineed += point[6].stones;
/* you doubled, so you have the roll */
yuneed -= 2;
/* odd number left? */
if (ineed % 2) ineed++;
if (yuneed % 2) yuneed++;
/* is the one point empty? */
if (ineed > 4 && mytop > 3 && point[24].stones == 0) ineed++;
if (yuneed > 4 && yrtop > 3 && point[ 1].stones == 0) yuneed++;
if (mytop < yrtop && ineed < yuneed) return (TRUE);
if (yrtop < 5 && yuneed < ineed) return (FALSE);
return ( yuneed >= ineed );
}
else return ( backgame() );
} /* end: cubeval */
testcube() {
if (cubeval()) {
debug("I accept the cube.");
notyrcube();
doubles.cube *= 2;
mycube(doubles.cube);
}
else winner(YU,0);
} /* end: testcube */
udouble() {
if (doubles.whosecube == ME) {
if (tone) beep();
debug("It's MY cube, dummy!");
}
else testcube();
} /* end: udouble */
#include "mylib2.c"