Application MineHunt

2004-06-02  -  22:39
 

MineHunt

-- MineHunt main handlers

-- To do:
--    make a routine to discover islands left, x to the right, y up.

-- Parameters
-- ==========

constant cCellSize               = 20   -- width and height of a field cell
constant cTopInfoHeight          = 20   -- minimum space for the indicator fields at the top
constant cBottomInfoHeight       = 40   -- minimum space for the buttons and fields at the bottom
constant cTitleBbarHeight        = 20   -- title bar is sometimes counted, sometimes not...
constant cGridMargin             = 20   -- minimum space between grid and surrounding objects
constant cOutlineMargin          = 10   -- minimum space between outline field and surrounding objects
constant cBottomRow              =  0   -- distance of interface objects from bottom of window
constant cDefaultNrLivesDesired  =  5   -- number of lives given at startup or after resizing the grid
constant cStateCovered=-1, cStateUncovered=-2, cStateFlagged=-3, cStateSuspected=-4, cStateWronglyFlagged=-5



-- Data Structures
-- ===============

--    Interface
--    ---------
global gGridWidth               -- width  of playing grid in number of cells
global gGridHeight              -- height of playing grid in number of cells
global gGridLeftMargin          -- distance between left side of window and centre of left column cells
global gGridTopMargin           -- distance between top of window and centre of top row cells
global gNrOfLivesDesired        -- last setting by the player
global gGameInProgress          -- true after first cell has been touched; reset when game is over
global gColours                 -- the different colours in use
global gSoundOn                 -- whether illegal moves are signalled with a beep
global gFreeLevel               -- proportion of free cells over cells holding bombs (inverse of bomb density)


--    Bookkeeping
--    -----------
--    grid-size arrays:
global gSpaces             -- an empty array used to blank out other arrays.
global gBombs              -- cells holding a bomb
global gBombCounts         -- the number of bombs surrounding each cell
global gCellStates         -- the states of the cells; one of the constants whose names start with "cState"
--    counters:
global gNrStillCovered          -- the number of cells still covered
global gNrBombsLeftToIdentify   -- number of bombs not having a flag yet
global gNrOfLivesLeft           -- number of lives left



on PreOpenstack


  lock messages; lock screen
  InitialiseGlobals
  send "OpenSize" to stack "MineHunt" in 1 second

on OpenSize


  -- for some reason the stack size does not come up right, so we set it here:
  set the width of stack "MineHunt" to 200; set the height of stack "MineHunt" to 260
  ResizeStack 200,260
  send "menupick "&quote&"Medium"&quote to button "Level"


on InitialiseGlobals


  
  -- Interface
  put 0 into gGridWidth; put 0 into gGridHeight; put 0 into gGridLeftMargin; put 0 into gGridTopMargin
  put 0 into gNrBombsLeftToIdentify
  put cDefaultNrLivesDesired into gNrOfLivesDesired
  put false into gGameInProgress
  
  put "133,166,112" into gColours["Outline"       ]
  put "120,120,120" into gColours["GridLines"     ]
  put "120,120,120" into gColours["Numbers"       ]
  put " 80, 80, 80" into gColours["Covered"       ]
  put "250,250,250" into gColours["Uncovered"     ]
  put "180,100,100" into gColours["Flagged"       ]
  put "180,180,100" into gColours["Suspected"     ]
  put "255,  0,  0" into gColours["Bomb"          ]
  put "100,255,100" into gColours["WronglyFlagged"]
  
  put true into gSoundOn; put 5 into gFreeLevel
   
  -- Bookkeeping  -  arrays
  put "" into gBombs; put "" into gBombCounts; put "" into gCellStates; put 0 into gNrStillCovered; put "" into gSpaces
  
  hide field "Message"
  





on LayoutTheGrid


  -- cells have a one-pixel border, the borders of neighbouring cells should overlap,
  -- so the distance between cell centres is cCellsize-1
  lock screen
  repeat with iY=1 to gGridHeight
    repeat with iX=1 to gGridWidth
      put  "C"&(D2(iX)&D2(iY)) into lfieldName
      create field lfieldName
      set the width           of field lfieldName to cCellSize
      set the height          of field lfieldName to cCellSize
      set the loc             of field lfieldName to (iX-1)*(cCellSize-1)+gGridLeftMargin,(gGridHeight-iY)*(cCellSize-1)+gGridTopMargin
      set the threeD          of field lfieldName to "false"
      set the traversalon     of field lfieldName to "false"
      set the locktext        of field lfieldName to "true"
      set the showfocusborder of field lfieldName to "false"
      set the borderwidth     of field lfieldName to 1
      set the foregroundcolor of field lfieldName to gColours["GridLines"]
      set the backgroundcolor of field lfieldName to gColours["Covered"  ]
      set the bordercolor     of field lfieldName to gColours["GridLines"]
      set the pCell           of field lfieldName to "true"
      set the pCoordinates    of field lfieldName to iX,iY
      set the script          of field lfieldName to "on MouseUp"&return&"CellClick"&return&"end MouseUp"
  set the layer of field "Message" to 100000




on CoverAndEmptyAllCells


  lock screen
   
  -- fill the cell state array:
  repeat with iY=1 to gGridHeight
    repeat with iX=1 to gGridWidth
      put cStateCovered into gCellStates[iX,iY]
  
  repeat with iCell=the number of fields down to 1
    if the pCell of field iCell = "true" then
      set the backgroundcolor of field iCell to gColours["Covered"  ]
      put "" into field iCell




on RemoveCells


  -- take all cells off the board
  lock screen
  repeat with iCell=the number of fields down to 1
    if the pCell of field iCell = "true" then
      delete field iCell




on ResizeStack fW,fH


  -- fW is the stack window width, but fH is the height of the stack minus the height of the title bar,
  -- i.e. the height of the drawable area.  However, y starts at the top of the title bar.
  
  RemoveCells
  
  put the topleft of this stack into lTopLeft  -- need to set this back later, otherwise stack moves on screen.
  -- restrict the size of the playing grid:  at least 8x8 cells and at most 20x20 cells
  put ( 8*cCellSize + cGridMargin*2) into lMinimumWidth
  put (20*cCellSize + cGridMargin*2) into lMaximumWidth
  put ( 8*cCellSize + cTopInfoHeight + cBottomInfoHeight + cGridMargin*2) into lMinimumHeight
  put (20*cCellSize + cTopInfoHeight + cBottomInfoHeight + cGridMargin*2) into lMaximumHeight
  if fW<lMinimumWidth  then put lMinimumWidth  into fW
  if fW>lMaximumWidth  then put lMaximumWidth  into fW
  if fH<lMinimumHeight then put lMinimumHeight into fH
  if fH>lMaximumHeight then put lMaximumHeight into fH
  set the width  of this stack to fW; set the height of this stack to fH; set the topleft of this stack to lTopLeft
  
  -- decide the grid size:
  -- cells have a one-pixel border, the borders of neighbouring cells should overlap, so the distance between cell
  -- centres is cCellsize-1
  put (fW - cGridMargin*2) div (cCellSize-1)                                      into gGridWidth
  put (fH - cTopInfoHeight - cBottomInfoHeight - cGridMargin*2) div (cCellSize-1) into gGridHeight
  -- centre the grid:
  -- the slack is:
  put fW - gGridWidth  * (cCellsize-1) +1                                      into lWidthSlack
  put fH - gGridHeight * (cCellsize-1) +1 - cTopInfoHeight - cBottomInfoHeight into lHeightSlack
  put lWidthSlack div 2 + cCellSize div 2     into gGridLeftMargin
  put cTitleBbarHeight + cTopInfoHeight + cCellSize div 2 + lHeightSlack div 2 into gGridTopMargin
  
  put gGridWidth&" x "&gGridHeight&" = "&gGridWidth*gGridHeight&" cells" into field "GridSize"
  
  -- Position Interface Objects
  set the width  of graphic "MineFieldOutline" to fW-2*cOutlineMargin
  set the height of graphic "MineFieldOutline" to fH-cTopInfoHeight-cBottomInfoHeight-2*cOutlineMargin
  set the left   of graphic "MineFieldOutline" to cOutlineMargin
  set the top    of graphic "MineFieldOutline" to cTitleBbarHeight+cTopInfoHeight+cOutlineMargin
  
  set the loc of field "GridSize" to 70,cTopInfoHeight+cTitleBbarHeight
  
  set the loc of field "NumberOfBombs"  to (the width of me)-65,cTopInfoHeight+cTitleBbarHeight
  set the loc of field "hNumberOfBombs" to (the width of me)-35,cTopInfoHeight+cTitleBbarHeight
  
  set the loc of button "NewGame" to 45,fH-cBottomRow
  
  set the loc of graphic "UpButton"      to 120,fH-cBottomRow-12; set the layer of graphic "UpButton"   to 1
  set the loc of graphic "DownButton"    to 120,fH-cBottomRow+12; set the layer of graphic "DownButton" to 1
  set the loc of field   "NumberOfLives" to  93,fH-cBottomRow
  set the loc of field   "hLives"        to 120,fH-cBottomRow
  
  set the loc of button "Solve" to (the width of me)-35,fH-cBottomRow
  
  set the loc of field "Message" to (the width of me) div 2,(the height of me) div 2
  
  LayoutTheGrid
  
  -- set or reset globals:
  
  -- fill the blank array:
  repeat with iY=1 to gGridHeight
    repeat with iX=1 to gGridWidth
      put " " into gSpaces[iX,iY]
  put gSpaces into gBombs; put gSpaces into gBombCounts
    
  put cDefaultNrLivesDesired into gNrOfLivesDesired
  
  -- start the game:
  send "MouseUp" to button "NewGame"
  







on SprinkleBombs


  put gSpaces into gBombs
  put (gGridWidth*gGridHeight) into lNrSquares
  put lNrSquares div gFreeLevel into gNrBombsLeftToIdentify
  put gNrBombsLeftToIdentify into field "NumberOfBombs"
  --DoFixedSet
  --exit SprinkleBombs
  repeat with iBomb=1 to gNrBombsLeftToIdentify
    repeat
      put random(lNrSquares) into lThisBomb
      put (lThisBomb div gGridWidth)+1 into lY
      put (lThisBomb mod gGridWidth) into lX
      if lX=0 then
        put gGridWidth into lX; subtract 1 from lY
      if gBombs[lX,lY] is not "*" then
        put "*" into gBombs[lX,lY]
        exit repeat


on SetBombCounts


  -- fill gBombCounts with the bombcounts
  put gBombs into gBombCounts
  repeat with iY=1 to gGridHeight
    repeat with iX=1 to gGridWidth
      -- count the number of bombs around this cell
      if gBombs[iX,iY] = "*" then next repeat
      put 0 into lNrBombsAround
      if gBombs[iX-1,iY-1] = "*" then add one to lNrBombsAround
      if gBombs[iX  ,iY-1] = "*" then add one to lNrBombsAround
      if gBombs[iX+1,iY-1] = "*" then add one to lNrBombsAround
      if gBombs[iX-1,iY  ] = "*" then add one to lNrBombsAround
      if gBombs[iX+1,iY  ] = "*" then add one to lNrBombsAround
      if gBombs[iX-1,iY+1] = "*" then add one to lNrBombsAround
      if gBombs[iX  ,iY+1] = "*" then add one to lNrBombsAround
      if gBombs[iX+1,iY+1] = "*" then add one to lNrBombsAround
      put lNrBombsAround into gBombCounts[iX,iY]








on CellClick


  -- this one is for the human player:
  put                               "Normal"       into lClickType
  if the optionkey is down then put "PlantFlag"    into lClickType
  if the shiftkey  is down then put "PlantSuspect" into lClickType
  put the short name of the target into lThisCell
  put item 1 of the pCoordinates of field lThisCell into lX; put item 2 of the pCoordinates of field lThisCell into lY
  ClickOnCell lClickType,lx,ly
  

on ClickOnCell fClickType,fX,fY


  -- and this one is for both the human and the machine:
  put "C"&D2(fX)&D2(fY) into lThisCell
  
  switch gCellStates[fX,fY]
    
    -- ---------------------------------------------------------------------- U N C O V E R E D
    -- ---------------------------------------------------- refuse to do anything if cell is already uncovered
  case cStateUncovered
    switch fClickType
    case "Normal"
      break
    case "PlantFlag"
      break
    case "PlantSuspect"
      break
    AlertPlayer
    exit ClickOnCell
    break
     
    -- ---------------------------------------------------------------------- C O V E R E D
  case cStateCovered
    switch fClickType
    case "Normal"
      if gBombs[fX,fY] = "*" then
        -- ---------------------------------------------------- NORMAL:  die if a bomb
        AlertPlayer
        put cStateUncovered into gCellStates[fX,fY]
        set the backgroundcolor of field lThisCell to gColours["Bomb"]
        subtract one from field "NumberOfBombs"
        subtract one from gNrBombsLeftToIdentify
        subtract one from gNrStillCovered
        subtract 1 from gNrOfLivesLeft; put gNrOfLivesLeft into field "NumberOfLives"
        if gNrOfLivesLeft=0 then
          DisplayAllBombs
      else
        -- ---------------------------------------------------- NORMAL:  show bombcount
        put gBombCounts[fX,fY] into lThisSurround
        if lThisSurround>0 then
          put lThisSurround into field lThisCell
          put cStateUncovered into gCellStates[fX,fY]
          set the backgroundcolor of field lThisCell to gColours["Uncovered"]
          subtract one from gNrStillCovered
        else  -- uncover all connected free terrain
          UncoverFreeTerrain fX,fY        
      break
    case "PlantFlag"
      -- ---------------------------------------------------- FLAG:  plant flag
      if field "NumberOfBombs" = 0 then -- can't plant more flags than there are bombs!
        AlertPlayer
        exit ClickOnCell
      put cStateFlagged into gCellStates[fX,fY]
      set the backgroundcolor of field lThisCell to gColours["Flagged"]
      subtract one from field "NumberOfBombs"
      subtract one from gNrStillCovered
      if gBombs[fX,fY] = "*" then
        subtract one from gNrBombsLeftToIdentify
      break
    case "PlantSuspect"
      -- ---------------------------------------------------- SUSPECT:  consider still among covered cells
      put cStateSuspected into gCellStates[fX,fY]
      set the backgroundcolor of field lThisCell to gColours["Suspected"]
      break
    break
     
    -- ---------------------------------------------------------------------- F L A G G E D
  case cStateFlagged
    switch fClickType
    case "Normal"
      -- ---------------------------------------------------- NORMAL:  disallowed
      AlertPlayer
      exit ClickOnCell
      break
    case "PlantFlag"
      -- ---------------------------------------------------- FLAG:  remove flag
      put cStateCovered into gCellStates[fX,fY]
      set the backgroundcolor of field lThisCell to gColours["Covered"]
      add one to field "NumberOfBombs"
      add one to gNrStillCovered
      if gBombs[fX,fY] = "*" then
        add one to gNrBombsLeftToIdentify
      break
    case "PlantSuspect"
      -- ---------------------------------------------------- SUSPECT:  remove flag then suspect
      add one to field "NumberOfBombs"
      add one to gNrStillCovered
      if gBombs[fX,fY] = "*" then
        add one to gNrBombsLeftToIdentify
      put cStateSuspected into gCellStates[fX,fY]
      set the backgroundcolor of field lThisCell to gColours["Suspected"]
      break
    break
     
    -- ---------------------------------------------------------------------- S U S P E C T E D
  case cStateSuspected
    switch fClickType
    case "Normal"
      -- ---------------------------------------------------- NORMAL:  disallowed
      AlertPlayer
      exit ClickOnCell
      break
    case "PlantFlag"
      -- ---------------------------------------------------- FLAG:  plant flag
      put cStateFlagged into gCellStates[fX,fY]
      set the backgroundcolor of field lThisCell to gColours["Flagged"]
      subtract one from field "NumberOfBombs"
      subtract one from gNrStillCovered
      if gBombs[fX,fY] = "*" then
        subtract one from gNrBombsLeftToIdentify
      break
    case "PlantSuspect"
      -- ---------------------------------------------------- SUSPECT:  remove suspect
      put cStateCovered into gCellStates[fX,fY]
      set the backgroundcolor of field lThisCell to gColours["Covered"]
      break
    break
     
  
  if gNrBombsLeftToIdentify = 0 and gNrStillCovered = 0 then
    DisplayMessage "Congratulations,"&return&"you won!"
    put false into gGameInProgress
  else
    put true into gGameInProgress
   








on UncoverFreeTerrain fX,fY


  --
  -- Cell fX,fY has a zero.  Start with a blank field, mark this position with "0".
  -- Then:  for any zero, replace it with "x" and copy the bombcounts of its neighbours that are blank. 
  -- Repeat this until no more zeroes found.  This leaves a mixture of numbers, "x"s and blank positions.
  -- Uncover all non-blank positions.
  --
  put gSpaces into lMarks
  
  put "0" into lMarks[fX,fY]
  repeat
    put false into lZeroFound
    repeat with iX=1 to gGridWidth
      repeat with iY=1 to gGridHeight
        if lMarks[iX,iY] is "0" then
          if  lMarks[iX-1,iY-1] = " " then put gBombCounts[iX-1,iY-1] into lMarks[iX-1,iY-1]
          if  lMarks[iX  ,iY-1] = " " then put gBombCounts[iX  ,iY-1] into lMarks[iX  ,iY-1]
          if  lMarks[iX+1,iY-1] = " " then put gBombCounts[iX+1,iY-1] into lMarks[iX+1,iY-1]
          if  lMarks[iX-1,iY  ] = " " then put gBombCounts[iX-1,iY  ] into lMarks[iX-1,iY  ]
          put "x" into lMarks[iX,iY]
          if  lMarks[iX+1,iY  ] = " " then put gBombCounts[iX+1,iY  ] into lMarks[iX+1,iY  ]
          if  lMarks[iX-1,iY+1] = " " then put gBombCounts[iX-1,iY+1] into lMarks[iX-1,iY+1]
          if  lMarks[iX  ,iY+1] = " " then put gBombCounts[iX  ,iY+1] into lMarks[iX  ,iY+1]
          if  lMarks[iX+1,iY+1] = " " then put gBombCounts[iX+1,iY+1] into lMarks[iX+1,iY+1]
          put true into lZeroFound
    if not lZeroFound then exit repeat
  -- uncover:
  repeat with iX=1 to gGridWidth
    repeat with iY=1 to gGridHeight
      put lMarks[iX,iY] into lMark
      if lMark is not space then
        put "C"&D2(iX)&D2(iY) into lCellName
        switch gCellStates[iX,iY]
        case cStateCovered
          subtract one from gNrStillCovered
          break
        case cStateUncovered
          break
        case cStateFlagged
          add one to field "NumberOfBombs"
          break
        case cStateSuspected
          break
        put cStateUncovered into gCellStates[iX,iY]
        set the backgroundcolor of field lCellName to gColours["Uncovered"]
        if lMark = "x" then
          put "" into field lCellName
        else
          put lMark into field lCellName
  




on DisplayAllBombs


  repeat with iX=1 to gGridWidth
    repeat with iY=1 to gGridHeight
      if gBombs[iX,iY] is "*" then
        if gCellStates[iX,iY] is not cStateFlagged then
          put "C"&D2(iX)&D2(iY) into lCellName
          set the backgroundcolor of field lCellName to gColours["Bomb"]
      else
        if gCellStates[iX,iY] is cStateFlagged then
          put "C"&D2(iX)&D2(iY) into lCellName
          set the backgroundcolor of field lCellName to gColours["WronglyFlagged"]
  DisplayMessage "Sorry,"&return&"you did not make it..."
  put false into gGameInProgress







on DisplayMessage fMessage


  show field "Message"
  put return&fMessage into field "Message"




function D2 fi


  if fi<10 then
    return "0"&fi
  else
    return fi


on AlertPlayer


  if gSoundOn then beep



-- Debugging



on DoFixedSet


  put \
      "* *  ** * *  "&return&\
      "  *   * *  * "&return&\
      "   *         "&return&\
      "  *      *   "&return&\
      " *  *      * "&return&\
      " *           "&return&\
      "          *  "&return&\
      "   *   *     " into lbombs
  repeat with iX=1 to 13
    repeat with iY=1 to 8
      if char iX of line (9-iY) of lBombs = "*" then put "*" into gBombs[iX,iY]


on PutSomeGrid fName,fGrid


  global gDebug
  put gSpaces into gDebug
  repeat with y=1 to gGridHeight
    repeat with x=1 to gGridWidth
      put fGrid[x,y] into item x of line (gGridHeight - y+1) of gDebug
  put return&return&"["&fName&"]"&return&gDebug after msg

button NewGame


global gNrBombs, gNrOfLivesDesired, gNrOfLivesLeft, gGameInProgress, gNrStillCovered
global gGridWidth, gGridHeight

on MouseUp


  hide field "Message"
  CoverAndEmptyAllCells
  SprinkleBombs
  SetBombCounts
  put gNrOfLivesDesired into gNrOfLivesLeft
  put gNrOfLivesLeft into field "NumberOfLives"
  put gGridWidth*gGridHeight into gNrStillCovered
  put false into gGameInProgress

button Solve

-- Button "Solve"

-- Coordinate system:  origin in bottom left, x to the right, y up.

-- Parameters
-- ==========

constant cStateCovered=-1, cStateUncovered=-2, cStateFlagged=-3, cStateSuspected=-4, cStateWronglyFlagged=-5



-- Data Structures
-- ===============

--    Interface
--    ---------
global gGridWidth               -- width  of playing grid in number of cells
global gGridHeight              -- height of playing grid in number of cells
global gGridLeftMargin          -- distance between left side of window and centre of left column cells
global gGridTopMargin           -- distance between top of window and centre of top row cells
global gNrOfLivesDesired        -- last setting by the player
global gGameInProgress          -- true after first cell has been touched; reset when game is over
global gColours                 -- the different colours in use
global gSoundOn                 -- whether illegal moves are signalled with a beep
global gFreeLevel               -- proportion of free cells over cells holding bombs (inverse of bomb density)


--    Bookkeeping
--    -----------
--    grid-size arrays:
global gSpaces             -- an empty array used to blank out other arrays.
global gBombs              -- cells holding a bomb
global gBombCounts         -- the number of bombs surrounding each cell
global gCellStates         -- the states of the cells; one of the constants whose names start with "cState"
--    counters:
global gNrStillCovered          -- the number of cells still covered
global gNrBombsLeftToIdentify   -- number of bombs not having a flag yet
global gNrOfLivesLeft           -- number of lives left


global gBoardState         -- what this algorithm can see.




on GetBoardState


put gCellStates into gBoardState
  repeat with iY=1 to gGridHeight
    repeat with iX=1 to gGridWidth
      if gBoardState[iX,iY] = cStateUnCovered then
        put gBombCounts[iX,iY] into gBoardState[iX,iY]
  --PutSomeGrid "Board state",gBoardState





function UncoveredASea fBoardState


  put 0 into lSeaCount
  repeat with iY=1 to gGridHeight
    repeat with iX=1 to gGridWidth
      if fBoardState[iX,iY] = 0 then
        add 1 to lSeaCount
  if lSeaCount>0 then
    return true
  else 
    return false





function CoordinatesOfSurroundingCells fX,fY


  put empty into lResult; put 0 into ll
  if fX>1          and fY<gGridHeight    then
    add 1 to ll;   put (fX-1,fY+1) into line ll of lResult
  if                  fY<gGridHeight     then
    add 1 to ll;   put (fX  ,fY+1) into line ll of lResult
  if fX<gGridWidth and fY<gGridHeight    then
    add 1 to ll;   put (fX+1,fY+1) into line ll of lResult
  if fX>1                                then
    add 1 to ll;   put (fX-1,fY  ) into line ll of lResult
  if fX<gGridWidth                       then
    add 1 to ll;   put (fX+1,fY  ) into line ll of lResult
  if fX>1          and fY>1               then
    add 1 to ll;   put (fX-1,fY-1) into line ll of lResult
  if                  fY>1               then
    add 1 to ll;   put (fX  ,fY-1) into line ll of lResult
  if fX<gGridWidth and fY>1              then
    add 1 to ll;   put (fX+1,fY-1) into line ll of lResult
  return lResult



function CoordinatesOfSurroundingDiags fX,fY


  put empty into lResult; put 0 into ll
  if fX>1          and fY<gGridHeight    then
    add 1 to ll;   put (fX-1,fY+1) into line ll of lResult
  if fX<gGridWidth and fY<gGridHeight    then
    add 1 to ll;   put (fX+1,fY+1) into line ll of lResult
  if fX>1          and fY>1               then
    add 1 to ll;   put (fX-1,fY-1) into line ll of lResult
  if fX<gGridWidth and fY>1              then
    add 1 to ll;   put (fX+1,fY-1) into line ll of lResult
  return lResult





on MouseUp


  
  send "Mouseup" to button "NewGame"
  DisplayMessage "Not implemented yet"
  exit mouseup
  
  -- at the start we click at random.
  -- There are three possible results:
  -- a bomb, a number, a sea.
  
  -- if it is a bomb, try again:
  repeat
    put random(gGridWidth) into lX; put random(gGridHeight) into lY
    ClickOnCell "Normal",lX,lY
    GetBoardState
    if gBoardState[lX,lY]  "*" then
      exit repeat
    if gNrOfLivesLeft=0 then exit mouseup
  
  repeat
    -- if it is a sea, then we go intelligent...
    if UncoveredASea(gBoardState) then
      put "YAHOOOOOO!"; exit mouseup
    else if gBoardState[lX,lY] = "1" then -- we try to find a sea by taking one of its diagonals:
      repeat
        put CoordinatesOfSurroundingDiags(lX,lY) into lSurroundingDiags
        put line random(the number of lines of lSurroundingDiags) of lSurroundingDiags into lCellToClick
        put item 1 of lCellToClick into lX; put item 2 of lCellToClick into lY
        ClickOnCell "Normal",lX,lY
        if gNrOfLivesLeft=0 then exit mouseup
        GetBoardState
        if UncoveredASea(gBoardState) then exit mouseup
        if gBoardState[lX,lY]  "1" then exit repeat
    else -- click again at random
      put random(gGridWidth) into lX; put random(gGridHeight) into lY
      ClickOnCell "Normal",lX,lY
      GetBoardState
      if gNrOfLivesLeft=0 then exit mouseup
  
  exit mouseup
   
   
  global gNumberOfLives, gDefaultNumberOfLives, gGameInProgress
  if gGameInProgress then
    beep
    exit MouseUp
   
  -- solve the puzzle
   
  /*
  Border constraints:  the cells at the borders of the field have fewer than 8 surrounding cells.
  The set of surrounding cells of cell x,y is:
   
  A B C
  D   E
  F G H
   
  They exist if:                    and the coordinates are:
   
  A if x>1          and y<gGridHeight     x-1,y+1
  B if                  y<gGridHeight     x  ,y+1
  C if x<gGridWidth and y<gGridHeight     x+1,y+1
  D if x>1                                x-1,y
  E if x<gGridWidth                       x+1,y
  F if x>1          and y>1               x-1,y-1
  G if                  y>1               x  ,y-1
  H if x<gGridWidth and y>1               x+1,y-1
   
  Keep the set of still covered cells in gCovered.
  Keep the set of uncovered cells in gUncovered, just for speed.
  Keep the set of known bombs in gKnownBombs
   
  Uncovered cells have a certain probability  to harbour a bomb.
  Naively this is the number of unknown bombs divided by the number of uncovered cells.
  However, the numbers in the uncovered cells give us more info:
  Say a cell displays a number of 
  n surrounding bombs, it is surrounded by
  p uncovered cells,
  q covered ones and 
  r covered but known bombs.
   
  Clearly r<=n and p+q+r = 8.
  If r=n then the probability that any of the q covered cells has a bomb is zero.
  if r<n then there must be n-r unknown bombs in the q covered cells and, without any other
  information, the probability of any of the q covered cells having a bomb is (n-r)/q.
  If (n-r)/q = 1 then the number of unknown bombs equals the number of covered neighbours
  and therefore they are all bombs and can be marked accordingly.
   
  There is only ambiguity if q>(n-r) i.e. more uncovered cells than unknown bombs.  In this case
  we need info from neighbouring cells or, if not possible, a guess.
   
  We could try all configurations of the remaining bombs in the uncoverd squares of the board,
  and eleminate those that are in conflict with the known numbers.
  The remaining configurations together may show  possible inambiguities.  Think of it as placing
  all the configurations on transparent sheets and putting all sheets  on top of each other:
   
  1) if any cell does not appear as a possible bomb in any of the configurations, it is impossible
  that it holds a bomb and it can be uncovered. (you can see through it in the stack of sheets).
   
  2) if any cell has a bomb in all configurations, it can be flagged as a bomb. (it is a very
  opaque cell in the stack of sheets).
   
  3) if there are no cells conforming to 1) or 2), then only a guess remains.  The best guess
  corresponds to a cell that has fewest bombs in the set of configurations.
   
  However, to compute all the configurations takes a very  large amount of time.
  A strategy which is "next-best" may be to compute configurations only for the cells close to the
  area that we have already uncovered, i.e. the cells at the "frontier" of what we know.
   
  A cell is a frontier cell if it has at least one uncovered neighbour.
  Keep the set of frontier cells in gFrontier.
   
  We know there are b bombs remaining to be identified.  Try to place them in the frontier region.
  If all configurations require all bombs to be placed, then all of the non-frontier region can be
  uncovered!  
   
  */
   
  global gGridWidth, gGridHeight, gBombs, gNumbers, gSpaces, gKnown
  global gNoMoreSimpleMovesPossible
   
  put gSpaces into gKnown
   
  repeat
    put false into gNoMoreSimpleMovesPossible
    repeat
      DoASimpleMove
      if gNoMoreSimpleMovesPossible then exit repeat
    DoARandomMove
   


on DoASimpleMove


  -- 
  global gGridWidth, gGridHeight, gBombs, gNumbers, gSpaces, gKnown
  global gNoMoreSimpleMovesPossible
  
  repeat with iX=1 to gGridWidth
    repeat with iY=1 to gGridHeight
      put char iX of line iY of gKnown into lThisCell
      if lThisCell is in "12345678" then  -- examine this cell
        -- count the surrounding blank cells:
        put 0 into lSurroundingBlanks
        if char iX-1 of line iY-1 of gKnown is space then add one to lSurroundingBlanks
        if char iX   of line iY-1 of gKnown is space then add one to lSurroundingBlanks
        if char iX+1 of line iY-1 of gKnown is space then add one to lSurroundingBlanks
        if char iX-1 of line iY   of gKnown is space then add one to lSurroundingBlanks
        if char iX+1 of line iY   of gKnown is space then add one to lSurroundingBlanks
        if char iX-1 of line iY+1 of gKnown is space then add one to lSurroundingBlanks
        if char iX   of line iY+1 of gKnown is space then add one to lSurroundingBlanks
        if char iX+1 of line iY+1 of gKnown is space then add one to lSurroundingBlanks
        if lThisCell is lSurroundingBlanks then
          
  

/*

*/


on DoARandomMove


  global gGridWidth, gGridHeight, gBombs, gSpaces
  put (gGridWidth*gGridHeight) into lNrSquares
  put random(lNrSquares) into lThisBomb
  put (lThisBomb div gGridWidth)+1 into lY
  put (lThisBomb mod gGridWidth) into lX
  if lX=0 then
    put gGridWidth into lX; subtract 1 from lY
  if char lX of line lY of gBombs is not "*" then
    put "*" into char lX of line lY of gBombs
    exit repeat
   
  
  

button File


button Edit


button Sound

on MenuPick fWhich


  global gSoundOn
  if fWhich = "Sound Off" then
    put false into gSoundOn
    set the text of button "Sound" to "Sound On"
  else
    put true into gSoundOn
    set the text of button "Sound" to "Sound Off"

button Level


global gFreeLevel

on MenuPick fWhich


  go to card "PlayingField"
  if fWhich = "Easy" then
    put 10 into gFreeLevel
    set the text of button "Level" to "!cEasy"&return&"!nMedium"&return&"!nHard"
    set the backgroundcolor of this card to "200,255,180"
    set the backgroundcolor of graphic "MineFieldOutline" to "150,230,100"
  else if fWhich = "Medium" then
    put 5 into gFreeLevel
    set the text of button "Level" to "!nEasy"&return&"!cMedium"&return&"!nHard"
    set the backgroundcolor of this card to "180,200,255"
    set the backgroundcolor of graphic "MineFieldOutline" to "100,150,230"
  else if fWhich = "Hard" then
    put 3 into gFreeLevel
    set the text of button "Level" to "!nEasy"&return&"!nMedium"&return&"!cHard"
    set the backgroundcolor of this card to "255,200,180"
    set the backgroundcolor of graphic "MineFieldOutline" to "230,150,100"
  send "MouseUp" to button "NewGame"

button Help

on MenuPick fWhich


  if fWhich = "Rules" then
    go to card "Rules"
  else if fWhich = "How to" then
    go to card "HowTo"
  else if fWhich = "About" then
      go to card "About"

button


field GridSize


field NumberOfLives


field hLives


field NumberOfBombs


field hNumberOfBombs


field C0101

on MouseUp


CellClick

field C0201

on MouseUp


CellClick

field C0301

on MouseUp


CellClick

field C0401

on MouseUp


CellClick

field C0501

on MouseUp


CellClick

field C0601

on MouseUp


CellClick

field C0701

on MouseUp


CellClick

field C0801

on MouseUp


CellClick

field C0102

on MouseUp


CellClick

field C0202

on MouseUp


CellClick

field C0302

on MouseUp


CellClick

field C0402

on MouseUp


CellClick

field C0502

on MouseUp


CellClick

field C0602

on MouseUp


CellClick

field C0702

on MouseUp


CellClick

field C0802

on MouseUp


CellClick

field C0103

on MouseUp


CellClick

field C0203

on MouseUp


CellClick

field C0303

on MouseUp


CellClick

field C0403

on MouseUp


CellClick

field C0503

on MouseUp


CellClick

field C0603

on MouseUp


CellClick

field C0703

on MouseUp


CellClick

field C0803

on MouseUp


CellClick

field C0104

on MouseUp


CellClick

field C0204

on MouseUp


CellClick

field C0304

on MouseUp


CellClick

field C0404

on MouseUp


CellClick

field C0504

on MouseUp


CellClick

field C0604

on MouseUp


CellClick

field C0704

on MouseUp


CellClick

field C0804

on MouseUp


CellClick

field C0105

on MouseUp


CellClick

field C0205

on MouseUp


CellClick

field C0305

on MouseUp


CellClick

field C0405

on MouseUp


CellClick

field C0505

on MouseUp


CellClick

field C0605

on MouseUp


CellClick

field C0705

on MouseUp


CellClick

field C0805

on MouseUp


CellClick

field C0106

on MouseUp


CellClick

field C0206

on MouseUp


CellClick

field C0306

on MouseUp


CellClick

field C0406

on MouseUp


CellClick

field C0506

on MouseUp


CellClick

field C0606

on MouseUp


CellClick

field C0706

on MouseUp


CellClick

field C0806

on MouseUp


CellClick

field C0107

on MouseUp


CellClick

field C0207

on MouseUp


CellClick

field C0307

on MouseUp


CellClick

field C0407

on MouseUp


CellClick

field C0507

on MouseUp


CellClick

field C0607

on MouseUp


CellClick

field C0707

on MouseUp


CellClick

field C0807

on MouseUp


CellClick

field C0108

on MouseUp


CellClick

field C0208

on MouseUp


CellClick

field C0308

on MouseUp


CellClick

field C0408

on MouseUp


CellClick

field C0508

on MouseUp


CellClick

field C0608

on MouseUp


CellClick

field C0708

on MouseUp


CellClick

field C0808

on MouseUp


CellClick

field Message

on MouseUp


  hide me

( end application MineHunt)