Click here to Skip to main content
Click here to Skip to main content

Pairs game

, 15 Aug 2012 BSD
Rate this:
Please Sign up or sign in to vote.
How to create a pairs game for about 90% of Smartphones?
  Content

Introduction

Pairs, also known as Concentration, Memory cards, Pexeso etc. is a simple card game, where player must find two identical cards. This game is great for memory training and good to kill some time for kids and adults. Nevertheless, our goal is to show developers how to create an easy game like this with only a few lines of code for many mobile operation systems.

Background

The best option for creating a game for more than one platform is using a cross-platform SDK. This article talks about cross-platform development tools called Moscrif SDK. At present, iOS, Android and Bada devices are supported.

Our applications can be run on many different devices from low-end devices with only 600mHz processor to latest high-end devices like newest iPad, Samsung Galaxy S 3 etc. These devices have different performance capabilities and screen resolutions. The best way to overcome different picture dimensions is using vector graphics; however, this is not always possible.

User interface 

By now, applications are being developed at rapid pace and it usually comes down to offering a user-friendly interface to earn customers’ loyalty. Our game application consists of tool bar with only two buttons (close and new game) and timer. Main part of our application is a grid with twelve cards. The cards are uncovering with a nice 3D rotation effect.

Background and toolbar are filled with bitmap graphics, which is created separately for three resolutions. Simple image on the cards allows us to use vector graphics for them. Vector graphics maintain all details also after resizing. Vector graphics are smaller in size than bitmap.

Image: graphics proposal



Development process

To develop this game we used Moscirf’s game framework (Game, Scene, Layer, sprite, GameControl and ImageButton classes).

  • Game class is a base class, which creates environment to run our game. It manages user and system events and contains other scene or sprite objects.
  • Scene is a part of our game which contains several layers or directly Sprite objects. Developer can also manage user and system events in scene.
  • Layer is used to create separate parts of scene like menu, playground etc.
  • Sprites create final objects like pucks, balls, paddles etc. the Sprites objects do not manages user events. They can be added to scene, layer or game objects.
  • GameControl class's instances can be added to scenes, layers or directly to the game object but it also manages user events. In our game Card class is extended from GameControl class.

Start up file

By default, the start up file is main.ms. We put only an instance of Game class into this file, draw a background. It also manages event onKeyPressed. This event is raised when user hits the hardware key.

Example: start up file

include "lib://game2d/game.ms"
include "lib://game2d/layer.ms"
include "lib://game2d/imageButton.ms"
include "app://vectorImage.ms"
 
include "app://resources.ms"
include "app://cardLayer.ms"
include "app://gameScene.ms"
include "app://card.ms"
include "app://menuLayer.ms"
include "app://timerSprite.ms"
 
var game = new Game();
var res = new Resources()
 
// onstart what top do
game.onStart = function(sender) {
    this.gameScene = new GameScene({width : System.width, height : System.height});
    this.push(this.gameScene);
}
 
// clear screen
game.onDraw = function(sender, canvas) {
    canvas.drawBitmapRect(res.images.background, 0, 0, res.images.background.width, res.images.background.height, 0, 0, System.width, System.height);
}
 
game.onKeyPressed = function(sender, keyCode)
{
    // quit game when user click onto back hardwaere key
    if (keyCode == #back)
        this.quit();
}
 
game.run();

GameScene 

The game scene is made of only one scene in our game. This scene has two layers, first which creates all cards and second which creates menu.

Card

Every card is an instance of Card class. This class provides three features:

  • draw a card
  • show a front side of card
  • hide a front side of card

Card turns with 3D effects. Moscrif SDK offers View3D object, which manages object transform in 3D world. View3D only calculates transformations for normal canvas. This means that programmers only need to apply transform to the canvas.

this._view3D.applyToCanvas(canvas);

To rotating are used functions rotate(angle), rotateY(angle) and rotateZ(angle).

this._view3D.rotateY(degrees); 

By default, canvas is rotating around left top corner of the screen, therefore a card rotates unrealistically.

Image: card rotation





To achieve a realistic rotation effect, center of rotation has to be moved to the middle. Canvas is also scaled to create gabs between the cards.

Example: draw card

// drawing
function draw(canvas)
{
    super.draw(canvas);
 
    canvas.save();
    canvas.translate(this.x, this.y);
    canvas.scale(0.85, 0.85);
    // apply 3d rotation
    this._view3D.applyToCanvas(canvas);
    if (this._side == #front) {
        this._drawFront(canvas);
    } else {
        this._drawBack(canvas);
    }
    canvas.restore();
}

Image: card rotation




Animate cards 

Cards are rotated by two functions show and hide. These functions create animator objects. The animator object creates mathematical background for the animation. The most important animator’s properties are duration and transition. Using this class the animation does not have to have linear behaviour. According to the required transition the animator object calls call-back function specified with addSubject function. The call back function has only one parameter the state, which specifies the current position in the animation.

In our game we apply rotation to View3D in call back function. Because of the view3D rotates from the last position, not from the base position (without rotation) we need to restore view3D first to base position (without rotation) and then rotate it

Example: show card

function show()
{
    this.rotating = true;
    var animator = new Animator({
        transition: Animator.Transition.easeOut,
        duration: 1500,                         // length of animation in miliseconds
    });
    animator.addSubject(function(state) {       // state starts from 1.0 to 0.0
        var self = this super;
        self._view3D.restore();
        self._view3D.save();
        self._view3D.rotateY(state * 180);
        if (state * 180 > 90) {
            self._side = #front;
        }
    });
    animator.onComplete = function() {
        this super.rotating = false;
    };
    animator.play();
}

Card layer 

Card layer creates grid of cards and distributes images onto them.

Cards distribution

Each time game is restarted images on the top side of a card have to be redistributed. Pictures are loaded from resources and then pushed to availableImages array. Then, all cards are pushed to freeCards array. This array contains all cards, where we need to change images. The distribution continues in three steps, until all image are distributed:

  1. Load random image, and remove this loaded image from availableImages
  2. Set image to first card and remove this card from freeCards
  3. Set same image to the second card and remove this card from freeCards

This algorithm allows to distribute images very fast with a minimum number of cycle runs.

Image: distribute images onto cards




Example: distribute images onto cards

//fill image grid with random images
function _fillImages()
{
    // load images into resources
    res.getCards();
    // mark all card images as available
    var availableImages = res.images.cards;
    // mark all Cards
    var freeCards = [];
    for (var card in this._cards)
        freeCards.push(card);
    var needed = this._rows * this._columns / 2;
    for (var i=0; i < needed; i++) {
        // no more empty cards?
        if (freeCards.length == 0)
            return;
        // get random vector file
        var randomImageIndex = rand(availableImages.length);
        var imageFile = availableImages[randomImageIndex];
        // remove loaded file from available images
        availableImages.removeByValue(imageFile);
 
        // initialize cell
        var randomCardIndex = rand(freeCards.length);
        // create & write cell view
        freeCards[randomCardIndex].cardImage = imageFile;
        // remove cell from freeCards
        freeCards.removeByValue(freeCards[randomCardIndex]);
 
        // initialize cards's pair (random)
        randomCardIndex = rand(freeCards.length);
        freeCards[randomCardIndex].cardImage = imageFile;
        freeCards.removeByValue(freeCards[randomCardIndex]);
    }
    availableImages = null;
}

Cards comparison 

When two cards are uncovered, the card layer also compares these two cards. If both cards are same, they stay uncovered, otherwise they turn back after a short delay. When two cards are uncovered the _turnCards function is called. It uses function _compare to compare the uncovered cards. It compares their id, because cards with same image have same id.

Example: compare function

function _compare()
{
    if (this._opened[0]._id == this._opened[1]._id)
        return true;
    else
        return false;
}

If both card are same the variable, which carries number of onpened cards are set to zero, what allows user to uncover other cards, and also it is checked if it is not all cards uncvered. If cards are different, the timer which hide both cards starts

Example: compare cards

function _turnCards()
{
    var timer = null;
    if (this._compare()) {
        //let images uncovered, and resume game
        this._opened[0] = 0;
        this._opened[1] = 0;
        this._openedCount = 0;
        this.numberOfOpened += 1;
        this._wav.play();
        if (this.numberOfOpened == this._rows * this._columns / 2)
            game.gameScene.menu.timer.stop();
    } else {
        //after one second rotete card back
        timer = new Timer(10, false);
        timer.onTick = function()
        {
            if (this super._opened[0])
                this super._opened[0].hide();
            if (this super._opened[0])
                this super._opened[1].hide();
            this super._openedCount = 0;
        }
        timer.start(1200);
    }
}

Menu layer 

Menu layer creates simple menu used in our game. It consists of two buttons only. To start new game and quit game with a timer. Buttons are created simply by using ImageButton control. The next example shows how to create a new game button. This button has two different images for normal and pressed state. 

Example: create new game button

var newGame = new ImageButton({
    image           : res.images.newGame,
    imagePressed    : res.images.newGameClicked,
    mode            : #image,
    x               : System.width / 2,
    y               : System.height - this.height / 2,
});
newGame.onClick = function()
{
    game.gameScene.cardLayer.newGame();
}
this.add(newGame);

Summary

Now you know how to create your own Puzzle game, which can be easily adapted to your requirements by only changing the images or number of cards. The best thing is that using Moscrif SDK creates this application only once and can be published for about 90% of Smartphones currently on the market (statistics for the USA from June 2012). Happy coding.

License

This article, along with any associated source code and files, is licensed under The BSD License

Share

About the Author

PavolSatala

Slovakia Slovakia
Author develops in various programming languages included: C++, Javascript, and PHP. Last year he creates mobile cross platform applications in Moscrif SDK.
Follow on   Twitter

Comments and Discussions

 
QuestionRegarding Website PinmvpRanjan.D12-Oct-14 15:28 
Questionplease PinmemberAbdulrahman Khat'an9-Feb-13 3:01 
GeneralMy vote of 5 PinmemberHossainCo28-Oct-12 7:06 
GeneralMy vote of 5 Pinmemberselomcedric30-Sep-12 23:56 
GeneralRe: My vote of 5 PinmemberPavolSatala1-Oct-12 7:21 
GeneralMy vote of 5 PinmemberMember 346113218-Aug-12 23:56 
GeneralMy vote of 5 Pinmembergiangian8-Aug-12 5:59 
Excellent article!
GeneralMy vote of 5 PinmemberChristian Amado8-Aug-12 3:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 15 Aug 2012
Article Copyright 2012 by PavolSatala
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid