home *** CD-ROM | disk | FTP | other *** search
/ AmigActive 20 / AACD20.BIN / AACD / Programming / AmiSlate-Source / SlateRexx / backgammon.rexx next >
Encoding:
OS/2 REXX Batch file  |  1995-12-21  |  31.0 KB  |  1,133 lines

  1. /* Backgammon for AmiSlate v1.1! */
  2. /* This program should be run on a screen with at least 8 colors */
  3.  
  4. /* v1.1 : fixed a glitch that would sometimes leave a little garbage on
  5.           the screen after a spike redraw. */
  6.           
  7. /* Get our host's name--always given as first argument when run from Amislate */
  8. parse arg CommandPort ActiveString
  9.  
  10. if (length(CommandPort) == 0) then do
  11.     say ""
  12.     say "Usage:  rx backgammon.rexx <REXXPORTNAME>"
  13.     say "        (REXXPORTNAME is usually AMISLATE)"
  14.     say ""
  15.     say "Or run from the Rexx menu within AmiSlate."
  16.     say ""
  17.     exit 0
  18.     end
  19.     
  20. /* Send all commands to this host */
  21. address (CommandPort) 
  22. options results
  23.  
  24. lock ON
  25.  
  26. /* See if we're connected */
  27. GetRemoteStateAttrs stem rstateattrs.
  28. if (rstateattrs.mode > -1) then do
  29.         /* Parse command line argument to see if we've been activated by 
  30.            a remote request or a local user */
  31.         check = upper(left(ActiveString,3))
  32.         if (upper(left(ActiveString,3)) ~= 'RE') then 
  33.             GlobData.localplayer = 1
  34.         else
  35.             GlobData.localplayer = -1
  36.         end
  37.     else do
  38.         GlobData.localplayer = 0    /* i.e. we're both players */
  39.         end
  40.         
  41. if (GlobData.localplayer > 0) then do
  42.     call SetStatus("Requesting game from remote user... please wait.")
  43.     RemoteRexxCommand '"'||"Would you like to play backgammon?"||'"' "slaterexx:backgammon.rexx"
  44.     
  45.         waitevent stem handshake. MESSAGE
  46.         if (handshake.message == 0) then 
  47.         do
  48.             call SetStatus("Backgammon Game Refused")
  49.             lock off
  50.             exit 0
  51.         end
  52.     end
  53.  
  54. call SetStatus("Beginning Backgammon...")
  55.  
  56. call ResetGameState
  57. call SetGlobalData
  58. if (GlobData.localplayer >= 0) then call DrawBoard
  59.  
  60. call NextTurn
  61. call HandleEvents
  62.  
  63. lock OFF
  64. exit 0
  65.  
  66. /* Global Data structure:
  67.  
  68.     GlobData.Xspace.[step] (int)     : horizontal pixel offsets to the center of each coord (0-16)
  69.     GlobData.XSize    (int)         : total width of each element
  70.     GlobData.Yspace.[step] (int)     : vertical pixel offsets to the center of each coord (0-16)
  71.     GlobData.YSize    (int)         : total height of each element
  72.  
  73.     (board state)
  74.         GlobData.slots.[slotnum]         : array of slotstates (0..23) (int, > 0 = Pl1 pieces, < 0 = Pl-1 pieces)
  75.                      : note: slot -1 is player 1's "out" pile, and slot 24 is player -1's "out" pile.
  76.     GlobData.die.[dienum]            : the current number on each of the four die.  Usually only 0 & 1 are used,            
  77.  
  78.     (game state)
  79.     GlobData.turn             : whose turn it is
  80.     GlobData.exited.[playernum]      : number of pieces that have been taken off the board for player [playernum]
  81.     GlobData.localplayer              : which player is on local machine; 0 if both are
  82.      (color info)
  83.     GlobData.PieceColor.[playernum]  : color of pieces for this player
  84.     GlobData.SpikeColor.[playernum]  : spike color, alternates
  85.  
  86. */
  87.  
  88. /* --------------------------------------------------------------- */
  89. /* procedure HandleEvents                        */
  90. /* --------------------------------------------------------------- */
  91. HandleEvents: procedure expose GlobData. AMessage.
  92.  
  93. SetSquare = -10
  94. BReversed = 0
  95. BNeedsToRoll = 1
  96. BMustForfeit = 0
  97.  
  98. AMessage.TIMEOUT     = 1    /* No events occurred in specified time period */
  99. AMessage.MESSAGE     = 2    /* Message recieved from remote Amiga */
  100. AMessage.MOUSEDOWN   = 4    /* Left mouse button press in drawing area */
  101. AMessage.MOUSEUP     = 8    /* Left mouse button release in drawing area */
  102. AMessage.RESIZE      = 16    /* Window was resized--time to redraw screen? */ 
  103. AMessage.QUIT        = 32    /* AmiSlate is shutting down */
  104. AMessage.CONNECT     = 64    /* Connection established */
  105. AMessage.DISCONNECT  = 128    /* Connection broken */
  106. AMessage.TOOLSELECT  = 256    /* Tool Selected */
  107. AMessage.COLORSELECT = 512    /* Palette Color selected */
  108. AMessage.KEYPRESS    = 1024    /* Key pressed */
  109. AMessage.MOUSEMOVE   = 2048     /* Mouse was moved */
  110.  
  111. /* Turn on help */
  112. BHelp = 1
  113.  
  114. do while(1)
  115.     waitevent stem event. RESIZE MOUSEDOWN MOUSEUP TOOLSELECT MESSAGE DISCONNECT
  116.  
  117.     if (event.type == AMessage.QUIT) then exit 0
  118.     
  119.     if (event.type == AMessage.DISCONNECT) then do
  120.         call SetStatus("Connection broken--both players now local.")
  121.         GlobData.localplayer = 0
  122.         end
  123.    
  124.     if (event.type == AMessage.MESSAGE) then do
  125.         /* parse the message */
  126.         if (left(event.message,1) = "G") then do
  127.             /* It's our move! */
  128.         BNeedsToRoll = 1
  129.         BMustForfeit = 0
  130.         call NextTurn
  131.         end
  132.     else do
  133.         parse var event.message rfrom rto
  134.         call MovePiece((rfrom+0), (rto+0), 0)  /* update our internals--the (+0) forces the vars back into numeric format */
  135.         end
  136.     end
  137.  
  138.     if (event.type == AMessage.RESIZE) then do
  139.         if ((GlobData.localplayer == GlobData.turn)|(GlobData.localplayer == 0)) then do
  140.             call SetGlobalData
  141.             call DrawBoard
  142.             if (BMustForfeit == 1) then call ShowCantMove
  143.         end
  144.         else do
  145.             /* else just update our internal vars */
  146.             call SetGlobalData
  147.         end
  148.         /* call UpdateStatus */
  149.     end
  150.     
  151.     if ((event.type == AMessage.MOUSEDOWN)&((GlobData.turn = GlobData.localplayer)|(GlobData.localplayer == 0))) then do
  152.         whatclicked = ParseMouseClick(event.x, event.y)
  153.     if (SetSquare == -10) then do
  154.         if (whatclicked == 25) then 
  155.             if (BNeedsToRoll == 1) then do
  156.                 call Roll
  157.                 BNeedsToRoll = 0
  158.                 /* Check to see if we can move */
  159.                 can = CanMove()
  160.                 if (can == 0) then do
  161.                     call ShowCantMove
  162.                     BMustForfeit = 1
  163.                     end
  164.                 end
  165.             else do
  166.                 if (BMustForfeit == 1) then do
  167.                     /* Cancels rest of turn! */
  168.                     BNeedsToRoll = 1
  169.                     BMustForfeit = 0
  170.                     if (localplayer ~= 0) then sendmessage G
  171.                     call NextTurn
  172.                     end
  173.                 end
  174.         else 
  175.         /* Nothing was clicked before, mark this square? */
  176.         if ((BMustForfeit = 0)&(BNeedsToRoll = 0)&(whatclicked < 25)&((GlobData.slots.whatclicked * GlobData.turn) > 0)) then do
  177.             if (PieceCanMove(whatclicked) == 1) then do
  178.                 call HiliteTopPiece(whatclicked)
  179.                 if (BHelp == 1) then call ShowMoves(whatclicked)
  180.                 BReversed = ~BReversed
  181.                 SetSquare = whatclicked
  182.                 end
  183.                 else DisplayBeep LOCAL
  184.             end
  185.         end
  186.     else do
  187.         /* Already had one clicked, now either cancel it or move? */
  188.         if (whatclicked == SetSquare) then do
  189.             /* cancel the piece-move! */
  190.             if (BReversed == 1) then call HiliteTopPiece(whatclicked)
  191.             if (BHelp == 1) then call ClearArrows
  192.             BReversed = 0
  193.             SetSquare = -10
  194.             end
  195.         else do
  196.             dietouse = MoveOkay(SetSquare, whatclicked,1) 
  197.             if (dietouse > -1) then do
  198.                 if (BHelp == 1) then call ClearArrows
  199.                 call MovePiece(SetSquare, whatclicked, 1)
  200.                 BReversed = 0
  201.                 SetSquare = -10
  202.                 GlobData.die.dietouse = 0
  203.                 call DrawDie(dietouse)
  204.                 if ((GlobData.die.0 == 0)&(GlobData.die.1 == 0)&(GlobData.die.2 == 0)&(GlobData.die.3 == 0)) then do
  205.                     BNeedsToRoll = 1
  206.                     BMustForfeit = 0
  207.                     if (localplayer ~= 0) then sendmessage G
  208.                     call NextTurn
  209.                     end
  210.                 else do
  211.                     /* Check to see if we can still move */
  212.                     can = CanMove()
  213.                     if (can == 0) then do
  214.                         call ShowCantMove
  215.                         BMustForfeit = 1
  216.                         end
  217.                     end
  218.                 end
  219.                 else DisplayBeep LOCAL
  220.             end
  221.         end
  222.     end
  223.     end
  224.     return 1
  225.  
  226.  
  227. /* --------------------------------------------------------------- */
  228. /* procedure NextTurn                           */
  229. /*                                    */
  230. /* --------------------------------------------------------------- */
  231. NextTurn: procedure expose GlobData.
  232.     GlobData.Turn = -GlobData.Turn
  233.     do i = 0 to 3 
  234.         GlobData.die.i = 0
  235.         end
  236.  
  237.     /* local game code */
  238.     if (Globdata.localplayer == 0) then do
  239.         call DrawCup(0)
  240.         if (GlobData.turn == 1) then 
  241.             call SetStatus("Player 1, it's your turn to roll.")
  242.         else
  243.             call SetStatus("Player 2, it's your turn to roll.")
  244.         return 1
  245.         end
  246.     
  247.     /* two machine game code */
  248.     if (GlobData.turn == GlobData.localplayer) then do
  249.         call DrawCup(0)
  250.         if (GlobData.turn == 1) then 
  251.             call SetStatus("Player 1, it's your turn to roll.")
  252.         else
  253.             call SetStatus("Player 2, it's your turn to roll.")
  254.         end
  255.     else do
  256.         call SetStatus("Wait for other player to move.")
  257.         end
  258.     return 1
  259.     
  260.         
  261.  
  262. /* --------------------------------------------------------------- */
  263. /* procedure Roll                           */
  264. /*                                    */
  265. /* --------------------------------------------------------------- */
  266. Roll: procedure expose GlobData.
  267.  
  268.     /* First clear the die area */
  269.     cturn = GlobData.turn
  270.     SetFPen GlobData.PieceColor.cturn
  271.     square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  272.  
  273.     GlobData.die.0 = random(1,6,time('s'))
  274.     GlobData.die.1 = random(1,6,time('s')+500)
  275.     
  276.     call DrawDie(0)
  277.     call DrawDie(1)
  278.     
  279.     if (GlobData.die.0 == GlobData.die.1) then do
  280.         call SetStatus("You rolled a double!")
  281.  
  282.         GlobData.die.2 = GlobData.die.1
  283.         GlobData.die.3 = GlobData.die.1
  284.     
  285.         call DrawDie(2)
  286.         call DrawDie(3)    
  287.         end
  288.     else do
  289.         if (GlobData.turn == 1) then 
  290.             call SetStatus("Player 1, move your pieces")
  291.         else
  292.             call SetStatus("Player 2, move your pieces")
  293.         end
  294.     return 1        
  295.  
  296.     
  297.  
  298. /* --------------------------------------------------------------- */
  299. /* procedure MovePiece                           */
  300. /*                                    */
  301. /* --------------------------------------------------------------- */
  302. MovePiece: procedure expose GlobData.
  303.     parse arg from, to, showgfx
  304.         
  305.     origfrom = GlobData.slots.from
  306.     negone = -1
  307.     
  308.     /* If it's a two machine game, tell the other machine what we're doing */
  309.     if ((showgfx == 1)&(GlobData.localplayer ~= 0)) then call TransmitMove(from, to)
  310.     
  311.     /* First remove the piece from the old spike and redraw it */
  312.     call RemovePiece(from, GlobData.turn, showgfx)
  313.  
  314.     /* If the piece is moving "off the board", count it up */
  315.     if (to == 24) then do
  316.         /* Piece exited the board for player 1 */
  317.         GlobData.exited.1 = (GlobData.exited.1) + 1
  318.         call CheckForWin
  319.         return 1
  320.         end
  321.     else if (to == -1) then do
  322.         /* Piece exited the board for player 1 */
  323.         GlobData.exited.negone = (GlobData.exited.negone) + 1
  324.         call CheckForWin
  325.         return 1
  326.         end        
  327.  
  328.     /* Now add/replace our piece to the to spike */
  329.     if ((origfrom * GlobData.slots.to) < 0) then do
  330.         /* a piece got killed! */
  331.         call SetStatus("Bump!  Ahahahahaha!!!!")
  332.         if (GlobData.localplayer ~= 0) then DisplayBeep REMOTE
  333.         if (GlobData.slots.to < 0) then 
  334.             call AddPiece(24, -1, showgfx)
  335.         else 
  336.             call AddPiece(-1, 1, showgfx)
  337.             
  338.         GlobData.slots.to = 0
  339.         end
  340.  
  341.     call AddPiece(to, GlobData.turn, showgfx)
  342.     return 1
  343.  
  344.  
  345.  
  346. /* --------------------------------------------------------------- */
  347. /* procedure RemovePiece                       */
  348. /* --------------------------------------------------------------- */
  349. RemovePiece: procedure expose GlobData.    
  350.     parse arg from, which, showgfx
  351.     
  352.     GlobData.slots.from = GlobData.slots.from - which    
  353.     if (showgfx == 1) then call DrawSpike(from)
  354.     return 1
  355.     
  356.     
  357. /* --------------------------------------------------------------- */
  358. /* procedure AddPiece                           */
  359. /* --------------------------------------------------------------- */
  360. AddPiece: procedure expose GlobData.
  361.     parse arg to, which, showgfx
  362.     
  363.     GlobData.slots.to = GlobData.slots.to + which
  364.     if (showgfx == 1) then call DrawPiece(to, GlobData.PieceColor.which, abs(GlobData.slots.to), 0)
  365.     return 1
  366.     
  367.  
  368. /* --------------------------------------------------------------- */
  369. /* procedure MoveOkay                           */
  370. /*                                    */
  371. /* returns the index of the die to use if the move is acceptable,  */
  372. /* or -1 if it is illegal.                        */
  373. /*                                    */
  374. /* --------------------------------------------------------------- */
  375. MoveOkay: procedure expose GlobData.
  376.     parse arg from, to, CountExits
  377.  
  378.     negone = -1
  379.     numsquares = abs(to-from)
  380.         
  381.     /* Side specific rules! */
  382.     if (GlobData.slots.from < 0) then do
  383.         /* no moving backwards */
  384.         if (to > from) then return -1
  385.  
  386.         /* no moving anyone else if we have a guy "out" */
  387.         if ((GlobData.slots.24 ~= 0)&(from ~= 24)) then return -1
  388.  
  389.         /* No moves into the "out" unless all of our pieces are (exited or in the last quad) */
  390.         /* Also, we can't exit on a roll larger than necessary *unless* there is no other use for that roll. */
  391.         if (to <= -1) then do
  392.             disttoexit = from + 1
  393.  
  394.             /* If we're not checking for exits, return failure now */
  395.             if (CountExits == 0) then do
  396.                 /* Still need to watch for an exact out! */
  397.                 do checkdie = 0 to 3 
  398.                     if (GlobData.die.checkdie == disttoexit) then 
  399.                         if (CanMoveOut(1) == 1) then return checkdie
  400.                     end                
  401.                 return -1
  402.                 end                
  403.             
  404.             /* Otherwise check to see if we can move out */
  405.             if (CanMoveOut(-1) == 0) then return -1
  406.             else do
  407.                 do checkdie = 0 to 3 
  408.                     if (GlobData.die.checkdie == disttoexit) then return checkdie
  409.                     end
  410.                 do checkdie = 0 to 3
  411.                     if (GlobData.die.checkdie > disttoexit) then do
  412.                         if (CanExitOnExtra(GlobData.die.checkdie) == 1) then return checkdie
  413.                         end
  414.                     end
  415.                 /* default: can't move out */
  416.                 return -1
  417.                 end
  418.             end
  419.         end
  420.     else do
  421.         /* no moving backwards */
  422.         if (to < from) then return -1
  423.  
  424.         /* no moving anyone else if we have a guy "out" */
  425.         if ((GlobData.slots.negone ~= 0)&(from ~= -1)) then return -1
  426.  
  427.         /* No moves into the "out" unless all of our pieces are (exited or in the last quad) */
  428.         if (to >= 24) then do
  429.             disttoexit = 24 - from
  430.  
  431.             /* If we're not checking for exits, return failure now */
  432.             if (CountExits == 0) then do
  433.                 /* Still need to watch for an exact out! */
  434.                 do checkdie = 0 to 3 
  435.                     if (GlobData.die.checkdie == disttoexit) then 
  436.                         if (CanMoveOut(1) == 1) then return checkdie
  437.                     end                
  438.                 return -1
  439.                 end
  440.                 
  441.             /* Otherwise check to see if we can move out */
  442.             if (CanMoveOut(1) == 0) then 
  443.                 return -1 
  444.             else do
  445.                 do checkdie = 0 to 3
  446.                     if (GlobData.die.checkdie == disttoexit) then return checkdie
  447.                     end
  448.                 do checkdie = 0 to 3
  449.                     if (GlobData.die.checkdie > disttoexit) then do
  450.                         if (CanExitOnExtra(GlobData.die.checkdie) == 1) then return checkdie
  451.                         end
  452.                     end
  453.                 /* default: can't move out */
  454.                 return -1
  455.                 end
  456.             end
  457.         end
  458.     
  459.     /* first get this into absolute numbers */
  460.     if (GlobData.slots.from < 0) then do
  461.         fromsq = -(GlobData.slots.from)
  462.         tosq   = -(GlobData.slots.to)
  463.         end
  464.     else do
  465.         fromsq = GlobData.slots.from
  466.         tosq   = GlobData.slots.to
  467.         end
  468.     
  469.     /* At this point: fromsq > 0 */
  470.     if (tosq < -1) then return -1
  471.     
  472.     /* Make sure this move is available on one of the die */
  473.     diefound = -1
  474.     do i = 0 to 3
  475.         if (GlobData.die.i == numsquares) then diefound = i
  476.         end
  477.     
  478.     return diefound
  479.     
  480.     
  481.     
  482.  
  483. /* --------------------------------------------------------------- */
  484. /* procedure HiliteTopPiece                       */
  485. /* --------------------------------------------------------------- */
  486. HiliteTopPiece: procedure expose GlobData.
  487.     parse arg spikenum
  488.  
  489.     cturn = GlobData.turn
  490.     
  491.     call DrawPiece(spikenum, GlobData.PieceColor.cturn, abs(GlobData.slots.spikenum), 1)
  492.     return 1
  493.  
  494.  
  495.  
  496. /* --------------------------------------------------------------- */
  497. /* procedure ParseMouseClick                        */
  498. /*                                   */
  499. /* returns (-1)-(24) if a stack was clicked, or 25 if the center   */
  500. /* dice/cup area was clicked                        */
  501. /*                                   */
  502. /* --------------------------------------------------------------- */
  503. ParseMouseClick: procedure expose GlobData.
  504.     parse arg xp, yp
  505.     
  506.     /* figure out vertical zone */
  507.     yZone = 1    /* default */
  508.     if (yp < GlobData.YSpace.6) then yZone = 0
  509.     if (yp > GlobData.YSpace.10) then yZone = 2
  510.     
  511.     /* figure out horizontal zone */
  512.     xZone = 1    /* default */
  513.     if (xp < GlobData.XSpace.7) then xZone = 0
  514.     if (xp > GlobData.XSpace.9) then xZone = 2
  515.     
  516.     /* are we in the dice/cup zone? */
  517.     if (xZone == 1) then do
  518.        if (yZone == 0) then return -1
  519.        if (yZone == 1) then return 25
  520.        return 24
  521.        end
  522.     else do
  523.        /* force into yzone 0 or 2 */
  524.        if (yp < GlobData.YSpace.8) then 
  525.            yzone = 0 
  526.        else 
  527.            yzone = 2
  528.        end
  529.        
  530.     /* Must be a regular stack--get the horizontal offset */    
  531.     if (xZone == 0) then offset = (xp-(GlobData.XSize/2)) / GlobData.XSize         
  532.     if (xZone == 2) then offset = ((xp-GlobData.XSpace.9-(GlobData.XSize/2)) / GlobData.XSize)
  533.     
  534.     offset = trunc(offset)    
  535.     if (offset < 0) then offset = 0
  536.     if (offset > 5) then offset = 5
  537.  
  538.     if ((xZone = 0)&(yZone = 0)) then return 11-offset
  539.     if ((xZone = 2)&(yZone = 0)) then return 5-offset
  540.     if ((xZone = 0)&(yZone = 2)) then return 12+offset
  541.     if ((xZone = 2)&(yZone = 2)) then return 18+offset
  542.     return -55
  543.  
  544.  
  545. /* --------------------------------------------------------------- */
  546. /* procedure ResetGameState                       */
  547. /* --------------------------------------------------------------- */
  548. ResetGameState: procedure expose GlobData.
  549.     negone = -1;
  550.     
  551.     DO i = -1 to 24
  552.         GlobData.slots.i = 0    /* first clear the board */
  553.     end
  554.  
  555.     /* initial board config */
  556.     GlobData.slots.0 = 2
  557.     GlobData.slots.5 = -5
  558.     GlobData.slots.7 = -3
  559.     GlobData.slots.11 = 5
  560.     GlobData.slots.12 = -5
  561.     GlobData.slots.16 = 3
  562.     GlobData.slots.18 = 5
  563.     GlobData.slots.23 = -2
  564.  
  565.  
  566. /* Setup to test end game -- dont use
  567. GlobData.slots.6 = -1
  568. GlobData.slots.5 = -2
  569. GlobData.slots.4 = -2
  570. GlobData.slots.3 = -5
  571. GlobData.slots.2 = -1
  572. GlobData.slots.1 = -3
  573. GlobData.slots.0 = -1
  574.  
  575. GlobData.slots.15 = 1
  576. GlobData.slots.18 = 2
  577. GlobData.slots.19 = 2
  578. GlobData.slots.20 = 5
  579. GlobData.slots.21 = 1
  580. GlobData.slots.22 = 3
  581. GlobData.slots.23 = 1
  582. */
  583.  
  584.     GlobData.slots.negone = 0
  585.     GlobData.slots.24 = 0
  586.  
  587.     GlobData.out.1 = 0
  588.     GlobData.out.negone = 0
  589.  
  590.     GlobData.die.0 = 0
  591.     GlobData.die.1 = 0
  592.     GlobData.die.2 = 0
  593.     GlobData.die.3 = 0
  594.  
  595.     GlobData.exited.1 = 0
  596.     GlobData.exited.negone = 0
  597.     
  598.     GlobData.turn    = -1;
  599.  
  600.     return 1
  601.  
  602. /* --------------------------------------------------------------- */
  603. /* procedure SetGlobalData                       */
  604. /* --------------------------------------------------------------- */
  605. SetGlobalData: procedure expose GlobData.
  606.     negone = -1
  607.     
  608.     /* Check to see whether we are connected */
  609.        GetWindowAttrs stem winattrs.
  610.        BoardWidth = winattrs.width  - 58
  611.        BoardHeight= winattrs.height - 55
  612.  
  613.     /* Set up offsets */
  614.     fStep = 1/16
  615.     DO i=0 to 16 
  616.       GlobData.Xspace.i = trunc(BoardWidth * (fStep * i))
  617.       GlobData.Yspace.i = trunc(BoardHeight * (fStep * i))
  618.       end
  619.  
  620.     GlobData.XSize = trunc(BoardWidth / 16)
  621.     GlobData.YSize = trunc(BoardHeight / 16)
  622.  
  623.        if (winattrs.depth < 3) then do
  624.         EasyRequest BackGammon_Error '"'||"You need at least a 8-color screen to play backgammon!"||'"' '"'||"Abort Backgammon"||'"'
  625.         call SetStatus("Backgammon game exited.")
  626.         lock off
  627.         exit 0
  628.         end 
  629.  
  630.     GlobData.SpikeColor.1 = 2
  631.     GlobData.SpikeColor.negone = 7
  632.     GlobData.PieceColor.1 = 4
  633.     GlobData.PieceColor.negone = 3
  634.     return 1
  635.  
  636.  
  637. /* --------------------------------------------------------------- */
  638. /* procedure DrawBoard                                             */
  639. /* --------------------------------------------------------------- */
  640. DrawBoard: procedure expose GlobData.
  641.     SetFColor 0 0 0        /* Get a black pen */
  642.  
  643.     Clear
  644.  
  645.     SetWindowTitle '"' || "Hang on, drawing the board..." || '"'
  646.  
  647.     SetFPen 1
  648.     square GlobData.Xspace.0 GlobData.Yspace.0 GlobData.XSpace.16 GlobData.YSpace.16
  649.     Do i = -1 to 24
  650.         call DrawSpike(i)
  651.         end
  652.  
  653.     SetFPen 1
  654.     line GlobData.Xspace.7 GlobData.Yspace.0 GlobData.XSpace.7 GlobData.YSpace.16
  655.     line GlobData.Xspace.9 GlobData.Yspace.0 GlobData.XSpace.9 GlobData.YSpace.16
  656.     square GlobData.XSpace.7 GlobData.YSpace.6 GlobData.XSpace.9 GlobData.YSpace.10    
  657.         
  658.     if ((GlobData.die.0 + GlobData.die.1 + GlobData.die.2 + GlobData.die.3) > 0)  then do
  659.         cturn = GlobData.turn
  660.         bgcol = GlobData.PieceColor.cturn
  661.         SetFPen bgcol
  662.         square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  663.         Do i = 0 to 3 
  664.             call DrawDie(i)
  665.             end
  666.         end
  667.     else call DrawCup(0)
  668.  
  669.     call SetStatus("_LAST")
  670.     return 1
  671.  
  672.  
  673. /* --------------------------------------------------------------- */
  674. /* procedure DrawSpike                                             */
  675. /* --------------------------------------------------------------- */
  676. DrawSpike: procedure expose GlobData.
  677.     parse arg spikenum
  678.  
  679.     negone = -1
  680.  
  681.     /* Figure out what color to draw the spike as */
  682.     spikecol = GlobData.SpikeColor.1
  683.     if ((spikenum // 2) == 1) then spikecol = GlobData.SpikeColor.negone
  684.  
  685.     /* Figure out what color to draw any pieces as */
  686.     piececol = GlobData.PieceColor.1
  687.     numpieces  = GlobData.slots.spikenum
  688.     if (numpieces < 0) then do
  689.         piececol = GlobData.PieceColor.negone
  690.         numpieces = -numpieces
  691.         end
  692.  
  693.     /* Figure out our coords */
  694.     baseofspike = getSpikeBase(spikenum)
  695.     topofspike = getSpikeTop(spikenum)
  696.     spikecenter = getSpikecenter(spikenum)
  697.  
  698.     /* First blank out the area we are to draw on */
  699.     wid = trunc(GlobData.XSize/3)
  700.     BaseOfSpikeCoord = GlobData.YSpace.baseofspike + getSpikeDir(spikenum)
  701.     TopOfSpikeCoord = GlobData.Yspace.topofspike 
  702.     if ((spikenum == -1)|(spikenum == 24)) then TopOfSpikeCoord = TopOfSpikeCoord - getSpikeDir(spikenum)
  703.     /* extra is the few more pixels on the left we add in to ensure that any extra layers are erased */
  704.     extra = trunc((trunc(abs((GlobData.slots.spikenum)/5))+1)*(GlobData.XSize/10))
  705.     SetFPen 0    
  706.     square (GlobData.Xspace.spikecenter)-wid-extra BaseOfSpikeCoord (GlobData.Xspace.spikecenter)+wid TopOfSpikeCoord FILL
  707.  
  708.     SetFPen 1
  709.     if ((spikenum >= 0) & (spikenum <= 23)) then do
  710.       /* Draw the spike */
  711.       line (GlobData.Xspace.spikecenter)-wid BaseOfSpikeCoord GlobData.Xspace.spikecenter TopOfSpikeCoord
  712.       line (GlobData.Xspace.spikecenter)+wid BaseOfSpikeCoord GlobData.Xspace.spikecenter TopOfSpikeCoord
  713.     
  714.       /* calculate circle coords */
  715.       cx = trunc(GlobData.XSize / 4)
  716.       cy = trunc(GlobData.YSize / 2)
  717.  
  718.       SetFPen spikecol    
  719.       flood GlobData.Xspace.spikecenter trunc((BaseOfSpikeCoord + TopOfSpikeCoord)/2)
  720.       circle GlobData.Xspace.spikecenter TopOfSpikeCoord cx cy FILL
  721.       SetFPen 1
  722.       circle GlobData.Xspace.spikecenter TopOfSpikeCoord cx cy 
  723.       end
  724.  
  725.     /* Draw pieces if any */
  726.     if (numpieces > 0) then do 
  727.         do i=1 to numpieces 
  728.             call DrawPiece(spikenum, piececol, i, 0) 
  729.             end
  730.         end
  731.     return 1    
  732.  
  733.  
  734. /* Draws a piece on the spikenum spike, in pen piecepen, at position piecenum (where 0 is the 
  735.    base of the spike, and 5 is the top */
  736. DrawPiece: procedure expose GlobData.
  737.     parse arg spikenum, piecepen, piecenum, BXor
  738.  
  739.     dx = 0
  740.     dy = 0
  741.     
  742.     /* Additional rows if piecenum is > 5 */
  743.     do while (piecenum > 5)
  744.         piecenum = piecenum - 5
  745.         dx = dx - GlobData.XSize/10
  746.         dy = dy - GlobData.YSize/10
  747.         end
  748.  
  749.     dx = trunc(dx)
  750.     dy = trunc(dy)
  751.     
  752.     cx = getSpikeCenter(spikenum)
  753.     cy = getSpikeBase(spikenum) + (getSpikeDir(spikenum) * piecenum)
  754.  
  755.     rx = trunc(GlobData.XSize / 3)
  756.     ry = trunc(GlobData.YSize / 2)
  757.  
  758.  
  759.     if (BXor == 0) then do
  760.         SetFPen piecepen
  761.         circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry FILL
  762.         SetFPen 1
  763.         circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry 
  764.         end
  765.     else circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry FILL XOR
  766.     
  767.     return 1
  768.  
  769.  
  770. /* Draws a cup indicating that a player may roll.  The cup is drawn with the color
  771.    indicated by (cupcolor) */
  772. DrawCup: procedure expose GlobData.
  773.     parse arg BXor 
  774.     
  775.     negone = -1
  776.  
  777.     left = trunc((GlobData.XSpace.7 + GlobData.XSpace.8)/2)
  778.     right = trunc((GlobData.XSpace.8 + GlobData.XSpace.9)/2)
  779.  
  780.     top = trunc((GlobData.YSpace.6 + GlobData.YSpace.7)/2)
  781.     bottom = trunc((GlobData.YSpace.9 + GlobData.YSpace.10)/2)
  782.     
  783.     if (BXor == 1) then do
  784.         square left top right bottom XOR FILL
  785.         return 1
  786.         end
  787.     
  788.     /* Clear the whole area */
  789.     SetFPen 0
  790.     square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  791.  
  792.     ctr = trunc((right+left)/2)
  793.     midht  = trunc(GlobData.YSize/2)
  794.  
  795.     SetFPen 1
  796.     circle ctr top+midht trunc((right-left)/2) midht FILL
  797.     line left top+midht left bottom-midht
  798.     line right top+midht right bottom-midht
  799.     circle ctr bottom-midht trunc((right-left)/2) midht
  800.             
  801.     /* Fill it in */
  802.     currentturn = GlobData.turn
  803.     setFPen GlobData.PieceColor.currentturn
  804.     square left+1 top+midht+midht+2 right-1 bottom-midht FILL    
  805.     flood ctr bottom-midht+1
  806.     flood ctr top+midht+midht+1 
  807.     return 1
  808.  
  809.  
  810.  
  811. /* Draws dice number (dieindex) to show the number indicated in GlobData.die.(dieindex) */
  812. DrawDie: procedure expose GlobData.
  813.     parse arg dieindex
  814.  
  815.     left = 7
  816.     top = 7
  817.     if (dieindex >= 2) then top = top + 1
  818.     if (dieindex == 1) then left = left + 1
  819.     if (dieindex == 3) then left = left + 1
  820.  
  821.     /* convert to "real" coordinates */
  822.     left = GlobData.Xspace.left
  823.     top  = GlobData.Yspace.top
  824.  
  825.     /* shrink it 25% */
  826.     width = trunc(GlobData.XSize * 0.75)
  827.     height = trunc(GlobData.YSize * 0.75)
  828.  
  829.     /* and recenter it by moving it down&right 12.5% */
  830.     left = left + trunc(GlobData.XSize * 0.125)
  831.     top  = top  + trunc(GlobData.YSize * 0.125)
  832.  
  833.     /* now calculate the right and bottom edges */
  834.     right = left + width
  835.     bottom = top + height
  836.  
  837.     /* number to show on the die */
  838.     num = GlobData.die.dieindex
  839.  
  840.     /* if it's unset, just erase the area */
  841.     if (num < 1) then do
  842.         /* Calculate background color = player's piece color */
  843.         cturn = GlobData.turn
  844.         SetFPen GlobData.PieceColor.cturn
  845.         square left top right bottom FILL
  846.         return 1
  847.         end
  848.  
  849.     /* start with blank die */
  850.     SetFColor 15 15 15
  851.     square left top right bottom FILL
  852.     SetFPen 1
  853.     square left top right bottom 
  854.  
  855.     dotrx = trunc(width/12)        /* radii of dot */
  856.     dotry = trunc(height/12)
  857.     dx = trunc(width / 4)        /* offset from center dot */
  858.     dy = trunc(height / 4)
  859.  
  860.     x2 = trunc((right + left)/2)
  861.     y2 = trunc((bottom + top)/2)
  862.  
  863.     x1 = x2 - dx
  864.     x3 = x2 + dx
  865.     y1 = y2 - dy
  866.     y3 = y2 + dy
  867.  
  868.     if (num == 1) then do
  869.         circle x2 y2 dotrx dotry FILL
  870.         end        
  871.     else if (num == 2) then do
  872.         circle x1 y1 dotrx dotry FILL
  873.         circle x3 y3 dotrx dotry FILL
  874.         end        
  875.     else if (num == 3) then do
  876.         circle x1 y1 dotrx dotry FILL
  877.         circle x2 y2 dotrx dotry FILL
  878.         circle x3 y3 dotrx dotry FILL
  879.         end        
  880.     else if (num == 4) then do
  881.         circle x1 y1 dotrx dotry FILL
  882.         circle x1 y3 dotrx dotry FILL
  883.         circle x3 y1 dotrx dotry FILL
  884.         circle x3 y3 dotrx dotry FILL
  885.         end        
  886.     else if (num == 5) then do
  887.         circle x1 y1 dotrx dotry FILL
  888.         circle x1 y3 dotrx dotry FILL
  889.         circle x3 y1 dotrx dotry FILL
  890.         circle x3 y3 dotrx dotry FILL
  891.         circle x2 y2 dotrx dotry FILL
  892.         end        
  893.     else if (num == 6) then do
  894.         circle x1 y1 dotrx dotry FILL
  895.         circle x1 y2 dotrx dotry FILL
  896.         circle x1 y3 dotrx dotry FILL
  897.         circle x3 y1 dotrx dotry FILL
  898.         circle x3 y2 dotrx dotry FILL
  899.         circle x3 y3 dotrx dotry FILL
  900.         end        
  901.     return 1
  902.  
  903.  
  904.  
  905. /* Returns the horizontal coordinate number of a spike, given the spike index number */
  906. getSpikeCenter: procedure 
  907.     parse arg spikenum
  908.  
  909.     if (spikenum == -1) then return 8
  910.     if (spikenum == 24) then return 8
  911.  
  912.     if (spikenum <= 5) then do
  913.         /* in the upper right quadrant */
  914.         return 15-spikenum
  915.     end
  916.     else if (spikenum <= 11) then do
  917.         /* in the upper left quadrant */
  918.         return (6-spikenum)+6
  919.     end
  920.     else if (spikenum <= 17) then do
  921.         /* in the lower left quadrant */
  922.         return (spikenum-17)+6
  923.     end
  924.     else do
  925.         /* in the lower right quadrant */
  926.         return (spikenum-18)+10
  927.     end
  928.  
  929. /* returns the base coordinate of a spike */
  930. getSpikeBase: procedure
  931.     parse arg spikenum
  932.  
  933.     if (spikenum > 11) then
  934.         return 16
  935.     else
  936.         return 0
  937.  
  938. /* returns the top coordinate of a spike */
  939. getSpikeTop: procedure
  940.     parse arg spikenum
  941.  
  942.     if (spikenum > 11) then
  943.         return 10
  944.     else
  945.         return 6
  946.  
  947. /* returns 1 if the spike hangs down, else -1 if it pokes up */
  948. getSpikeDir: procedure
  949.     parse arg spikenum
  950.  
  951.     if (spikenum > 11) then do
  952.         return -1
  953.         end
  954.     else do
  955.         return 1
  956.         end
  957.  
  958.  
  959. SetStatus: procedure
  960.     parse arg newstatus
  961.     
  962.     if (newstatus == "_LAST") then do
  963.         SetWindowTitle '"' || getclip("PrevString") || '"'
  964.         end
  965.     else do
  966.         call setclip("PrevString",newstatus)
  967.         SetWindowTitle '"' || newstatus || '"'
  968.     end
  969.     return 1
  970.     
  971.  
  972. /* draws an x over the dice for now */    
  973. ShowCantMove: procedure expose GlobData.
  974.     SetFPen 1
  975.     line GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1
  976.     line  GlobData.XSpace.9-1 GlobData.YSpace.6+1 GlobData.XSpace.7+1 GlobData.YSpace.10-1 
  977.     call SetStatus("Sorry, you can't move!  Click on the dice to continue")
  978.     return 1
  979.  
  980.  
  981.     
  982. /* returns 1 if a move is available, else returns 0 */
  983. CanMove: procedure expose GlobData.
  984.     do i = -1 to 24
  985.         if (PieceCanMove(i) == 1) then return 1
  986.         end                  
  987.     return 0
  988.     
  989.  
  990. /* returns 1 if the piece on slot i can move, else 0 */
  991. PieceCanMove: procedure expose GlobData.
  992.     parse arg from
  993.     
  994.     if ((GlobData.slots.from * GlobData.turn) <= 0) then return 0    
  995.     
  996.     do d = 0 to 3
  997.         if (GlobData.die.d > 0) then do
  998.             if (MoveOkay(from,from+(GlobData.die.d * GlobData.turn),1) ~= -1) then return 1
  999.             end  
  1000.         end          
  1001.     return 0
  1002.     
  1003.     
  1004. /* returns 1 if a player can move a piece "out" to the given exit, else 0 */
  1005. CanMoveOut: procedure expose GlobData.
  1006.     parse arg playernum
  1007.  
  1008.     sum = GlobData.exited.playernum
  1009.     
  1010.     if (playernum == -1) then 
  1011.         do i = 0 to 5
  1012.             if (GlobData.slots.i < 0) then sum = sum - GlobData.slots.i
  1013.             end
  1014.     else if (playernum == 1) then
  1015.         do i = 18 to 23
  1016.             if (GlobData.slots.i > 0) then sum = sum + GlobData.slots.i
  1017.             end
  1018.             
  1019.     if (sum == 15) then return 1
  1020.     return 0
  1021.  
  1022.             
  1023. /* Draws arrows for possible moves for this piece */        
  1024. ShowMoves: procedure expose GlobData.
  1025.     parse arg from
  1026.     
  1027.     do d = 0 to 3
  1028.         if (GlobData.die.d > 0) then do
  1029.             if (MoveOkay(from,from+(GlobData.die.d * GlobData.Turn),1) ~= -1) then call DrawArrow(from+(GlobData.die.d * GlobData.Turn))
  1030.             end  
  1031.         end          
  1032.     return 1
  1033.  
  1034. /* Draw an arrow pointing to the specified spike */
  1035. DrawArrow: procedure expose GlobData.
  1036.     parse arg spikenum
  1037.     
  1038.     spikenum = ChopRange(spikenum,-1,24)
  1039.     
  1040.     hcoord = getSpikeCenter(spikenum)
  1041.     vcoord = getSpikeTop(spikenum) + getSpikeDir(spikenum)
  1042.  
  1043.     /* Special cases! */
  1044.     if (spikenum = -1) then    vcoord = 5
  1045.     if (spikenum = 24) then vcoord = 11
  1046.         
  1047.     SetFPen 1    
  1048.     circle GlobData.XSpace.hcoord GlobData.YSpace.vcoord trunc(GlobData.XSize/10) trunc(GlobData.YSize/10) FILL
  1049.     return 1
  1050.     
  1051.  
  1052. /* Clears all arrows from the board */    
  1053. ClearArrows: procedure expose GlobData.
  1054.     
  1055.     SetFPen 0
  1056.     
  1057.     /* right portion! */
  1058.     topofrect     = MidWayBetween(6,7,Y) + 1
  1059.     bottomofrect  = MidWayBetween(9,10,Y) - 1
  1060.     leftofrect    = MidWayBetween(9,10,X) + 1
  1061.     rightofrect   = MidWayBetween(15,16,X) - 1
  1062.     square leftofrect topofrect rightofrect bottomofrect FILL
  1063.     
  1064.     /* left portion! */
  1065.     leftofrect    = MidWayBetween(0,1,X)
  1066.     rightofrect  = MidWayBetween(6,7,X)
  1067.     square leftofrect topofrect rightofrect bottomofrect FILLĀ 
  1068.     
  1069.     /* special cases! */
  1070.     circle GlobData.XSpace.8 GlobData.YSpace.5  trunc(GlobData.XSize/10) trunc(GlobData.YSize/10) FILL
  1071.     circle GlobData.XSpace.8 GlobData.YSpace.11 trunc(GlobData.XSize/10) trunc(GlobData.YSize/10) FILL 
  1072.     return 1    
  1073.     
  1074.  
  1075. /* Returns the point midway between two coords */
  1076. MidWayBetween: procedure expose GlobData.
  1077.     parse arg left, right, XorY
  1078.     if (XorY = X) then 
  1079.         return trunc((GlobData.XSpace.left + GlobData.XSpace.right)/2)
  1080.     else    
  1081.         return trunc((GlobData.YSpace.left + GlobData.YSpace.right)/2)
  1082.  
  1083. ChopRange: procedure
  1084.     parse arg myval, lo, hi
  1085.     if (myval < lo) then return lo
  1086.     if (myval > hi) then return hi
  1087.     return myval
  1088.     
  1089.     
  1090. /* --------------------------------------------------------------- */
  1091. /* procedure CheckForWin                                           */
  1092. /* --------------------------------------------------------------- */
  1093. CheckForWin: procedure expose GlobData.
  1094.     winner = nobody
  1095.     negone = -1
  1096.     
  1097.     if (GlobData.exited.1 == 15) then winner = "Player 1"
  1098.     else if (GlobData.exited.negone == 15) then winner = "Player 2"
  1099.     
  1100.     /* nobody one yet */
  1101.     if (winner == nobody) then return 1
  1102.     
  1103.     EasyRequest "Winner!" '"'||winner||" has won the game!"||'"' "Okay"
  1104.     call SetStatus("Game Over.  Rerun the script to play again.")
  1105.     lock off
  1106.     exit
  1107.     return 0
  1108.     
  1109.         
  1110. /* Transmit our move to our opponent */
  1111. TransmitMove: procedure 
  1112.     parse arg from, to
  1113.     
  1114.     sstring = '"' || from || " " || to || '"'
  1115.     sendmessage sstring
  1116.     return 1
  1117.     
  1118. /* This function determines whether or not a piece may exit using
  1119.    a given die number (that is larger than needed to get to the exit).  
  1120.    It will return 1 iff there is no way to use that same die number
  1121.    to move any other piece anywhere else. */
  1122. CanExitOnExtra: procedure expose GlobData.
  1123.     parse arg dienum
  1124.     
  1125.     /* Only check valid die */
  1126.     if (dienum <= 0) then return 0
  1127.         
  1128.     do i = 0 to 23
  1129.         if (((GlobData.slots.i)*(GlobData.turn)) > 0) then do
  1130.             if (MoveOkay(i, i+(dienum*GlobData.turn), 0) ~= -1) then return 0
  1131.             end
  1132.         end
  1133.     return 1    /* no moves in --> can move out */