#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "GyverButton.h"
#define SCREEN_WIDTH 128 // Ширина OLED-дисплея, в пикселях
#define SCREEN_HEIGHT 64 // Высота OLED-дисплея, в пикселях
// Декларация для дисплея SSD1306, подключенного к I2C (контакты SDA, SCL)
#define OLED_RESET 7 // Сброс pin-кода # (или -1, если используется общий pin-код сброса Arduino)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define BTN_LEFT_PIN 4
#define BTN_UP_PIN 2
#define BTN_RIGHT_PIN 5
#define BTN_DOWN_PIN 3
GButton btnLeft(BTN_LEFT_PIN);
GButton btnUp(BTN_UP_PIN);
GButton btnRight(BTN_RIGHT_PIN);
GButton btnDown(BTN_DOWN_PIN);
typedef enum {
RUN,
WIN,
LOSS
} GameState;
#define SIZE 4
int field[SIZE*SIZE];
int score;
GameState gameState;
// Функция запускаемая в самом начел программы
void setup() {
Serial.begin(9600);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
randomSeed(analogRead(A0));
resetGame();
}
// Перезапускает игру
void resetGame() {
gameState = RUN;
score = 0;
resetField();
display.clearDisplay();
drawField();
drawNumbers();
drawScore();
display.display();
}
// Сбрасывает поле в начальное состояние
void resetField() {
// Инициализируем ячейки поля нулями
for (int i = 0; i < SIZE*SIZE; i++) {
field[i] = 0;
}
int t = 0;;
// Заполняем две случайные клетки двойками/четверками
for (int i = 0; i < 2; i++) {
t = getRandomNumber();
field[getRandomEmptyCell()] = t;
score += t;
}
}
// Возвращает адрес случайной пустой ячейки поля.
// Если нет свободных ячеек, вернет -1
int getRandomEmptyCell() {
int emptyCells[SIZE * SIZE];
int count = 0;
// Поиск свободных ячеек
for (int i = 0; i < SIZE * SIZE; ++i) {
if (field[i] == 0) {
emptyCells[count] = i;
++count;
}
}
// Если не нашлось ни одной
if (count == 0) {
return -1;
}
// Выбор случайной свободной ячейки
int randomIndex = random(0, count);
return emptyCells[randomIndex];
}
// Возвращает 2 с вероятностью 0.9 или 4 с вероятностью 0.1
int getRandomNumber() {
double probability = static_cast<double>(random(0, 10)) / 10;
if (probability < 0.9) {
return 2;
}
else {
return 4;
}
}
// Получает на вход массив чисел и преобразует в соответствии с правилами игры
void calculationSubarray(int* subarray) {
int res[SIZE];
int rightPreverifiedI = 0;
int unoccupiedI = 0;
for (int i = 0; i < SIZE; i++) {
res[i] = 0;
}
for (int i = 0; i < SIZE; i++){
if (subarray[i] != 0) {
if (subarray[i] != res[rightPreverifiedI]) {
rightPreverifiedI = unoccupiedI;
res[unoccupiedI++] = subarray[i];
}
else {
res[rightPreverifiedI++] += subarray[i];
}
}
}
for (int i = 0; i < SIZE; i++) {
subarray[i] = res[i];
}
}
// Делает одно передвижение поля в верх
void moveUp() {
int tempArr[SIZE];
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++) {
tempArr[j] = field[i + j * SIZE];
}
calculationSubarray(tempArr);
for (int j = 0; j < SIZE; j++)
{
field[i + j * SIZE] = tempArr[j];
tempArr[j] = 0;
}
}
}
// Делает одно передвижение поля в низ
void moveDown() {
int tempArr[SIZE];
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
tempArr[j] = field[i + (SIZE - j - 1) * SIZE];
}
calculationSubarray(tempArr);
for (int j = 0; j < SIZE; j++) {
field[i + (SIZE - j - 1) * SIZE] = tempArr[j];
}
}
}
// Делает одно передвижение поля в лево
void moveLeft() {
int tempArr[SIZE];
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
tempArr[j] = field[i * SIZE + j];
}
calculationSubarray(tempArr);
for (int j = 0; j < SIZE; j++) {
field[i * SIZE + j] = tempArr[j];
}
}
}
// Делает одно передвижение поля в право
void moveRight() {
int tempArr[SIZE];
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
tempArr[j] = field[i * SIZE + (SIZE - j - 1)];
}
calculationSubarray(tempArr);
for (int j = 0; j < SIZE; j++) {
field[i * SIZE + (SIZE - j - 1)] = tempArr[j];
}
}
}
// Проферает условие победы в игре
bool isWin() {
for (int i = 0; i < SIZE * SIZE; i++) {
if (field[i] == 2048) {
return true;
}
}
return false;
}
// Выполняет один ход игры и проверяет на проигрыш/выигрыш
void makeMove(int dir) {
// Выполняем движение в переданном направлении dir
switch (dir) {
case 0:
moveLeft();
break;
case 1:
moveUp();
break;
case 2:
moveRight();
break;
case 3:
moveDown();
break;
}
// Получение свободной ячейки
int i = getRandomEmptyCell();
if (isWin()) { // Проверяем на победу
gameState = WIN;
return;
}
else if (i == -1) { // Не одной свободной ячейки не найдено, значит проигрышь
gameState = LOSS;
return;
}
// Создание нового числа в ячейке с номером i
field[i] = getRandomNumber();
score += field[i];
}
// Главный цикл программы
void loop() {
if (gameState == RUN) { // Если игра находится в состоянии RUN
int dir = readDirection();
if (dir != 4) {
makeMove(dir);
display.clearDisplay();
drawField();
drawNumbers();
drawScore();
display.display();
}
}
else if (gameState == WIN) { // Если игра находится в состоянии WIN
display.println();
display.println("YOU WIN");
display.display();
delay(2000);
resetGame();
}
else if (gameState == LOSS) { // Если игра находится в состоянии LOSS
display.println();
display.println("YOU LOSS");
display.display();
delay(2000);
resetGame();
}
}
// Получение направления с нажатой кнопки
int readDirection() {
btnLeft.tick();
btnUp.tick();
btnRight.tick();
btnDown.tick();
if (btnLeft.isClick()) return 0;
else if (btnUp.isClick()) return 1;
else if (btnRight.isClick()) return 2;
else if (btnDown.isClick()) return 3;
else return 4;
}
// Отображение сетки игрогова поля
void drawField() {
int cellSize = SCREEN_HEIGHT / SIZE;
display.drawRect(SCREEN_WIDTH / 2, 0, SCREEN_HEIGHT, SCREEN_HEIGHT, SSD1306_WHITE);
for (int i = 0; i < SIZE*SIZE; i++) {
for (int j = 0; j < SIZE*SIZE; j++) {
display.drawRect(SCREEN_WIDTH / 2 + cellSize * j, cellSize * i, cellSize, cellSize, SSD1306_WHITE);
}
}
}
// Рисует условное обозначение числа num в ячейке поля с координатами (X, Y)
void drawNumber(int num, int X, int Y) {
// В ячейке записан 0
if (num == 0) {
return;
}
int cellSize = SCREEN_HEIGHT / SIZE;
int N = 1;
// Вычисление какой степенью 2 является число num
for (int i = 1; i <= 10; i++) {
N *= 2;
if (N == num) {
N = i;
break;
}
}
if (N <= 9) { // Если num <= 2^9
for (int i = 0; i < N; i++) {
div_t d = div(i, 3);
display.fillRect(
SCREEN_WIDTH / 2 + cellSize * X + 3 + 4 * d.rem,
cellSize * Y + 3 + 4 * d.quot,
2,
2,
SSD1306_WHITE
);
}
}
else if (N == 10){ // Если num = 2^10
display.fillRect(
SCREEN_WIDTH / 2 + cellSize * X + 4,
cellSize * Y + 4,
cellSize - 9,
cellSize - 9,
SSD1306_WHITE
);
}
else { // Если num = 2^11
display.fillRect(
SCREEN_WIDTH / 2 + cellSize * X + 4,
cellSize * Y + cellSize / 2 - 1,
cellSize - 8,
2,
SSD1306_WHITE
);
display.fillRect(
SCREEN_WIDTH / 2 + cellSize * X + cellSize / 2 - 1,
cellSize * Y + 4,
2,
cellSize - 8,
SSD1306_WHITE
);
}
}
// Рисует условная обозначения чисел на поле
void drawNumbers() {
for (int i = 0; i < SIZE; i++) {
for (int j = 0; j < SIZE; j++) {
drawNumber(field[i + j * SIZE], i, j);
}
}
}
// Отображает текущее значение счетчика
void drawScore() {
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(2,2);
display.print(F("Score:"));
display.println(score);
}