// Testbed for making a match3 board
// Created: 28/10/2017 09:05:33
// John Ryland (jryland@xiaofrog.com)
// InvertedLogic, (C) Copyright 2017
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <pthread.h>
#include "board.h"
#include "windowing.h"
#include "game.h"
#include "curves.h"
#include "common.h"
#include "state.h"
const int star0 = 78;
const int star1 = 79;
const int star2 = 80;
const int ring = 81;
int letterIndex(char ch)
{
const int startIdx = 9 + 4;
if (ch >= '0' && ch <= '9')
return ch - '0' + startIdx;
if (ch == '@') return startIdx + 10;
if (ch == '$') return startIdx + 11;
if (ch == '%') return startIdx + 12;
if (ch >= 'a' && ch <= 'z')
return ch - 'a' + startIdx + 13;
if (ch >= 'A' && ch <= 'Z')
return ch - 'A' + startIdx + 13 + 13 + 13;
return 0;
}
/*
class BaseState
{
public:
BaseState(MyWindow& win);
};
*/
/*
Add debug menu
- add display of curves by graphing them
*/
class MyWindow : public CanvasWindow
{
int splashFrame, newGameFrame;
Image gridImage, bgImage, splashImage, titleImage;
SpriteSheet dialogSheet;
int spacing;
int tileTypes;
std::vector<vec2f> testCurve;
SavedState state;
void save() {
if (!saveState(state))
printf("error saving state\n");
}
public:
MyWindow()
{
state.m_highestScore = 0;
if (!loadState(state))
printf("error loading state\n");
save();
createNewGame(game, 0, 0, 0);
selectedSprite = -1;
otherSprite = -1;
offX = 32 + 25;
offY = 80 + 25;
noMoreMoves = false;
splashFrame = 0;
spacing = 35;
tileTypes = 12;
loadPngImage(gridImage, "data/grid.png");
loadPngImage(bgImage, "data/background.png");
loadPngImage(titleImage, "data/title.png");
loadPngImage(splashImage, "data/splash.png");
// sprite sheet could come from DB
loadSpriteSheet(spriteSheet, "data/sprites-32x32.txt");
loadSpriteSheet(buttonSheet, "data/button.txt");
loadSpriteSheet(dialogSheet, "data/dialog.txt");
}
~MyWindow()
{
destroyGame(game);
}
void drawSparkles(int x, int y, int count, int frame, float radius)
{
float angle = 2.0 * 3.14 / (7.0 * count);
for (int i = 0; i < count; i++) {
int x1 = int(x + 5 + radius * 0.1 * cos(angle * ((frame + i) % (7*count))));
int y1 = int(y - 8 + radius * 0.1 * sin(angle * ((frame + i) % (7*count))));
drawSprite(buffer, x1, y1, spriteSheet.sprites[star0 + ((frame + i) % 3)], 255-(frame*25), 3);
}
}
/*
void drawCheatsMenu()
{
drawDialog((width()-200)/2, height()-120, 200, 110);
drawButton((width()-128)/2, height()-65, 128, 50);
drawText(40 + (width()-128)/2, 5 + height()-50, "New");
}
*/
void drawDialog(int x, int y, int w, int h)
{
drawNineSlice(x, y, w, h, dialogSheet);
}
void drawButton(int x, int y, int w, int h, const char* text)
{
int textWidth = strlen(text) * 14;
drawNineSlice(x, y, w, h, buttonSheet);
drawText(x + (w-textWidth)/2, y + h/2 - 11, text);
}
void drawNineSlice(int x, int y, int w, int h, const SpriteSheet& sheet)
{
if (sheet.sprites.size() != 9)
{
printf("not a 9-slice button\n");
return;
}
int srcW[3] = { sheet.sprites[0].w, sheet.sprites[1].w, sheet.sprites[2].w };
int srcH[3] = { sheet.sprites[0].h, sheet.sprites[3].h, sheet.sprites[6].h };
if (w < (srcW[0] + srcW[1] + srcW[2]))
{
printf("width not wide enough\n");
return;
}
if (h < (srcH[0] + srcH[1] + srcH[2]))
{
printf("height not high enough\n");
return;
}
int midW = w - (srcW[0] + srcW[2]);
int midH = h - (srcH[0] + srcH[2]);
int dstW[3] = { srcW[0], midW, srcW[2] };
int dstH[3] = { srcH[0], midH, srcH[2] };
int dstY = y;
// TODO: should turn this in to canvas items
for (int j = 0; j < 3; j++) {
int dstX = x;
for (int i = 0; i < 3; i++) {
if (i == 1 || j == 1) {
drawScaledSprite(buffer, dstX, dstY, dstW[i], dstH[j], sheet.sprites[j*3+i]);
} else {
drawSprite(buffer, dstX, dstY, sheet.sprites[j*3+i], 255);
}
dstX += dstW[i];
}
dstY += dstH[j];
}
}
void drawText(int x, int y, const char* text)
{
while (*text) {
if (!isspace(*text))
drawSprite(buffer, x, y, spriteSheet.sprites[letterIndex(*text)], 255);
x += 14;
text++;
}
}
void onDraw(int x1, int y1, int x2, int y2)
{
refresh();
if (splashFrame >= 0) {
CanvasWindow::onDraw(x1, y1, x2, y2);
// TODO: turn in to canvas items
if (splashFrame < 7) {
drawImage(buffer, 0, 0, bgImage);
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, titleImage, splashFrame * (255/7));
} else if (splashFrame < 14) {
drawImage(buffer, 0, 0, bgImage);
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, titleImage, 255);
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, splashImage, (splashFrame-7) * (255/7));
} else if (splashFrame < 60) {
drawImage(buffer, 0, 0, bgImage);
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, titleImage, 255);
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, splashImage, 255);
} else if (splashFrame < 67) {
drawTestGradient(buffer);
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, bgImage, (67-splashFrame) * (255/7));
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, titleImage, (67-splashFrame) * (255/7));
drawImageWithMaskWithConstantAlpha(buffer, 0, 0, splashImage, (67-splashFrame) * (255/7));
} else {
drawTestGradient(buffer);
drawButton((width()-128)/2, height()/2 - 80, 128, 50, "Play");
}
splashFrame++;
if (splashFrame > 75) {
//splashFrame = -1;
//newGameFrame = 0;
}
return;
}
int finishedCount = 0;
for (int i = 0; i < game.m_events.size(); i++)
{
//printf("board events\n");
BoardEvent& evt = game.m_events[i];
if (!evt.userInit)
{
//printf("initing\n");
evt.frame = 0;
evt.frames = 14;
evt.userInit = true;
if (evt.type == BoardEvent::Added) {
//printf("added value: %i, x: %i y: %i\n", evt.value, evt.newX, evt.newY);
canvasAddSprite(canvas, offX + evt.newX*spacing, offY + evt.newY*spacing, spriteSheet.sprites[evt.value]);
//printf("added index: before: %i after: %i\n", evt.itemIndex, canvas.sprites.size() - 1);
evt.itemIndex = canvas.sprites.size() - 1;
}
if (evt.type == BoardEvent::Moved) {
int dy = evt.delay;
evt.frames += dy * 2;
}
if (evt.type == BoardEvent::NoMoreMoves) {
noMoreMoves = true;
evt.frames = 0;
printf("no more moves\n");
save();
}
} else {
//printf("not initing\n");
}
if (evt.frame >= evt.frames)
{
//printf("finished\n");
finishedCount++;
evt.type = BoardEvent::None;
}
else
{
//printf("updating\n");
switch (evt.type)
{
case BoardEvent::Swapped:
{
//printf("swapped\n");
CanvasItem copy1 = canvas.sprites[evt.itemIndex];
canvas.sprites[evt.itemIndex] = canvas.sprites[evt.newItemIndex];
canvas.sprites[evt.newItemIndex] = copy1;
evt.type = BoardEvent::None;
break;
}
case BoardEvent::Added:
{
//printf("added\n");
CanvasItem& item = canvas.sprites[evt.itemIndex];
item.x = offX + spacing*evt.newX;
item.y = offY + lerp<int>(spacing*evt.y, spacing*evt.newY, Ratio<int>(evt.frame, evt.frames));
//printf("cur y: %i \n", item.y );
item.alpha = lerp<int>(32, 255, Ratio<int>(evt.frame, evt.frames));
break;
}
case BoardEvent::Removed:
{
//printf("removed\n");
CanvasItem& item = canvas.sprites[evt.itemIndex];
item.alpha = lerp<int>(255, 0, Ratio<int>(evt.frame, evt.frames));
//printf("item.y: %i evt.y: %i\n", item.y, evt.y);
// animate them moving up to top right of screen as they are collected
int x = offX + spacing*evt.x, y = offY + spacing*evt.y;
float per1 = float(std::max(evt.frame - evt.delay*2, 0))/(evt.frames - evt.delay*2);
float per = easeOutCurve(per1);
vec2i pt = bezierCurve((vec2i){x, y}, (vec2i){x-70, y-70}, (vec2i){335, -3}, per);
item.x = pt.x;
item.y = pt.y;
item.mode = 4;
item.xScaling = easeOutElasticCurve(per1);
item.yScaling = item.xScaling;
break;
}
case BoardEvent::Moved:
{
//printf("moved %i,%i\n", evt.x, evt.y);
CanvasItem& item = canvas.sprites[evt.itemIndex];
int dy = evt.delay;
float per1 = float(std::max(evt.frame - dy*2, 0))/(evt.frames - dy*2);
float per = easeInOutBackCurve(per1);
item.y = int(offY + spacing*evt.y + per * (spacing*evt.newY - spacing*evt.y));
item.x = int(offX + spacing*evt.x + per * (spacing*evt.newX - spacing*evt.x));
if (per1 > 0.7f && per1 < 0.9f) {
item.mode = 4;
item.xScaling = std::max(1.0f, per*per*per);//1.0 - 2.8*easeInElasticCurve(per1));
item.yScaling = std::max(0.2f, 2.0f - item.xScaling);
} else {
item.mode = 2;
item.xScaling = 1.0f;
item.yScaling = 1.0f;
}
break;
}
case BoardEvent::None:
case BoardEvent::NoMoreMoves:
case BoardEvent::ScoredPoints:
break;
}
/*
static int ff = 0;
ff++;
if (ff > 5) {
ff = 0;
evt.frame++;
}
*/
evt.frame++;
}
}
//printf("finished iterating board events\n");
if (finishedCount != 0 && finishedCount == game.m_events.size())
{
//printf("anims finished\n");
game.m_events.clear();
// re-normalize the board<->sprites
populateCanvasItems();
if (!noMoreMoves)
checkForMoreMatches(game);
}
int canvasX = 0;
int canvasY = 0;
if (newGameFrame >= 0) {
float curvePos = easeOutBackCurve(float(newGameFrame)/20);
canvasX = lerp<int>(350, 0, curvePos);
newGameFrame++;
if (newGameFrame > 20)
newGameFrame = -1;
}
//printf("calling canvas draw\n");
drawTestGradient(buffer);
drawImageWithAlpha(buffer, canvasX + offX-5, canvasY + offY-5, gridImage);
CanvasWindow::onDraw(canvasX, canvasY, x2, y2);
//printf("finished drawing canvas\n");
if (selectedSprite != -1) {
CanvasItem& item = canvas.sprites[selectedSprite];
drawSprite(buffer, item.x, item.y, *item.sprite, 255);
drawSprite(buffer, item.x, item.y, spriteSheet.sprites[12], 255);
}
for (int i = 0; i < game.m_events.size(); i++)
{
BoardEvent& evt = game.m_events[i];
if (evt.type == BoardEvent::Removed)
{
int frame = evt.frame - 1;
//CanvasItem& item = canvas.sprites[evt.itemIndex];
//item.alpha = lerp<int>(evt.frame, evt.frames, 255, 0);
int x = offX + spacing*evt.x, y = offY + spacing*evt.y;
float per = float(std::max(evt.frame - evt.delay*2, 0))/(evt.frames - evt.delay*2);
per = easeOutCurve(per);
vec2i pt = bezierCurve((vec2i){x, y}, (vec2i){x-70, y-70}, (vec2i){335, -3}, per);
//item.x = pt.x;
//item.y = pt.y;
drawSparkles(pt.x, pt.y, 3, evt.frame, 15.0 + evt.frame);
}
}
static int f = 0;
static int sparkleX = 4;
static int sparkleY = 5;
static int sparkleDelay = 50;
f++;
if (f < 20) {
int x = offX + spacing * sparkleX, y = offY + spacing * sparkleY;
drawSparkles(x, y, 1, f, 10.0 + f);
} else if (f > sparkleDelay) {
sparkleX = rand() % game.width();
sparkleY = rand() % game.height();
sparkleDelay = 10 + (rand() % 70);
f = 0;
}
char buf[256];
snprintf(buf, 255, "Highest %04i Score %04i", state.m_highestScore, game.m_score);
drawText(40, 5, buf);
if (noMoreMoves) {
drawText(140, 160, "NO MORE MOVES");
}
//drawSprite(buffer, (width()-128)/2, height()-55, spriteSheet.sprites[8], 255);
drawDialog((width()-200)/2, height()-120, 200, 110);
drawButton((width()-128)/2, height()-65, 128, 50, "New");
#if CURVE_TEST
for (int i = 0; i < 1000; i++) {
vec2f pnt = kochanekBartelsSpline(testCurve, float(i) / 1000.0); // a_tension = 0.0f, a_bias = 0.0f, a_continuity = 0.0f
buffer.pixels[ int(height() - 1 - pnt.y) * width() + int(pnt.x) ] = 0xFFFFFFFF;
}
#endif // CURVE_TEST
}
int width()
{
#if USE_RETINA
return buffer.w / 2;
#else
return buffer.w;
#endif
}
int height()
{
#if USE_RETINA
return buffer.h / 2;
#else
return buffer.h;
#endif
}
void populateCanvasItems()
{
updateTiles(game);
canvas.sprites.clear();
foreach (Tile& t, game.m_tiles)
canvasAddSprite(canvas, offX + t.x*spacing, offY + t.y*spacing, spriteSheet.sprites[t.tileTypeId]);
}
void onMouseEvent(int x, int y, int buttons)
{
if (buttons && splashFrame != -1 && splashFrame > 75) {
splashFrame = -1;
newGameFrame = 0;
}
#if CURVE_TEST
static int oldButtons = 0;
if (!oldButtons && buttons) {
printf("adding point\n");
testCurve.push_back((vec2f){ x, y });
}
oldButtons = buttons;
#endif // CURVE_TEST
if ( x > ((width()-128)/2) && x < ((width()-128)/2 + 128)
&& y > (height()-50) && y < (height()-50+32) ) {
if (!buttons) {
//newGameFrame = 0;
newGame();
return;
}
}
//printf("mouse event: %i %i %i\n", x, y, buttons);
if (buttons) {
if (selectedSprite != -1) {
CanvasItem& item = canvas.sprites[selectedSprite];
int dx = x - selx;
int dy = y - sely;
int boardX = selectedSprite % game.width();
int boardY = selectedSprite / game.width();
int curOtherSprite = -1;
dx = std::min<int>(std::max<int>(dx, -40), 40);
dy = std::min<int>(std::max<int>(dy, -40), 40);
if (boardX == 0 && dx < 0) dx = 0;
if (boardX == (game.width()-1) && dx > 0) dx = 0;
if (boardY == 0 && dy < 0) dy = 0;
if (boardY == (game.height()-1) && dy > 0) dy = 0;
item.x = selitemx;
item.y = selitemy;
if (dx*dx > dy*dy) {
item.x += dx;
// find the item that will be swapped with this
if (dx > 0)
curOtherSprite = selectedSprite + 1;
if (dx < 0)
curOtherSprite = selectedSprite - 1;
} else {
item.y += dy;
// find the item that will be swapped with this
if (dy > 0)
curOtherSprite = selectedSprite + game.width();
if (dy < 0)
curOtherSprite = selectedSprite - game.width();
}
if (otherSprite != -1 && curOtherSprite != otherSprite)
{
CanvasItem& other = canvas.sprites[otherSprite];
other.x = otherX;
other.y = otherY;
}
if (curOtherSprite != -1)
{
CanvasItem& other = canvas.sprites[curOtherSprite];
if (curOtherSprite != otherSprite)
{
otherX = other.x;
otherY = other.y;
otherSprite = curOtherSprite;
}
other.x = otherX;
other.y = otherY;
if (dx*dx > dy*dy) {
other.x -= dx;
} else {
other.y -= dy;
}
}
} else {
// HACK: offset -3,-3 to make it line up - perhaps there is a border between the view and the window
selectedSprite = canvasHitTestWithAlpha(canvas, x-3, y-3);
if (selectedSprite != -1) {
size_t n = canvas.sprites.size() - 1;
CanvasItem itemS = canvas.sprites[selectedSprite];
/*
// Reorder so the selected one will be rendered on top
// if do this, the selected index is used to infer the position in the board, so will need a mapping of index to board position
memmove(&canvas.sprites[selectedSprite], &canvas.sprites[selectedSprite+1], sizeof(CanvasItem)*(n-selectedSprite+1));
canvas.sprites[n] = itemS;
selectedSprite = n;
*/
selx = x;
sely = y;
selitemx = itemS.x;
selitemy = itemS.y;
}
}
} else {
bool canSwap = false;
if (selectedSprite != -1 && otherSprite != -1) {
int boardX1 = selectedSprite % game.width();
int boardY1 = selectedSprite / game.width();
int boardX2 = otherSprite % game.width();
int boardY2 = otherSprite / game.width();
canSwap = swapTiles(game, boardX1, boardY1, boardX2, boardY2);
//printf("can swap: %s\n", canSwap ? "true" : "false");
if (canSwap)
{
int tmp = otherSprite;
otherSprite = selectedSprite;
selectedSprite = tmp;
} else {
printf("failed swap attempt\n");
CanvasItem& item0 = canvas.sprites[selectedSprite];
CanvasItem& item1 = canvas.sprites[otherSprite];
BoardEvent evt;
evt.type = BoardEvent::Moved;
evt.delay = 0;
evt.userInit = false;
evt.itemIndex = selectedSprite;
evt.x = boardX2;
evt.y = boardY2;
evt.newX = boardX1;
evt.newY = boardY1;
game.m_events.push_back(evt);
evt.itemIndex = otherSprite;
evt.x = boardX1;
evt.y = boardY1;
evt.newX = boardX2;
evt.newY = boardY2;
game.m_events.push_back(evt);
}
}
if (selectedSprite != -1) {
CanvasItem& item = canvas.sprites[selectedSprite];
item.x = selitemx;
item.y = selitemy;
// snaps back - but could then apply a swap if criteria is met
}
if (otherSprite != -1) {
CanvasItem& item = canvas.sprites[otherSprite];
item.x = otherX;
item.y = otherY;
// snaps back - but could then apply a swap if criteria is met
}
selectedSprite = -1;
otherSprite = -1;
}
}
void newGame()
{
if (game.m_score > state.m_highestScore) {
state.m_highestScore = game.m_score;
}
int sprites = tileTypes;// spriteSheet.sprites.size();
printf("New match3 board [%ix%i] %i\n", bw, bh, sprites);
destroyGame(game);
createNewGame(game, bw, bh, sprites);
populateCanvasItems();
noMoreMoves = false;
//canvasAddSprite(canvas, x-32, y-32, spriteSheet.sprites[0]);
save();
}
void setBoardDimensions(int a_width, int a_height)
{
bw = a_width;
bh = a_height;
newGame();
}
private:
Game game;
SpriteSheet spriteSheet;
SpriteSheet buttonSheet;
int selectedSprite;
int selx, sely, selitemx, selitemy;
int otherSprite;
int otherX, otherY;
int offX, offY;
bool noMoreMoves;
int bw, bh;
};
class MyApp : public Application
{
public:
MyApp(int argc, char* argv[])
: Application()
{
for (int i = 0; i < argc; i++)
printf("argv[%i]: %s\n", i, argv[i]);
int boardWidth = (argc > 1) ? atoi(argv[1]) : 10;
int boardHeight = (argc > 2) ? atoi(argv[2]) : 10;
window.setBoardDimensions(boardWidth, boardHeight);
}
void onStart()
{
window.create(480, 650);
}
private:
MyWindow window;
};
int main(int argc, char ** argv)
{
MyApp app(argc, argv);
return app.run();
}