LiveCode 23 Matches

Posted by Scott McDonald

Another simple game. 23 Matches is a completely unoriginal classic game. There is a row of matches. You and another player, here you play against the CPU, take turns at removing 1, 2 or 3 matches from the end at the right. Your goal is to get to a position where at the end of your turn only one match remains. You do this because the player who is forced to take the last match is the loser. So if there is only 1 match left at the start of the CPU's turn you win! You will need to play smart because the CPU plays to win, some of the time.

In this game you will see:

  • A turn based game without a game loop
  • Object names used to simplify coding
  • How to autonomously move an image with a single line code
  • Images you can click and drag around the screen

Further below is the complete StartGame handler. This handler is called from the openCard handler and when the Play Again button is clicked. There are only 5 local variables on the card so this handler is quite short.

The sSmartCPU variable keeps track of whether the CPU player is going to play to win, or in a random way. When sSmartCPU is true, the algorithm used for the CPU player is optimal where (unless you are careful) it will win. When sSmartCPU is false, the CPU player takes a random number of matches each turn, and it can only win by luck.

The line:

put not sSmartCPU into sSmartCPU

means the CPU alternates between smart and random each time you play. This gives you a fighting chance of winning every second game. We don't want you to get too discouraged by making the CPU too smart. Who wants to play against a computer who wins every time? Boring.

Next a few variables are initialised so the human player goes first, 23 matches are visible, and none taken away.

Then a loop lines up the matches in neat row. Each match is a PNG image that was added to the card with the Import As Control, Image File command in the File menu. Actually, this command wasn't used for all the matches. After using it a few times, realising there must be a better way, the Import As Control, All Images in Folder command was used instead. After putting the remaining image files into a folder of their own, this command made the process too easy.

The images were created by taking a photo of actual matches on a black background and then using a bitmap image editor to remove the background. The surrounds were left transparent and each match was cut and copied into a separate PNG image file 25 by 100 pixels in size. Each image is larger than the actual match to make it easier to click and drag later in the game. The PNG image format allows transparency in the bitmap. You can also use the GIF format if you like. The images were named Match1.png, Match2.png, Match3.png and so on.

Using a row of real match images gives the game some class, and not repeating the same image 23 times helps make an otherwise simple game a little more impressive. After creating the 23 image controls, the .png extension was removed from the name of each control in the LiveCode Property Inspector.

This work and preparation with the images makes the loop to lay out the matches in a line a simple piece of code. Each image is made visible because after a game all the matches are invisible after being dragged off the "table". The table is another photo that has been suitably sized in a bitmap editor and imported as a control and then put behind the matches with the Send to back command in the Object menu.

Lastly, the two buttons are disabled and UpdateView is called to display the status below the table.

command StartGame
  local x
  put not sSmartCPU into sSmartCPU
  put empty into sMatchToDrag
  put true into sYourTurn
  put 23 into sNumberOfVisibleMatches
  put 0 into sRemoveCount
  put 100 into x
  repeat with loopMatchNumber = 1 to sNumberOfVisibleMatches
    set the loc of image ("Match" & loopMatchNumber) to x,280
    add 25 to x
    show image ("Match" & loopMatchNumber)
  end repeat
  disable button "butnPlayAgain"
  disable button "butnFinishTurn"
end StartGame

UpdateView has a few conditional statements that update the status indicating whose turn it is, and the number of matches that are left. An interesting part of this handler is the enabling and disabling of the Play Again and Finished Turn buttons.

In the first version of LiveCode 23 Matches, the buttons were never disabled. This meant that if a button was clicked at the wrong time, for example, a click on Finished Turn before taking any matches, required a message saying in effect, "You can't do that." That was fine, but depending on the game, it is better to prevent a player from being able to do an invalid action. Instead of allowing the action and then saying don't do that. Also not allowing invalid actions can make for easier programming because you no longer need to consider and handle events that shouldn't happen anyway. Hence the enabling and disabling of the buttons.

command UpdateView
  if sYourTurn then
    if sNumberOfVisibleMatches = 1 then
      enable button "butnPlayAgain"
      put "Game Over. I WIN!" into field "labeTurn"
      put "Your turn. " & RemainingMatches(sNumberOfVisibleMatches) into field "labeTurn"
    end if
    if sNumberOfVisibleMatches < 1 then
      enable button "butnPlayAgain"
      put "Game Over. You WIN. Do you want to play again to give me a chance to show how smart I am?" into field "labeTurn"
      put "My turn." into field "labeTurn"
    end if
    disable button "butnFinishTurn"
    put 0 into sRemoveCount
  end if
end UpdateView

The FinishedTurn handler is called by the button "butnFinishTurn" object. It ends your turn and then calls ProcessAI for the CPU to have a go.

command FinishTurn
  put false into sYourTurn
end FinishTurn

When the CPU is playing a smart game, a simple mathematical formula using the mod operator sets the best number of matches to take. I won't explain how it works. If you want to understand the details, but a careful search on Google may reveal the answer.

If the CPU is not playing smart, then a random number of matches are taken. Always taking at most one less than the number of visible matches if there are less than 4. You don't want the CPU to lose by taking the last match during it's own turn!

A loop then repeatedly waits a random interval, moves the right most match off the table, and hides the image. The move command is one of those neat LiveCode features that can achieve what would take many lines in a less high-level language.

move image ("Match" & sNumberOfVisibleMatches) to (880,150+random(300)) in 60 ticks

This command moves a control, here the match at the right end of the line to a location in a set amount of time. This is where the name of the image control for each match simplifies the code. The name of the visible match at the right end is always "Match" followed by the number of visible matches.

The match is moved to a location off the right end of the table, with a random value used for the y position to make the movement more interesting. Lastly, the move takes 60 ticks which is the same as one second. As the number of matches gets less and each match needs to move a further distance, the speed of movement increases so the distance is still covered in the one second. Perhaps it would be more "realistic" if the number of ticks had a random variation, but I think the variation in direction of the match movement is sufficient to make it seem more human. As with Pong in a simple game like this, little effects that make the CPU player seem more human make for more enjoyable gameplay.

After the loop to move the matches if there are any left, then it is your turn and the status is updated.

command ProcessAI
  local takeCount
  put 0 into takeCount
  if sSmartCPU then put (sNumberOfVisibleMatches - 1) mod 4 into takeCount
  if takeCount = 0 then put random(max(1,min(3,sNumberOfVisibleMatches - 1))) into takeCount
  repeat takeCount times
    wait 1 + random(60) ticks
    move image ("Match" & sNumberOfVisibleMatches) to (880,150+random(300)) in 60 ticks
    hide image ("Match" & sNumberOfVisibleMatches)
    subtract 1 from sNumberOfVisibleMatches
  end repeat
  if sNumberOfVisibleMatches > 0 then put true into sYourTurn
end ProcessAI

RemainingMatches is a small handler that is called when displaying the number of remaining matches. Computers are stupid, but I still find it annoying when software uses incorrect grammar like 1 matches remain. Sure, you could get around the problem by using text like 1 match(es) remain(s) but that is ugly and emphasises that you are playing against an unthinking computer. Given only a few lines are needed, why not use a handler like RemainingMatches to give the game some polish?

function RemainingMatches pNumber
  if pNumber = 1 then
    return pNumber & " match remains."
    return pNumber & " matches remain."
  end if
end RemainingMatches

The next three handlers let the player click and drag to remove matches from the table. In mouseDown a check is made that it is actually your turn and then there is an interesting line.

put the short name of the target into targetName

The target is the object that first receives a message, which in this case is the mouseDown messge sent to the image control you clicked on. Using the short name phrase is a way of getting just the name, i.e. what you see in the Name field of the Property Inspector for the control and throwing away details like the type of control it is. Since we know only the match image controls have a short name starting with "Match" the short name is adequate to identify a click on a match.

When a match has the mouseDown message, a check is made that it is the one at the right end and the name is stored in the sMatchToDrag variable on the card. This variable is on the card (not in the handler) because it must maintain its value after the mouseDown handler ends.

on mouseDown
  local targetName
  if sYourTurn then
    put the short name of the target into targetName
    if targetName = "Match" & sNumberOfVisibleMatches then
      put targetName into sMatchToDrag
    end if
  end if
end mouseDown

While the mouse button is kept pressed down the mouseMove handler moves the match object until mouseUp is called when the button is released. Then a check is made to see if the match has been dragged off the table. If it is, the match image is hidden and the number of visible matches is reduced by 1. If this is the third match that has been removed the turn automatically ends, otherwise the status is updated and the Finish Turn button is enabled.

The button is enabled here because at the start of your turn it is disabled. This ensures that you take at least one match before ending your turn.

on mouseMove
  if sMatchToDrag is not empty then
    set the loc of image sMatchToDrag to the mouseLoc
  end if
end mouseMove

on mouseUp
  if sMatchToDrag is not empty then
    if the mouseLoc is not within the rectangle of image "Table.png" then
      hide image sMatchToDrag
      subtract 1 from sNumberOfVisibleMatches
      add 1 to sRemoveCount
      if sRemoveCount = 3 then
        put RemainingMatches(sNumberOfVisibleMatches) into field "labeTurn"
      end if
      enable button "butnFinishTurn"
    end if
    put empty into sMatchToDrag
  end if
end mouseUp

This ends the code for the game. Nothing interesting in the openCard handler to comment on. As you can see a game loop is not used. Instead nothing happens until a button is clicked, or an image control (of a match) has a click and drag with the mouse. The 23 Matches code does nothing until the LiveCode engine (which is working all the time behind the scenes) sends a message about an event started by a mouse click.

That's about it. Preparing the image controls seemed like almost as much work as the actual coding. The ability of LiveCode to work with images, including motion, in few lines makes the required coding quite easy. That's one reason why I use LiveCode for making games and other software.

Download the complete LiveCode 23 Matches stack made in LiveCode 6.1.2 from here:LiveCode 23 Matches.livecode.

Happy LiveCoding.

Credit: This version used the game 23 Matches in BASIC Computer Games, 1978 edition as a starting point for the idea.

Tagged: beginner logic turn based

Wednesday, November 27, 2013

0 Responses

Be the first to make a comment.



Legal Stuff

All blog posts are copyright and cannot be re-used without permission. But all the code and scripts are dedicated to the public domain. Use such code and scripts in any way you want, but I am not responsible if they don't work for you.


Further comments or feedback? You can contact me by email at: