#include <FastLED.h>
#include <Keypad.h>
// ================= LED =================
#define LED_PIN_P1 46
#define LED_PIN_P2 3
#define NUM_LEDS_PER_PLAYER 32
#define BRIGHTNESS 255
CRGB ledsP1[NUM_LEDS_PER_PLAYER];
CRGB ledsP2[NUM_LEDS_PER_PLAYER];
#define SERPENTINE true
// ================= MODE =================
#define MODE_SWITCH 14
// ================= RESET =================
#define RESET_BUTTON 17
// ================= TURN LEDS =================
#define TURN_LED_P1 15
#define TURN_LED_P2 16
// ================= KEYPAD =================
char keymap[4][4] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins1[4] = {13,12,11,10};
byte colPins1[4] = {7,6,5,4};
Keypad keypad1 = Keypad(makeKeymap(keymap), rowPins1, colPins1, 4, 4);
byte rowPins2[4] = {41,40,39,38};
byte colPins2[4] = {47,21,20,19};
Keypad keypad2 = Keypad(makeKeymap(keymap), rowPins2, colPins2, 4, 4);
// ================= GAME DATA =================
bool ships1[4][4];
bool ships2[4][4];
bool hits1[4][4];
bool misses1[4][4];
bool hits2[4][4];
bool misses2[4][4];
bool player1Turn = true;
bool gameOver = false;
// ================= RESET FUNCTION =================
void resetGame()
{
memset(ships1,0,sizeof(ships1));
memset(ships2,0,sizeof(ships2));
memset(hits1,0,sizeof(hits1));
memset(hits2,0,sizeof(hits2));
memset(misses1,0,sizeof(misses1));
memset(misses2,0,sizeof(misses2));
player1Turn = true;
gameOver = false;
Serial.println("GAME RESET");
}
// ================= XY =================
int XY(int x, int y, int matrix)
{
int i;
if (SERPENTINE)
{
if (y % 2 == 0)
i = y * 4 + x;
else
i = y * 4 + (3 - x);
}
else
{
i = y * 4 + x;
}
return i + (matrix * 16);
}
// ================= KEYPAD MAP =================
bool keyToXY(char key, int &x, int &y)
{
switch (key)
{
case '1': x=0; y=0; break;
case '2': x=1; y=0; break;
case '3': x=2; y=0; break;
case 'A': x=3; y=0; break;
case '4': x=0; y=1; break;
case '5': x=1; y=1; break;
case '6': x=2; y=1; break;
case 'B': x=3; y=1; break;
case '7': x=0; y=2; break;
case '8': x=1; y=2; break;
case '9': x=2; y=2; break;
case 'C': x=3; y=2; break;
case '*': x=0; y=3; break;
case '0': x=1; y=3; break;
case '#': x=2; y=3; break;
case 'D': x=3; y=3; break;
default:
return false;
}
return true;
}
// ================= CHECK WIN =================
bool checkWin(bool ships[4][4], bool enemyHits[4][4])
{
bool anyShip = false;
for (int y=0;y<4;y++)
{
for (int x=0;x<4;x++)
{
if (ships[x][y])
{
anyShip = true;
if (!enemyHits[x][y])
return false;
}
}
}
return anyShip;
}
// ================= DRAW =================
void drawPlayer(CRGB *leds,
bool ships[4][4],
bool hits[4][4],
bool misses[4][4],
bool enemyHits[4][4],
bool winner)
{
if (gameOver)
{
fill_solid(leds, NUM_LEDS_PER_PLAYER,
winner ? CRGB::Green : CRGB::Red);
return;
}
fill_solid(leds, NUM_LEDS_PER_PLAYER, CRGB::Black);
for (int y=0;y<4;y++)
{
for (int x=0;x<4;x++)
{
if (ships[x][y])
leds[XY(x,y,0)] = CRGB::Blue;
if (enemyHits[x][y])
leds[XY(x,y,0)] = CRGB::Red;
if (hits[x][y])
leds[XY(x,y,1)] = CRGB::Red;
else if (misses[x][y])
leds[XY(x,y,1)] = CRGB::White;
}
}
}
// ================= INPUT =================
void handlePlayer(Keypad &kp,
bool isP1,
bool ships[4][4],
bool enemyShips[4][4],
bool hits[4][4],
bool misses[4][4],
bool programMode)
{
if (gameOver) return;
char key = kp.getKey();
if (!key) return;
int x,y;
if (!keyToXY(key,x,y)) return;
if (programMode)
{
// ✅ TOGGLE ship placement
ships[x][y] = !ships[x][y];
return;
}
if ((isP1 && !player1Turn) || (!isP1 && player1Turn))
return;
if (!hits[x][y] && !misses[x][y])
{
if (enemyShips[x][y])
hits[x][y] = true;
else
misses[x][y] = true;
player1Turn = !player1Turn;
}
}
// ================= SETUP =================
void setup()
{
Serial.begin(115200);
pinMode(MODE_SWITCH, INPUT_PULLUP);
pinMode(RESET_BUTTON, INPUT_PULLUP);
pinMode(TURN_LED_P1, OUTPUT);
pinMode(TURN_LED_P2, OUTPUT);
FastLED.addLeds<WS2812B, LED_PIN_P1, GRB>(ledsP1, 32);
FastLED.addLeds<WS2812B, LED_PIN_P2, GRB>(ledsP2, 32);
FastLED.setBrightness(BRIGHTNESS);
resetGame();
Serial.println("READY");
}
// ================= LOOP =================
void loop()
{
// ✅ RESET BUTTON
if (digitalRead(RESET_BUTTON) == LOW)
{
delay(200); // debounce
resetGame();
}
bool programMode = (digitalRead(MODE_SWITCH) == LOW);
handlePlayer(keypad1, true, ships1, ships2, hits1, misses1, programMode);
handlePlayer(keypad2, false, ships2, ships1, hits2, misses2, programMode);
digitalWrite(TURN_LED_P1, player1Turn);
digitalWrite(TURN_LED_P2, !player1Turn);
if (!gameOver)
{
if (checkWin(ships2, hits1) || checkWin(ships1, hits2))
gameOver = true;
}
drawPlayer(ledsP1, ships1, hits1, misses1, hits2, checkWin(ships2, hits1));
drawPlayer(ledsP2, ships2, hits2, misses2, hits1, checkWin(ships1, hits2));
FastLED.show();
delay(20);
}