Compare commits

...

13 Commits
v1.0.1 ... main

5 changed files with 159 additions and 29 deletions

View File

@ -2,6 +2,10 @@
DrugWars (DopeWars) clone for the [Arduboy](https://www.arduboy.com).
You hit the streets of New York with $2000 in your pocket and a $5000 debt to the loan shark. Buy low and sell high to pay off your debt as quickly as possible, then spend the rest of the time getting rich to earn a high score. Each day has the possibility for a random event that could help or hinder your progress, and if you hold on to too much product Officer Hardass and his deputies might notice.
You can play either the classic 30 day mode, or an extended mode where you go for 60 days, and high scores for each mode are persisted to the EEPROM so they'll save between reboots.
[Reference implementation.](https://gist.github.com/mattmanning/1002653/b7a1e88479a10eaae3bd5298b8b2c86e16fb4404)
## Building
@ -23,7 +27,7 @@ Your port path (`-p` argument) may differ depending on your system.
## CandyWar
Run `res/candywar.sh` to generate an alternate version of the game if you have delicate sensibilities.
Run `res/candywar.sh` to generate an alternate version of the game with replaced title images and strings if you have delicate sensibilities.
## Resources
@ -32,3 +36,11 @@ Font libraries used:
- [Font4x6](https://github.com/filmote/Font4x6)
Slightly modified to add missing comma and apostrophe characters.
## Screenshots
![slangin-title.png](https://f000.backblazeb2.com/file/img-rdsm-ca/2022/06/22/slangin-title.png)
![slangin-menu.png](https://f000.backblazeb2.com/file/img-rdsm-ca/2022/06/22/slangin-menu.png)
![slangin-drugs.png](https://f000.backblazeb2.com/file/img-rdsm-ca/2022/06/22/slangin-drugs.png)
![slangin-buy.png](https://f000.backblazeb2.com/file/img-rdsm-ca/2022/06/22/slangin-buy.png)
![slangin-hardass.png](https://f000.backblazeb2.com/file/img-rdsm-ca/2022/06/22/slangin-hardass.png)

View File

@ -160,3 +160,36 @@ void buildDrugMenu(const long extra[6]) {
menuCols = true;
menuSmall = col1Max + col2Max > 22;
}
int getMenuBackOut() {
// this whole thing could have been done so much better :(
// i suck at C++
switch (sGameState) {
case STATE_SELL_MENU:
case STATE_BORROW_INPUT:
case STATE_WITHDRAW_INPUT:
return 1;
case STATE_INVENTORY:
case STATE_HIGH_SCORES:
return 2;
case STATE_JET_MENU:
return 3;
case STATE_SHARK_MENU:
return 4;
case STATE_BANK_MENU:
return 5;
case STATE_BUY_QTY_INPUT:
case STATE_SELL_QTY_INPUT:
return sCurrentDrug;
case STATE_INFO_DIALOG:
switch (sPreviousGameState) {
case STATE_BUY_MENU:
case STATE_SELL_MENU:
return sCurrentDrug;
default:
return 0;
}
default:
return 0;
}
}

View File

@ -10,6 +10,7 @@ mv candywar/slangin.ino candywar/candywar.ino
for file in candywar/*.ino; do
sed -i "s/Slangin'/CandyWar/g" "$file"
sed -i "s/Hit the Streets/Hit the Sweets/" "$file"
sed -i "s/Drugs/Candy/g" "$file"
sed -i "s/drugs/candy/g" "$file"
sed -i "s/Loan Shark/Rich Buddy/g" "$file"

View File

@ -5,6 +5,7 @@ License: WTFPL
*/
#include <Arduboy2.h>
#include <EEPROM.h>
#include "src/Font3x5.h"
#include "src/Font4x6.h"
@ -14,6 +15,7 @@ Font3x5 font3x5 = Font3x5();
enum GameState {
STATE_TITLE = 0,
STATE_HIGH_SCORES,
STATE_TURN_MENU,
STATE_JET_MENU,
STATE_BUY_MENU,
@ -75,21 +77,27 @@ const uint8_t PROGMEM spriteWeed[] = {
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;
int sCurrentDay;
short sMaxDays;
short sCurrentDay;
long sDrugPrices[6];
int sCurrentDrug;
short sCurrentDrug;
long sCurrentQty;
long sQtyMax;
long sLastDebounce = 0;
short sDebounceCount = 0;
int sDebounceCount = 0;
bool sAlreadyBorrowed;
int sPigs = 0;
short sPigs = 0;
// default high scores
long s30High = 100000;
long s60High = 200000;
/****************/
/* player state */
@ -103,11 +111,12 @@ int pCapacity;
int pHealth;
long pInventory[6];
String menu[8];
String menu[6];
int menuLength = 0;
bool menuCols = false;
bool menuSmall = false;
int menuSelected = 0;
int menuBackOut = 0;
String dialog[5];
int dialogLength;
@ -118,11 +127,23 @@ bool dialogSmall = false;
/***********************/
void setup() {
arduboy.boot();
arduboy.bootLogoSpritesOverwrite();
arduboy.setFrameRate(15);
arduboy.initRandomSeed();
sGameState = STATE_TITLE;
screenInitialized = false;
arduboy.begin();
// 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() {
@ -137,11 +158,12 @@ void loop() {
if (!screenInitialized) {
arduboy.clear();
menuSelected = 0;
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 >= 31) {
&& sCurrentDay >= sMaxDays) {
sGameState = STATE_GAME_OVER;
}
@ -149,18 +171,27 @@ void loop() {
case STATE_TITLE:
Sprites::drawOverwrite(0, 0, spriteWeed, 0);
Sprites::drawOverwrite(98, 0, spriteWeed, 0);
arduboy.setCursor(42, 15);
arduboy.setCursor(42, 10);
arduboy.print(F("Slangin'"));
font3x5.setCursor(103, 58);
font3x5.print(F("v1.0.1"));
menu[0] = F("Hit the Streets!!");
menuLength = 1;
menuSmall = false;
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));
@ -196,6 +227,7 @@ void loop() {
buildDrugMenu(sDrugPrices);
drawMenu(menuSmall, menuCols, menuLength, menu);
drawMenuIndicator(menuSelected, menuSmall, menuCols, menuLength, menu, false);
drawDrugsOwned(menuSelected);
break;
case STATE_JET_MENU: {
@ -347,17 +379,33 @@ void loop() {
break;
case STATE_GAME_OVER: {
int score = floor(sqrt((pSavingsAmount + pMoney - pLoanAmount) / 31.5));
if (score > 100) score = 100;
dialog[0] = "";
dialog[1] = " Game over!";
dialog[2] = " Your score: ";
dialog[2] += String(score);
dialog[3] = " out of 100.";
dialogSmall = false;
dialogLength = 4;
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;
}
@ -430,14 +478,19 @@ void loop() {
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:
@ -521,13 +574,20 @@ void loop() {
break;
}
screenInitialized = false;
menuBackOut = getMenuBackOut();
sGameState = sPreviousGameState;
} else {
const unsigned long currentMillis = millis();
const int incAmount = sGameState == STATE_BUY_QTY_INPUT ||
const long 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 (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;
@ -535,7 +595,8 @@ void loop() {
drawQtyInputTotal();
} else if (arduboy.pressed(RIGHT_BUTTON)) {
sLastDebounce = currentMillis;
if (sCurrentQty <= sQtyMax - incAmount * 10) sCurrentQty += incAmount * 10;
if (sCurrentQty + incAmount * 10 * incMult <= sQtyMax)
sCurrentQty += incAmount * 10 * incMult;
else sCurrentQty = sQtyMax;
drawQtyInputTotal();
} else if (arduboy.pressed(DOWN_BUTTON)) {
@ -545,7 +606,8 @@ void loop() {
drawQtyInputTotal();
} else if (arduboy.pressed(LEFT_BUTTON)) {
sLastDebounce = currentMillis;
if (sCurrentQty >= incAmount * 10) sCurrentQty -= incAmount * 10;
if (sCurrentQty >= incAmount * 10 * incMult)
sCurrentQty -= incAmount * 10 * incMult;
else sCurrentQty = 0;
drawQtyInputTotal();
} else {
@ -658,3 +720,12 @@ void drawFightPrompt() {
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);
}

View File

@ -5,7 +5,7 @@ Author: Rudis Muiznieks
License: WTFPL
*/
void initializeNewGame() {
void initializeNewGame(int days) {
sGameState = STATE_TURN_MENU;
pMoney = 2000;
pLoanAmount = 5000;
@ -19,6 +19,7 @@ void initializeNewGame() {
pInventory[i] = 0;
}
sMaxDays = days + 1;
sCurrentDay = 0;
incrementDay(false); // start on day 1
}
@ -198,7 +199,18 @@ void handleMenuAction() {
case STATE_TITLE:
switch (menuSelected) {
case 0: // new game
initializeNewGame();
initializeNewGame(30);
break;
case 1: // extended game
initializeNewGame(60);
break;
case 2: // high scores
dialog[0] = F("Normal Game:");
dialog[1] = F("");
dialog[2] = F("Extended Game:");
dialogSmall = false;
dialogLength = 3;
sGameState = STATE_HIGH_SCORES;
break;
}
break;
@ -418,6 +430,7 @@ bool checkBackedOut(const bool eitherButton) {
if (arduboy.justPressed(B_BUTTON) ||
(eitherButton && arduboy.justPressed(A_BUTTON))) {
screenInitialized = false;
menuBackOut = getMenuBackOut();
sGameState = sPreviousGameState;
return true;
}