slangin/slangin.ino

732 lines
21 KiB
C++

/*
Slangin'
Author: Rudis Muiznieks
License: WTFPL
*/
#include <Arduboy2.h>
#include <EEPROM.h>
#include "src/Font3x5.h"
#include "src/Font4x6.h"
Arduboy2 arduboy;
Font4x6 font4x6 = Font4x6();
Font3x5 font3x5 = Font3x5();
enum GameState {
STATE_TITLE = 0,
STATE_HIGH_SCORES,
STATE_TURN_MENU,
STATE_JET_MENU,
STATE_BUY_MENU,
STATE_SELL_MENU,
STATE_SHARK_MENU,
STATE_BANK_MENU,
STATE_FIGHT_MENU,
STATE_FIGHT_DAMAGE,
STATE_INVENTORY,
STATE_INFO_DIALOG,
STATE_BUY_GUN_DIALOG,
STATE_BUY_COAT_DIALOG,
STATE_DO_WEED_DIALOG,
STATE_DID_WEED_DIALOG_1,
STATE_DID_WEED_DIALOG_2,
STATE_HEAL_DIALOG,
STATE_BUY_QTY_INPUT,
STATE_SELL_QTY_INPUT,
STATE_PAY_LOAN_INPUT,
STATE_BORROW_INPUT,
STATE_DEPOSIT_INPUT,
STATE_WITHDRAW_INPUT,
STATE_GAME_OVER
};
enum GameLocation {
LOC_BRONX = 0,
LOC_GHETTO = 1,
LOC_CENTRAL_PARK = 2,
LOC_MANHATTEN = 3,
LOC_CONEY_ISLAND = 4,
LOC_BROOKLYN = 5
};
enum Drug {
DRUG_COCAINE = 0,
DRUG_HEROINE = 1,
DRUG_ACID = 2,
DRUG_WEED = 3,
DRUG_SPEED = 4,
DRUG_LUDES = 5
};
const uint8_t PROGMEM spriteDollar[] = {
5, 5,
0x12, 0x15, 0x1f, 0x15, 0x09,
};
const uint8_t PROGMEM spriteAdjust[] = {
5, 8,
0x24, 0x66, 0xe7, 0x66, 0x24,
};
const uint8_t PROGMEM spriteWeed[] = {
30, 30,
0x00, 0x00, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x78, 0x07, 0x06, 0x78, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x40, 0x00,
0x00, 0x00, 0x03, 0x3c, 0xc1, 0x02, 0x04, 0x08, 0x10, 0x20, 0xc0, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xc0, 0x20, 0x10, 0x08, 0x04, 0x04, 0xc2, 0x39, 0x07, 0x00, 0x00,
0x38, 0x50, 0x90, 0x90, 0x90, 0x23, 0x24, 0x48, 0x50, 0x60, 0x81, 0x26, 0x08, 0xe7, 0x00, 0x90, 0xe7, 0x08, 0x26, 0x01, 0x60, 0x50, 0x48, 0x26, 0xa1, 0x90, 0x90, 0x90, 0x50, 0x30,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x22, 0x32, 0x2a, 0x24, 0x22, 0x10, 0x0a, 0x08, 0x0c, 0x08, 0x12, 0x21, 0x24, 0x2a, 0x32, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
const uint8_t eepStart = EEPROM_STORAGE_SPACE_START + 1;
/**************/
/* game state */
/**************/
bool screenInitialized;
GameState sGameState;
GameState sPreviousGameState;
short sMaxDays;
short sCurrentDay;
long sDrugPrices[6];
short sCurrentDrug;
long sCurrentQty;
long sQtyMax;
long sLastDebounce = 0;
int sDebounceCount = 0;
bool sAlreadyBorrowed;
short sPigs = 0;
// default high scores
long s30High = 100000;
long s60High = 200000;
/****************/
/* player state */
/****************/
long pMoney;
long pLoanAmount;
long pSavingsAmount;
GameLocation pLocation;
int pGuns;
int pCapacity;
int pHealth;
long pInventory[6];
String menu[6];
int menuLength = 0;
bool menuCols = false;
bool menuSmall = false;
int menuSelected = 0;
int menuBackOut = 0;
String dialog[5];
int dialogLength;
bool dialogSmall = false;
/***********************/
/* game setup and loop */
/***********************/
void setup() {
arduboy.boot();
arduboy.bootLogoSpritesOverwrite();
arduboy.setFrameRate(15);
arduboy.initRandomSeed();
sGameState = STATE_TITLE;
screenInitialized = false;
// read high scores from eeprom
char check[5];
EEPROM.get(eepStart, check);
if (strcmp(check, "slng") == 0) {
EEPROM.get(eepStart + 5, s30High);
EEPROM.get(eepStart + 9, s60High);
} else {
EEPROM.put(eepStart, "slng");
EEPROM.put(eepStart + 5, s30High);
EEPROM.put(eepStart + 9, s60High);
}
}
void loop() {
if (!(arduboy.nextFrame()))
return;
arduboy.pollButtons();
/******************/
/* SCREEN DRAWING */
/******************/
if (!screenInitialized) {
arduboy.clear();
menuSelected = menuBackOut;
menuBackOut = 0;
// if we're entering the turn menu on day 31 then game is over instead
if (sGameState == STATE_TURN_MENU
&& sCurrentDay >= sMaxDays) {
sGameState = STATE_GAME_OVER;
}
switch (sGameState) {
case STATE_TITLE:
Sprites::drawOverwrite(0, 0, spriteWeed, 0);
Sprites::drawOverwrite(98, 0, spriteWeed, 0);
arduboy.setCursor(42, 10);
arduboy.print(F("Slangin'"));
font3x5.setCursor(103, 58);
font3x5.print(F("v1.2.0"));
menu[0] = F("Hit the Streets");
menu[1] = F("Extended Game");
menu[2] = F("High Scores");
menuLength = 3;
menuSmall = true;
menuCols = false;
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
break;
case STATE_HIGH_SCORES:
drawDialog();
drawMoney(10, 19, s30High);
drawMoney(10, 39, s60High);
sPreviousGameState = STATE_TITLE;
break;
case STATE_TURN_MENU:
drawStatusBar();
drawTitle(lookupLocation(pLocation));
menu[0] = F("Buy Drugs");
menu[1] = F("Sell Drugs");
menu[2] = F("Trenchcoat");
menu[3] = F("Jet");
if (pLocation == LOC_BRONX) {
menu[4] = F("Loan Shark");
menu[5] = F("Bank");
menuLength = 6;
} else {
menuLength = 4;
}
menuSmall = false;
menuCols = true;
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
break;
case STATE_INVENTORY:
drawStatusBar();
drawTitle(F("Trenchcoat"));
buildDrugMenu(pInventory);
drawMenu(menuSmall, menuCols, menuLength, menu);
break;
case STATE_BUY_MENU:
case STATE_SELL_MENU:
drawStatusBar();
if (sGameState == STATE_BUY_MENU) drawTitle(F("Buy Drugs"));
else drawTitle(F("Sell Drugs"));
buildDrugMenu(sDrugPrices);
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
drawDrugsOwned(menuSelected);
break;
case STATE_JET_MENU: {
drawStatusBar();
String title = F("Jet from ");
title += lookupLocation(pLocation);
drawTitle(title);
for (int i = 0; i < 5; i++) {
int loc = i >= pLocation ? i + 1 : i;
menu[i] = lookupLocation(loc);
}
menuLength = 5;
menuSmall = true;
menuCols = true;
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
}
break;
case STATE_SHARK_MENU:
drawStatusBar();
drawTitle(F("Loan Shark"));
menu[0] = F("Repay Loan");
menu[1] = F("Borrow Money");
menuLength = 2;
menuSmall = false;
menuCols = false;
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
break;
case STATE_BANK_MENU:
drawStatusBar();
drawTitle(F("Bank"));
menu[0] = F("Deposit Money");
menu[1] = F("Withdraw Money");
menuLength = 2;
menuSmall = false;
menuCols = false;
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
break;
case STATE_FIGHT_MENU:
dialog[0] = F("Being chased!!");
dialog[1] = F("There are ");
dialog[1] += String(sPigs);
dialog[1] += F(" pigs");
dialog[2] = F("still after you!");
dialog[3] = F("Your health: ");
dialog[3] += String(pHealth);
dialogLength = 4;
dialogSmall = false;
drawDialog();
drawFightPrompt();
break;
case STATE_FIGHT_DAMAGE:
handleFightDamage();
drawDialog();
break;
case STATE_HEAL_DIALOG:
dialog[0] = F("Will you pay 1000");
dialog[1] = F("dollars for a");
dialog[2] = F("doctor to sew you");
dialog[3] = F("up?");
dialogLength = 4;
dialogSmall = false;
drawDialog();
drawYesNoPrompt();
sPreviousGameState = STATE_TURN_MENU;
break;
case STATE_INFO_DIALOG:
case STATE_BUY_GUN_DIALOG:
case STATE_BUY_COAT_DIALOG:
case STATE_DO_WEED_DIALOG:
drawDialog();
if (sGameState != STATE_INFO_DIALOG) drawYesNoPrompt();
break;
case STATE_DID_WEED_DIALOG_1:
dialog[0] = "You hallucinate on the";
dialog[1] = "wildest trip of your";
dialog[2] = "life, stumble onto the";
dialog[3] = "subway tracks, and get";
dialog[4] = "creamed by a train.";
dialogLength = 5;
dialogSmall = true;
drawDialog();
sPreviousGameState = STATE_DID_WEED_DIALOG_2;
break;
case STATE_DID_WEED_DIALOG_2:
dialog[0] = "";
dialog[1] = " Just say no";
dialog[2] = " to drugs.";
dialogLength = 3;
dialogSmall = false;
drawDialog();
sPreviousGameState = STATE_GAME_OVER;
break;
case STATE_BUY_QTY_INPUT:
case STATE_SELL_QTY_INPUT:
dialog[0] = sGameState == STATE_BUY_QTY_INPUT ? F("Buy ") : F("Sell ");
dialog[0] += lookupDrug(sCurrentDrug);
dialog[1] = F("");
dialog[2] = F("");
dialog[3] = sGameState == STATE_BUY_QTY_INPUT ? F("Costs") : F("Price");
dialog[4] = F("You have");
dialogLength = 5;
dialogSmall = true;
drawDialog();
drawMoney(dialog[0].length() * 5 + 15, 10, sDrugPrices[sCurrentDrug]);
if (sGameState == STATE_BUY_QTY_INPUT) drawMoney(55, 46, pMoney);
else {
font4x6.setCursor(55, 46);
font4x6.print(pInventory[sCurrentDrug]);
}
drawQtyInputTotal();
drawQtySelector(F("Qty"));
break;
case STATE_PAY_LOAN_INPUT:
case STATE_BORROW_INPUT:
case STATE_DEPOSIT_INPUT:
case STATE_WITHDRAW_INPUT:
dialog[0] = sGameState == STATE_PAY_LOAN_INPUT
? F("Pay Loan")
: sGameState == STATE_BORROW_INPUT
? F("Borrow Money")
: sGameState == STATE_DEPOSIT_INPUT
? F("Deposit Money")
: F("Withdraw Money");
dialog[1] = F("");
dialog[2] = F("");
dialog[3] = sPreviousGameState == STATE_SHARK_MENU
? F("You owe") : F("Savings");
dialog[4] = F("You have");
dialogLength = 5;
dialogSmall = true;
drawDialog();
drawMoney(50, 37, sPreviousGameState == STATE_SHARK_MENU
? pLoanAmount : pSavingsAmount);
drawMoney(55, 46, pMoney);
drawQtySelector(F("Amt"));
break;
case STATE_GAME_OVER: {
dialogSmall = false;
dialogLength = 3;
sPreviousGameState = STATE_TITLE;
pMoney += pSavingsAmount - pLoanAmount;
for (int i = 0; i < 6; i++) {
pMoney += pInventory[i] * sDrugPrices[i];
}
if (pMoney < 0) {
pMoney = 0;
}
bool newHigh = sMaxDays == 31 ? pMoney > s30High : pMoney > s60High;
dialog[0] = F("Game over! Score:");
dialog[1] = F("");
dialog[2] = newHigh ? F("Old ") : F("");
dialog[2] += F("High Score:");
drawDialog();
drawMoney(10, 19, pMoney);
drawMoney(10, 39, sMaxDays == 31 ? s30High : s60High);
if (newHigh) {
if (sMaxDays == 31) {
s30High = pMoney;
EEPROM.put(eepStart + 5, s30High);
} else {
s60High = pMoney;
EEPROM.put(eepStart + 9, s60High);
}
}
break;
}
default:
arduboy.setCursor(5, 5);
arduboy.print(F("ERROR "));
arduboy.print(sGameState);
break;
}
screenInitialized = true;
}
/******************/
/* INPUT HANDLING */
/******************/
// menu screens
switch (sGameState) {
case STATE_TITLE:
case STATE_TURN_MENU:
case STATE_JET_MENU:
case STATE_BUY_MENU:
case STATE_SELL_MENU:
case STATE_SHARK_MENU:
case STATE_BANK_MENU: {
if (arduboy.justPressed(UP_BUTTON) ||
arduboy.justPressed(DOWN_BUTTON) ||
arduboy.justPressed(LEFT_BUTTON) ||
arduboy.justPressed(RIGHT_BUTTON) ||
arduboy.justPressed(A_BUTTON) ||
arduboy.justPressed(B_BUTTON)) {
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, true);
int col1Count = round(menuLength / 2.0);
int inCol = menuSelected < col1Count ? 1 : 2;
if (arduboy.justPressed(DOWN_BUTTON)) {
menuSelected++;
if (menuSelected >= menuLength) menuSelected = menuLength - 1;
} else if (arduboy.justPressed(UP_BUTTON)) {
menuSelected--;
if (menuSelected < 0) menuSelected = 0;
} else if (arduboy.justPressed(RIGHT_BUTTON)) {
if (menuCols && inCol == 1) {
menuSelected += col1Count;
} else {
menuSelected++;
}
if (menuSelected >= menuLength) menuSelected = menuLength - 1;
} else if (arduboy.justPressed(LEFT_BUTTON)) {
if (menuCols && inCol == 2) {
menuSelected -= col1Count;
} else {
menuSelected--;
}
if (menuSelected < 0) menuSelected = 0;
} else if (arduboy.justPressed(A_BUTTON)) {
// advance to next state
handleMenuAction();
} else if (arduboy.justPressed(B_BUTTON)) {
// return to turn menu from the other menus
if (sGameState == STATE_JET_MENU ||
sGameState == STATE_BUY_MENU ||
sGameState == STATE_SELL_MENU ||
sGameState == STATE_SHARK_MENU ||
sGameState == STATE_BANK_MENU) {
screenInitialized = false;
menuBackOut = getMenuBackOut();
sGameState = STATE_TURN_MENU;
}
}
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
if (sGameState == STATE_BUY_MENU || sGameState == STATE_SELL_MENU) {
drawDrugsOwned(menuSelected);
}
}
break;
}
case STATE_INVENTORY:
case STATE_HIGH_SCORES:
case STATE_INFO_DIALOG:
case STATE_DID_WEED_DIALOG_1:
case STATE_DID_WEED_DIALOG_2:
case STATE_FIGHT_DAMAGE:
case STATE_GAME_OVER:
checkBackedOut(true);
break;
case STATE_BUY_GUN_DIALOG:
case STATE_BUY_COAT_DIALOG:
case STATE_DO_WEED_DIALOG:
case STATE_HEAL_DIALOG:
if (!checkBackedOut(false)) { // backed out = no
if (arduboy.justPressed(A_BUTTON)) { // yes
screenInitialized = false;
switch (sGameState) {
case STATE_BUY_GUN_DIALOG:
pGuns++;
pMoney -= 400;
pCapacity -= 5;
sGameState = STATE_TURN_MENU;
break;
case STATE_BUY_COAT_DIALOG:
pCapacity += 10;
pMoney -= 200;
sGameState = STATE_TURN_MENU;
break;
case STATE_DO_WEED_DIALOG:
sGameState = STATE_DID_WEED_DIALOG_1;
break;
case STATE_HEAL_DIALOG:
pMoney -= 1000;
pHealth = 50;
sGameState = STATE_TURN_MENU;
break;
}
}
}
break;
case STATE_FIGHT_MENU:
handleFightMenuInput();
break;
case STATE_SELL_QTY_INPUT:
case STATE_BUY_QTY_INPUT:
case STATE_PAY_LOAN_INPUT:
case STATE_BORROW_INPUT:
case STATE_WITHDRAW_INPUT:
case STATE_DEPOSIT_INPUT:
if (!checkBackedOut(false)) {
if (arduboy.justPressed(A_BUTTON)) {
switch (sGameState) {
case STATE_SELL_QTY_INPUT:
pInventory[sCurrentDrug] -= sCurrentQty;
pMoney += sCurrentQty * sDrugPrices[sCurrentDrug];
break;
case STATE_BUY_QTY_INPUT:
pInventory[sCurrentDrug] += sCurrentQty;
pMoney -= sCurrentQty * sDrugPrices[sCurrentDrug];
break;
case STATE_PAY_LOAN_INPUT:
pLoanAmount -= sCurrentQty;
pMoney -= sCurrentQty;
break;
case STATE_BORROW_INPUT:
pLoanAmount += sCurrentQty;
pMoney += sCurrentQty;
sAlreadyBorrowed = true;
break;
case STATE_WITHDRAW_INPUT:
pSavingsAmount -= sCurrentQty;
pMoney += sCurrentQty;
break;
case STATE_DEPOSIT_INPUT:
pSavingsAmount += sCurrentQty;
pMoney -= sCurrentQty;
break;
}
screenInitialized = false;
menuBackOut = getMenuBackOut();
sGameState = sPreviousGameState;
} else {
const unsigned long currentMillis = millis();
const long incAmount = sGameState == STATE_BUY_QTY_INPUT ||
sGameState == STATE_SELL_QTY_INPUT ? 1 : 10;
if (currentMillis - sLastDebounce > (sDebounceCount < 2 ? 500 : sDebounceCount < 21 ? 100 : 0)) {
const long incMult = sDebounceCount < 100
? 1 : sDebounceCount < 125
? 10 : sDebounceCount < 150
? 100 : sDebounceCount < 175
? 1000
: 10000;
if (sDebounceCount < 1000) sDebounceCount++;
if (arduboy.pressed(UP_BUTTON)) {
sLastDebounce = currentMillis;
if (sCurrentQty <= sQtyMax - incAmount) sCurrentQty += incAmount;
else sCurrentQty = sQtyMax;
drawQtyInputTotal();
} else if (arduboy.pressed(RIGHT_BUTTON)) {
sLastDebounce = currentMillis;
if (sCurrentQty + incAmount * 10 * incMult <= sQtyMax)
sCurrentQty += incAmount * 10 * incMult;
else sCurrentQty = sQtyMax;
drawQtyInputTotal();
} else if (arduboy.pressed(DOWN_BUTTON)) {
sLastDebounce = currentMillis;
if (sCurrentQty >= incAmount) sCurrentQty -= incAmount;
else sCurrentQty = 0;
drawQtyInputTotal();
} else if (arduboy.pressed(LEFT_BUTTON)) {
sLastDebounce = currentMillis;
if (sCurrentQty >= incAmount * 10 * incMult)
sCurrentQty -= incAmount * 10 * incMult;
else sCurrentQty = 0;
drawQtyInputTotal();
} else {
sLastDebounce = 0;
sDebounceCount = 0;
}
}
}
}
break;
}
arduboy.display();
}
/***********************/
/* screen draw heplers */
/***********************/
void drawStatusBar() {
if (sGameState == STATE_INVENTORY) {
const int remaining = playerCapacityRemaining();
const int chars = String(remaining).length();
const int x = 128 - ((chars + 9) * 4);
font3x5.setCursor(1, 0);
font3x5.print(F("Guns "));
font3x5.print(pGuns);
font3x5.setCursor(x, 0);
font3x5.print(F("Capacity "));
font3x5.print(remaining);
} else {
const int chars = String(pMoney).length();
const int x = 128 - (chars * 4);
if (sGameState == STATE_SHARK_MENU || sGameState == STATE_BANK_MENU) {
font3x5.setCursor(1, 0);
font3x5.print(sGameState == STATE_SHARK_MENU ? F("Loan") : F("Acct"));
Sprites::drawOverwrite(19, 1, spriteDollar, 0);
font3x5.setCursor(25, 0);
font3x5.print(sGameState == STATE_SHARK_MENU ? pLoanAmount : pSavingsAmount);
} else {
font3x5.setCursor(1, 0);
font3x5.print(F("Day"));
font3x5.setCursor(14, 0);
font3x5.print(sCurrentDay);
}
Sprites::drawOverwrite(x - 6, 1, spriteDollar, 0);
font3x5.setCursor(x, 0);
font3x5.print(pMoney);
}
arduboy.fillRect(0, 8, 128, 1, WHITE);
}
void drawTitle(const String title) {
font4x6.setCursor(6, 12);
font4x6.print(title);
}
void drawDialog() {
arduboy.drawRoundRect(0, 0, 128, 64, 5, WHITE);
if (dialogSmall) {
for (int i = 0; i < dialogLength; i++) {
font4x6.setCursor(10, i * 9 + 10);
font4x6.print(dialog[i]);
}
} else {
for (int i = 0; i < dialogLength; i++) {
arduboy.setCursor(10, i * 10 + 10);
arduboy.print(dialog[i]);
}
}
}
void drawMoney(const int x, const int y, const long amount) {
Sprites::drawOverwrite(x, y + 2, spriteDollar, 0);
font4x6.setCursor(x + 6, y);
font4x6.print(amount);
}
void drawQtySelector(String label) {
font4x6.setCursor(10, 24);
font4x6.print(label);
arduboy.drawRect(30, 22, 88, 12, WHITE);
drawQtySelectorAmount();
Sprites::drawOverwrite(111, 24, spriteAdjust, 0);
}
void drawQtySelectorAmount() {
arduboy.fillRect(31, 23, 80, 10, BLACK);
font4x6.setCursor(35, 24);
font4x6.print(sCurrentQty);
}
void drawQtyInputTotal() {
if (sGameState == STATE_SELL_QTY_INPUT ||
sGameState == STATE_BUY_QTY_INPUT) {
arduboy.fillRect(40, 37, 87, 7, BLACK);
drawMoney(40, 37, sCurrentQty * sDrugPrices[sCurrentDrug]);
}
drawQtySelectorAmount();
}
void drawYesNoPrompt() {
font3x5.setCursor(74, 55);
font3x5.print(F("A Yes B No"));
arduboy.drawCircle(75, 58, 5, WHITE);
arduboy.drawCircle(103, 58, 5, WHITE);
}
void drawFightPrompt() {
font3x5.setCursor(66, 55);
font3x5.print(F("A Fight B Run"));
arduboy.drawCircle(67, 58, 5, WHITE);
arduboy.drawCircle(103, 58, 5, WHITE);
}
void drawDrugsOwned(Drug drug) {
// wipe old indicator
arduboy.fillRect(60, 9, 68, 12, BLACK);
String numStr = String(pInventory[drug]);
font3x5.setCursor(92 - numStr.length() * 4, 13);
font3x5.print("You own ");
font3x5.print(numStr);
}