DEV Community

Cover image for Coin Collector Game
Sebastian Nozzi
Sebastian Nozzi

Posted on

Coin Collector Game

In this post we'll create a "coin collector" game for the Mini Micro.

Coin collector game screenshot

The idea is taken from the book Computer Coding Python Games for Kids.

Rules

The game works as follows:

  • There is a fox and a coin on the screen.
  • You move the fox with the arrow keys.
  • When the fox collects the coin, the score is increased and a new coin is placed somewhere else on the screen.
  • You have a certain amount of time to collect as many coins as possible.
  • When the time is up the game is over and you are shown the final score.

By the way, if you are already Mini Micro expert you might try implementing the game with these rules and then comparing with the implementation on this post! Otherwise, keep reading ...

Assets

I'll be using the assets made available for readers of the book (which I borrowed from my local library).

I didn't read anywhere that they could not be used for other languages other than Python. As I understand it, we'll be using these assets for personal / fair use - as they are freely available on the Internet.

What I will not be doing is re-publishing these assets somewhere else; nor claiming these to be my own. Neither should you. So I think we all should be fine.

Project setup

Download the assets file.

Create a project folder and navigate there. Alternatively create a new "usr" folder and mount it.

Put the two images (fox and coin) of the coin collector game in your project folder.

Within Mini Micro, save an empty file "game.ms" on your project location.

Placing the fox

Let's start by loading the fox's image and placing it as a sprite on the screen:

// Fox Sprite

foxImg = file.loadImage("fox.png")

fox = new Sprite
fox.image = foxImg
fox.x = 200
fox.y = 200

// Game setup

clear

gfx.clear "#308A37"
sprd = display(4)

sprd.sprites.push fox
Enter fullscreen mode Exit fullscreen mode

Note the following:

  • We created to "sections" to organize our code ("Fox Sprite" and "Game setup")
  • We are making use of the pre-defined SpriteDisplay at slot 4
  • We are making use of the pre-defined PixelDisplay accessible via the global "gfx". We only use it to fill (clear) it with a background color (same as in the original implementation)

Running this should show the fox on the screen.

Fox sprite on the screen

Moving the fox around

Modify the fox-sprite block to add a "speed" property:

fox = new Sprite
fox.image = foxImg
fox.speed = 10
...
Enter fullscreen mode Exit fullscreen mode

And at the end of the program add the following while-loop:

// Game Loop

while true

    if key.pressed("left") then
        fox.x -= fox.speed
    else if key.pressed("right") then
        fox.x += fox.speed
    else if key.pressed("up") then
        fox.y += fox.speed
    else if key.pressed("down") then
        fox.y -= fox.speed
    end if

    yield
end while
Enter fullscreen mode Exit fullscreen mode

When running the program the fox should now move with the arrow keys.

Fox moving around

(In order to keep this post short and the program simple we won't be checking if the fox leaves the screen. It's up to you to keep it inside!)

Showing the coin

Now it's turn to show the coin somewhere on the screen.

Let's add this new section after the "Fox Sprite" one, before the "Game Setup":

// Coin Sprite

coinImg = file.loadImage("coin.png")

coin = new Sprite
coin.image = coinImg
coin.x = 500
coin.y = 500
Enter fullscreen mode Exit fullscreen mode

And we should not forget to add the coin sprite to the sprite-display. In the "Game Setup" section add this line:

sprd.sprites.push coin
Enter fullscreen mode Exit fullscreen mode

This should give you a movable fox and a static coin.

Fox and coin

Collision detection

As a next step we need to add local bounds so that collision detection works.

First we add local bounds to the fox sprite. These lines could go right after assigning the image:

fox.localBounds = new Bounds
fox.localBounds.width = foxImg.width
fox.localBounds.height = foxImg.height
Enter fullscreen mode Exit fullscreen mode

Same thing for the coin sprite:

coin.localBounds = new Bounds
coin.localBounds.width = coinImg.width
coin.localBounds.height = coinImg.height
Enter fullscreen mode Exit fullscreen mode

Let's test if collision detection works. Add this check inside the while-loop, before the "yield":

if fox.overlaps(coin) then
    print "Fox gets coin!"
end if
Enter fullscreen mode Exit fullscreen mode

Try it out. You should see the text written many times (as the collision is detected many times per second).

Fox gets the coin

Placing the coin randomly

Now, let's start fleshing out the proper game logic. Replace the above lines with these ones:

if fox.overlaps(coin) then
    coin.moveToRandomLocation
end if
Enter fullscreen mode Exit fullscreen mode

We of course need to write this new method moveToRandomLocation for the coin object. Add this to the "Coin Sprite" section:

coin.moveToRandomLocation = function
    self.x = floor(rnd * 960)
    self.y = floor(rnd * 640)
end function
Enter fullscreen mode Exit fullscreen mode

Note the usage of the "rnd" and "floor" intrinsic functions.

Try it out. You will see that except for the score and timer it looks like the game we want to have!

Coin at random locations

Score

Let's add a new section "Score" before "Game Setup". We will write a small object to take care of the score.

// Score

score = {}
score.value = 10
Enter fullscreen mode Exit fullscreen mode

For printing the score we will use the pre-allocated TextDisplay. Because we will be updating the score text on the screen many times, we will also some way of deleting the old content.

Let's start by creating a new section with these helper functions:

// Text helper functions

eraseText = function(row,column,length)
    text.row = row
    text.column = column
    // Print "length" amount of spaces, with no "newline"
    print " " * length, ""
end function

printTextAtPosition = function(txt,row,column,txtColor)
    // Save current color and set color from parameter
    previousColor = text.color
    text.color = txtColor
    // Move to position and print text
    text.row = row
    text.column = column
    print txt
    // Restore previous color
    text.color = previousColor
end function
Enter fullscreen mode Exit fullscreen mode

With these helper functions we can easily implement the score-rendering. Add this method to the "Score" section:

score.print = function
    eraseText 25, 5, "SCORE: 999999".len
    printTextAtPosition "SCORE: "+self.value, 25, 5, color.white
end function
Enter fullscreen mode Exit fullscreen mode

As you can see we reserve an area for quite a big score number, which we'll probably never reach.

Finally, add a first "score.print" call at the end of the "Game Setup" section:

score.print

// Game Loop 
...
Enter fullscreen mode Exit fullscreen mode

Try it out.

Initial score

Updating the score

The score now appears on the screen, but is not is not yet updated when coins are collected. We will now change that.

Let's add a method to increase and update the score on screen:

score.increase = function
    score.value += 10
    score.print
end function
Enter fullscreen mode Exit fullscreen mode

Of course this alone is not enough. The method needs to be invoked when the fox collects a coin. Modify the corresponding if-block in the game-loop to read like this:

if fox.overlaps(coin) then
    coin.moveToRandomLocation
    score.increase
end if
Enter fullscreen mode Exit fullscreen mode

Coins increasing score

Displaying the countdown

The fox can now collect coins at leisure ... but that's about to change. Now we'll add a timer to put some pressure.

We will start slow and just showing a countdown on screen.

Let's create a "Countdown" section right after the "Score" one, initially with these lines:

// Countdown

countdown = {}
// Countdown value in seconds
countdown.value = 0
Enter fullscreen mode Exit fullscreen mode

As a first step, let's write a "start" method with which we can start / initialize the countdown:

countdown.start = function(initialValue)
    self.value = initialValue
    self.print
end function
Enter fullscreen mode Exit fullscreen mode

Accordingly we'll add the method to render the countdown text on screen:

countdown.print = function
    eraseText 25, 50, "TIME: 9999".len
    printTextAtPosition "TIME: "+self.value, 25, 50, color.white
end function
Enter fullscreen mode Exit fullscreen mode

Again, for reserving a printable area (erasing the previous text) we are choosing a very generous time value.

Now let's add an invocation to this "start" step when setting up the game. In the "Game Setup" section add this:

countdown.start 20
Enter fullscreen mode Exit fullscreen mode

Feel free to decide the amount of seconds which best work for you.

If you try this out you should see the countdown being displayed on screen. But it does nothing yet. We'll change that in the next section.

Static countdown

Counting down

At this point we need to stop and think: how would the countdown logic work?

We can do the following:

  • The countdown starts with an initial value (of seconds)
  • Inside the game-loop we'll call an "update" method on the countdown object
  • The method will check how much time it elapsed since it has last decreased its value
  • If more than one second elapsed, the countdown will decrease its value and register this new "last decrease timestamp".

The strategy is clear, but some important pieces are missing. Besides the obvious "update" method we need to add a new property to keep track of "decrease events".

Add this after the "value" property to the countdown object:

countdown.lastDecreaseTs = -1
Enter fullscreen mode Exit fullscreen mode

We use some weird value like "-1" to make clear that it needs to be initialized. We will do just that now. Modify the "start" method to read like this:

countdown.start = function(initialValue)
    self.value = initialValue
    self.lastDecreaseTs = time
    self.print
end function
Enter fullscreen mode Exit fullscreen mode

By initializing it to the current timestamp we will have one full second before we need to decrease its value for the first time.

Now we are in position to implement our countdown logic inside the "update" method. Add this method to the countdown object:

countdown.update = function
    currentTs = time
    elapsed = currentTs - self.lastDecreaseTs
    // A second elapsed since last check
    if elapsed > 1 then
        self.decrease
        self.lastDecreaseTs = currentTs
    end if
end function
Enter fullscreen mode Exit fullscreen mode

It references a "decrease" method which we do not yet have. That method should decrease the value internally and update it on screen. Let's add it now:

countdown.decrease = function
    self.value -= 1
    if self.value < 0 then self.value = 0
    self.print
end function
Enter fullscreen mode Exit fullscreen mode

Because we do not want negative numbers in our countdown, we add a corresponding check.

Finally we need to update our game-loop to regularly call the "update" method. We can put that invocation right at the top of our while-loop:

while true

    countdown.update

    if ...
Enter fullscreen mode Exit fullscreen mode

When you try it out you will see that our game is almost done!

Collecting coins with no limit

Stopping the game

If you play the game in its current state you will merrily be able to still collect coins even if the time is up. Time to change that.

Let's add a small method to the countdown to indicate that it is finished:

countdown.isFinished = function
    return self.value <= 0
end function
Enter fullscreen mode Exit fullscreen mode

And now let's add a check in the game loop. Right after decreasing the countdown we will add this line:

if countdown.isFinished then break
Enter fullscreen mode Exit fullscreen mode

This will effectively get us out of the while-loop when the countdown is done. As it is, the game just ends. You can of course see your final score on the screen.

But we should be able to make it nicer.

A better ending

First, let's add this function to our "Text helper functions" section which will allow us to print text centered on the screen:

printTextCentered = function(txt,row,txtColor)
    totalColumns = 68
    column = totalColumns / 2 - txt.len / 2
    printTextAtPosition txt,row,column,txtColor
end function
Enter fullscreen mode Exit fullscreen mode

With this let's print the final score and ask the user to play again or not. Add this outside (after) our game-loop:

// Game ending

printTextCentered "FINAL SCORE: " + score.value, 16, color.white
printTextCentered "Do you want to play again? (y/n)", 14, color.white
Enter fullscreen mode Exit fullscreen mode

The user should press either key "y" or key "n". Because the user will probably still fiercely holding an arrow key, we need to "consume" these until we detect any of the keys we want.

Add this next to our section:

while true
    k = key.get.lower
    if k == "y" then run
    if k == "n" then break
end while
Enter fullscreen mode Exit fullscreen mode

If the user presses "y" the program will be run again.

But if the user presses "n" this while-loop is exited and the program is over. Before that happens, let's add a short farewell message. Add this after this while-loop:

print
print "Bye!"
Enter fullscreen mode Exit fullscreen mode

And you could say we are done!

Complete gameplay

Try it out and have fun!

Final words

Glad you made it this far. Did you like this game tutorial? Did you learn something new? How many coins can you collect? Let us know in the comments.

Happy coding in Mini Micro!

Top comments (4)

Collapse
 
joestrout profile image
JoeStrout

I love it! This is a great tutorial. It shows a lot of key concepts central to almost any game.

One suggestion: rather than explicitly checking for the arrow keys, you might use key.axis, like so:

   fox.x += key.axis("Horizontal") * fox.speed
   fox.y += key.axis("Vertical") * fox.speed
Enter fullscreen mode Exit fullscreen mode

It's not only less code, but also lets the user use WASD or a gamepad if they so choose.

But in any case, it's a fantastic post and I hope you'll do more!

Collapse
 
sebnozzi profile image
Sebastian Nozzi

As always, there is no shortage of things to learn about Mini Micro.

Thanks for the suggestion! I am planning on writing shorter "recipes" tutorials for Mini Micro and one will be "moving things around", so this will come in handy.

I'm glad you liked the tutorial.

Collapse
 
sebnozzi profile image
Sebastian Nozzi

I don't seem to be able to score more than 160 ...

Collapse
 
sebnozzi profile image
Sebastian Nozzi

By the way, the complete code can be found here: gist.github.com/sebnozzi/94c7890fc...