In this post we'll create a "coin collector" game for the Mini Micro.
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
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.
Moving the fox around
Modify the fox-sprite block to add a "speed" property:
fox = new Sprite
fox.image = foxImg
fox.speed = 10
...
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
When running the program the fox should now move with the arrow keys.
(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
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
This should give you a movable fox and a static 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
Same thing for the coin sprite:
coin.localBounds = new Bounds
coin.localBounds.width = coinImg.width
coin.localBounds.height = coinImg.height
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
Try it out. You should see the text written many times (as the collision is detected many times per second).
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
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
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!
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
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
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
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
...
Try it out.
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
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
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
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
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
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
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.
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
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
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
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
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 ...
When you try it out you will see that our game is almost done!
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
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
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
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
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
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!"
And you could say we are done!
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)
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: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!
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.
I don't seem to be able to score more than 160 ...
By the way, the complete code can be found here: gist.github.com/sebnozzi/94c7890fc...