//Copyright (C) 18.06.2025, Kirill ZHivotkov
/*
Игра "Color Lines".
*/
#include <Adafruit_NeoPixel.h>
#include <Vector.h>
#include <limits.h>
#define PIN 4
#define RGB 7
#define SIZE 9
#define START_BALLS 5
#define NEW_BALLS 3
#define MOVE 512
#define SLEEP 0
#define SPEED 200
#define BLACK 167777215
#define CYAN 65535
#define RED 65280
#define GREEN 16711680
#define MAGENTA 16711935
#define BLUE 255
#define YELLOW 16776960
#define ORANGE 16744192
#define XJ1 A0
#define YJ1 A1
#define SW1 A2
Adafruit_NeoPixel pixels(SIZE * SIZE, PIN);
struct Color {
byte r;
byte g;
byte b;
};
struct Coord {
byte x;
byte y;
Color c;
};
int p_x = 0;
int p_y = 0;
int c_x = 0;
int c_y = 0;
int b_x = 0;
int b_y = 0;
static int k = 0;
bool error = false;
bool isSelected = false;
bool isBlinking = false;
bool isOverTheBall = false;
Coord ball;
Coord bb;
Coord storage[START_BALLS] = {};
Vector<Coord> v(storage, START_BALLS);
//String direction[SHIP] = {"HOR", "VER", "HOR", "VER", "HOR", "VER", "HOR", "VER", "HOR", "VER"};
//Deck decks[SHIP] = {{4, 0}, {3, 0}, {3, 0}, {2, 0}, {2, 0}, {2, 0}, {1, 0}, {1, 0}, {1, 0}, {1, 0}};
Color colors[RGB] = {{0, UCHAR_MAX, UCHAR_MAX /*Cyan*/},
{0, UCHAR_MAX, 0 /*Green*/},
{UCHAR_MAX, 0, 0 /*Red*/},
{UCHAR_MAX, 0, UCHAR_MAX /*Magenta*/},
{0, 0, UCHAR_MAX /*Blue*/},
{UCHAR_MAX, UCHAR_MAX, 0 /*Yellow*/},
{UCHAR_MAX, UCHAR_MAX / 2, 0 /*Orange*/}};
void setup() {
Serial.begin(9600);
pixels.begin();
//Link joystick
pinMode(XJ1, INPUT);
pinMode(YJ1, INPUT);
pinMode(SW1, INPUT);
//Link joystick
//Clear vectors initialized with zeroes
v.clear();
//Clear vectors initialized with zeroes
//Shuffle decks and colors (primarily for the random set)
ShuffleColors(colors, RGB);
//Shuffle decks and colors (primarily for the random set)
//Set the initial led position (orange one in the left top corner)
pixels.setPixelColor(0, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
//Set the initial led position (orange one in the left top corner)
GenerateRandomBallSet(START_BALLS);
}
//Shuffle decks and their colors (template function)
template<class T>
void ShuffleColors(T arr[], byte limit) {
for (byte i = 0; i < limit; i++) {
byte n = random(0, limit);
auto temp = arr[n];
arr[n] = arr[i];
arr[i] = temp;
}
}
//Shuffle decks and their colors (template function)
//Clear game field
void ClearGameField() {
for (int i = 0; i < v.size(); i++) {
pixels.setPixelColor(XY(v[i].x, v[i].y), pixels.Color(0, 0, 0));
pixels.show();
}
}
//Clear game field
Coord GetBlinkingBallCoord() {
for (byte j = 0; j < v.size(); j++) {
if ((v[j].y == p_x) && (v[j].x == p_y)) {
return v[j];
}
}
}
//Draw all balls at once
void DrawAllBallsAtOnce() {
for (int i = 0; i < v.size(); i++) {
if (v[i].x != bb.x && v[i].y != bb.y) {
pixels.setPixelColor(XY(v[i].x, v[i].y), pixels.Color(v[i].c.r, v[i].c.g, v[i].c.b));
pixels.show();
}
}
}
//Draw all balls at once
//Check if one ball does not overlap with another
void ResetBallAfterOverlap(int x, int y, int color) {
for (byte j = 0; j < v.size(); j++) {
if ((v[j].x == x) && (v[j].y == y)) {
Serial.println("OVERLAPPED!");
delay(SLEEP);
error = true;
}
}
}
//Check if one ball does not overlap with another
//Set a random ball RGB color
void SetBallRGBColor(byte x, byte y, int index) {
ball.x = x;
ball.y = y;
ball.c = colors[index];
v.push_back(ball);
pixels.setPixelColor(XY(x, y), pixels.Color(ball.c.r, ball.c.g, ball.c.b));
pixels.show();
}
//Set a random ball RGB color
//Set a random ball position
void SetRandomBallPosition(int x, int y, int color) {
error = false;
ResetBallAfterOverlap(x, y, color);
if (error == false) {
SetBallRGBColor(x, y, color);
delay(SLEEP);
Serial.println("OK!");
}
}
//Set a random ball position
//Set a player joystick control
void PlayerJoystickControl() {
//Start of the game
if ((analogRead(SW1) == 0) && (isSelected == false) && (v.size() == START_BALLS)) {
isSelected = true;
Serial.println("Start Blinking!");
k = 0;
isBlinking = true;
bb = GetBlinkingBallCoord();
}
//Start of the game
int x = analogRead(A1);
int y = analogRead(A0);
if (x == 2 * MOVE - 1 && y == MOVE) { //LEFT
if (p_y - 1 >= 0) {
isSelected = false;
if (pixels.getPixelColor(p_y + p_x * SIZE) != CYAN &&
pixels.getPixelColor(p_y + p_x * SIZE) != GREEN &&
pixels.getPixelColor(p_y + p_x * SIZE) != RED &&
pixels.getPixelColor(p_y + p_x * SIZE) != MAGENTA &&
pixels.getPixelColor(p_y + p_x * SIZE) != BLUE &&
pixels.getPixelColor(p_y + p_x * SIZE) != YELLOW &&
pixels.getPixelColor(p_y + p_x * SIZE) != ORANGE) {
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(0, 0, 0));
pixels.show();
DrawAllBallsAtOnce();
}
p_y--;
if (pixels.getPixelColor(p_y + p_x * SIZE) != 0) {
Serial.println("1");
isOverTheBall = true;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
} else {
Serial.println("2");
isOverTheBall = false;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
}
}
delay(SPEED);
} else if (x == 0 && y == MOVE) { //RIGHT
if (p_y + 1 < SIZE) {
isSelected = false;
if (pixels.getPixelColor(p_y + p_x * SIZE) != CYAN &&
pixels.getPixelColor(p_y + p_x * SIZE) != GREEN &&
pixels.getPixelColor(p_y + p_x * SIZE) != RED &&
pixels.getPixelColor(p_y + p_x * SIZE) != MAGENTA &&
pixels.getPixelColor(p_y + p_x * SIZE) != BLUE &&
pixels.getPixelColor(p_y + p_x * SIZE) != YELLOW &&
pixels.getPixelColor(p_y + p_x * SIZE) != ORANGE) {
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(0, 0, 0));
pixels.show();
DrawAllBallsAtOnce();
}
p_y++;
if (pixels.getPixelColor(p_y + p_x * SIZE) != 0) {
Serial.println("1");
isOverTheBall = true;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
} else {
Serial.println("2");
isOverTheBall = false;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
}
}
delay(SPEED);
} else if (y == 2 * MOVE - 1 && x == MOVE) { //DOWN
if (p_x + 1 < SIZE) {
isSelected = false;
if (pixels.getPixelColor(p_y + p_x * SIZE) != CYAN &&
pixels.getPixelColor(p_y + p_x * SIZE) != GREEN &&
pixels.getPixelColor(p_y + p_x * SIZE) != RED &&
pixels.getPixelColor(p_y + p_x * SIZE) != MAGENTA &&
pixels.getPixelColor(p_y + p_x * SIZE) != BLUE &&
pixels.getPixelColor(p_y + p_x * SIZE) != YELLOW &&
pixels.getPixelColor(p_y + p_x * SIZE) != ORANGE) {
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(0, 0, 0));
pixels.show();
DrawAllBallsAtOnce();
}
p_x++;
if (pixels.getPixelColor(p_y + p_x * SIZE) != 0) {
Serial.println("1");
isOverTheBall = true;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
} else {
Serial.println("2");
isOverTheBall = false;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
}
}
delay(SPEED);
} else if (y == 0 && x == MOVE) { //UP
if (p_x - 1 >= 0) {
isSelected = false;
if (pixels.getPixelColor(p_y + p_x * SIZE) != CYAN &&
pixels.getPixelColor(p_y + p_x * SIZE) != GREEN &&
pixels.getPixelColor(p_y + p_x * SIZE) != RED &&
pixels.getPixelColor(p_y + p_x * SIZE) != MAGENTA &&
pixels.getPixelColor(p_y + p_x * SIZE) != BLUE &&
pixels.getPixelColor(p_y + p_x * SIZE) != YELLOW &&
pixels.getPixelColor(p_y + p_x * SIZE) != ORANGE) {
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(0, 0, 0));
pixels.show();
DrawAllBallsAtOnce();
}
p_x--;
if (pixels.getPixelColor(p_y + p_x * SIZE) != 0) {
Serial.println("1");
isOverTheBall = true;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
} else {
Serial.println("2");
isOverTheBall = false;
pixels.setPixelColor(p_y + p_x * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, UCHAR_MAX));
pixels.show();
}
}
delay(SPEED);
}
}
//Set a player joystick control
//Generate a random multi-colored field with ships (according to the Naval Clash game rules)
void GenerateRandomBallSet(int qty) {
ClearGameField();
v.clear();
for (byte i = 0; i < qty; i++) {
SetRandomBallPosition(random(1, SIZE), random(1, SIZE), random(0, RGB));
if (error == true) {
i--;
}
}
}
//Generate a random multi-colored field with ships (according to the Naval Clash game rules
void loop() {
randomSeed(micros() + analogRead(A0));
PlayerJoystickControl();
static int lastBallBlinkingCheck = 0;
if (millis() - lastBallBlinkingCheck > 500) {
lastBallBlinkingCheck = millis();
if (isBlinking == true && isOverTheBall == false) {
if (k % 2 == 0) {
pixels.setPixelColor(XY(bb.x, bb.y), pixels.Color(0, 0, 0));
pixels.show();
} else {
pixels.setPixelColor(XY(bb.x, bb.y), pixels.Color(bb.c.r, bb.c.g, bb.c.b));
pixels.show();
}
}
k++;
}
/*
pixels.setPixelColor(0 + 0 * SIZE, pixels.Color(0, UCHAR_MAX, UCHAR_MAX));
Serial.println(pixels.getPixelColor(0 + 0 * SIZE));
pixels.setPixelColor(1 + 0 * SIZE, pixels.Color(0, UCHAR_MAX, 0));
Serial.println(pixels.getPixelColor(1 + 0 * SIZE));
pixels.setPixelColor(2 + 0 * SIZE, pixels.Color(UCHAR_MAX, 0, 0));
Serial.println(pixels.getPixelColor(2 + 0 * SIZE));
pixels.setPixelColor(3 + 0 * SIZE, pixels.Color(UCHAR_MAX, 0, UCHAR_MAX));
Serial.println(pixels.getPixelColor(3 + 0 * SIZE));
pixels.setPixelColor(4 + 0 * SIZE, pixels.Color(0, 0, UCHAR_MAX));
Serial.println(pixels.getPixelColor(4 + 0 * SIZE));
pixels.setPixelColor(5 + 0 * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX, 0));
Serial.println(pixels.getPixelColor(5 + 0 * SIZE));
pixels.setPixelColor(6 + 0 * SIZE, pixels.Color(UCHAR_MAX, UCHAR_MAX / 2, 0));
Serial.println(pixels.getPixelColor(6 + 0 * SIZE));
delay(60000 * 5);
*/
}
int XY(int x, int y) {
return y * SIZE + x;
}