/* Slangin' Author: Rudis Muiznieks License: WTFPL */ #include #include #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); }