## Adding some "features" to our Card Game.

Ok,
the first demo of this card game was rather simple, so we're going to add a few features to this one to spice it up a bit.

The first thing to add is a bit of sound, of course, so whenever a new card is drawn on the screen, we'll play a simple .wav file, to give the user an audio cue. Since this will be played from a number of locations, I simply wrapped the playSound() function into my own sound_off() procedure.

The previous game demo was also a bit slow, since sleep() can only be called with a minimum value of one second, so I've written a simple delay loop procedure, which I call stall(), so I can get some shorter time delays into the game's behavior.

Next, the most important new feature, is, of course adding mouse input to the game, so that cards to be played can be selected by a simple mouse click, instead of the keyboard. This will be done in the on_bitmap() routine, which is called whenever the mouse is on our program's window, by an onMouse[] event handler.

Since this routine will be called whenever the mouse is moved over our game's window, the first thing we do is check if the left mouse button is pressed down. If it's not, the routine falls through, without doing all the other checks, as quickly as possible.

The next big 'stack' of if statements check to see where the mouse is, when it's been clicked, to determine which card the user has selected.

Here's an explanation of how this is done.
The first things to keep in mind, are the dimensions of the 'dealpix' pixmap, and it's location in the client area of our program's window.
Since the pixmap is 131 by 98 pixels in size, and it's top-left corner is always located in our client area at 80 by 140 pixels, it's bottom-right corner is located at 210 by 237 pixels.

These two locations on our window, establish the 'total' zone we check for, when the left mouse button is clicked.
To determine which of four cards is being selected, keep in mind that the cards are overlapped so that only 20 pixels for each card are shown. This divides our 'total' area into 4 zones on the left side, from which we can calculate which card has been clicked, leaving a remainder on the far right for the last card, since it's shown fully.

While the above description shows how the bitmap is split up for mouse selection, you'll note that I've used greater-than, ( > ), and less-than, ( < ), ( ..and +1, -1 ) in my calculations. This was done to create a dead-zone within the bitmap that would *not* be sensitive to selection. The 3 pixels 'between' the cards, for example, won't react to a mouse click so the user can't select the wrong card 'accidentally".

-- card-trix-2.exw

    --
    include win32lib.ew
    without warning

    sequence deck, cards
    deck = {1, 2, 3, 4, 5}
    cards = {"Ten", "Jack", "Queen", "King", "Ace"}
    integer ready, picked, myscore, yourscore, cheat
    ready = 0
    myscore = 0
    yourscore = 0
    cheat = 0

    ----

    constant
    msg0 = "...dealing cards.",
    msg1 = "...draw a card ( 1 to 4 )",
    msg2 = "You Lose  : )",
    msg3 = "You Win  !!",

    MyWin    = create( Window, "Card-Trix-2", 0, 10, 10, 300, 320, 0 ),
    yours    = create( LText, "You", MyWin, 5, 10, 100, 20, 0 ),
    mine     = create( RText, "Me", MyWin, 180, 10, 100, 20, 0 ),
    menuBar  = create( Menu, "&Program", MyWin, 0, 0, 0, 0, 0 ),
    menuCheat = create( MenuItem, "&Cheat", menuBar, 0, 0, 0, 0, 0 ),
    menuOut  = create( MenuItem, "E&xit", menuBar, 0, 0, 0, 0, 0 ),
    statBar  = create( StatusBar, "", MyWin, 0, 0, 0, 0, 0 ),
    deckPix  = create( Pixmap, "", 0, 0, 0, 421, 71, 0 ),
    dealPix  = create( Pixmap, "", 0, 0, 0, 131, 98, 0 ),
    playPix  = create( Pixmap, "", 0, 0, 0, 149, 98, 0 ),

    myTimer  = 1,
    myTimer2 = 2
    ----

    sequence temp_wins = { MyWin, yours, mine } -- fix a snag
    for i=1 to length(temp_wins) do
        setWindowBackColor( temp_wins[i], Green )
    end for

    setFont( yours, "System", 0, 0 )
    setFont( mine, "System", 0, 0 )
    setPixmap( deckPix, loadBitmapFromFile( ".\\Bin\\cardtrix.bmp" ) )

    ----

    function shuffle( sequence s )
    integer r
    object temp
      for i = length( s ) to 1 by -1 do
        r = rand( i )
        temp = s[r]
        s[r] = s[i]
        s[i] = temp
      end for
      return s
    end function

    ----

    procedure green( )
      setPenColor( dealPix, Green )
      drawRectangle( dealPix, 1, 0, 0, 131, 98 )
      copyBlt( MyWin, 80, 140, dealPix )
      setPenColor( playPix, Green )
      drawRectangle( playPix, 1, 0, 0, 149, 98 )
      copyBlt( MyWin, 70, 35, playPix )
    end procedure

    ----

    -- play pop.wav every time a card is drawn
    procedure sound_off( )
      object jk
      jk = playSound( ".\\Bin\\pop.wav" )
    end procedure

    ----

    function anything_but( integer x )
    integer r
      r = rand( 3 )
      if r >= x then r += 1 end if
      return r
    end function

    ----

    procedure play( integer picked )
    integer pos, rnd, you, me
      if ready = 1 then
        ready = 0
        setText( statBar, "...you drew the " & cards[deck[picked]] )
        you = deck[picked]
        pos = deck[picked]*70
        bitBlt( playPix, 0, 0, deckPix, pos, 0, 71, 98, SrcCopy )
        copyBlt( MyWin, 70, 35, playPix )
        sound_off( )
        sleep( 1 )
        rnd = anything_but( picked )
        me = deck[rnd]
        pos = deck[rnd]*70
        bitBlt( playPix, 78, 0, deckPix, pos, 0, 71, 98, SrcCopy )
        copyBlt( MyWin, 70, 35, playPix )
        sound_off( )
        sleep( 2 )
        if me > you then
          setText( statBar, msg2 )
          myscore += 1
          setText( mine, "Me " & sprintf( "%d", myscore ) )
        else
          setText( statBar, msg3 )
          yourscore += 1
          setText( yours, "You " & sprintf( "%d", yourscore ) )
        end if
        sleep( 3 )
        green( )
        deck = shuffle( deck )
        setTimer( MyWin, myTimer, 500 )
      end if
    end procedure

    ----

    procedure start_up( atom self, atom event, sequence params )
      green( )
      deck = shuffle( deck )
      setText( statBar, msg0 )
      setTimer( MyWin, myTimer, 500 )
    end procedure
    setHandler( MyWin, w32HOpen, routine_id( "start_up" ) )

    ----

    -- delay routine
    procedure stall( atom x )
      atom delay
      delay=time( )
      while time( ) < delay + x do
      end while
    end procedure

    ----

    procedure do_deal( atom self, atom event, sequence params )
       atom timerId = params[1]
      if timerId = 1 then
        killTimer( MyWin, myTimer )
        setTimer( MyWin, myTimer2, 2500 )
        setText( statBar, msg0 )
        if cheat = 0 then
          for i = 0 to 3 do
            -- give visual cue, as well as sound
            bitBlt( dealPix, i*20, 0, deckPix, 0, 0, 71, 98, SrcCopy ) --**
            copyBlt( MyWin, 80, 140, dealPix )                         --**
            bitBlt( dealPix, i*20, 0, deckPix, 0, 0, 71, 98, PatInvert )
            copyBlt( MyWin, 80, 140, dealPix )
            sound_off( )
            stall( .1 )
            bitBlt( dealPix, i*20, 0, deckPix, 0, 0, 71, 98, SrcCopy )
            copyBlt( MyWin, 80, 140, dealPix )
            stall( .4 )
          end for
        else
          -- don't bother with visual cue here, but,
          -- show first card to 'cheat'...
          bitBlt( dealPix, 0, 0, deckPix, deck[1]*70, 0, 71, 98, SrcCopy )
          copyBlt( MyWin, 80, 140, dealPix )
          stall( .5 )
          for i = 0 to 3 do
            sound_off( )
            bitBlt( dealPix, i*20, 0, deckPix, 0, 0, 71, 98, SrcCopy )
            copyBlt( MyWin, 80, 140, dealPix )
            stall( .5 )
          end for
        end if
      elsif timerId = 2 then
        killTimer( MyWin, myTimer2 )
        ready = 1
        setText( statBar, msg1 )
      end if
    end procedure
    setHandler( MyWin, w32HTimer, routine_id( "do_deal" ) )

    ----

    procedure kill_timers( )
      killTimer( MyWin, myTimer )
      killTimer( MyWin, myTimer2 )
    end procedure

    ----

    procedure cheat_deal( atom self, atom event, sequence params )
      kill_timers( )
      ready=0
      if cheat = 0 then cheat = 1 else cheat = 0 end if
      green( )
      do_deal( 0, 0, {1} )
    end procedure
    setHandler( menuCheat, w32HClick, routine_id( "cheat_deal" ) )

    ----

    procedure pick( atom self, atom event, sequence params )
      atom key = params[1]
      if key > 48 and key < 53 then
        picked = key - 48
        play( picked )
      end if
    end procedure
    setHandler( MyWin, w32HKeyDown, routine_id( "pick" ) )

    ----

    procedure on_bitmap( atom self, atom event, sequence params )
    atom x = params[2]
    atom y = params[3]
    integer picked
      if event = LeftDown then
        if x > 80 then       -- left x co-ordinate of 'total'
          if x < 210 then    -- right x co-ordinate of 'total'
            if y > 141 and y < 237 then  -- y co-ordinates of 'total'
              picked = 0
              for i = 80 to 140 by 20 do -- 'overlapped' zones
                -- leave a 'dead' zone between cards !
                if x > i+1 and x < i + ( 20 - 1 ) then
                  picked = floor( ( i - 80 ) / 20 ) + 1
                end if
              end for
              if x > 140 and x < 210   -- 'remainder' of last card
                then picked = 4
              end if
              if picked != 0 and ready = 1 then
                play( picked )
              end if
            end if
          end if
        end if
      end if
    end procedure
    setHandler( MyWin, w32HMouse, routine_id( "on_bitmap" ) )

    ----

    procedure re_paint( atom self, atom event, sequence params )
      copyBlt( MyWin, 80, 140, dealPix )
      copyBlt( MyWin, 70, 35, playPix )
    end procedure
    setHandler( MyWin, w32HPaint, routine_id( "re_paint" ) )

    ----

    procedure quit( atom self, atom event, sequence params )
      closeWindow( MyWin )
    end procedure
    setHandler( menuOut, w32HClick, routine_id( "quit" ) )

    ----

    WinMain( MyWin, Normal )

    ----

I've used a little bitBlt() trick within the do_deal() routine to momentarily change the color of the dealt card, by using 'PatInvert' as the last bitBlt() parameter.

But, we're not done yet !
Our program is still not very responsive to the user. Clicking on the menu while cards are being drawn, for example, does nothing until the drawing routine is finished, so there's more work to do ;-)

..end of lesson.

CONTENTS