Cocos2dx – Jedi’s Light Saber

Lets try creating a stunning Jedi’s Light Saber in this tutorial.

Downloads and Source

Project File (ZIP)

Project @ Github: https://github.com/saiy2k/cocos2dx-lightsaber

1. Create new Cocos2DX Project

Refer to official wiki on creating a new cocos2dx project.

2. `LightSaber` Class

  1. Create a C++ class named `LightSaber` that inherits from `CCLayer`
    //LightSaber.h
    class LightSaber : public CCLayer {
    public:
    
        // MEMBER VARIABLES
        // ****************
        CCSprite                        *textureSprite;
        CCArray                         *saberArray;
    
        // OBJECT LIFE CYCLE
        // *****************
        virtual bool                    init();
        CREATE_FUNC(LightSaber);
        ~LightSaber();
    
    };

    `textureSprite` will hold the texture to be used by the particle system and `saberArray` will hold the lightSaber particle systems. All particle systems in this array will be exact copies of each other, but together they intensify the effect.

  2. The `init` function and the `destructor` allocates memory to the `saberArray` and releases it.
    bool LightSaber::init() {
        if ( !CCLayer::init() ) return false;
    
        saberArray                  =   CCArray::create();
        saberArray->retain();
        return                          true;
    }
    
    LightSaber::~LightSaber() {
        saberArray->release();
    }
  3. Create an Instance of `LightSaber` and it to the running scene in `AppDelegate` as follows:
        // in applicationDidFinishLaunching() of AppDelegate.cpp
        CCScene *pScene = CCScene::create();
        pScene->addChild(LightSaber::create());
        pDirector->runWithScene(pScene);

    Its standard Layer setup logic till now. Fancier things will follow.

3. Enable Touches via Touch Dispatcher

  1. The user should be able to move the Light Saber by dragging. So enable touches in the layer as follows
        // in LightSaber.h
        virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
        virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
        virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
        virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
        void                            registerWithTouchDispatcher();
    
        // in LightSaber.cpp
        bool LightSaber::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) {}
        void LightSaber::registerWithTouchDispatcher() {
            CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, INT_MIN, true);
        }
    
        // `init()` of LightSaber
        this->setTouchEnabled(true);
  2. Write a function `createSabers()` that will create a particleSystem, when touch is made.
        void LightSaber::createSabers() {
            CCParticleSystem                *saber;
    
            textureSprite               =   CCSprite::create("swipeParticle.png");
            textureSprite->retain();
    
            saber                      =   CCParticleSun::create();
            saber->setTexture(textureSprite->getTexture());
            saber->setAutoRemoveOnFinish(true);
            saber->setEmissionRate(2000);
            saber->setLife(0.06);
            saber->setLifeVar(0.01);
            saber->setSpeed(0.0);
            saber->setSpeedVar(0.0);
            saber->setStartColor(ccc4f(0.2, 0.2, 0.9, 0.9));
            saber->setEndColor(ccc4f(0.1, 0.1, 0.9, 0.5));
            saber->setStartSize(16.0);
            saber->setStartSizeVar(2.0);
            saber->setEndSize(8.0);
            saber->setEndSizeVar(2.0);
            saber->setBlendAdditive(true);
            saber->setPosition( ccp(2020, 2020) ); // this is to place the saber out of screen initially.
            saberArray->addObject(saber);
            this->addChild(saber);
    
            textureSprite->release();
        }
    
        // in `ccTouchBegan()`
        this->createSabers();
        return true;
  3. Also, write `updateSabers()` and `removeSabers()` functions as follows and call them from `ccTouchMoved()` and `ccTouchEnded()` respectively.
        void LightSaber::updateSabers(CCArray *systems, CCPoint startPoint, CCPoint endPoint) {
            CCParticleSystem                *saber;
            for (int i = 0; i < systems->count(); i++) {
                saber           =   (CCParticleSystem *)systems->objectAtIndex(i);
                saber->setPosition( startPoint );
            }
        }
        void LightSaber::removeSabers() {
            CCObject                        *obj;
            CCParticleSystem                *particleSys;
            CCARRAY_FOREACH(saberArray, obj) {
                particleSys             =   (CCParticleSystem *)obj;
                particleSys->stopSystem();
                particleSys->setAutoRemoveOnFinish(true);
            }
        }

    Now we will have a nice light dot that moves around your finger with a mini trail.

4. Transformation of the `DOT` into a Real Saber (some Math)

Instead of creating a number of particle system along the line of the Saber, I planned to use the `setPosVar()` to spread the particles along the length of the saber. But spreading the available particles across a bigger length reduces the intensity of the whole system. To compensate for this, I create multiple copies of the Particle system and overlays one over other. That is why, we have a `saberArray` instead of a single `CCParticleSystem`.

Light Saber Geomtery

In the above diagram, the touchPoint is P1, which is the holding end of the Saber and the other end of the Saber is P2, which is an offset by certain distance from P1 for now. `ccTouchMoved()` determines this 2 points P1 and P2 and passes it onto `updateSabers()`. Update `updateSabers()` with the following code, which is to determine the spread area and rotation of the saber based on P1 and P2.

    float                           dist;
    CCPoint                         velocity;
    CCPoint                         delta, offset, final;
    CCPoint                         centerPoint;
    CCParticleSystem                *saber;
    float                           ang;

    delta                       =   ccpSub(endPoint, startPoint);
    centerPoint                 =   ccpAdd(ccpMult(delta, 0.5), startPoint);
    ang                         =   atan2f(delta.y, delta.x) * 180 / 3.14;
    dist                        =   ccpDistance(startPoint, endPoint);

    for (int i = 0; i < systems->count(); i++) {
        saber           =   (CCParticleSystem *)systems->objectAtIndex(i);
        saber->setPosition( centerPoint );
        saber->setPosVar( ccp(0, dist * 0.5) );
        saber->setRotation(90 - ang);
    }

In the above code, `delta` represents the distance vector between `startPoint` and `endPoint`, which when multiplied by 0.5, gives half the distance between startPoint and endPoint. This half-distance is then added to the `startPoint` which gives `centerPoint`, where our System will be positioned.

But without proper call to `setPosVar()`, this will again look like a dot offset by some position from the touchPoint. Since the System is now placed exactly in between P1 and P2, whose distance is `dist`, the emissions should be from -dist / 2 to +dist / 2, which is set by the call to `setPosVar()`

Its not yet complete, since the light saber will stand vertically through the centerPoint and it need to be rotated to align to P1 and P2, which is done by the `setRotation()` call.

5. Strengthening the Effect

Since the particle system is stretched along the length of the saber, the saber doesn’t look intensive. So to increase the intensity, lets replicate the particle system and overlay one over the other.

Since we already have an array for the particle system and all our `ccTouchMoved()` and `ccTouchEnded()` functions deal with the array, we need just one more change to achieve this, which is to create more number of Particle Systems and add it to the array, by modifying the `createSabers()` function.

Full Code

void LightSaber::createSabers() {
    CCParticleSystem                *saber;
    textureSprite               =   CCSprite::create("swipeParticle.png");
    textureSprite->retain();
    for (int i = 0; i < strength; i++) {         saber                      =   CCParticleSun::create();         saber->setTexture(textureSprite->getTexture());
        saber->setAutoRemoveOnFinish(true);
        saber->setEmissionRate(2000);
        saber->setLife(0.06);
        saber->setLifeVar(0.01);
        saber->setSpeed(0.0);
        saber->setSpeedVar(0.0);
        saber->setStartColor(ccc4f(0.2, 0.2, 0.9, 0.9));
        saber->setEndColor(ccc4f(0.1, 0.1, 0.9, 0.5));
        saber->setStartSize(16.0);
        saber->setStartSizeVar(2.0);
        saber->setEndSize(8.0);
        saber->setEndSizeVar(2.0);
        saber->setBlendAdditive(true);
        saber->setPosition( ccp(2020, 2020) );
        saberArray->addObject(saber);
        this->addChild(saber);
    }
    textureSprite->release();
}

void LightSaber::updateSabers(CCArray *systems, CCPoint startPoint, CCPoint endPoint) {
    float                           dist;
    CCPoint                         velocity;
    CCPoint                         delta, offset, final;
    CCPoint                         centerPoint;
    CCParticleSystem                *saber;
    float                           ang;
    delta                       =   ccpSub(endPoint, startPoint);
    centerPoint                 =   ccpAdd(ccpMult(delta, 0.5), startPoint);
    ang                         =   atan2f(delta.y, delta.x) * 180 / 3.14;
    dist                        =   ccpDistance(startPoint, endPoint);
    for (int i = 0; i < systems->count(); i++) {
        saber           =   (CCParticleSystem *)systems->objectAtIndex(i);
        saber->setPosition( centerPoint );
        saber->setPosVar( ccp(0, dist * 0.5) );
        saber->setRotation(90 - ang);
    }
}

void LightSaber::removeSabers() {
    CCObject                        *obj;
    CCParticleSystem                *particleSys;
    CCARRAY_FOREACH(saberArray, obj) {
        particleSys             =   (CCParticleSystem *)obj;
        particleSys->stopSystem();
        particleSys->setAutoRemoveOnFinish(true);
    }
}

#pragma mark -
#pragma mark TOUCH OVERRIDES

bool LightSaber::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) {
    this->createSabers();
    return                          true;
}

void LightSaber::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) {
    CCPoint                         point;
    point                       =   pTouch->getLocation();
    this->updateSabers(saberArray, point, ccpAdd(point, ccp(-40, 100)));
}

void LightSaber::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) {
    this->removeSabers();
}

void LightSaber::ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent) {
}

void LightSaber::registerWithTouchDispatcher() {
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, INT_MIN, true);
}

#pragma mark -
#pragma mark OBJECT LIFE CYCLE

bool LightSaber::init() {
    if ( !CCLayer::init() ) {
        return                      false;
    }

    this->setTouchEnabled(true);
    strength                    =   8;
    saberArray                  =   CCArray::create();
    saberArray->retain();
    return                          true;
}

LightSaber::~LightSaber() {
    saberArray->release();
}

Cocos2dx – Spinning Globe by Masking technique

In this post, lets see how we can implement a spinning world in cocos2d-x using masking technique. See the video below for sample output:


Low FPS is because of my Mac Mini; In device, it runs at 60 FPS

Downloads

1. Full Project

1. Create a Cocos2d-x Project

I am not going to discuss about how to setup a cocos2d-x project for iOS here. For info on how to setup cocos2d-x project, refer the official wiki.

All our code will go into `HelloWorld` class that comes with the template.

2. Masking Functionality

The hardest part in achieving our goal would be ‘Masking’ and we resort to Pavel Hancak’s tutorial on cocos2d-x masking. Please check the tutorial on how to mask a sprite. Based on that code, I rewrote the function as below:

CCSprite* HelloWorld::maskedSpriteWithSprite(CCSprite* pTextureSprite, CCSprite* pMaskSprite, float xoffset, float yoffset)
{
    // store the original positions of both sprites
    CCPoint textureSpriteOrigPosition(pTextureSprite->getPosition().x, pTextureSprite->getPosition().y);
    CCPoint maskSpriteOrigPosition(pMaskSprite->getPosition().x, pMaskSprite->getPosition().y);

    // convert the texture sprite position into mask sprite coordinate system
    pTextureSprite->setPosition(ccp(pTextureSprite->getContentSize().width/2 - pMaskSprite->getPosition().x + pMaskSprite->getContentSize().width/2 - xoffset, pTextureSprite->getContentSize().height/2 - pMaskSprite->getPosition().y + pMaskSprite->getContentSize().height/2 + yoffset));

    // position the mask sprite so that the bottom left corner lies on the (o,o) coordinates
    pMaskSprite->setPosition(ccp(pMaskSprite->getContentSize().width/2, pMaskSprite->getContentSize().height/2));

    CCRenderTexture* rt = CCRenderTexture::renderTextureWithWidthAndHeight((int)pMaskSprite->getContentSize().width, (int)pMaskSprite->getContentSize().height);

    ccBlendFunc bfMask = ccBlendFunc();
    bfMask.src = GL_ONE;
    bfMask.dst = GL_ZERO;
    pMaskSprite->setBlendFunc(bfMask);

    // turn off anti-aliasing around the mask sprite
    pMaskSprite->getTexture()->setAliasTexParameters();

    ccBlendFunc bfTexture = ccBlendFunc();
    bfTexture.src = GL_DST_ALPHA;
    bfTexture.dst = GL_ZERO;
    pTextureSprite->setBlendFunc(bfTexture);

    rt->begin();
    pMaskSprite->visit();
    pTextureSprite->visit();
    rt->end();

    // generate the resulting sprite
    CCSprite* pOutcome = CCSprite::spriteWithTexture(rt->getSprite()->getTexture());
    pOutcome->setFlipY(true);

    // restore the original sprite positions
    pTextureSprite->setPosition(textureSpriteOrigPosition);
    pMaskSprite->setPosition(maskSpriteOrigPosition);
    pOutcome->setPosition(maskSpriteOrigPosition);

    return pOutcome;
}

I added two parameters `xoffset` and `yoffset` to the original code. These parameters are to offset the position of underlying map. By varying this offset parameter in gameloop, we move the map linearly over a period of time.

3. Setup Sprites

Add two sprites `map` and `mask`, where

2d world map

`map` contains the full map. Only a part of will be seen at any time

When one edge of the scrolls into the keyhole (mask), we dont want the background to be visible, but the other edge to wrap and fit the gap. For this purpose, we duplicated the map horizontally, and when one edge is within keyhole, the duplicated part will fit in and when the edge crosses the keyhole completely, we reset the map to initial position. (Will see implementation details in subsequent sections).

mask for world map

`mask` picture is like a circular keyhole through which we sees part of a map.

Code to add these two sprites is as follows. Dont add the sprite as child of layer yet.

    
    // in init() function
    map = CCSprite::create("worldmap2d.jpg");
    map->setPosition( ccp(512, 768/2) );
    map->retain();

    mask = CCSprite::create("worldmapmask.png");
    mask->setPosition( ccp(512, 768/2) );
    mask->retain();

Apart from the `map` and `mask`, a star background is added behind.

4. Masking

With Pavel Hancak’s tutorial, masking is now very simple as following:

    // in init() function
    masked = maskedSpriteWithSprite(map, mask, 0, 100);
    addChild(masked);

Only the masked sprite should be added to the `layer`. I found the offsets on trial and error basis 😛 At this point, you should see circular part of the map and not the whole. We masked the map, but still it looks like a flattened map without any depth. To add some depth, add a `shade` picture, above all the sprites as follows

    // in init() function
    shade = CCSprite::create("worldmapshade.png");
    shade->setPosition( ccp(512, 768/2) );
    addChild(shade);
shade over world map

shade to be applied over the world map to create an illusion of depth

5. Spinning Animation

We dont have any real `3D` object here and so we achieve spinning animation by moving the map horizontally behind the keyhole (mask) infinitely. Add this final piece of code and we are done:

    // in init() function
    this->scheduleUpdate();

    void HelloWorld::update(float dt) {
        CCSprite *m;
        xoff += dt * 100;
        if (xoff > 1024) {
            xoff = 0;
        }
        m = maskedSpriteWithSprite(map, mask, xoff, 120);
        masked->setTexture(m->getTexture());
    }

In above code, `xoff` determines the amount of distance that the map should be moved. `Update` is the gamploop, where we increment `xoff` by small amounts in each frame, thus moving the map. When the `xoff` reaches its limit, we reset it. Changing `xoff` doen’t do anything magically. We create a new sprite based on offset position and apply the texture to our `masked` sprite.

Run the project and see the world spinning. Thats the end of the tutorial. Hope it helped :-)

Source can be browsed @ https://github.com/saiy2k/cocos2dx-masking 

Cocos2dx – Platformer Physics Tutorial

I tried implementing the platforming physics logic in cocos2d-x and it came out pretty well as per my expectations. This tutorial is about how I made it. It might not be the best solution out there, but it is something that works. Before diving in, here is the demo video showcasing the final output:

Downloads

1. Mid Project (with just the platforms render)
2. Full Project (all functionalities)

1. Create a Cocos2d-x Project

I am not going to discuss about how to setup a cocos2d-x project for iOS here. I will directly dwelve into the platforming mechanism here. For info on how to setup cocos2d-x project, refer the official wiki.

All our code will go into `HelloWorld` class that comes with the template.

2. Define the Platforms

Add the following struct in HelloWorld.h to model a Line

typedef struct {
    CCPoint p1, p2;
} Line;

Declare a pointer to Line struct in HelloWorld, which will be an array of all platforms and randomly add a few lines in it:

// in HelloWorld.h
Line *lines;

// in init function of HelloWorld.cpp
lines = (Line *)malloc(sizeof(Line) * COUNT);

lines[0].p1 = ccp(100, 100);
lines[0].p2 = ccp(400, 100);

lines[1].p1 = ccp(500, 600);
lines[1].p2 = ccp(700, 600);

lines[2].p1 = ccp(400, 400);
lines[2].p2 = ccp(600, 500);

lines[3].p1 = ccp(500, 200);
lines[3].p2 = ccp(1000, 200);

The platforms are defined well with their points. Now is the time to render them on screen. Add the `draw` function to the `HelloWorld` scene.

// in header file
virtual void draw();

// in cpp file
void HelloWorld::draw() {
    glLineWidth(4.0);
    for (int i = 0; i < COUNT; i++) {
        ccDrawLine( lines[i].p1, lines[i].p2 );
    }
    CCLayer::draw();
}

The above code sets the line width to 4 pixels, loops through our platform array and draws the lines. Our screen should look like this:

3. Character Sprite and Controls

The platforms are setup right. Now lets add the hero sprite which will run through the platforms and two button controls to make the character move. To add the hero and controls, do this:

// in header file
CCSprite* pSprite;
void menuCloseCallback(CCObject* pSender);

// in init function of HelloWorld.cpp
CCMenuItemImage *pCloseItem = CCMenuItemImage::create("CloseNormal.png",
                                                      "CloseSelected.png",
                                                      this,
                                                      menu_selector(HelloWorld::menuCloseCallback) );
pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 60, 60) );
pCloseItem->setTag(1);
pCloseItem->setScale(2.5f);

CCMenuItemImage *pCloseItem1 = CCMenuItemImage::create("CloseNormal.png",
                                                       "CloseSelected.png",
                                                       this,
                                                       menu_selector(HelloWorld::menuCloseCallback) );
pCloseItem1->setPosition( ccp(60, 60) );
pCloseItem1->setTag(2);
pCloseItem1->setScale(2.5f);

// controls for left and right movement
CCMenu* pMenu = CCMenu::create(pCloseItem, pCloseItem1, NULL);
pMenu->setPosition( CCPointZero );
this->addChild(pMenu, 1);   

// hero sprite
pSprite = CCSprite::create("CloseNormal.png");
pSprite->setPosition( ccp(600, 700) );
pSprite->setTag(-1);
this->addChild(pSprite, 0);

By now our screen should look like this:

4. Basic Movement

The stage is set with all the objects put in place. Now is the time to start moving the objects. Add two variables `speedX` and `speedY` that controls the movement of our hero and schedule the update function of CCLayer to get into the game loop.

// in header file
virtual void update(float dt);
float speedX, speedY;

// in init function of HelloWorld.cpp
speedX = 0;
speedY = 0;
this->scheduleUpdate();

// in HelloWorld.cpp
// moves the hero around the screen as per the speedX and speedY values
void HelloWorld::update(float dt) {
    CCPoint p = pSprite->getPosition();    
    pSprite->setPosition( ccp( p.x + speedX * dt, p.y + speedY * dt ) );
}

// makes the hero to move to left / right based on the touched control
void HelloWorld::menuCloseCallback(CCObject* pSender) {
    if (((CCNode *)pSender)->getTag() == 1) {
        speedX += 100;
    } else {
        speedX -= 100;
    }
}

Now touching the left control button, increases the movement of the hero in left direction and the same with the right control button. As of now, Movement happens only in x-axis and there is no gravity (falling down), friction and collision. The project at this point can be downloaded here for reference.

5. Physics Magic

First replace the `update` function with following code.

void HelloWorld::update(float dt) {
    CCPoint p = pSprite->getPosition();

    speedX *= 0.95; // friction reduces the speed over time

    if (pSprite->getTag() == -1) { // free falling
        CCLog("searching for platform... hero is falling");
        // iterate through all platforms
        for (int i = 0; i < COUNT; i++) {
            // if hero position lies between the x bounds of any platform
            if ( lines[i].p1.x < p.x && lines[i].p2.x > p.x ) {
                // also it is closer than 100 pixels to the platform
                if ( p.y - lines[i].p1.y < 100 && p.y - lines[i].p1.y > 0) {
                    // assign the platform to the hero
                    pSprite->setTag(i);
                    break;
                }
            }
        }
        // gravity pull decreases the y-speed
        speedY -= 10.0;

        // not allowing hero to go down indefinitely, by making him swap to the top
        if ( p.y < 0 ) {
             p.y = 768;
        }
    } else { // resting in or closer to some platform
        int tag = pSprite->getTag();
        CCLog("in platform %d", tag);

        // make sure if hero is still in the x bounds of the platform or moved away
        if ( lines[tag].p1.x < p.x && lines[tag].p2.x > p.x ) {
            float x2, y2;
            x2 = p.x;
            // platform may be a sloppy one, so interpolate the y value based on end points of platform and current hero position 
            y2 = (x2 - lines[tag].p1.x) * (lines[tag].p2.y - lines[tag].p1.y) / (lines[tag].p2.x - lines[tag].p1.x) + lines[tag].p1.y;
            if (p.y - y2 > 0) { // if hero is above platform, make him fall down
                speedY -= 10.0;
            } else {            // nullify the y speed and rest on platform
                speedY = 0;
                p.y = y2;
            }
        } else { // switch to free fall mode, if hero is moved away
            pSprite->setTag(-1);
        }
    }

    pSprite->setPosition( ccp( p.x + speedX * dt, p.y + speedY * dt ) );
}

I believe the above code is self-explanatory with all the comments put in. So I will just add few notes that deserve some explanation here.

Tag

`tag` property of Hero is used to refer to the platform he is in. If he is free falling (i.e., not attached to any platform) then `tag` will be -1.

Friction

 speedX *= 0.95;

Above code decrements the x speed of the hero by 5% on each update, thus simulating the friction effect.

Interpolation

If all the lines are perfectly horizontal, then y values of the end points or any other point in that line would be equal. If the lines are sloppy, things would be different and we need to calculate the y-value of any arbitrary point in the given line for the given x. If its confusing, see the picture below and read on.


For any platform, we have the end points A (x1, y1) and B (x2, y2) and we have the position of the hero represented here as H(hx, hy). But to check if the hero is above or below the line, we need the point that lies in the line which is P(x, y). Of course, x would be same as hx, but how to find y? Not to worry, we can interpolate the y value with the following formula:

y = y1 + (hx - x1) * (y2 - y1) / (x2 - x1)

The above formula is translated to CPP in our code with our variables as follows:

y2 = (x2 - lines[tag].p1.x) * (lines[tag].p2.y - lines[tag].p1.y) / (lines[tag].p2.x - lines[tag].p1.x) + lines[tag].p1.y;

Thats the end of the tutorial and here is the full project. This project can be easily extended to 2d side scrolling platformer games. I will try to write a post on that in future. There might be several places where a beginner can easily get lost. Post the queries in comments and I will try to resolve.

C Program for 21 Sticks game

Here is a good brain refreshing puzzle that I solved recently. Thanks to my friend Raghul for sharing this good piece of puzzle with me. Its a turn based 2 player game (player and computer in our case). c

Now, I am to write a program for this game in such a way that however the player plays, he always picks the last one and thus the computer wins. Looked very simple at first and I came with the following program in 15 minutes and thought to myself (very proudly) that I solved it.

    
    int lineCount;
    int userPick;
    int aiPick;

    lineCount = 21;
    while (lineCount > 1) {
        printf("\n\n%d lines remaining. Pick 1 or 2 or 3 or 4: ", lineCount);
        scanf("%d", &userPick);
        lineCount -= userPick;

        if (lineCount == 1) {
            printf("\nyou win and AI lose\n");
            return;
        }
        if (lineCount <= 5) {
            aiPick = lineCount - 1;
        } else {
            aiPick = rand() % 4 + 1;
        }

        printf("you took %d lines", userPick);
        printf("\nAI took %d lines", aiPick);
        lineCount -= aiPick;
    }
    if (lineCount == 1) {
        printf("\nyou win and AI lose\n");
        return;
    } else {
        printf("\n1 line remains and u lose\n\n");
    }

The AI seems to be working good, but its not perfect. It fails occasionally, especially when the player gets a turn with the remaining number of sticks in the range 2 to 5. So I tried to fix this by playing in such a way that AI always gets its turn when the remaining number of sticks is in the range 2 to 5. But I couldn’t figure out how to do this inside my little mind. So I took a pen and paper and started writing the remaining number of lines, all possibilities of AI’s move and corresponding result, which resulted in a table such as this:

Possible States with results
Remaining Sticks AI Move Worst Case
2 1 Win
3 2 Win
4 3 Win
5 4 Win
6 1 Lose
2 Lose
3 Lose
4 Lose
7 1 Win
2 Lose
3 Lose
4 Lose
8 1 Lose
2 Win
3 Lose
4 Lose
9 1 Lose
2 Lose
3 Win
4 Lose
20 1 Lose
2 Lose
3 Lose
4 Win
21 1 Lose
2 Lose
3 Lose
4 Lose

Only after completely writing out the above table, I found that when there are some certain magic remaining sticks at your turn, regardless of your move, if ur opponent plays his best move, he can win. The magic numbers are found to be 21, 16, 11, 6 and of course 1.

Since the player begins the game with 21 remaining sticks (one of the magic number), as long as my AI plays its best move, it can win. The AI’s best move nothing but the move which makes the remaining number of lines to the next lowest magic number and as we continue when the player plays his move, he will have the remaining sticks as 16, 11, 6 and finally 1 and he loses. Here is the final working program with perfect AI:

#include <stdio.h>

int lineCount;

void main() {

    int userPick;
    int aiPick;

    lineCount = 21;
    while (lineCount > 1) {
        printf("\n\n%d lines remaining. Pick 1 or 2 or 3 or 4: ", lineCount);
        scanf("%d", &userPick);

        lineCount -= userPick;

        if (lineCount >= 17 && lineCount <= 20) {
            aiPick = lineCount - 16;
        } else if (lineCount >= 12 && lineCount <= 15) {
            aiPick = lineCount - 11;
        } else if (lineCount >= 7 && lineCount <= 10) {
            aiPick = lineCount - 6;
        } else {
            aiPick = lineCount - 1;
        }

        printf("you took %d lines", userPick);
        printf("\nAI took %d lines", aiPick);
        lineCount -= aiPick;
    }

    printf("\n1 line remains and u lose\n\n");
}

I am sure this is not the best way to solve this problem, I should have done some research on maths and find some elegant solution, but I guess this is fine for the first step and to achieve the goal. I will look into other possible elegant solutions and if I find any, I will post about it later.

Happy Programming !!!

C program to implement evaluation of Postfix expression using Stack

Program:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct node {
    double element;
    struct node *next;
} *head;

void push(int c);      // function to push a node onto the stack
int pop();             // function to pop the top node of stack
void traceStack();      // function to //print the stack values

int main() {
    int i = 0, j = 0;   // indexes to keep track of current position for input output strings
    char *exp = (char *)malloc(sizeof(char)*100);
    double res = 0;
    char tmp;
    head = NULL;

    printf("Enter the postfix expression: ");
    scanf("%s", exp);

    while( (tmp=exp[i++]) != '\0') {    // repeat till the last null terminator
        // if the char is operand, pust it into the stack
        if(tmp >= '0' && tmp <= '9') {
            int no = tmp - '0';
            push(no);
            continue;
        }

        if(tmp == '+') {
            int no1 = pop();
            int no2 = pop();
            push(no1 + no2);
        } else if (tmp == '-') {
            int no1 = pop();
            int no2 = pop();
            push(no1 - no2);
        } else if (tmp == '*') {
            int no1 = pop();
            int no2 = pop();
            push(no1 * no2);
        } else if (tmp == '/') {
            int no1 = pop();
            int no2 = pop();
            push(no1 / no2);
        }
    }

    printf("Result of the evalution is %d", pop());
    return 0;
}

void push(int c) {
    if(head == NULL) {
        head = malloc(sizeof(struct node));
        head->element = c;
        head->next = NULL;
    } else {
        struct node *tNode;
        tNode = malloc(sizeof(struct node));
        tNode->element = c;
        tNode->next = head;
        head = tNode;
    }
}

int pop() {
    struct node *tNode;
    tNode = head;
    head = head->next;
    return tNode->element;
}

Sample Output:

Enter the postfix expression: 65*3+
Result of the evalution is 33