Pokitto turn based strategy game.

Dependencies:   PokittoLib

wars.cpp

Committer:
trelemar
Date:
2017-12-05
Revision:
1:189e78ff65b1
Parent:
0:86d8ece873ca
Child:
2:af768faf5329

File content as of revision 1:189e78ff65b1:

#include "Pokitto.h"
#include "warsprites.h"
#include "warfont.cpp"
#include <vector>
#include <stdint.h>

float solids[64] {
    0,1,1,1,1,0,0,0,
    0,0,1,0,1,0,0,0,
    0,0,1,1,1,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
    0,0,0,0,0,0,0,0,
};
//Sprite sheet:2x1
const uint8_t cursor_sprites [][244] ={
//[0] cell:0x0
{
22,22,
204,0,12,204,204,204,204,204,192,0,204,
192,255,240,204,204,204,204,204,15,255,12,
15,160,12,204,204,204,204,204,192,10,240,
15,12,204,204,204,204,204,204,204,192,240,
15,12,204,204,204,204,204,204,204,192,240,
192,204,204,204,204,204,204,204,204,204,12,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
192,204,204,204,204,204,204,204,204,204,12,
15,12,204,204,204,204,204,204,204,192,240,
15,12,204,204,204,204,204,204,204,192,240,
15,160,12,204,204,204,204,204,192,10,240,
192,255,240,204,204,204,204,204,15,255,12,
204,0,12,204,204,204,204,204,192,0,204,
},
//[1] cell:1x0
{
22,22,
204,204,204,204,204,204,204,204,204,204,204,
204,192,0,204,204,204,204,204,0,12,204,
204,15,255,12,204,204,204,192,255,240,204,
192,250,0,204,204,204,204,204,0,175,12,
192,240,204,204,204,204,204,204,204,15,12,
192,240,204,204,204,204,204,204,204,15,12,
204,12,204,204,204,204,204,204,204,192,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,204,204,204,204,204,204,204,204,204,204,
204,12,204,204,204,204,204,204,204,192,204,
192,240,204,204,204,204,204,204,204,15,12,
192,240,204,204,204,204,204,204,204,15,12,
192,250,0,204,204,204,204,204,0,175,12,
204,15,255,12,204,204,204,192,255,240,204,
204,192,0,204,204,204,204,204,0,12,204,
204,204,204,204,204,204,204,204,204,204,204,
},
};

using namespace std;

int clamp(int low, int n, int high) {return min(max(low, n), high);}

float dist(int x1, int y1, int x2, int y2) {
    int x = x1 - x2;
    int y = y1 - y2;
    float dist;
    dist = pow((float)x,2)+pow((float)y,2);
    dist = sqrt((float)dist);
    return dist;
}

int lerp(int a, int b, float t) {
    return (1-t)*a + t*b;
}

enum GameState {MAIN, PAUSE, MENU} State = MENU;

#define KEY_REPEAT 5

#define TASK_MOVE 0
#define TASK_ITEM 1
#define TASK_ATTACK 2
#define TASK_WAIT 3
#define TASK_NONE 4
#define TEAM_USER 0
#define TEAM_CPU 1

int current_team = TEAM_USER;
uint8_t selected_task = 0;
uint8_t current_task = TASK_NONE;
static char task_names[4][7] {
    {"MOVE"},
    {"ITEM"},
    {"ATTACK"},
    {"WAIT"},
};
int task_colors[4] {
    11,9,6,8
};

Pokitto::Core game;
Pokitto::Display disp;
Pokitto::Buttons btn;

#define SW disp.width
#define SH disp.height

int map_x, map_y, cursor_x, cursor_y;

bool center_cursor = true;

void centerCursor() {
    map_x = cursor_x - 3;
    map_y = cursor_y - 2;
}

static void printb(int x, int y, int text) {
    disp.setColor(0);
    disp.print(x,y-1,text);
    disp.print(x,y+1,text);
    disp.print(x+1,y,text);
    disp.print(x-1,y,text);
    disp.setColor(15);
    disp.print(x,y,text);
}
static void printb(int x, int y, char text[]) {
    disp.setColor(0);
    disp.print(x,y-1,text);
    disp.print(x,y+1,text);
    disp.print(x+1,y,text);
    disp.print(x-1,y,text);
    disp.setColor(15);
    disp.print(x,y,text);
}

struct Dialog {
    char text[20];
    int start_time;
    int position;
    Dialog(char _text[20]) {
        strcpy(text, _text);
        position = 0;
        start_time = game.frameCount;
    };
    void update() {
        position = min(position + 1, 19);
    };
    void draw(int x, int y) {
        disp.setCursor(x, y);
        for (int p=0;p<position;p++) {
            disp.print(text[p]);
        }
    };
};

struct Transition {
    int on_val, off_val, val;
    bool enabled;
    Transition() {};
    Transition(int _onv, int _offv, bool _enabled) {
        on_val = _onv;
        off_val = _offv;
        enabled = _enabled;
        if (enabled) val=on_val;
        else val = off_val;
    };
    void toggle() {enabled=!enabled;};
    void update() {
        if (enabled) val = lerp(val, on_val, 0.2);
        else val = lerp(val, off_val, 0.2);
    };
};

struct Vec2 {
    int x, y;
    Vec2(int _x, int _y) {x=_x; y=_y;};
};

struct Item {
    int id, x, y;
    Item(int _id, int _x, int _y) {
        id = _id;
        x = _x;
        y = _y;
    };
    void Draw() {
        int dx = (x-map_x)*16;
        int dy = (y-map_y)*16;
        //disp.setColor(1);
        //disp.fillRectangle(dx+4,dy+12,8,3);
        //disp.drawBitmap(dx, dy+(sin(game.frameCount/4))*2, sprites[id]);
    };
};

#define ID_SOLDIER 0
#define ID_ORC 1

char id_names[][8] {
    {"SOLDIER"},
    {"ORC"},
};

struct Hero {
    unsigned int index, team, id, x, y;
    uint8_t atk, def, hp, max_hp;
    float range;
    int spr;
    int hurt_time;
    bool tasks[4], flip;
    Hero(int _x, int _y, int _id, int _team) {
        x = _x; y = _y; flip = false;
        id = _id; team = _team;
        if (_team == TEAM_USER) {
            spr = 32;
        }
        else {spr = 26; flip=true; id = ID_ORC;}
        max_hp = 10;
        hp = max_hp;
        atk = random(4,6);
        def = random(1,3);
        range = 2.1;
        NewTurn();
    };
    void NewTurn() {
        for (int i=0;i<4;i++) {tasks[i]=true;}
    };
    void update(int i) {
        index = i;
    };
    void Draw() {
        int ox = 0;
        int oy = 0;
        int dx = (x-map_x)*16;
        int dy = (y-map_y)*16;
        bool draw = true;
        if (game.frameCount-hurt_time<10) {
            ox = random(-2,2);
            oy = random(-2,2);
            if (game.frameCount%2==0) {draw=false;}
        }
        int spr_id = spr + (game.frameCount/6)%2;
        disp.invisiblecolor = 13;
        if (draw) disp.drawBitmap((dx)+ox,(dy)+oy,sprites[spr_id], 0, flip);
        disp.invisiblecolor = 12;
    };
    bool inRange(Hero * enemy) {
        return (dist(x, y, enemy->x, enemy->y)<=range);
    };
    bool inRange(int dx, int dy) {
        return (dist(x, y, dx, dy)<=range);
    }
};

void DrawCursorHeroUI (Hero * h, int x, int y) {
    int bw = 24;
    int hw = (bw/h->max_hp) * h->hp;
    disp.setColor(0);
    disp.drawRoundRect(x-1,y-1,bw+2,5,2); //HEALTH BAR
    disp.fillRoundRect(x-1, y+26,bw+2,7,2);  //TEXT BG BOX
    disp.setColor(6);
    disp.fillRect(x, y, bw, 2);
    disp.setColor(11);
    disp.fillRect(x, y, hw+4, 2);
    disp.setColor(11);
    disp.print(x,y+27,"^");
    disp.setColor(15);
    disp.print((int)h->atk);
    disp.setColor(6);
    disp.print("n");
    disp.setColor(15);
    disp.print((int)h->def);
}

void DrawSelectedHeroUI(Hero * h, int y) {
    disp.setColor(0);
    //disp.fillRect(0,66,SW,88-66);
    disp.fillRoundRect(1,y,SW-2,88-67,3);
    //disp.setColor(15);
    if (!h) return;
    printb(3,y-3,id_names[h->id]);
    disp.setColor(8);
    disp.print(3,y+3,">");
    disp.setColor(15);
    disp.print((int)h->atk);
    disp.print("  ATK");
    disp.setColor(6);
    disp.print(3,y+9,"n");
    disp.setColor(15);
    disp.print((int)h->def);
    disp.print("  DEF");
    disp.setColor(11);
    disp.print(3,y+15,"^");
    disp.setColor(15);
    disp.print((int)h->range);
    disp.print("  RNG");
    printb(16,(-y)+68,task_names[selected_task]);

    for (int i=0;i<4;i++) {
        int spr = i+48;
        if (!h->tasks[i]) spr-=8;
        else if (selected_task==i) spr+=8;
        disp.drawBitmap(44+(i*16), y+4, sprites[spr]);
    }
    int bw = 62;
    int hw = (bw/h->max_hp) * h->hp;
    disp.setColor(0);
    disp.drawRoundRect(44,y-4,bw+2,7,3);
    disp.setColor(6);
    disp.fillRoundRect(45,y-3,bw,5,2);
    disp.setColor(11);
    disp.fillRoundRect(45,y-3,hw+2,5,2);
    disp.setColor(15);
    printb(50+(bw/2)-10,y-3,(int)h->hp);
}

void DrawItemList(Hero * h, int y) {
    disp.setColor(0);
    disp.fillRoundRect(32,y,SW-63,12,3);
    disp.setColor(15);
    for (int i = 0; i<4; i++) {
        disp.drawRoundRect(34+(i*11),y+1,10,10,2);
    }

}

void DrawTurnCounter(vector<Hero> &_heros) {
    disp.invisiblecolor = 13;
    int spr = 44;
    if (current_team==TEAM_CPU) spr = 45;
    disp.drawBitmap(0, 0, sprites[spr]);
    disp.invisiblecolor = 12;
    int count = 0;
    for (int i=0; i<_heros.size();i++) {
        if (_heros[i].team == current_team) count++;
    }
    printb(11,10,(int)count);
}

#define MAP_WIDTH 12
#define MAP_HEIGHT 12
int Map[MAP_HEIGHT][MAP_WIDTH];
void loadTileMap() {
    fileOpen("file_out.war",FILE_MODE_BINARY);
    for (int i = 0; i<144; i++) {
        int x = i%MAP_WIDTH;
        int y = (i-x)/MAP_WIDTH;
        //fileSetPosition(i);
        Map[y][x] = (int)filePeek(i);
    }
    //fileClose();
}

void saveTileMap() {

}

void DrawTileMap(int8_t x, int8_t y, Hero * hero) {
    disp.setColor(15);
    for (int map_x=0; map_x<7; map_x++) {
        for (int map_y=0; map_y<6; map_y++) {
            int spr;
            if (map_x+x<0||x+map_x>MAP_WIDTH-1||y+map_y<0||y+map_y>MAP_HEIGHT-1) {
                spr = 9;
            }
            else {
                spr = Map[map_y+y][map_x+x];
            }

            float distance;

            disp.drawBitmap((map_x*16), (map_y*16),sprites[spr]);

            //RANGE BOXES
            if (hero && (current_task == TASK_MOVE || current_task == TASK_ATTACK)) {
                distance = dist(map_x+x, map_y+y, hero->x, hero->y);
                if (distance<hero->range&&x+map_x<MAP_WIDTH&&y+map_y<MAP_HEIGHT) {
                    disp.setColor(8);
                    if (solids[Map[map_y+y][map_x+x]]) {disp.setColor(6);}
                    disp.drawRoundRect(map_x*16+2, map_y*16+2, 12, 12,3);
                    //disp.print(map_x*16,map_y*16,(float)distance);
                }
            }
        }
    }
}

void DrawCursor(int x, int y) {
    //uint8_t spr = 52 + (game.frameCount/12)%2;
    disp.drawBitmap(x*16-3, y*16-3,cursor_sprites[(game.frameCount/4)%2]);
}

Hero * getHeroAtCoords(int x, int y, vector<Hero> &herovect) {
    Hero *h = NULL;
    bool found = false;
    for (int i=0;i<herovect.size();i++) {
        if (herovect[i].x==x&&herovect[i].y==y) {
            h = &herovect[i];
            break;
        }
    }
    return h;
}



void CursorInput() {
    int last_x=cursor_x;
    int last_y=cursor_y;
    if (btn.repeat(BTN_UP,KEY_REPEAT)) {
        if (cursor_y-map_y<=0) {
            map_y=max(map_y-1,0);
        }
        cursor_y=max(cursor_y-1,0);
    }
    if (btn.repeat(BTN_DOWN,KEY_REPEAT)) {
        if (cursor_y-map_y>=4) {
            map_y=min(map_y+1,MAP_HEIGHT-6);
        }
        cursor_y=min(cursor_y+1,MAP_HEIGHT-1);

    }
    if (btn.repeat(BTN_LEFT,KEY_REPEAT)) {
        if (cursor_x-map_x<=0) {
            map_x=max(map_x-1,0);
        }
        cursor_x=max(cursor_x-1,0);
    }
    if (btn.repeat(BTN_RIGHT,KEY_REPEAT)) {
        if (cursor_x-map_x>=6) {
            map_x=min(map_x+1,MAP_WIDTH-7);
        }
        cursor_x=min(cursor_x+1,MAP_WIDTH-1);
    }

    if (center_cursor) {
        centerCursor();
    }
}

struct MainMenu {
    struct Transition trans;
    char items[3][10];
    int selected_item;
    MainMenu() {
        trans.off_val=-16;
        trans.on_val = 24;
        trans.val=trans.off_val;
        trans.enabled=true;
        strcpy(items[0], "CAMPAIGN");
        strcpy(items[1], "SKIRMISH");
        strcpy(items[2], "OPTIONS");
        selected_item = 0;
    };
    void update() {
        if (btn.pressed(BTN_RIGHT)) {
            selected_item = min(selected_item + 1, 2);
        }
        if (btn.pressed(BTN_LEFT)) {
            selected_item = max(selected_item - 1, 0);
        }

        if (btn.pressed(BTN_A)) {
            switch (selected_item){
            case (0):   //CAMPAIGN
                break;
            case (1):   //SKIRMISH
                disp.clear();
                disp.print("LOADING");
                pokInitSD();
                loadTileMap();
                State = MAIN;
                break;
            case (2):   //OPTIONS
                break;
            }
        }
    };
    void draw() {
        disp.clear();
        trans.update();
        disp.setColor(15);
        disp.print(2,trans.val,"POKIT WARS");
        disp.print(2,SH/2, items[selected_item]);
    };
};

int main() {
    MainMenu main_menu;
    Transition tTrans(66,96,false);
    Transition itemlist(40,72,false);
    game.begin();
    game.setFrameRate(30);
    disp.invisiblecolor = 12;
    disp.bgcolor=12;
    disp.persistence = 1;
    disp.load565Palette(sprite_pal);
    disp.setFont(fontWAR);
    map_x=0;
    map_y=0;
    cursor_x=4;
    cursor_y=6;
    vector<Hero> heros;
    pokInitSD();
    loadTileMap();
    /*
    heros.push_back(Hero(4,5,ID_SOLDIER, TEAM_USER));
    heros.push_back(Hero(5,6,ID_SOLDIER, TEAM_USER));
    heros.push_back(Hero(3,8,ID_SOLDIER, TEAM_USER));
    heros.push_back(Hero(8,5,ID_SOLDIER, TEAM_CPU));
    heros.push_back(Hero(8,7,ID_SOLDIER, TEAM_CPU));
    heros.push_back(Hero(6,9,ID_SOLDIER, TEAM_CPU));
    */

    for (int i=0;i<6;i++) {
        int team = TEAM_USER;
        if (i>2) team = TEAM_CPU;
        heros.push_back(Hero(random(0,MAP_WIDTH-1),random(0,MAP_HEIGHT-1),ID_SOLDIER, team));
    }

    Hero * cursor_hero = NULL;
    Hero * current_hero = NULL;
    Item itest(52, 3, 3);

    while (game.isRunning()) {
        if (game.update()) {
            switch (State) {
            case MENU:
                main_menu.update();
                main_menu.draw();
            break;
            case MAIN:
            tTrans.update();
            itemlist.update();
            //If no current hero or current task is to move our current hero, accept cursor movement.
            if (!current_hero||current_task==TASK_MOVE||current_task==TASK_ATTACK) {
                CursorInput();
                cursor_hero = getHeroAtCoords(cursor_x,cursor_y, heros);
            }
            //This will call when there is a current hero and not in a current task
            else if (current_hero) {
                //TASK SELECTOR
                int dir = 0;
                if (btn.pressed(BTN_RIGHT)) {
                    selected_task=min(selected_task+1,3);
                }
                if (btn.pressed(BTN_LEFT)) {
                    selected_task=max(selected_task-1,0);
                }
                if (selected_task==TASK_ITEM) {
                    itemlist.enabled = true;
                }
                else itemlist.enabled = false;
                //selected_task=selected_task%4;
            }

            DrawTileMap(map_x, map_y, current_hero);
            for (int i = 0; i<heros.size(); i++) {
                heros[i].Draw();
                heros[i].update(i);
            }
            itest.Draw();
            DrawCursor(cursor_x-map_x,cursor_y-map_y);
            disp.setColor(15);

            if (btn.pressed(BTN_A)) {
                if (current_hero&&current_task!=TASK_NONE) {
                    //THIS IS WITHIN A TASK, EX: SELECTING A MOVE LOCATION OR ATTACK TARGET.
                    switch (current_task) {
                        case TASK_MOVE:
                            //MOVE HERO TO CURSOR, NO COLISSIONS YET.
                            //ONLY IF IN RANGE AND NOT SOLID
                            //FIX STACKABLE HEROS
                            if (current_hero->inRange(cursor_x, cursor_y)&&!getHeroAtCoords(cursor_x, cursor_y, heros)&&!solids[Map[cursor_y][cursor_x]]) {
                                if (cursor_x<current_hero->x) current_hero->flip=true;
                                else if (cursor_x>current_hero->x) current_hero->flip=false;
                                current_hero->x = cursor_x;
                                current_hero->y = cursor_y;

                                current_task = TASK_NONE;
                                current_hero->tasks[TASK_MOVE] = false;
                            }
                        break;
                        case TASK_ATTACK:
                            if (cursor_hero&&current_hero->inRange(cursor_hero)) {
                                //FOUND TARGET AT CURSOR.
                                cursor_x = current_hero->x;
                                cursor_y = current_hero->y;
                                int8_t result = cursor_hero->hp - (current_hero->atk-cursor_hero->def);
                                cursor_hero->hp = result;
                                cursor_hero->hurt_time=game.frameCount;
                                if (result<=0) {
                                        heros.erase(heros.begin()+cursor_hero->index);
                                }

                                current_task = TASK_NONE;
                                current_hero->tasks[TASK_ATTACK] = false;
                            }
                            else {
                                //NO TARGET FOUND, ERROR SOUND SHOULD PLAY.
                            }
                        break;

                        //DUMMY CODE, THESE ARE CALLED BELOW. NOT HERE.
                        case TASK_ITEM:
                            current_task = TASK_NONE;
                            current_hero->tasks[TASK_ITEM] = false;
                        break;
                        case TASK_WAIT:
                            current_task = TASK_NONE;
                            current_hero->tasks[TASK_WAIT] = false;
                        break;
                    }
                }
                else if (current_hero&&current_hero->tasks[selected_task]==true) {
                    //TASK SWITCH HERE
                    //THIS HAPPENS DIRECTLY AFTER PRESSING BTN_A ON A NEW TASK.
                    current_task=selected_task;

                    switch (selected_task) {
                        case TASK_ITEM:
                            current_task = TASK_NONE;
                            current_hero->tasks[TASK_ITEM] = false;
                        break;
                        case TASK_WAIT:
                            current_task = TASK_NONE;
                            current_hero->tasks[TASK_WAIT] = false;
                        break;
                    }
                }
                else if (cursor_hero) {
                    //NO CURRENT HERO, SELECT A HERO ONLY IF IN CURSOR HERE.
                    if (cursor_hero->team == current_team) {
                        current_hero = cursor_hero;
                        selected_task = TASK_MOVE;
                        tTrans.enabled = true;
                    }
                }
            }
            if (btn.pressed(BTN_B)) {
                if (current_task!=TASK_NONE) {
                        //BACK OUT OF CURRENT TASK.
                        current_task=TASK_NONE;
                        cursor_x = current_hero->x;
                        cursor_y = current_hero->y;
                }
                else if (current_hero&&current_task==TASK_NONE) {
                        tTrans.enabled=false; current_hero = NULL; //UNSELECT HERO.
                }
            }
            if (btn.pressed(BTN_C)&& !current_hero) {
                //heros.push_back(Hero(cursor_x,cursor_y,0,0));
                current_team=(current_team+1) % 2;
                for (int i=0;i<heros.size();i++) heros[i].NewTurn();
                State = PAUSE;
                disp.print("PAUSED");
                //tTrans.toggle();
            }
            if (cursor_hero&&cursor_hero!=current_hero&&current_task) {
                DrawCursorHeroUI(cursor_hero, (cursor_hero->x-map_x)*16-4,(cursor_hero->y-map_y)*16-7);
            }
            if (tTrans.val==tTrans.on_val) {
                DrawItemList(current_hero, itemlist.val);
            }
            DrawSelectedHeroUI(current_hero, tTrans.val);

            disp.setCursor(0,0);
            DrawTurnCounter(heros);
            //disp.print(0,32,dtest.text);
            //disp.print(1,SH-6,(int)game.battery.level);
            //disp.print(" ");
            break;
            case PAUSE:
                if (btn.pressed(BTN_C)) {
                    State = MAIN;
                }
                disp.print(isThisFileOpen("file_out.war"));
            break;
        }//GAME STATE SWITCH

        }//UPDATE
    }//IS RUNNING
    return 1;
}