XYamb.f90 Source

Build as a Windows application. Link with XFT.lib.


!=======================================================================
!                 ___________
!             \\//-----------
!              )(   //== //
!             //\\ //   // 
!=======================================================================
!      EXTENDED FORTRAN TYPES LIBRARY SAMPLES
!=======================================================================
! _Yamb.f90 - Main application module for XYamb sample. 
! 
! XYamb is a simple game written in XFT. The Yamb game is slightly
! modified "Yacht" or "Yachtzee". The sample demonstrates:
! * usage of SDI architecture with menu, view window, toolbar and status bar
! * drawing on application windows using XFTGDI routines
! * usage of registry routines to store last user name and high score list
!
! 2005. Jugoslav Dujic (jdujic@uns.ns.ac.yu)
! 
! You are free to use the code for both commercial and non-commercial
! use. The code is provided as-is without warranties. You are free to
! distribute and modify this source code provided that the list of 
! original author(s) remains untouched and your revisions are indicated
! as such. Contributions are welcome.
!=======================================================================
!XChild_OnPaint
!XFrame_OnGetMMInfo
!XChild_OnLButtonUp
!OnNewRoll
!OnRoll
!OnTopTen
!OnAnnounce
!OnUndo
!OnNewGame
!OnExit
!DrawTable
!CalcResult
!GameOver
!ResetState
!XInit
!=======================================================
MODULE XYamb

USE XFT
USE DFWIN

IMPLICIT NONE

TYPE(X_WINDOW)::              xChild                   !View window
TYPE(X_MENU)::                xMenu                    !Main menu
TYPE(X_TOOLBAR)::             xTb                      !Toolbar

INTEGER, PARAMETER::          COMPLETE = 0,          &   !Field is completed
                              LOCKED = 1,            &   !Field is free, but disabled
                              FREE = 2                   !Field is free, but enabled
INTEGER, PARAMETER::          ACT_WRITE = 1,         &   !Last action is "enter the result"
                              ACT_ANNOUNCE = 2,      &   !Last action is "announce"
                              ACT_ANNOUNCE_SEL = 3       !Last action is "select announced field"
INTEGER, PARAMETER::          nColumns = 4
INTEGER::                     iResult(15, nColumns), &   !Result of each field (including the intermediates)
                              iState(15, nColumns),  &   !State of each field in the table
                              nLastAction = 0,       &   !Last action to be undid
                              nLastCol = 0,          &   !Last filled column
                              nLastRow = 0,          &   !Last filled row
                              iDice(6),              &   !Numbers on each dice
                              nRolls = 1,            &   !Current roll
                              iTotal                     !Total game result
CHARACTER(100)                sPlayerName                !Name of actual player
CHARACTER(*), PARAMETER::     APP_REGKEY = "Software\XFT\Yamb\2.0"
LOGICAL::                     bKeep(6) = .FALSE.,    &   !Selected dice
                              bAnnounce = .FALSE.        !TRUE if "Announce" is selected but the field is not chosen yet
CHARACTER(20)::               sTopTenName(10)            !Names of top 10 players
INTEGER::                     iTopTenResult(10)          !Results of top 10 players
INTEGER::                     IDC_STATUS
!=======================================================
CONTAINS
!=======================================================
!PURPOSE: Initialization function called by XFT library on app start.
INTEGER FUNCTION XInit(szCmdLine, nCmdShow)
!DEC$ATTRIBUTES DEFAULT, DECORATE, ALIAS: 'XINIT':: XInit

USE XFT
USE XFTMENU
USE XFTCTRL
USE DFWIN
!USE COMCTL32

IMPLICIT NONE

CHARACTER*(*)        szCmdLine
INTEGER, INTENT(IN):: nCmdShow

TYPE(X_WINDOW)::     xWnd
INTEGER::            iSt, iParts(2) = (/80, -1/), iX1, iY1, iX2, iY2, iWidth, iHeight, &
                     hKey, iSize, iType, tbHeight, sbHeight
LOGICAL::            bSt
TYPE(T_RECT)::       Rect
TYPE(X_WINDOW)::     Dlg
TYPE(X_TBBUTTON)::   xButtons(5)
TYPE(X_BITMAP)::     xBmp

INCLUDE 'Resource.fd'

!Loading menu resource & attaching it to the frame
xMenu = XLoadMenu(IDR_MENU_YAMB)

!Creation of App & frame window
iSt = XCreateSDIApp(xMenu, "XYamb", IDI_ICON_YAMB)
iSt = XSetWindowMenu(XW_FRAME, xMenu)


!Moving frame to screen center
iSt = SystemParametersInfo(SPI_GETWORKAREA, 0, LOC(Rect), 0)
iX1 = Rect%Right/2
iY1 = Rect%Bottom/2
iSt = XSetWindowPos(XW_FRAME, iX1-200, iY1-300, 400, 600)
iSt = XGetClientRect(XW_FRAME, iWidth, iHeight)

!Creation of toolbar
iSt = XCreateToolbar(XW_FRAME, xTb, IDR_TOOLBAR2, WS_BORDER.OR.TBSTYLE_FLAT.OR.TBSTYLE_TOOLTIPS, 16, 16, 16, 15)
bSt = XCreateBitmap(xBmp, IDR_TOOLBAR2, LR_LOADMAP3DCOLORS)
iSt = XToolbarSet(xTb, IDR_TOOLBAR2, xBmp%hBmp, CTL_BITMAP)

xButtons(1) = X_TBBUTTON(ID_ACTION_ROLL, TBSTATE_ENABLED, TBSTYLE_BUTTON, 1, 0)
xButtons(2) = X_TBBUTTON(ID_ACTION_NEWROLL, TBSTATE_ENABLED, TBSTYLE_BUTTON, 2, 0)
xButtons(3) = X_TBBUTTON(0, 0, TBSTYLE_SEP, 0, 0)
xButtons(4) = X_TBBUTTON(ID_ACTION_ANNOUNCE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 3, 0)
xButtons(5) = X_TBBUTTON(ID_ACTION_UNDO, TBSTATE_ENABLED, TBSTYLE_BUTTON, 4, 0)
iSt = XToolbarAddButtons(xTb, xButtons(1:5), 0)


IDC_STATUS = XCtlCreate(XW_FRAME, 2, XCTL_STATUS, 0)
iSt = XCtlSet(XW_FRAME, IDC_STATUS, iParts, CTL_RECTS)
iSt = XCtlSet(XW_FRAME, IDC_STATUS, .TRUE., CTL_VISIBLE)

iSt = XCtlGetPos(XW_FRAME, IDC_STATUS, iHeight=sbHeight)
iSt = XToolbarGetPos(xTb, iHeight=tbHeight)
iSt = XCreateWindow(xChild, XW_FRAME, WS_CHILD, ""C, XS_EX_DBLBUFFERED, 0, tbHeight, iWidth, iHeight-tbHeight-sbHeight)
iSt = XShowWindow(xChild, SW_SHOW)

bSt = XSetHandler(xChild, WM_PAINT, XChild_OnPaint)
bSt = XSetHandler(xChild, WM_LBUTTONUP, XChild_OnLButtonUp)
bSt = XSetHandler(XW_FRAME, WM_GETMINMAXINFO, XFrame_OnGetMMInfo)

bSt = XSetCommand(XW_FRAME, ID_GAME_NEW, OnNewGame)
bSt = XSetCommand(XW_FRAME, ID_GAME_TOPTEN, OnTopTen)
bSt = XSetCommand(XW_FRAME, ID_GAME_EXIT, OnExit)

bSt = XSetCommand(XW_FRAME, ID_ACTION_ROLL, OnRoll)
bSt = XSetCommand(XW_FRAME, ID_ACTION_NEWROLL, OnNewRoll)
bSt = XSetCommand(XW_FRAME, ID_ACTION_ANNOUNCE, OnAnnounce)
bSt = XSetCommand(XW_FRAME, ID_ACTION_UNDO, OnUndo)

hKey = XRegOpen(HKEY_LOCAL_MACHINE, APP_REGKEY)
iSt = XRegRead(hKey, "Player", sPlayerName)
iSt = XRegRead(hKey, "TopTenResult", LOC(iTopTenResult), SIZEOF(iTopTenResult))
iSt = XRegRead(hKey, "TopTenName", LOC(sTopTenName), SIZEOF(sTopTenName))

iSt = XLoadDialog(IDD_NAME, Dlg, XW_FRAME)
iSt = XCtlSet(Dlg, IDC_EDIT_NAME, sPlayerName, CTL_STATE)
iSt = XModalDialog(Dlg)
iSt = XCtlGet(Dlg, IDC_EDIT_NAME, sPlayerName, CTL_STATE)
CALL XDestroyDialog(Dlg)

iSt = XCtlSet(XW_FRAME, IDC_STATUS, sPlayerName, 1)
iSt = XRegWrite(hKey, "Player", sPlayerName)
CALL XRegClose(hKey)

iDice = 0
XInit = 1

CALL OnNewGame(XW_FRAME, 0, 0)

END FUNCTION XInit
!=======================================================
!PURPOSE: handler for WM_GETMINMAXINFO message. Prevents the
!frame from resizing by setting nMinSize and nMaxSize to (400, 600)
INTEGER FUNCTION XFrame_OnGetMMInfo(xWnd, xSizeMax, xMaxPos, xMinSize, xMaxSize)

TYPE(X_WINDOW), INTENT(IN)::      xWnd
TYPE(X_POINT), INTENT(INOUT)::    xSizeMax
TYPE(X_POINT), INTENT(INOUT)::    xMaxPos
TYPE(X_POINT), INTENT(INOUT)::    xMinSize    !Minimal allowed size
TYPE(X_POINT), INTENT(INOUT)::    xMaxSize    !Maximal allowed size

xMinSize = X_POINT(400, 600)
xMaxSize = X_POINT(400, 600)
XFrame_OnGetMMInfo = 0

END FUNCTION XFrame_OnGetMMInfo
!=======================================================
!PURPOSE: handler for WM_PAINT message. Draws the dice and
!the table.
INTEGER FUNCTION XChild_OnPaint(xWnd, xDC, iX1, iY1, iX2, iY2)

TYPE(X_WINDOW)::     xWnd              !Window ( =  xChild)
TYPE(X_DC)::         xDC               !Window's DC
INTEGER, INTENT(IN):: iX1, iY1, iX2, iY2   !Dimensions of update rectangle

INTEGER::            iSt, i, nColor, iX, iY, n

INTEGER, PARAMETER:: iSpot(9,6) = (/0, 0, 0, 0, 1, 0, 0, 0, 0,     &    !Spots on dice
                                    1, 0, 0, 0, 0, 0, 0, 0, 1,     &
                                    0, 0, 1, 0, 1, 0, 1, 0, 0,     &
                                    1, 0, 1, 0, 0, 0, 1, 0, 1,     &
                                    1, 0, 1, 0, 1, 0, 1, 0, 1,     &
                                    1, 0, 1, 1, 0, 1, 1, 0, 1/)

!ClearScreen
CALL XSetBrush(xDC, XCOLOR_GREEN)
CALL XSetPen(xDC, XCOLOR_GREEN)
iSt = XRectangle(xDC, 0, 0, xDC%xView%iXExt, xDC%xView%iYExt)

XChild_OnPaint = 0
!Don't draw if the game is not started yet
IF (iDice(1).EQ.0) RETURN
!Drawing dice
DO i = 1, 6
      IF (bKeep(i)) THEN
            nColor = XCOLOR_LTCYAN
      ELSE
            nColor = XCOLOR_YELLOW
      END IF

      iSt = XRectangle(xDC, 20, 80*i-60, 80, 80*i,             &
                     X_PEN(PS_SOLID, 0, nColor),           &
                     X_BRUSH(BS_SOLID, nColor, 0))

      CALL XSetBrush(xDC, XCOLOR_GREEN)
      CALL XSetPen(xDC, XCOLOR_GREEN)
      iSt = XRectangle(xDC, 20, 80*I-60, 30, 80*I-50)
      iSt = XRectangle(xDC, 70, 80*I-60, 80, 80*I-50)
      iSt = XRectangle(xDC, 20, 80*I-10, 30, 80*I)
      iSt = XRectangle(xDC, 70, 80*I-10, 80, 80*I)

      CALL XSetBrush(xDC, nColor)
      CALL XSetPen(xDC, nColor)
 
      iSt = XEllipse(xDC, 20, 80*I-60, 40, 80*I-40)
      iSt = XEllipse(xDC, 60, 80*I-60, 80, 80*I-40)
      iSt = XEllipse(xDC, 20, 80*I-20, 40, 80*I)
      iSt = XEllipse(xDC, 60, 80*I-20, 80, 80*I)

      CALL XSetBrush(xDC, XCOLOR_BLACK)
      !Draw spots
      DO ix = 1, 3
            DO iy = 1, 3
                  n = ix+3*(iy-1)
                  IF(iSpot(n, iDice(i)).eq.1) THEN
                        iSt = XEllipse(xDC, 16+15*ix, 80*i-64+15*iy, 24+15*ix, 80*i-60+15*iy+4)
                  END IF
            END DO
      END DO
END DO

CALL DrawTable(xDC)

END FUNCTION XChild_OnPaint
!=======================================================
!PURPOSE: handler for WM_LBUTTONUP message
INTEGER FUNCTION XChild_OnLButtonUp(xWnd, iX, iY, Msg, iKeyState)

TYPE(X_WINDOW)::  xWnd              !Window ( = xChild)
INTEGER::         iX, iY, Msg, iKeyState   !Click coordinates and state of keys

INTEGER::         iSt, i, j, k, m, iX1, iY1, iX2, iY2, iXStep, iYStep

INCLUDE "Resource.fd"

!If one of dice is hit, toggle its keep state
DO i = 1, 6
      IF(iY.GE.80*I-60 .AND. iY.LE.80*I .AND. iX.GE.20 .AND. (iX.LE.80))   &
         bKeep(i) = .NOT.bKeep(i)
END DO

iXStep = 50
iYStep = 30
XChild_OnLButtonUp = 0
!Checking the table for click
DO j = 1, nColumns
      DO k = 1, 14
            ix1 = 100+j*(iXStep)
            ix2 = ix1+45
            iy1 = k*(iYStep)
            iy2 = iy1+25
            IF(iY.GE.iy1 .AND. iY.LE.iy2 .AND. iX.GE.ix1 .AND.iX.LE.ix2) THEN
                  IF(iState(k, j).eq.FREE) THEN
                        !The clicked field (k, j) is found
                        IF (bAnnounce) THEN
                              bAnnounce = .FALSE.
                              !Enable roll & disable re-announce
                              iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .TRUE., XC_ENABLE)
                              iSt = XMenuSet(xMenu, ID_ACTION_ANNOUNCE, .FALSE., XC_ENABLE)
                              iSt = XToolbarSet(xTb, ID_ACTION_ROLL, .TRUE., XC_ENABLE)
                              iSt = XToolbarSet(xTb, ID_ACTION_ANNOUNCE, .FALSE., XC_ENABLE)
                              !Save Undo information
                              nLastCol = j
                              nLastRow = k
                              nLastAction = ACT_ANNOUNCE_SEL
                              iSt = XMenuSet(xMenu, ID_ACTION_UNDO, .TRUE., XC_ENABLE)
                              iSt = XToolbarSet(xTb, ID_ACTION_UNDO, .TRUE., XC_ENABLE)
                              !Lock all other fields for writing
                              DO m = 1, 14
                                    IF (iState(m, 4).NE.COMPLETE .AND. m.NE.k) iState(m, 4) = LOCKED
                              END DO
                              iSt = XCtlSet(XW_FRAME, IDC_STATUS, "Roll the dice.", 2)
                        ELSE
                              !Save Undo information
                              nLastCol = j
                              nLastRow = k
                              nLastAction = ACT_WRITE
                              !Enable undo except if last roll with announcement
                              IF (.NOT.(j.EQ.4 .AND. nRolls.EQ.3)) THEN
                                    iSt = XMenuSet(xMenu, ID_ACTION_UNDO, .TRUE., XC_ENABLE)
                                    iSt = XToolbarSet(xTb, ID_ACTION_UNDO, .TRUE., XC_ENABLE)
                              END IF
                              CALL CalcResult(k, j)
                              !Lock entire table - no entry can be made until undo or new roll
                              WHERE (iState.EQ.FREE)
                                    iState = LOCKED
                              END WHERE
                        END IF
                        CALL XUpdateWindow(xWnd)
                        RETURN
                  ELSE
                        RETURN
                  END IF
            END IF
      END DO
END DO
CALL XUpdateWindow(xWnd)

END FUNCTION XChild_OnLButtonUp

!=======================================================
!PURPOSE: Handler for Action/New Roll menu.
SUBROUTINE OnNewRoll(xWnd, ID, nCode)

TYPE(X_WINDOW)::        xWnd
INTEGER, INTENT(IN)::    ID, nCode

INTEGER::               iSt

INCLUDE "Resource.fd"

IF (nCode.EQ.0) THEN
   iSt = XMenuSet(xMenu, ID_ACTION_NEWROLL, .FALSE., XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_NEWROLL, .FALSE., XC_ENABLE)

   !Reset keep status and number of rolls
   bKeep = .FALSE.
   nRolls = 0
   !Reset the table to normal state
   CALL ResetState
   CALL OnRoll(xWnd, ID, nCode)
END IF

END SUBROUTINE OnNewRoll

!=======================================================
!PURPOSE: Handler for Action/Roll menu.
SUBROUTINE OnRoll(xWnd, ID, nCode)

USE MSFLIB

TYPE(X_WINDOW)::        xWnd
INTEGER, INTENT(IN)::    ID, nCode

INTEGER::               iSt, i
REAL::                  fRnd
CHARACTER(30)::         sMessage

INCLUDE "Resource.fd"

IF (nCode.EQ.0) THEN
   nRolls = nRolls+1

   IF (nRolls.EQ.3) THEN
         sMessage = "Enter the result."
   ELSE
         WRITE(sMessage, "('Roll ', i1, a1)") nRolls
   END IF
   !iSt = XSendMessage(xStatus, SB_SETTEXT, 1, LOC(sMessage))
   iSt = XCtlSet(XW_FRAME, IDC_STATUS, sMessage, 2)

   !Enable announce if 1st roll; disable roll if 3rd roll
   iSt = XMenuSet(xMenu, ID_ACTION_ANNOUNCE, nRolls.EQ.1, XC_ENABLE)
   iSt = XMenuSet(xMenu, ID_ACTION_ROLL, nRolls.LT.3, XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_ANNOUNCE, nRolls.EQ.1, XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_ROLL, nRolls.LT.3, XC_ENABLE)
   IF (nRolls.EQ.1) THEN
         !If only "announced" column is left, disable "Roll" item
         IF (ALL(iState(1:14, 1:3).EQ.COMPLETE)) iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
   END IF
   iSt = XMenuSet(xMenu, ID_ACTION_UNDO, .FALSE., XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_UNDO, .FALSE., XC_ENABLE)

   !Assign random numbers to dice
   CALL SEED(RND$TIMESEED)
   DO i = 1, 6
         IF (.NOT.bKeep(i)) THEN
               CALL RANDOM(fRnd)
               iDice(i) = INT(6*fRnd)+1
         END IF
   END DO

   CALL XUpdateWindow(xChild)
END IF

END SUBROUTINE OnRoll

!=======================================================
!PURPOSE: Callback for Game/Top ten menu item. Displays the "Top ten"
!Dialog
SUBROUTINE OnTopTen(xWnd, ID, nCode)

USE XSTRINGS
USE XFTCTRL
!USE XFLOGM2

TYPE(X_WINDOW)::        xWnd
INTEGER, INTENT(IN)::    ID, nCode

TYPE(X_WINDOW)::        Dlg
!TYPE(DIALOG)::          Dlg
CHARACTER*5::           sTemp
INTEGER::               iSt, i

INCLUDE "Resource.fd"

iSt =  XLoadDialog(IDD_TOPTEN, Dlg, XW_FRAME)
DO i = 1, 10
      iSt = XCtlSet(Dlg, IDC_IME1+i-1, sTopTenName(i), CTL_TITLE)
      WRITE(sTemp, '(i4)') iTopTenResult(i)
      iSt = XCtlSet(Dlg, IDC_REZ1+i-1, sTemp, CTL_TITLE)
      IF (iTopTenResult(i).EQ.iTotal .AND. STRCMP(sTopTenName(i), sPlayerName)) THEN
            iSt = XCtlSet(Dlg, IDC_IME1+i-1, #FF, CTL_COLOR)
      END IF
END DO
iSt = XModalDialog(Dlg)
CALL XDestroyDialog(Dlg)

!xDlg = XLoadDialog(IDD_TOPTEN)
!DO i = 1, 10
!      iSt = XCtlSet(xDlg, IDC_IME1+i-1, sTopTenName(i), CTL_TITLE)
!      WRITE(sTemp, '(i4)') iTopTenResult(i)
!      iSt = XCtlSet(xDlg, IDC_REZ1+i-1, sTemp, CTL_TITLE)
!      IF (iTopTenResult(i).EQ.iTotal .AND. sTopTenName(i).EQ.sPlayerName) THEN
!!            iSt = XCtlSet(xDlg, IDC_IME1+i-1, #FF, CTL_COLOR)
!      END IF
!END DO
!iSt = XModalDialog(xDlg, xChild%hWnd)
!CALL xDlgUnInit(xDlg)

END SUBROUTINE OnTopTen
!=======================================================
!PURPOSE: Callback for Announce/Top Ten menu item.
SUBROUTINE OnAnnounce(xWnd, ID, nCode)

TYPE(X_WINDOW)::        xWnd
INTEGER, INTENT(IN)::    ID, nCode

INTEGER::   iSt, j, k

INCLUDE "Resource.fd"

IF (nCode.EQ.0) THEN
   !Lock the entire table except "announce" column
   DO j = 1, 14
         IF (iState(j, 4).eq.LOCKED) iState(j, 4) = FREE
         DO k = 1, 3
               IF(iState(j, k).eq.FREE) iState(j, k) = LOCKED
         END DO
   END DO
   !Save undo information
   nLastAction = ACT_ANNOUNCE
   bAnnounce = .TRUE.
   !Disable "Announce" & "Roll" (until a field is selected in OnLButtonUp).
   !Enable "Undo".
   iSt = XMenuSet(xMenu, ID_ACTION_ANNOUNCE, .FALSE., XC_ENABLE)
   iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
   iSt = XMenuSet(xMenu, ID_ACTION_UNDO, .TRUE., XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_ANNOUNCE, .FALSE., XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_UNDO, .TRUE., XC_ENABLE)

   iSt = XCtlSet(XW_FRAME, IDC_STATUS, "Select the field to announce.", 2)

   CALL XUpdateWindow(xChild)
END IF

END SUBROUTINE OnAnnounce

!=======================================================
!PURPOSE: Handler for Action/Undo menu item. Restores the previous 
!state (applicable in case of misclicking a field or changing mind
!when making an announcement)
SUBROUTINE OnUndo(xWnd, ID, nCode)

TYPE(X_WINDOW)::        xWnd
INTEGER, INTENT(IN)::   ID, nCode

CHARACTER(40)::         sMessage
INTEGER::               iSt, j, k

INCLUDE "Resource.fd"

IF (nCode.EQ.0) THEN
   SELECT CASE (nLastAction)
   CASE(ACT_ANNOUNCE)
         !Last action is Action/Announce. Reset the table into
         !normal state, re-enable "Roll" and "Announce" menu items.
         bAnnounce = .FALSE.
         iSt = XCtlGet(XW_FRAME, IDC_STATUS, sMessage, 2)
         CALL ResetState()
         iSt = XCtlSet(XW_FRAME, IDC_STATUS, sMessage, 2)
         iSt = XMenuSet(xMenu, ID_ACTION_ANNOUNCE, .TRUE., XC_ENABLE)
         iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .TRUE., XC_ENABLE)
         iSt = XToolbarSet(xTb, ID_ACTION_ANNOUNCE, .TRUE., XC_ENABLE)
         iSt = XToolbarSet(xTb, ID_ACTION_ROLL, .TRUE., XC_ENABLE)
   CASE(ACT_ANNOUNCE_SEL)
         !Last action is selection of announce field. Allow only
         !to change selection, not to get back to another column.
         !Free selected field & unlock "Announced" column
         iState(nLastRow, nLastCol) = FREE
         DO j = 1, 14
               IF (iState(j, 4).EQ.LOCKED) iState(j, 4) = FREE
         END DO
         !Disable roll & go back to announcement-selection mode
         iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
         iSt = XToolbarSet(xTb, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
         bAnnounce = .TRUE.
         iSt = XCtlSet(XW_FRAME, IDC_STATUS, "Select the field to announce.", 2)
   CASE(ACT_WRITE)
         !Last action is writing down the result.
         iSt = XCtlGet(XW_FRAME, IDC_STATUS, sMessage, 2)
         IF (nLastCol.NE.4) THEN
               !If in first three columns, free the clicked field
               !and reset the table.
               iState(nLastRow, nLastCol) = FREE
               iResult(nLastRow, nLastCol) = 0
               CALL CalcResult(0, 0)
               CALL ResetState()
         ELSE IF (nLastCol.EQ.4 .AND. nRolls.LT.3) THEN
               !If in "Announced" column, free only that field, and recalculate
               !the result 
               iState(nLastRow, nLastCol) = FREE
               iResult(nLastRow, nLastCol) = 0
               CALL CalcResult(0, 0)
         END IF
         IF (nRolls.LT.3) THEN
               iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .TRUE., XC_ENABLE)
               iSt = XToolbarSet(xTb, ID_ACTION_ROLL, .TRUE., XC_ENABLE)
         END IF
         !Disable new roll until the result is written down (OnLButtonUp)
         iSt = XCtlSet(XW_FRAME, IDC_STATUS, sMessage, 2)
         iSt = XMenuSet(xMenu, ID_ACTION_NEWROLL, .FALSE., XC_ENABLE)
         iSt = XToolbarSet(xTb, ID_ACTION_NEWROLL, .FALSE., XC_ENABLE)
   END SELECT

   iSt = XMenuSet(xMenu, ID_ACTION_UNDO, .FALSE., XC_ENABLE)
   iSt = XToolbarSet(xTb, ID_ACTION_UNDO, .FALSE., XC_ENABLE)

   CALL XUpdateWindow(xChild)
END IF

END SUBROUTINE OnUndo
!=======================================================
!PURPOSE: Handler for Game/New menu item
SUBROUTINE OnNewGame(xWnd, ID, nCode)

TYPE(X_WINDOW)::        xWnd
INTEGER, INTENT(IN)::    ID, nCode

INTEGER::               iSt, nFiles

IF (nCode.EQ.0) THEN
   !Free entire table
   iState = LOCKED
   iState(1, 1) = FREE
   iState(14, 3) = FREE
   iState(:, 2) = FREE
   iState(7, :) = COMPLETE
   iState(10, :) = COMPLETE
   iState(15, :) = COMPLETE

   iResult = 0
   iResult(7, :) = 0
   iResult(10, :) = 0
   iResult(15, :) = 0
   iTotal = 0

   CALL OnNewRoll(xWnd, ID, nCode)
END IF

END SUBROUTINE OnNewGame
!=======================================================
!PURPOSE: Handler for exit menu item
SUBROUTINE OnExit(xWnd, ID, nCode)

TYPE(X_WINDOW)::  xWnd
INTEGER::   ID, nCode

INTEGER::   iSt

iSt = XSendMessage(XW_FRAME, WM_CLOSE, 0, 0)

END SUBROUTINE OnExit

!=======================================================
!PURPOSE: Draws the table on xDC. Called from OnPaint callback.
SUBROUTINE DrawTable(xDC)

TYPE(X_DC)::      xDC

!Column titles (arrows in "Symbol" font)
INTEGER, PARAMETER::        sColTitle(8)  = (/175, 171, 173, 65, 65, 65, 65, 65/)
CHARACTER(15), PARAMETER::  sGames = '123456S+-SSFPYS'
CHARACTER(4)::             sNum

INTEGER::         iSt, iXStep, iYStep, j, k, iLength, iX1, iY1, iX2, iY2, nColor1, nColor2, nColorText

iXStep = 50
iYStep = 30

DO k = 1, 15
      IF (k.NE.7 .AND. k.NE.10 .AND. k.NE.15) THEN
            iSt = XSetFont(xDC, "Arial"C, 12)
      ELSE
            iSt = XSetFont(xDC, "Symbol"C, 12)
      END IF
      iSt = XTextOut(xDC, 140, k*30+5, sGames(k:k), XCOLOR_LTGRAY)
END DO
      
DO j = 1, nColumns
      iSt = XSetFont(xDC, "Symbol"C, 12)
      iSt = XTextOut(xDC, 100+j*iXStep+iXStep/2-3, 5, CHAR(sColTitle(j)), XCOLOR_LTGRAY)
      iSt = XSetFont(xDC, "Arial"C, 12)
      DO k = 1, 15
            ix1 = 100+j*(iXStep)
            ix2 = ix1+45
            iy1 = k*(iYStep)
            iy2 = iy1+25
            SELECT CASE (iState(k, j))
            CASE(COMPLETE)
                  nColor1 = XCOLOR_GREEN
                  nColor2 = XCOLOR_BLACK
                  nColorText = XCOLOR_LTGRAY
            CASE(LOCKED)
                  nColor1 = XCOLOR_LTGRAY
                  nColor2 = XCOLOR_GRAY
                  nColorText = XCOLOR_GRAY
            CASE(FREE)
                  nColor1 = XCOLOR_WHITE
                  nColor2 = XCOLOR_BLACK
                  nColorText = XCOLOR_BLACK
            END SELECT
            IF (k.NE.7 .AND. k.NE.10 .AND. k.NE.15) THEN
                  CALL XSetBrush(xDC, nColor1)
                  CALL XSetPen(xDC, nColor2)
                  iSt = XRectangle(xDC, ix1, iy1, ix2, iy2)
                  iSt = XRectangle(xDC, ix1+1, iy1+1, ix2-1, iy2-1)
                  CALL XSetPen(xDC, XCOLOR_LTGRAY)
                  CALL XMoveTo(xDC, ix1, iy2)
                  iSt = XLineTo(xDC, ix2, iy2)
                  iSt = XLineTo(xDC, ix2, iy1)
                  CALL XMoveTo(xDC, ix1+1, iy2-1)
                  iSt = XLineTo(xDC, ix2-1, iy2-1)
                  iSt = XLineTo(xDC, ix2-1, iy1+1)
            END IF

            IF((iState(k, j).eq.COMPLETE)) THEN
                  WRITE(sNum, "(i4)") iResult(k, j)
                  CALL XGetTextExtent(xDC, sNum, iLength, iSt)