/* Slangin' Author: Rudis Muiznieks License: WTFPL */ #include #include "src/fonts/Font3x5.h" #include "src/fonts/Font4x6.h" Arduboy2 arduboy; Font4x6 font4x6 = Font4x6(); Font3x5 font3x5 = Font3x5(); enum GameState { STATE_TITLE = 0, STATE_TURN_MENU, STATE_JET_MENU, STATE_BUY_MENU, STATE_SELL_MENU, STATE_SHARK_MENU, STATE_BANK_MENU, STATE_FIGHT_MENU, STATE_INVENTORY, STATE_INFO_DIALOG, STATE_BUY_GUN_DIALOG, STATE_BUY_COAT_DIALOG, STATE_DO_WEED_DIALOG, STATE_HEAL_DIALOG, STATE_BUY_QTY_INPUT, STATE_SELL_QTY_INPUT, STATE_PAY_LOAN_INPUT, STATE_BORROW_INPUT, STATE_DEPOSIT_INPUT, STATE_WITHDRAW_INPUT }; 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, }; /**************/ /* game state */ /**************/ bool screenInitialized; GameState sGameState; GameState sPreviousGameState; int sCurrentDay; int sDrugPrices[6]; int sCurrentDrug; long sCurrentQty; long sQtyMax; long sLastDebounce = 0; short sDebounceCount = 0; bool sAlreadyBorrowed; /****************/ /* player state */ /****************/ long pMoney; long pLoanAmount; long pSavingsAmount; GameLocation pLocation; int pGuns; int pCapacity; int pHealth; int pInventory[6]; String menu[8]; int menuLength = 0; bool menuCols = false; bool menuSmall = false; int menuSelected = 0; String dialog[5]; int dialogLength; bool dialogSmall = false; /***********************/ /* game setup and loop */ /***********************/ void setup() { sGameState = STATE_TITLE; screenInitialized = false; arduboy.begin(); arduboy.setFrameRate(15); arduboy.initRandomSeed(); } void loop() { if (!(arduboy.nextFrame())) return; arduboy.pollButtons(); /******************/ /* SCREEN DRAWING */ /******************/ if (!screenInitialized) { arduboy.clear(); menuSelected = 0; switch (sGameState) { case STATE_TITLE: arduboy.setCursor(25, 10); arduboy.print(F("Slangin' v0.9")); menu[0] = F("New Game"); menuLength = 1; menuSmall = false; menuCols = false; drawMenu(menuSmall, menuCols, menuLength, menu); drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false); break; case STATE_TURN_MENU: drawStatusBar(); drawTitle(lookupLocation(pLocation)); menu[0] = F("Trenchcoat"); menu[1] = F("Buy Drugs"); menu[2] = F("Sell Drugs"); 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("Trenchcoat"); buildDrugMenu(pInventory); drawMenu(menuSmall, menuCols, menuLength, menu); break; case STATE_BUY_MENU: case STATE_SELL_MENU: drawStatusBar(); if (sGameState == STATE_BUY_MENU) drawTitle("Buy Drugs"); else drawTitle("Sell Drugs"); buildDrugMenu(sDrugPrices); drawMenu(menuSmall, menuCols, menuLength, menu); drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false); 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("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("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_INFO_DIALOG: drawDialog(); 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; } 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_FIGHT_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; sGameState = STATE_TURN_MENU; } } drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false); } break; } case STATE_INVENTORY: case STATE_INFO_DIALOG: checkBackedOut(true); 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; sGameState = sPreviousGameState; } else { const unsigned long currentMillis = millis(); const int incAmount = sGameState == STATE_BUY_QTY_INPUT || sGameState == STATE_SELL_QTY_INPUT ? 1 : 10; if (currentMillis - sLastDebounce > (sDebounceCount < 2 ? 500 : 100)) { if (sDebounceCount < 2) 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 <= sQtyMax - incAmount * 10) sCurrentQty += incAmount * 10; 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) sCurrentQty -= incAmount * 10; 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 = numberChars(remaining); const int x = 128 - ((chars + 9) * 4); font3x5.setCursor(1, 0); font3x5.print("Guns "); font3x5.print(pGuns); font3x5.setCursor(x, 0); font3x5.print("Capacity "); font3x5.print(remaining); } else { const int chars = numberChars(pMoney); 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 ? "Loan" : "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("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 buildDrugMenu(int extra[6]) { int lengths[6]; int col1Max = 0; int col2Max = 0; for (int i = 0; i < 6; i++) { int extraLen = numberChars(extra[i]); lengths[i] = lookupDrug(i).length() + 1 + extraLen; if (i < 3 && lengths[i] > col1Max) col1Max = lengths[i]; if (i >= 3 && lengths[i] > col2Max) col2Max = lengths[i]; } for (int i = 0; i < 6; i++) { int colMax = i < 3 ? col1Max : col2Max; menu[i] = lookupDrug(i) + F(" "); for (int s = 0; s < colMax - lengths[i]; s++) { menu[i] += F(" "); } menu[i] += extra[i]; } menuLength = 6; menuCols = true; menuSmall = col1Max + col2Max > 22; } int numberChars(long num) { int length = 1; while (num /= 10) length++; return length; } 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(); }