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