2278 lines
58 KiB
C
2278 lines
58 KiB
C
/* 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"
|
||
|
||
|