// Battleships Arduino
// 2 Player Game
//
// Jun 6, 2022
// - Started project
// Jan 2, 2023
// -
#include <LiquidCrystal.h>
#include <LedControl.h>
// Debug mode ------
const bool DEBUG = true;
// Player [1] Dot Matrix Displays
#define P0DIN 52
#define P0CS 50
#define P0CLK 48
// Player [1] Controls
// Joystick
#define P0_V_PIN A0
#define P0_H_PIN A1
#define P0_S_PIN 46
// Buttons
#define P0_B_ONE 44
#define P0_B_TWO 42
// Player [2] Dot Matrix Displays
#define P1DIN 53
#define P1CS 51
#define P1CLK 49
// Player [2] Controls
// Joystick
#define P1_V_PIN A2
#define P1_H_PIN A3
#define P1_S_PIN 47
// Buttons
#define P1_B_ONE 45
#define P1_B_TWO 43
// 16x2 LCD Pins
#define LCD_RS 13
#define LCD_E 12
#define LCD_D4 11
#define LCD_D3 10
#define LCD_D2 9
#define LCD_D1 8
class PLAYER
{
public:
int selectedShip[2] = {0, 0}; // ship #, rotation of ship
int shipConfirmation = 0;
char opponentBoard[8][8]; // '-'=unknown, 'M'=miss, 'X'=hit
char tempBoard[8][8]; // '-'=empty, 'O'=ship, 'N'=new ship
char Board[8][8]; // '-'=empty, 'O'=ship, 'M'=miss, 'X'=hit
private:
double health;
};
// Ships 3D Array
// Ships[i][ ][ ] -> Which ship would like to be selected
// Ships[ ][j][ ] -> Ship length, when j == 4, the ship ID can be retrieved
// Ships[ ][ ][k] -> Ship width
const int SHIPS[4][5][4] =
{
{ // Ship 0, 2x1:
{1, 0, 0, 0}, // #---
{1, 0, 0, 0}, // #---
{0, 0, 0, 0}, // ----
{0, 0, 0, 0}, // ----
{2, 1, 2, 1} // ID (length, width, count, solid)
},
{ // Ship 1, 3x1:
{1, 0, 0, 0}, // #---
{1, 0, 0, 0}, // #---
{1, 0, 0, 0}, // #---
{0, 0, 0, 0}, // ----
{3, 1, 3, 1} // ID (length, width, count, solid)
},
{ // Ship 2, 4x1:
{1, 0, 0, 0}, // #---
{1, 0, 0, 0}, // #---
{1, 0, 0, 0}, // #---
{1, 0, 0, 0}, // #---
{4, 1, 4, 1} // ID (length, width, count, solid)
},
{ // Ship 3, 3x2:
{1, 0, 0, 0}, // #---
{1, 1, 0, 0}, // ##--
{0, 1, 0, 0}, // -#--
{0, 0, 0, 0}, // ----
{3, 2, 4, 0} // ID (length, width, count, solid)
},
};
const byte boat[8] = {
B00100,
B01100,
B11100,
B01100,
B00100,
B11111,
B01110,
};
const byte arrowRight[8] = {
B00100,
B00110,
B11111,
B11111,
B11111,
B00110,
B00100,
};
// Global Variables
const int SIZE = 8;
const int maxX = SIZE - 1;
const int maxY = SIZE - 1;
int TURN = 0;
int PHASE = 0;
int X0 = 0; // Player 0 x-position
int Y0 = 0; // Player 0 y-position
int X1 = 0; // Player 1 x-position
int Y1 = 0; // Player 1 y-position
PLAYER player[2];
LiquidCrystal lcd(LCD_RS, LCD_E, LCD_D4, LCD_D3, LCD_D2, LCD_D1);
LedControl P0HLCD = LedControl(P0DIN, P0CLK, P0CS, 0); // Player 0 Home board
LedControl P0ALCD = LedControl(P0DIN, P0CLK, P0CS, 1); // Player 0 Attack board
LedControl P1HLCD = LedControl(P1DIN, P1CLK, P1CS, 0); // Player 1 Home board
LedControl P1ALCD = LedControl(P1DIN, P1CLK, P1CS, 1); // Player 1 Attack board
// Functions
void tempBoard_print();
void cursor_draw(int X, int Y, int p);
bool validate_position(int H, int V, int& X, int& Y, int p);
void selection(int H, int V, int& X, int& Y, int p);
void record_ship(int X, int Y, int p);
void draw_ship(int& X, int& Y, int p);
void update_display(int X, int Y, int ship, int p);
void rotate(int ship, int p);
void draw_display(int p);
void battleships(int current_player, int turn);
void setup()
{
lcd.createChar(0, arrowRight);
lcd.createChar(1, boat);
lcd.begin(16, 2);
lcd.print(" Battleships ");
lcd.write(byte(1));
lcd.setCursor(0, 1);
lcd.print("Conquer the sea!");
delay(1750);
lcd.clear();
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
player[0].opponentBoard[i][j] = '-';
player[0].Board[i][j] = '-';
player[0].tempBoard[i][j] = '-';
player[1].opponentBoard[i][j] = '-';
player[1].Board[i][j] = '-';
player[1].tempBoard[i][j] = '-';
}
}
Serial.begin(9600);
if (DEBUG)
Serial.println("[!] DEBUG MODE: ON");
pinMode(P0_V_PIN, INPUT);
pinMode(P0_H_PIN, INPUT);
pinMode(P0_S_PIN, INPUT_PULLUP);
pinMode(P0_B_ONE, INPUT_PULLUP);
pinMode(P0_B_TWO, INPUT_PULLUP);
if (DEBUG)
Serial.println("[*] Player 0 pins initialized");
pinMode(P1_V_PIN, INPUT);
pinMode(P1_H_PIN, INPUT);
pinMode(P1_S_PIN, INPUT_PULLUP);
pinMode(P0_B_ONE, INPUT_PULLUP);
pinMode(P0_B_TWO, INPUT_PULLUP);
if (DEBUG)
Serial.println("[*] Player 1 pins initialized");
P0HLCD.shutdown(0, false);
P0ALCD.shutdown(0, false);
P1HLCD.shutdown(0, false);
P1ALCD.shutdown(0, false);
P0HLCD.setIntensity(0, 8);
}
// -------------------------
void loop()
{
int V0 = map(analogRead(P0_H_PIN), 0, 1023, -1, 1);
int H0 = map(analogRead(P0_V_PIN), 0, 1023, -1, 1);
int V1 = map(analogRead(P1_H_PIN), 0, 1023, -1, 1);
int H1 = map(analogRead(P1_V_PIN), 0, 1023, -1, 1);
if (PHASE == 0)
{
selection(H0, V0, X0, Y0, 0);
selection(H1, V1, X1, Y1, 1);
if (PHASE == 0)
{
lcd.print("P0");
lcd.setCursor(5, 0);
lcd.print("PHASE");
lcd.setCursor(7, 1);
lcd.print(PHASE);
lcd.setCursor(12, 0);
lcd.print("P1");
}
lcd.setCursor(0, 1);
lcd.write(byte(0));
lcd.print(maxX - X0);
lcd.print(",");
lcd.print(maxY - Y0);
lcd.setCursor(12, 1);
lcd.write(byte(0));
lcd.print(maxX - X1);
lcd.print(",");
lcd.print(maxY - Y1);
}
delay(30);
}
void tempBoard_print()
{
if (DEBUG)
{
Serial.print("[!] Board printing\n");
Serial.print("P0 temp, P1 temp, P0 main, P1 main\n");
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++)
{
Serial.print(player[0].tempBoard[i][j]);
}
Serial.print(" ");
for (int j = 0; j < SIZE; j++)
{
Serial.print(player[1].tempBoard[i][j]);
}
Serial.print(" ");
for (int j = 0; j < SIZE; j++)
{
Serial.print(player[0].Board[i][j]);
}
Serial.print(" ");
for (int j = 0; j < SIZE; j++)
{
Serial.print(player[1].Board[i][j]);
}
Serial.println();
}
}
}
void cursor_draw(int X, int Y, int p)
{
if (p == 0)
{
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++)
{
if (player[p].Board[i][j] == '-')
{
P0HLCD.setLed(0, i, maxX - j, false);
}
else
{
P0HLCD.setLed(0, i, maxY - j, true);
}
}
}
if (player[p].selectedShip[0] < 3)
{
for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][0]; i++)
{ // i = 1
for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][1]; j++)
{ // j = 2
if (SHIPS[player[p].selectedShip[0]][j][i] == 1)
{
P0HLCD.setLed(0, Y + i, X + j, true);
}
}
}
}
else
{
for (int i = 4; i > 0; i--)
{ // i = 1
for (int j = 4; j < 0; j--)
{ // j = 2
if (SHIPS[player[p].selectedShip[0]][i][j] == 1)
{
P0HLCD.setLed(0, maxX - X + i, maxY - Y + j, true);
}
}
}
}
}
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][1]; j++)
// { // j = 3
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][0]; i++)
// { // i = 2
// if (player[p].tempBoard[X + i][Y + j] == '-')
// {
// if (SHIPS[player[p].selectedShip[0]][i][j] == 1)
// {
// P0HLCD.setLed(0, X + j, Y + i, true);
// Serial.print("X");
// }
// else
// {
// Serial.print("O");
// }
// }
// }
// }
// Serial.println("--");
//}
}
bool validate_position(int H, int V, int& X, int& Y, int p)
{
if (p == 0)
{
for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
{
for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
{
if (SHIPS[player[p].selectedShip[0]][i][j] == 0)
{
continue;
}
if (X + i > maxX)
{
Serial.println("[*] Cannot move there.");
X = X - H;
return 1;
}
if (Y + j > maxY)
{
Serial.println("[*] Cannot move there.");
Y = Y - V;
return 1;
}
if (player[p].Board[X + i][maxY - Y + j] == 'O')
{
Serial.println("[*] Ships overlapping");
X = X - H;
Y = Y - V;
return 2;
}
}
}
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
Serial.print(SHIPS[player[p].selectedShip[0]][i][j]);
if (SHIPS[player[p].selectedShip[0]][i][j] == 1)
{
player[p].tempBoard[Y + j][maxX - X + i] = 'N'; //[Y+j][maxX - X + i]
player[p].Board[Y + j][maxX - X + i] = 'O';
}
}
Serial.println();
}
player[p].selectedShip[0]++;
player[p].selectedShip[1] = 0;
X = 0;
Y = 0;
tempBoard_print();
}
return 0;
}
// Selection function
// H is the corresponding player joystick horizontal movement
// V is the corresponding player joystick vertical movement
// X is the corresponding player X position on board
// Y is the correspondong player Y position on board
// p is the corresponding player requesting changes
void selection(int H, int V, int& X, int& Y, int p)
{
char vertString[10];
char horzString[10];
char CWString[30];
sprintf(vertString, "[P%i] V %i", p, V);
sprintf(horzString, "[P%i] H %i", p, H);
sprintf(CWString, "[P%i] Ship rotated 90 (%i/3)", p, player[p].selectedShip[1]);
if (V != 0) // Joystick Moved Vertically
{
Y = max(0, min(Y + V, maxY));
Serial.println(vertString);
}
if (H != 0) // Joystick Moved Vertically
{
X = max(0, min(X - H, maxX));
Serial.println(horzString);
}
cursor_draw(X, Y, p);
if (digitalRead(P0_S_PIN) == LOW)
{
validate_position(H, V, X, Y, p);
}
}
// if (PHASE >= 0)
// {
// selection(H0, V0, X0, Y0, 0);
// selection(H1, V1, X1, Y1, 1);
// if (PHASE == 0)
// {
// lcd.print("P0");
// lcd.setCursor(12,0);
// lcd.print("P1");
// }
// lcd.setCursor(0,1);
// lcd.write(byte(0));
// lcd.print(X0);
// lcd.print(",");
// lcd.print(Y0);
// lcd.setCursor(12,1);
// lcd.write(byte(0));
// lcd.print(X1);
// lcd.print(",");
// lcd.print(Y1);
// }
// delay(125);
// }
// // -------------------------
// // Selection function
// // H is the corresponding player joystick horizontal movement
// // V is the corresponding player joystick vertical movement
// // X is the corresponding player X position on board
// // Y is the correspondong player Y position on board
// // p is the corresponding player requesting changes
// void selection(int H, int V, int& X, int& Y, int p)
// {
// char vertString[10];
// char horzString[10];
// char CWString[30];
// char OOB[20];
// sprintf(vertString,"[P%i] V %i", p, V);
// sprintf(horzString,"[P%i] H %i", p, H);
// sprintf(CWString, "[P%i] Ship rotated 90 (%i/3)", p, player[p].selectedShip[1]);
// sprintf(OOB,"Out of Bounds");
// if (V != 0) // Joystick Moved Vertically
// {
// Y = max(0, min(Y + V, maxY));
// Serial.println(vertString);
// }
// if (H != 0) // Joystick Moved Vertically
// {
// X = max(0, min(X - H, maxX));
// Serial.println(horzString);
// }
// if (p == 0) // Check for player 0 joystick button for ship rotation
// {
// if (digitalRead(P0_S_PIN) == LOW)
// {
// player[p].selectedShip[1]++;
// if (player[p].selectedShip[1] > 3)
// {
// player[p].selectedShip[1] = 0;
// }
// Serial.println(CWString);
// delay(125);
// }
// // if (digitalRead(P0_B_ONE) == LOW)
// // {
// // draw_ship(X, Y, p);
// // }
// if (digitalRead(P0_B_TWO) == LOW)
// {
// record_ship(X, Y, p);
// player[p].selectedShip[0]++;
// player[p].selectedShip[1] = 0;
// }
// }
// if (p == 1) // Check for player 1 joystick button for ship rotation
// {
// if (digitalRead(P1_S_PIN) == LOW)
// {
// player[p].selectedShip[1]++;
// if (player[p].selectedShip[1] > 3)
// {
// player[p].selectedShip[1] = 0;
// }
// Serial.println(CWString);
// delay(125);
// }
// // if (digitalRead(P1_B_ONE) == LOW)
// // {
// // check_ship(X, Y, p);
// // }
// // if (digitalRead(P1_B_TWO) == LOW)
// // {
// // record_ship(X, Y, p);
// // }
// }
// draw_ship(X, Y, p);
// }
// void draw_ship(int& X, int& Y, int p)
// {
// for (int i = 0; i < 8; i++)
// {
// for (int j = 0; j < 8; j++)
// {
// player[p].tempBoard[i][j] = player[p].Board[i][j];
// }
// }
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
// {
// if (Y + j > maxY || Y + j < 0)
// {
// Y = max(0, min(Y + j, maxY));
// Serial.println("Cannot fit ship, doesn't fit on board");
// player[p].shipConfirmation = 0;
// return;
// }
// if (X + i > maxX || X + i < 0)
// {
// X = max(0, min(X + i, maxX));
// Serial.println("Cannot fit ship, doesn't fit on board");
// player[p].shipConfirmation = 0;
// return;
// }
// if (player[p].tempBoard[X + i][Y + j] != 0)
// {
// Serial.println("Cannot fit ship, overlapping other ships");
// player[p].shipConfirmation = 0;
// return;
// }
// }
// }
// if (p == 0)
// {
// for (int i = 0; i < SIZE; i++)
// {
// for (int j = 0; j < SIZE; j++)
// {
// if (player[p].Board[i][j] == 0)
// {
// P0HLCD.setLed(0, i, j, false);
// }
// }
// }
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
// {
// player[p].tempBoard[X+i][Y+j] = 1;
// P0HLCD.setLed(0, X+i, Y+j, true);
// }
// }
// return;
// }
// if (p == 1)
// {
// P1HLCD.clearDisplay(0);
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
// {
// player[p].tempBoard[X+i][Y+j] = 1;
// P1HLCD.setLed(0, X+i, Y+j, true);
// }
// }
// for (int i = 0; i < SIZE; i++)
// {
// for (int j = 0; j < SIZE; j++)
// {
// P0HLCD.setLed(0, i, j, player[p].Board[i][j]);
// }
// }
// return;
// }
// }
// void record_ship(int X, int Y, int p)
// {
// if (p == 0)
// {
// P0HLCD.clearDisplay(0);
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
// {
// player[p].Board[X+i][Y+j] = player[p].tempBoard[X+i][Y+j];
// P0HLCD.setLed(0, X+i, Y+j, true);
// }
// }
// return;
// }
// if (p == 1)
// {
// P1HLCD.clearDisplay(0);
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
// {
// player[p].Board[X+i][Y+j] = player[p].tempBoard[X+i][Y+j];
// P1HLCD.setLed(0, X+i, Y+j, true);
// }
// }
// return;
// }
// }
// void check_ship(int X, int Y, int p)
// {
// if (player[p].selectedShip[1] = 0)
// {
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][1]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][0]; j++)
// {
// if (X + i > maxX || X + i < 0 || Y + j > maxY || Y + j < 0)
// {
// Serial.println("Cannot fit ship, doesn't fit on board");
// player[p].shipConfirmation = 0;
// return;
// }
// if (player[p].tempBoard[X + i][Y + j] != 0)
// {
// Serial.println("Cannot fit ship, overlapping other ships");
// player[p].shipConfirmation = 0;
// return;
// }
// }
// }
// }
// Serial.println("Successful fit");
// player[p].shipConfirmation = 1;
// }
// if (p != 1)
// {
// if (V < 0) {
// Serial.print(vertString);
// Y = min(Y + 1, maxY);
// }
// else if (V > 0) {
// Serial.print(vertString);
// Y = max(Y - 1, 0);
// }
// if (H > 0) {
// Serial.print(horzString);
// X = min(X + 1, maxX);
// }
// else if (H < 0) {
// Serial.print(horzString);
// X = max(X - 1, 0);
// }
// if (digitalRead(P0_S_PIN) == LOW)
// {
// Serial.println("Ship rotated!");
// if (player[p].selectedShip[1] == 0)
// {
// player[p].selectedShip[1]++; // rotate ship
// }
// else
// {
// player[p].selectedShip[1] = 0;
// }
// }
// if (player[p].selectedShip[1] == 0)
// {
// for (int i = 0; i < SHIPS[player[p].selectedShip[0]][4][0]; i++)
// {
// for (int j = 0; j < SHIPS[player[p].selectedShip[0]][4][1]; j++)
// {
// if (SHIPS[player[p].selectedShip[0]][i][j])
// {
// if (X + i <= player[p].tempBoard[X + i][Y + j] != 0)
// {
// return;
// }
// }
// }
// }
// }
// }
// }
// void update_display(int X, int Y, int p, int s)
// {
// for (int i = 0; i < SHIPS[s][0][1]; i++)
// {
// for (int j = 0; j < SHIPS[s][0][2]; j++)
// {
// if (SHIPS[s][i][j] == 1)
// {
// player[p].tempBoard[X + j][Y + i] = 1;
// }
// }
// }
// Serial.print("\n");
// }
// void draw_display(int p)
// {
// for (int i = 0; i < maxY; i++)
// {
// for (int j = 0; j < maxX; j++)
// {
// if (player[p].tempBoard[i][j] != 0)
// {
// switch (p)
// {
// case 0: P0HLCD.setLed(0, Y, X, true); break;
// case 1: P1HLCD.setLed(0, Y, X, true); break;
// }
// }
// }
// }
// }
// TODO: Change to 8x8 array
// if (p == 0)
// {
// if (digitalRead(P0_S_PIN) == LOW) {
// P0HLCD.clearDisplay(0);
// }
// P0HLCD.setLed(0, Y, X, true);
// }
// else
// {
// if (digitalRead(P1_S_PIN) == LOW) {
// P0HLCD.clearDisplay(0);
// }
// P1HLCD.setLed(0, Y, X, true);
// }