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)
                  iSt = XTextOut(xDC, ix2-3-iLength, iy1+4, sNum, nColorText)
            END IF
      END DO
END DO
ix1 = 100+4*iXStep
ix2 = ix1+45
iy1 = 16*iYStep
iy2 = iy1+25

CALL XSetBrush(xDC, XCOLOR_GREEN)
CALL XSetPen(xDC, XCOLOR_BLACK)
iSt = XRectangle(xDC, ix1, iy1, ix2, iy2)
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)

!Total result
WRITE(sNum, "(i4)") iTotal
CALL XGetTextExtent(xDC, sNum, iLength, iSt)
iSt = XTextOut(xDC, ix2-3-iLength, iy1+4, sNum, nColor2)

END SUBROUTINE DrawTable

!=======================================================
!PURPOSE: Calculates the score in field (k, j), partial scores and 
!total scores on the basis of state of the dice. Called from OnLButtonUp
!(and Undo, when undoing state after LButtonUp)
SUBROUTINE CalcResult(k, j)

USE MSFWIN

INTEGER, INTENT(IN)::    k, j

LOGICAL::       b2345
INTEGER::       iSt, hKey, nMin, nMax, n, nZeros, iSuma1, iSuma2, iTemp, iRez
INTEGER::       iAux(6)

INCLUDE "Resource.fd"

iRez = 0

SELECT CASE(k)
CASE(1:6)
      !First six column
      iRez = COUNT(iDice.eq.k)*k
CASE(8)
      !Maximum
      nmin = MINVAL(iDice)  !Die with min. value
      iRez = SUM(iDice)-nmin
CASE(9)
      !Min
      nMax = MAXVAL(iDice)  !Die with max. value
      iRez = SUM(iDice)-nMax
CASE(11)
      !Straight
      b2345 = .TRUE.
      !Check if there's at least one 2, 3, 4 & 5 in dice set
      DO n = 2, 5
            b2345 = b2345.AND.(ANY((iDice).eq.n))
      END DO
      !If it is, and there's at least one 1 or 6, set the score according to roll
      IF(b2345.AND.(ANY(iDice.eq.1).OR.ANY(iDice.eq.6))) THEN
            IF(nRolls.eq.1)THEN
                  iRez = 66
            ELSE IF(nRolls.eq.2) THEN
                  iRez = 56
            ELSE
                  iRez = 46
            END IF
      END IF
CASE (12:14)
      !Full, Poker, Yamb
      DO n = 1, 6
            iAux(n) = COUNT(iDice.eq.n)
      END DO
      IF(k.eq.12) THEN   ! Full
            nZeros = COUNT(iAux.eq.0)
            nMax = MAXVAL(iAux)
            !Condition: there are 3 or 4 dice with the same (nonzero) value 
            !and at least two more with another value
            IF((nZeros.GE.3 .AND. nMax.GT.2 .AND. nMax.LT.5 .AND. &
               COUNT(iAux.GE.2).gt.1)) THEN
                  iSuma1 = 0
                  iSuma2 = 0
                  !Take care that combination 111666 equals to (66611), not (66111)
                  DO n = 1, 6
                        IF(iAux(n).ge.3) THEN
                              iTemp = iSuma1
                              iSuma1 = 3*n+iSuma2
                              iSuma2 = 2*n+iTemp
                        ELSE IF(iAux(n).eq.2) THEN
                              iSuma1 = 2*n+iSuma1
                              iSuma2 = 2*n+iSuma2
                        END IF
                  END DO
                  iRez = MAX(iSuma1, iSuma2)+30
            END IF
      END IF
      IF(k.eq.13) THEN   ! Poker
            nMax = MAXVAL(iAux)
            IF(nMax.ge.4) THEN
                  !Actually, this is equal to MAXLOC
                  DO n = 1, 6
                        IF(iAux(n).gt.3) THEN
                              iRez = 4*n+40
                              EXIT
                        END IF
                  END DO
            END IF
      END IF
      IF(k.eq.14) THEN   !Yamb
            nMax = MAXVAL(iAux)
            IF(nMax.ge.5) THEN
                  DO n = 1, 6
                        IF(iAux(n).gt.4) THEN
                              iRez = 5*n+50
                              EXIT
                        END IF
                  END DO
            END IF
      END IF
END SELECT

IF (k.NE.0) THEN
   !Mark the field as complete, and enter the result.
   iResult(k, j) = iRez
   iState(k, j) = COMPLETE
   IF (iState(k, j).eq.0) THEN
         iSt = XCtlSet(XW_FRAME, IDC_STATUS, "New roll", 2)
   END IF

   !If the first column, free the next field, but skip the ones with 
   !intermediate results
   IF(j.eq.1 .AND. K.LT.14) THEN
         IF (K+1.EQ.7 .OR. K+1.EQ.10) THEN
               iState(k+2, j) = FREE
         ELSE
               iState(k+1, j) = FREE
         END IF
   END IF

   !If the third column, free the next field, but skip the ones with 
   !intermediate results
   IF(j.eq.3 .AND. K.GT.1) THEN
         IF (K-1.EQ.7 .OR. K-1.EQ.10) THEN
               iState(k-2, j) = FREE
         ELSE
               iState(k-1, j) = FREE
         END IF
   END IF
END IF

!Sum the intermediates
DO n = 1, nColumns
      !Rows 1-6
      iResult(7, n) = sum(iResult(1:6, n))
      IF (iResult(7, n).GE.60) iResult(7, n) = iResult(7, n)+30
      !Maximal column
      IF (iState(1, n).eq.COMPLETE .AND. iState(8, n).eq.COMPLETE .AND. &
         iState(9, n).eq.COMPLETE) THEN
            iResult(10, n) = (iResult(8, n)-iResult(9, n))*iResult(1, n)
      END IF
      !S+F+P+Y
      iResult(15, n) = sum(iResult(11:14, n))
END DO

iSt = XMenuSet(xMenu, ID_ACTION_NEWROLL, .TRUE., XC_ENABLE)
iSt = XMenuSet(xMenu, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
iSt = XMenuSet(xMenu, ID_ACTION_ANNOUNCE, .FALSE., XC_ENABLE)
iSt = XToolbarSet(xTb, ID_ACTION_NEWROLL, .TRUE., XC_ENABLE)
iSt = XToolbarSet(xTb, ID_ACTION_ROLL, .FALSE., XC_ENABLE)
iSt = XToolbarSet(xTb, ID_ACTION_ANNOUNCE, .FALSE., XC_ENABLE)

iTotal = SUM(iResult(7, :))+SUM(iResult(10, :))+SUM(iResult(15, :))

IF (ALL(iState(1:15, 1:nColumns).EQ.COMPLETE)) CALL GameOver

END SUBROUTINE CalcResult

!=======================================================
!PURPOSE: called when the game is over.
SUBROUTINE GameOver()

!USE XFLOGM2

INTEGER::        iSt, i, hKey, iType
TYPE(X_WINDOW):: Dlg

INCLUDE "Resource.fd"

IF(ALL(iTopTenResult.GE.iTotal)) THEN
      !"No luck" dialog
      iSt = XLoadDialog(IDD_FAIL, Dlg, XW_FRAME)
      iSt = XModalDialog(Dlg)
      CALL XDestroyDialog(Dlg)
ELSE
      !Updating top ten
      DO i = 10, 1, -1
            !Move current positions downwards
            IF (i.NE.10) THEN
                  sTopTenName(i+1) = sTopTenName(i)
                  iTopTenResult(i+1) = iTopTenResult(i)
            END IF
            !Insert the top ten & exit
            IF(i.EQ.1 .OR. iTotal.LT.iTopTenResult(i-1) ) THEN
                  sTopTenName(i) = sPlayerName
                  iTopTenResult(i) = iTotal
                  EXIT
            END IF
      END DO
      !Display the dialog & write the registry
      CALL OnTopTen(XW_FRAME, 0, 0)
      hKey = XRegOpen(HKEY_LOCAL_MACHINE, APP_REGKEY)
      iSt = XRegWrite(hKey, "TopTenResult", LOC(iTopTenResult), SIZEOF(iTopTenResult))
      iSt = XRegWrite(hKey, "TopTenName", LOC(sTopTenName), SIZEOF(sTopTenName))
      CALL XRegClose(hKey)
END IF

!Ask for continuation
iSt = XLoadDialog(IDD_CONTINUE, Dlg, XW_FRAME)
iSt = XModalDialog(Dlg)
CALL XDestroyDialog(Dlg)
IF (iSt.EQ.IDOK) THEN
      CALL OnNewGame(XW_FRAME, 0, 0)
ELSE
      CALL OnExit(XW_FRAME, 0, 0)
END IF

END SUBROUTINE GameOver
!=======================================================
!PURPOSE: unlocks all fields which are not written
SUBROUTINE ResetState

INTEGER::   k

DO k = 1, 14
      IF(iState(k, 4).ne.COMPLETE) iState(k, 4) = LOCKED  !Disable announced column
      IF(iState(k, 2).ne.COMPLETE) iState(k, 2) = FREE    !Enable free column
END DO

DO k = 1, 14
      IF(iState(k, 1).ne.COMPLETE) THEN
            iState(k, 1) = FREE   !Enable remainder of first column
            EXIT
      END IF
END DO

DO k = 14, 1, -1
      IF(iState(k, 3).ne.COMPLETE) THEN
            iState(k, 3) = FREE    !Enable remainder of third column
            EXIT
      END IF
END DO

END SUBROUTINE ResetState
!=======================================================
END MODULE XYamb