/*----------------------------------------------------------------------------*/
/*                                                                            */
/* Copyright (c) 1995, 2004 IBM Corporation. All rights reserved.             */
/* Copyright (c) 2005-2014 Rexx Language Association. All rights reserved.    */
/*                                                                            */
/* This program and the accompanying materials are made available under       */
/* the terms of the Common Public License v1.0 which accompanies this         */
/* distribution. A copy is also available at the following address:           */
/* https://www.oorexx.org/license.html                                        */
/*                                                                            */
/* Redistribution and use in source and binary forms, with or                 */
/* without modification, are permitted provided that the following            */
/* conditions are met:                                                        */
/*                                                                            */
/* Redistributions of source code must retain the above copyright             */
/* notice, this list of conditions and the following disclaimer.              */
/* Redistributions in binary form must reproduce the above copyright          */
/* notice, this list of conditions and the following disclaimer in            */
/* the documentation and/or other materials provided with the distribution.   */
/*                                                                            */
/* Neither the name of Rexx Language Association nor the names                */
/* of its contributors may be used to endorse or promote products             */
/* derived from this software without specific prior written permission.      */
/*                                                                            */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS        */
/* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT          */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS          */
/* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT   */
/* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,      */
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED   */
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,        */
/* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING    */
/* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS         */
/* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.               */
/*                                                                            */
/*----------------------------------------------------------------------------*/

/**
 *  oobandit.rex  An ooDialog example, the Jackpot Slot Machine.
 *
 *  This example demonstrates one way of animating bitmaps, by drawing them on
 *  the face of a button.  It also shows how to correctly use dialog units in
 *  a UserDialog to size and place the controls.
 *
 *  Creating the dialog and its controls is done this way:
 *
 *  The pixel size of the bitmaps is known.  Although bitmaps can be stretched
 *  by the OS to fit a specific size, bitmaps look best displayed in their
 *  actual size.  The pixel size of the bitmaps is first converted to the
 *  correct dialog unit size, correct for the actual dialog to be constructed.
 *
 *  Then the size of the dialog and the size and placement of the dialog
 *  controls are calculated around the bitmap size.
 *
 * Note: this program uses the public routine, locate(), to get the full path
 * name to the directory this source code file is located. In places, the
 * variable holding this value has been callously abbreviated to 'sd' which
 * stands for source directory.
 *
 */

   -- Use the global .constDir for symbolic IDs, and add IDs for this example.
   .application~useGlobalConstDir('O')
   .constDir[IDC_PB_STOP]        = 1100
   .constDir[IDC_STATIC_JACKPOT] = 1200
   .constDir[IDC_PB_BMP_LEFT]    = 1201
   .constDir[IDC_PB_BMP_CENTER]  = 1202
   .constDir[IDC_PB_BMP_RIGHT]   = 1203
   .constDir[IDC_EDIT]           = 120
   .constDir[IDC_UD]             = 1206

         /* 1ms fast, 500ms slow, 200ms start, equals random every 25th */
   d = .BanditDlg~new(1, 1000, 1000, 25)
   d~execute("SHOWTOP")

   return 0

/*---------------------------------- requires ------------------------*/

::requires "ooDialog.cls"
::requires "samplesSetup.rex"

/*---------------------------------- dialog class --------------------*/

::class 'BanditDlg' subclass UserDialog

::constant BITMAP_X           152
::constant BITMAP_Y           178
::constant FONT_NAME          "MS Shell Dlg"
::constant FONT_SIZE          14

::constant MARGIN_X           10
::constant MARGIN_Y           5
::constant JACKPOT_LINE_Y     22
::constant TEXT_Y             12

::constant BUTTON_X           35
::constant BUTTON_Y           15

::method init
   expose kind3 initialSpeed minSpeed maxSpeed maxCycle cycle equal misses initPot won bitMapSize dlgSize
   use arg minSpeed, maxSpeed, initialSpeed, kind3

   self~init:super()

   -- Set the font the dialog will use when created.  Without this step, dialog
   -- units can not be calculated correctly.
   self~setDlgFont(self~FONT_NAME, self~FONT_SIZE)

   -- Set our various instance variables.
   minSpeed = max(1,minSpeed)
   maxCycle = 200; initPot = 1000; equal = 0; misses = 0; cycle = maxCycle; won = .false

   -- Calculate the size of a bitmap in dialog units.
   bitMapSize = .Size~new(self~BITMAP_X, self~BITMAP_Y)
   self~pixel2dlgUnit(bitMapSize)

   -- Calculate the size of this dialog based on the bitmap size.
   dlgSize = self~calcSize(bitMapSize)

   title = "Jackpot Slot Machine - Stop on 3 of a Kind and Win $$$"
   self~initCode = self~createcenter(dlgSize~width, dlgSize~height, title)

::method defineDialog
   expose bmp. initialSpeed dlgSize bitMapSize

   sd = locate()

   -- Load the bitmaps into memory.
   bmp.1 = self~loadBitmap(sd"bmp\tiger.bmp")
   bmp.2 = self~loadBitmap(sd"bmp\chihuahu.bmp")
   bmp.3 = self~loadBitmap(sd"bmp\eleph2.bmp")
   bmp.4 = self~loadBitmap(sd"bmp\horse.bmp")
   bmp.5 = self~loadBitmap(sd"bmp\sealion.bmp")
   bmp.6 = self~loadBitmap(sd"bmp\moose.bmp")
   bmp.7 = self~loadBitmap(sd"bmp\rhinoce.bmp")
   bmp.8 = self~loadBitmap(sd"bmp\goat.bmp")
   bmp.0 = 8

   -- Note that for a static text control, the CENTERIMAGE flag has the effect
   -- of vertically centering the text within the control.

   -- Create the jackpot line.  First a frame around the whole thing.
   x = self~MARGIN_X
   y = self~MARGIN_Y
   self~createBlackFrame(IDC_STATIC, x, y, dlgSize~width - (2 * self~MARGIN_X), self~JACKPOT_LINE_Y, "BORDER")

   -- Static text on the right, centered over the 1st bitmap
   txt = "Jackpot  $$$"
   txtSize = self~getTextSizeDU(txt)
   x += trunc((bitMapSize~width / 2) - (txtSize~width / 2))
   y += self~MARGIN_Y
   self~createStaticText(IDC_STATIC, x, y, txtSize~width, self~TEXT_Y, "CENTER CENTERIMAGE", txt)

   -- The jackpot number, could be up to 9 digits.  Just a static control with a
   -- fancy frame, centered over the middle bitmap
   txt = "888888888"
   txtSize = self~getTextSizeDU(txt)
   x = trunc((dlgSize~width / 2) - ((txtSize~width + 6) / 2))
   self~createBlackFrame(IDC_STATIC,   x + 0, y - 2, txtSize~width + 6, self~TEXT_Y + 4, "BORDER")
   self~createBlackFrame(IDC_STATIC,   x + 1, y - 1, txtSize~width + 4, self~TEXT_Y + 2, "BORDER")
   self~createStaticText(IDC_STATIC_JACKPOT, x + 3, y - 0, txtSize~width + 0, self~TEXT_Y + 0, "RIGHT CENTERIMAGE")

   -- Static text on the left, centered over the 3rd bitmap.
   txt = "$$$  Jackpot"
   txtSize = self~getTextSizeDU(txt)
   x = (bitMapSize~width * 2) + (3 * self~MARGIN_X)            -- The left edge of the 3rd bitmap ...
   x += trunc((bitMapSize~width / 2) - (txtSize~width / 2))    -- ... and center
   self~createStaticText(IDC_STATIC, x, y, txtSize~width, self~TEXT_Y, "CENTER CENTERIMAGE", txt)

   -- Now place the bitmaps
   x = self~MARGIN_X
   y = (2 * self~MARGIN_Y) + self~JACKPOT_LINE_Y
   self~createBitmapButton(IDC_PB_BMP_LEFT, x, y, bitMapSize~width, bitMapSize~height, "INMEMORY  USEPAL", , , bmp.1)

   x += bitMapSize~width + self~MARGIN_X
   self~createBitmapButton(IDC_PB_BMP_CENTER, x, y, bitMapSize~width, bitMapSize~height, "INMEMORY ", , , bmp.1)

   x += bitMapSize~width + self~MARGIN_X
   self~createBitmapButton(IDC_PB_BMP_RIGHT, x, y, bitMapSize~width, bitMapSize~height, "INMEMORY ", , , bmp.1)

   -- Stop and cancel buttons, placed at left margin and under bitmaps
   x = self~MARGIN_X
   y += bitMapSize~height + self~MARGIN_Y
   buttons = "&Stop" .constDir[IDC_PB_STOP] "onStop &Cancel" .constDir[IDCANCEL] "Cancel"
   self~createPushButtonGroup(x, y, self~BUTTON_X, self~BUTTON_Y, buttons, .false, "DEFAULT")

   -- A group box to hold the speed adjustment controls
   txt = 'Speed (in ms) lower is faster'
   x += bitMapSize~width + self~MARGIN_X
   cy = dlgSize~height - y - self~MARGIN_Y
   self~createGroupBox(IDC_STATIC, x, y, (bitMapSize~width * 2) + self~MARGIN_X, cy, , txt)

   -- And finally the speed adjustment controls them selves.  The top of a group box
   -- is higher than the top line of the group box (to allow for text.)  So in order
   -- for the speed controls to look centered with the group box lines, we need to
   -- calculate the center, adjust for the height of the controls, and then push it
   -- "down a bit."  I arbitrarily choose 3 as 'a bit.'
   x += self~MARGIN_X
   y += trunc((cy / 2) - (self~TEXT_Y / 2)) + 3
   txt = ' Faster :'
   txtSize = self~getTextSizeDU(txt)
   self~createStaticText(IDC_STATIC, x, y, txtSize~width, self~TEXT_Y, 'RIGHT CENTERIMAGE', txt)

   x += txtSize~width + 2
   self~createEdit(IDC_EDIT, x , y, 35, self~TEXT_Y, "NUMBER")

   x += 35
   self~createUpDown(IDC_UD, x, y, 25, self~TEXT_Y, "RIGHT ARROWKEYS AUTOBUDDY BUDDYINT HORIZONTAL NOTHOUSANDS", 'speed')

   x += 2
   self~createStaticText(IDC_STATIC, x, y, 30, self~TEXT_Y, 'LEFT CENTERIMAGE', ': Slower')

   -- Set the up down position to the initial speed.
   self~speed = initialSpeed

::method initDialog
   expose minSpeed maxSpeed notStopped jackPotCtrl speedCtrl

   self~newUpDown(IDC_UD)~setRange(minSpeed, maxSpeed)

   speedCtrl = self~newEdit(IDC_EDIT)
   speedCtrl~setLimit(maxSpeed~length - 1)
   ret = speedCtrl~connectCharEvent(onKey)

   jackPotCtrl = self~newStatic(IDC_STATIC_JACKPOT)

   notStopped = .true
   self~disableControl(IDC_PB_STOP)
   self~start("bandit")

-- An attempt to disallow cut and paste into the speed control.
::method onKey unguarded
  expose speedCtrl
  use arg key, shift, control, alt, info

  s = speedCtrl~selection

  if control then return .false -- Don't allow cut and paste
  if key == 57 then return .false
  else return .true

::method bandit unguarded
   expose x y z bmp. kind3 cycle maxCycle equal misses notStopped won

   rand =  random(1, 8, time('S') * 7) /* init random */
   ret = play("WHISTLE.WAV")

   -- The user could have canceled while the whistle was playing..
   if \self~isDialogActive then return 0

   self~enableControl(IDC_PB_STOP)

   do cycle = maxCycle by -1 to 1 until \self~isDialogActive
      if self~checkSpeed = 0 then leave

      sleep = format(max(1, min(100, self~speed / 2)), , 0)
      do j = 1 to self~speed / sleep
	       if  self~isDialogActive then call msSleep sleep
      end

      if \self~isDialogActive then return 0
      guard on when notStopped     -- Don't change the bitmaps out from under the user.

      if random(1, kind3) = 3 then do
         x = equal // 8 + 1; y = x; z = x; equal += 1
      end
      else do
         x = random(1, 8); y = random(1, 8); z = random(1, 8)
      end

      -- It's an error to invoke changeBitmapButton if the underlying dialog
      -- no longer exists.  (Which may be if the user hit cancel.)
      if \self~isDialogActive then return 0

      self~changeBitmapButton(IDC_PB_BMP_LEFT, bmp.x,,,,"INMEMORY STRETCH")
      self~changeBitmapButton(IDC_PB_BMP_CENTER, bmp.y,,,,"INMEMORY STRETCH")
      self~changeBitmapButton(IDC_PB_BMP_RIGHT, bmp.z,,,,"INMEMORY STRETCH")

      guard off
      if \self~isDialogActive then return 0
   end

   if \won then do
      self~disableControls

      msg = 'Sorry, you did not get the jackpot in '     || .endOfLine ||   -
            misses 'tries.' || .endOfLine~copies(2)                    ||   -
            'There were' equal 'chances. ('equal' three' || .endOfLine || -
            'of a kind were shown.)'
      title = 'End of run'
      ret = messageDialog(msg, self~hwnd, title, "OK", "INFORMATION")
   end

   -- Clean up is in our cancel method, so we invoke that rather than self~ok:super
   if self~isDialogActive then return self~cancel
   else return 1

::method disableControls private
   self~disableControl(IDC_PB_STOP)
   self~disableControl(IDCANCEL)
   self~disableControl(IDC_EDIT)
   self~disableControl(IDC_UD)

::method onStop
   expose x y z misses initPot notStopped won jackpotCtrl

   notStopped = .false  -- Prevent the 'bandit' from changing the bitmaps

   if ((x=y) & (y=z)) then do
      -- All 3 bitmaps are the same, jackpot.
      won = .true
      ret = play("tada.wav")
      self~setWindowTitle(self~get,"Congratulations !")
      do i = 40 by 20 to 120
         self~write(i*self~factorx,i*self~factory,"Congratulations...","Arial",14,'BOLD')
      end
      money = jackpotCtrl~getText
      self~write(10*self~factorx+5,75*self~factory,"You won the jackpot:" money,"Arial",18,'BOLD')
      do i=1 to min(money%500 + 1,10)
         ret = play("jackpot.wav")
         money = max(0,money - 500)
         jackpotCtrl~setText(money)
      end
      jackpotCtrl~setText(0)
      call msSleep 1000
      return self~cancel
   end

   -- Not 3 of a kind
   misses += 1
   ret = play("nope.wav", "yes")

   if ((x=y) | (y=z) | (x=z)) then do
        ret = infoDialog("2 equal, not bad, try again... jackpot reduced 25%")
        initPot = trunc(initPot * .75)
   end
   else do
        ret = infoDialog("Not a chance, try again... jackpot is halfed!")
        initPot = trunc(initPot * .5)
   end

   if initPot = 1 then ret = infoDialog("One more chance to hit the jackpot....")
   self~checkSpeed
   notStopped = .true  -- Unblock the 'bandit'
   return 0

::method checkSpeed
   expose minSpeed maxSpeed cycle initPot jackpotCtrl

   if \self~isDialogActive then return 0

   self~getDataAttribute('speed')

   -- Although the edit control is numbers only, and the up down control won't
   -- allow the user to spin outside of the range, or use the arrow keys to move
   -- outside of the range, it is possible for the user to delete all the
   -- numbers in the edit control, or to type in numbers larger than the
   -- maximum.  In which case the up down control seems to return the empty
   -- string for its position ??
   if self~speed == "" then do
     say 'Up down position: ' self~newUpDown(IDC_UD)~getPosition
     say 'Edit control text:' self~newEdit(IDC_EDIT)~getText
   end

   money = trunc(cycle * initPot / self~speed)
   jackpotCtrl~setText(money)

   return money


 -- You can not remove the bitmap handles while the dialog is still displayed
 -- on the screen.  As long as the dialog is showing, the os will try to
 -- repaint  the dialog when needed.  If the bitmaps are destroyed, the
 -- program will crash.  The leaving method is provided for just this purpose,
 -- it allows you to clean up resources.
::method leaving
   expose bmp.

   do i = 1 to bmp.0
     self~removeBitmap(bmp.i)
   end


::method cancel
   expose notStopped

   self~disableControls
   self~cancel:super

   call Play "byebye.wav"

   -- Be sure the bandit() method is not blocked so that this Rexx program ends.
   notStopped = .true

   return 1

::method calcSize private
   use strict arg bitMapSize

   s = .Size~new

   -- For the width of the dialog, we have 3 bitmaps, an X magin on both sides,
   -- and we space the bitmaps apart horizontaly using the X margin.  3 bitmaps
   -- and 4 X margins
   s~width = (3 * bitMapSize~width) + (4 * self~MARGIN_X)

   -- The height is sligthly more complicated.  It goes like this from top to
   -- bottom: Y margin, jackpot line, Y margin, bitmap height, Y margin, push
   -- button group height, Y margin.
   s~height = (4 * self~MARGIN_Y) + self~JACKPOT_LINE_Y + bitMapSize~height +  -
              self~getButtonGroupHeight

   return s

::method getButtonGroupHeight private
   expose h

   if \h~dataType('W') then do
      -- To calculate the height of the push button group, we need to know the
      -- height of a button, the number of buttons (2), and the vertical spacing
      -- between buttons.  It so happens that I know the vertical spacing is 1/2
      -- the button height, truncated.
      h = (2 * self~BUTTON_Y) + trunc(.5 * self~BUTTON_Y)
   end
   return h

