#include "sprites.h"
using fabgl::iclamp;
struct IntroScene : public Scene {
static const int TEXTROWS = 4;
static const int TEXT_X = 130;
static const int TEXT_Y = 122;
static int controller_;
int textRow_ = 0;
int textCol_ = 0;
int starting_ = 0;
IntroScene()
: Scene(0)
{
}
void init()
{
Canvas.
drawText(50, 15,
"SPACE INVADERS");
Canvas.
drawText(10, 40,
"ESP32 version by Fabrizio Di Vittorio");
Canvas.
drawText(105, 55,
"www.fabgl.com");
Canvas.
drawText(72, 97,
"* SCORE ADVANCE TABLE *");
Canvas.
drawBitmap(TEXT_X - 20 - 2, TEXT_Y, &bmpEnemyD);
Canvas.
drawBitmap(TEXT_X - 20, TEXT_Y + 15, &bmpEnemyA[0]);
Canvas.
drawBitmap(TEXT_X - 20, TEXT_Y + 30, &bmpEnemyB[0]);
Canvas.
drawBitmap(TEXT_X - 20, TEXT_Y + 45, &bmpEnemyC[0]);
controller_ = 0;
}
void update(int updateCount)
{
static const char * scoreText[] = {"= ? MISTERY", "= 30 POINTS", "= 20 POINTS", "= 10 POINTS" };
if (starting_) {
if (starting_ > 50)
stop();
++starting_;
} else {
if (updateCount > 30 && updateCount % 5 == 0 && textRow_ < 4) {
int x = TEXT_X + textCol_ * Canvas.
getFontInfo()->width;
int y = TEXT_Y + textRow_ * 15 - 4;
Canvas.
drawChar(x, y, scoreText[textRow_][textCol_]);
++textCol_;
if (scoreText[textRow_][textCol_] == 0) {
textCol_ = 0;
++textRow_;
}
}
if (updateCount % 20 == 0) {
Canvas.
drawText(45, 75,
"Press [SPACE] or CLICK to Play");
Canvas.
drawText(80, 75,
"Press [SPACE] to Play");
Canvas.
drawText(105, 75,
"Click to Play");
}
if (updateCount > 50) {
controller_ = 1;
controller_ = 2;
starting_ = (controller_ > 0);
}
}
}
void collisionDetected(Sprite * spriteA, Sprite * spriteB, Point collisionPoint)
{
}
};
int IntroScene::controller_ = 0;
struct GameScene : public Scene {
enum SpriteType { TYPE_PLAYERFIRE, TYPE_ENEMIESFIRE, TYPE_ENEMY, TYPE_PLAYER, TYPE_SHIELD, TYPE_ENEMYMOTHER };
struct SISprite : Sprite {
SpriteType type;
uint8_t enemyPoints;
};
enum GameState { GAMESTATE_PLAYING, GAMESTATE_PLAYERKILLED, GAMESTATE_ENDGAME, GAMESTATE_GAMEOVER, GAMESTATE_LEVELCHANGING, GAMESTATE_LEVELCHANGED };
static const int PLAYERSCOUNT = 1;
static const int SHIELDSCOUNT = 4;
static const int ROWENEMIESCOUNT = 11;
static const int PLAYERFIRECOUNT = 1;
static const int ENEMIESFIRECOUNT = 1;
static const int ENEMYMOTHERCOUNT = 1;
static const int SPRITESCOUNT = PLAYERSCOUNT + SHIELDSCOUNT + 5 * ROWENEMIESCOUNT + PLAYERFIRECOUNT + ENEMIESFIRECOUNT + ENEMYMOTHERCOUNT;
static const int ENEMIES_X_SPACE = 16;
static const int ENEMIES_Y_SPACE = 10;
static const int ENEMIES_START_X = 0;
static const int ENEMIES_START_Y = 30;
static const int ENEMIES_STEP_X = 6;
static const int ENEMIES_STEP_Y = 8;
static const int PLAYER_Y = 170;
static int lives_;
static int score_;
static int level_;
static int hiScore_;
SISprite * sprites_ = new SISprite[SPRITESCOUNT];
SISprite * player_ = sprites_;
SISprite * shields_ = player_ + PLAYERSCOUNT;
SISprite * enemies_ = shields_ + SHIELDSCOUNT;
SISprite * enemiesR1_ = enemies_;
SISprite * enemiesR2_ = enemiesR1_ + ROWENEMIESCOUNT;
SISprite * enemiesR3_ = enemiesR2_ + ROWENEMIESCOUNT;
SISprite * enemiesR4_ = enemiesR3_ + ROWENEMIESCOUNT;
SISprite * enemiesR5_ = enemiesR4_ + ROWENEMIESCOUNT;
SISprite * playerFire_ = enemiesR5_ + ROWENEMIESCOUNT;
SISprite * enemiesFire_ = playerFire_ + PLAYERFIRECOUNT;
SISprite * enemyMother_ = enemiesFire_ + ENEMIESFIRECOUNT;
int playerVelX_ = 0;
int playerAbsX_ = -1;
int enemiesX_ = ENEMIES_START_X;
int enemiesY_ = ENEMIES_START_Y;
int enemiesDir_ = 1;
int enemiesAlive_ = ROWENEMIESCOUNT * 5;
SISprite * lastHitEnemy_ = nullptr;
GameState gameState_ = GAMESTATE_PLAYING;
bool updateScore_ = true;
int64_t pauseStart_;
Bitmap bmpShield[4] = { Bitmap(22, 16, shield_data, 1, RGB(0, 3, 0), true),
Bitmap(22, 16, shield_data, 1, RGB(0, 3, 0), true),
Bitmap(22, 16, shield_data, 1, RGB(0, 3, 0), true),
Bitmap(22, 16, shield_data, 1, RGB(0, 3, 0), true), };
GameScene()
: Scene(SPRITESCOUNT)
{
}
~GameScene()
{
delete [] sprites_;
}
void initEnemy(Sprite * sprite, int points)
{
SISprite * s = (SISprite*) sprite;
s->addBitmap(&bmpEnemyExplosion);
s->type = TYPE_ENEMY;
s->enemyPoints = points;
addSprite(s);
}
void init()
{
player_->addBitmap(&bmpPlayer)->addBitmap(&bmpPlayerExplosion[0])->addBitmap(&bmpPlayerExplosion[1]);
player_->moveTo(152, PLAYER_Y);
player_->type = TYPE_PLAYER;
addSprite(player_);
playerFire_->addBitmap(&bmpPlayerFire);
playerFire_->visible = false;
playerFire_->type = TYPE_PLAYERFIRE;
addSprite(playerFire_);
for (int i = 0; i < 4; ++i) {
shields_[i].addBitmap(&bmpShield[i])->moveTo(35 + i * 75, 150);
shields_[i].isStatic = true;
shields_[i].type = TYPE_SHIELD;
addSprite(&shields_[i]);
}
for (int i = 0; i < ROWENEMIESCOUNT; ++i) {
initEnemy( enemiesR1_[i].addBitmap(&bmpEnemyA[0])->addBitmap(&bmpEnemyA[1]), 30 );
initEnemy( enemiesR2_[i].addBitmap(&bmpEnemyB[0])->addBitmap(&bmpEnemyB[1]), 20 );
initEnemy( enemiesR3_[i].addBitmap(&bmpEnemyB[0])->addBitmap(&bmpEnemyB[1]), 20 );
initEnemy( enemiesR4_[i].addBitmap(&bmpEnemyC[0])->addBitmap(&bmpEnemyC[1]), 10 );
initEnemy( enemiesR5_[i].addBitmap(&bmpEnemyC[0])->addBitmap(&bmpEnemyC[1]), 10 );
}
enemiesFire_->addBitmap(&bmpEnemiesFire[0])->addBitmap(&bmpEnemiesFire[1]);
enemiesFire_->visible = false;
enemiesFire_->type = TYPE_ENEMIESFIRE;
addSprite(enemiesFire_);
enemyMother_->addBitmap(&bmpEnemyD)->addBitmap(&bmpEnemyExplosionRed);
enemyMother_->visible = false;
enemyMother_->type = TYPE_ENEMYMOTHER;
enemyMother_->enemyPoints = 100;
enemyMother_->moveTo(getWidth(), ENEMIES_START_Y);
addSprite(enemyMother_);
Canvas.
drawText(125, 20,
"WE COME IN PEACE");
if (IntroScene::controller_ == 2) {
}
showLives();
}
void drawScore()
{
if (score_ > hiScore_)
hiScore_ = score_;
}
void moveEnemy(SISprite * enemy, int x, int y)
{
if (enemy->visible) {
enemy->moveTo(x, y);
enemy->setFrame(enemy->getFrameIndex() ? 0 : 1);
updateSprite(enemy);
if (y >= PLAYER_Y) {
gameState_ = GAMESTATE_ENDGAME;
}
}
}
void gameOver()
{
for (int i = 0; i < ROWENEMIESCOUNT * 5; ++i)
enemies_[i].allowDraw = false;
if (IntroScene::controller_ == 1)
Canvas.
drawText(110, 100,
"Press [SPACE]");
else if (IntroScene::controller_ == 2)
Canvas.
drawText(93, 100,
"Click to continue");
gameState_ = GAMESTATE_GAMEOVER;
level_ = 1;
lives_ = 3;
score_ = 0;
}
void levelChange()
{
++level_;
gameState_ = GAMESTATE_LEVELCHANGED;
pauseStart_ = esp_timer_get_time();
}
void update(int updateCount)
{
if (updateScore_) {
updateScore_ = false;
drawScore();
}
if (gameState_ == GAMESTATE_PLAYING || gameState_ == GAMESTATE_PLAYERKILLED) {
if ((updateCount % max(3, 21 - level_ * 2)) == 0) {
if (lastHitEnemy_) {
lastHitEnemy_->visible = false;
lastHitEnemy_ = nullptr;
}
enemiesX_ += enemiesDir_ * ENEMIES_STEP_X;
for (int i = 0; i < ROWENEMIESCOUNT; ++i) {
moveEnemy(&enemiesR1_[i], enemiesX_ + i * ENEMIES_X_SPACE, enemiesY_ + 0 * ENEMIES_Y_SPACE);
moveEnemy(&enemiesR2_[i], enemiesX_ + i * ENEMIES_X_SPACE, enemiesY_ + 1 * ENEMIES_Y_SPACE);
moveEnemy(&enemiesR3_[i], enemiesX_ + i * ENEMIES_X_SPACE, enemiesY_ + 2 * ENEMIES_Y_SPACE);
moveEnemy(&enemiesR4_[i], enemiesX_ + i * ENEMIES_X_SPACE, enemiesY_ + 3 * ENEMIES_Y_SPACE);
moveEnemy(&enemiesR5_[i], enemiesX_ + i * ENEMIES_X_SPACE, enemiesY_ + 4 * ENEMIES_Y_SPACE);
}
bool leftSide = enemiesX_ <= 0;
bool rightSide = enemiesX_ >= getWidth() - ROWENEMIESCOUNT * ENEMIES_X_SPACE;
if (rightSide || leftSide) {
if (enemiesDir_ == 0) {
enemiesDir_ = leftSide ? 1 : -1;
} else {
enemiesDir_ = 0;
enemiesY_ += ENEMIES_STEP_Y;
}
}
if (!enemiesFire_->visible) {
int shottingEnemy = random(enemiesAlive_);
for (int i = 0, a = 0; i < ROWENEMIESCOUNT * 5; ++i) {
if (enemies_[i].visible) {
if (a == shottingEnemy) {
enemiesFire_->x = enemies_[i].x + enemies_[i].getWidth() / 2;
enemiesFire_->y = enemies_[i].y + enemies_[i].getHeight() / 2;
enemiesFire_->visible = true;
break;
}
++a;
}
}
}
}
if (gameState_ == GAMESTATE_PLAYERKILLED) {
if ((updateCount % 20) == 0) {
if (player_->getFrameIndex() == 1)
player_->setFrame(2);
else {
player_->setFrame(0);
gameState_ = GAMESTATE_PLAYING;
}
}
} else if (IntroScene::controller_ == 1 && playerVelX_ != 0) {
player_->x += playerVelX_;
player_->x = iclamp(player_->x, 0, getWidth() - player_->getWidth());
updateSprite(player_);
} else if (IntroScene::controller_ == 2 && playerAbsX_ != -1) {
player_->x = playerAbsX_;
playerAbsX_ = -1;
updateSprite(player_);
}
if (playerFire_->visible) {
playerFire_->y -= 3;
if (playerFire_->y < ENEMIES_START_Y)
playerFire_->visible = false;
else
updateSpriteAndDetectCollisions(playerFire_);
}
if (enemiesFire_->visible) {
enemiesFire_->y += 2;
enemiesFire_->setFrame( enemiesFire_->getFrameIndex() ? 0 : 1 );
if (enemiesFire_->y > PLAYER_Y + player_->getHeight())
enemiesFire_->visible = false;
else
updateSpriteAndDetectCollisions(enemiesFire_);
}
if (enemyMother_->visible && enemyMother_->getFrameIndex() == 0) {
enemyMother_->x -= 1;
if (enemyMother_->x < -enemyMother_->getWidth())
enemyMother_->visible = false;
else
updateSprite(enemyMother_);
}
if ((updateCount % 800) == 0) {
enemyMother_->x = getWidth();
enemyMother_->setFrame(0);
enemyMother_->visible = true;
}
if (IntroScene::controller_ == 1) {
playerVelX_ = -1;
playerVelX_ = +1;
else
playerVelX_ = 0;
playerFire_->moveTo(player_->x + 7, player_->y - 1)->visible = true;
} else if (IntroScene::controller_ == 2) {
MouseDelta delta;
if (delta.buttons.left && !playerFire_->visible)
playerFire_->moveTo(player_->x + 7, player_->y - 1)->visible = true;
}
}
}
if (gameState_ == GAMESTATE_ENDGAME)
gameOver();
if (gameState_ == GAMESTATE_LEVELCHANGING)
levelChange();
if (gameState_ == GAMESTATE_LEVELCHANGED && esp_timer_get_time() >= pauseStart_ + 2500000)
stop();
if (gameState_ == GAMESTATE_GAMEOVER) {
if ((updateCount % 20) == 0)
player_->setFrame( player_->getFrameIndex() == 1 ? 2 : 1);
stop();
}
}
void damageShield(SISprite * shield, Point collisionPoint)
{
uint8_t * data = (uint8_t*) shield->getFrame()->data;
int x = collisionPoint.X - shield->x;
int y = collisionPoint.Y - shield->y;
for (int i = 0; i < 64; ++i) {
int px = iclamp(x + random(-4, 5), 0, shield->getWidth() - 1);
int py = iclamp(y + random(-4, 5), 0, shield->getHeight() - 1);
*(data + px + shield->getWidth() * py) = 0;
}
}
void showLives()
{
for (int i = 0; i < lives_; ++i)
Canvas.
drawBitmap(15 + i * (bmpPlayer.width + 5), 183, &bmpPlayer);
}
void collisionDetected(Sprite * spriteA, Sprite * spriteB, Point collisionPoint)
{
SISprite * sA = (SISprite*) spriteA;
SISprite * sB = (SISprite*) spriteB;
if (!lastHitEnemy_ && sA->type == TYPE_PLAYERFIRE && sB->type == TYPE_ENEMY) {
sA->visible = false;
sB->setFrame(2);
lastHitEnemy_ = sB;
--enemiesAlive_;
score_ += sB->enemyPoints;
updateScore_ = true;
if (enemiesAlive_ == 0)
gameState_ = GAMESTATE_LEVELCHANGING;
}
if (sB->type == TYPE_SHIELD) {
sA->visible = false;
damageShield(sB, collisionPoint);
sB->allowDraw = true;
}
if (gameState_ == GAMESTATE_PLAYING && sA->type == TYPE_ENEMIESFIRE && sB->type == TYPE_PLAYER) {
--lives_;
gameState_ = lives_ ? GAMESTATE_PLAYERKILLED : GAMESTATE_ENDGAME;
player_->setFrame(1);
showLives();
}
if (sB->type == TYPE_ENEMYMOTHER) {
sA->visible = false;
sB->setFrame(1);
lastHitEnemy_ = sB;
score_ += sB->enemyPoints;
updateScore_ = true;
}
}
};
int GameScene::hiScore_ = 0;
int GameScene::level_ = 1;
int GameScene::lives_ = 3;
int GameScene::score_ = 0;
void setup()
{
PS2Controller.
begin(PS2Preset::KeyboardPort0_MousePort1, KbdMode::GenerateVirtualKeys);
}
void loop()
{
if (GameScene::level_ == 1) {
IntroScene introScene;
introScene.start();
}
GameScene gameScene;
gameScene.start();
}