Шарики в колбах (скетч на халяву)

Как и обещал создаю тему по типу открытого на предмет завершения проекта.
Есть такая игра “Шарики в колбах” - чисто для релакса на 5-7 минут в день.

Реализована на ПК и смартфонах. А вот на ардуино (rp2040) - нет. Попробовал и сделал пока графику и мат.модель (массив двухмерный). А вот дальше пока присел.
Подробнее что сделал, что клянчу позже дополню.

1 лайк

Так это то же самое что с кольцами пирамидками. Решается методом рекурсии. Главное, чтобы стека хватило. Нет?

Наверное. Но в вопрос не в решении - перекладывай шарики и само решится как сказала мне супруга :slight_smile:
…Графика состоит из фоновой картинки 320Х240 и 14 мелких 25Х25:


Модель - двухмерный массив 6Х5 (5 строк, 6 столбцов) на 30 элементов с значениями 0-6.
Собственно в этих пределах и планируются полёты шариков между колбами.
скетч-проба графики на 2 функции:

//игра колбы и шарики
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include "ris_.h"//файл хранения массивов фотокартинок
#define TFT_CS     17
#define TFT_DC     20
//#define TFT_RST -1            // Пин подключения вывода RESET (ПРИ -1 СОЕДИНЯЕМ С +3,3В)
//#define TFT_MISO 16           // Пин подключения вывода дисплея SDO(MISO)
//#define TFT_MOSI 19           // Пин подключения вывода дисплея SDI(MOSI)
//#define TFT_CLK 18            // Пин подключения вывода дисплея SCK
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS,  TFT_DC);//
const struct { 
    const unsigned char *bitmap;
  }
 schar_kolba[]={ris_00,ris_01,ris_02,ris_03,ris_04,ris_05,ris_06};// картинки шариков на фоне колбы
const struct { 
    const unsigned char *bitmap;
  }
 schar_fon[]={ris_000,ris_001,ris_002,ris_003,ris_004,ris_005,ris_006};// картинки шариков на фоне игрового поля 
byte massiv_model[5][6]={ {0,2,0,4,0,6},//массив-модель игрового поля
                          {1,0,3,0,5,0},
                          {1,2,3,4,5,6},
                          {1,2,3,4,5,6},
                          {1,2,3,4,5,6},
};
void setup() {
 //Serial.begin(9600);
  tft.begin();
  tft.setRotation(3);
// tft.fillScreen(tft.color565(0,0,0));
 drawFoto(0,0,fon_,320,240);//фоновая картинка игрового поля
 ris_model();//отрисовка массива-модели
 }
void loop() {

}
/////////////////////////////////////////////////////////////////
void drawFoto(int x,int y, const uint8_t *bitmap,int w,int h) {//функция вывода фотокартинки
if(x<0||x+w>320||y<0||y+h>240){return;} 
// tft.setAddrWindow(x,y,w,h);
 SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));
  digitalWrite(TFT_DC, HIGH);
  digitalWrite(TFT_CS, LOW);
 tft.setAddrWindow(x,y,w,h);  
 for(int j=0; j<h; j++) {
    for(int i=0; i<2*w; i=i+2) {
    SPI.transfer(bitmap[i+1+j*2*w]);SPI.transfer(bitmap[i+j*2*w]);
  // SPI.transfer(pgm_read_byte(bitmap+i+1+j*2*w));SPI.transfer(pgm_read_byte(bitmap+i+j*2*w)); 
    }
  }
  digitalWrite(TFT_CS, HIGH);
  SPI.endTransaction();
 ///  
}
//////////////////////////////////////////////////////////
void ris_scharik(int stroka,int stolbez){// функция отрисовки шарика или пусто-места на игровом поле по его положению в массиве-модели
if(stroka==0){drawFoto(22+stolbez*50,30,schar_fon[massiv_model[stroka][stolbez]].bitmap,25,25);}//
else {drawFoto(22+stolbez*50,45+stroka*25,schar_kolba[massiv_model[stroka][stolbez]].bitmap,25,25);}  
}
//////////////////////////////////////////////////////////
void ris_model(){// функция отрисовки всей модели-массива
for(int i=0;i<5;i++){
  for(int j=0;j<6;j++){
    ris_scharik(i,j);
  }
 }
}
//////////////////////////////////////////////////////////

Первая функция-задача перемешивание шаров по колбам, т.е. формирование исходного массива-задания.
… где то вычитал умное словосочетание по этой игре - обратная генерация.

1 лайк

Не похоже. Там три пирамидки, а здесь колб много.

Колб может быть много и вместимость у них различная может быть. Правила варьируются широко. Я для опыта правила упростил - сбор шаров по цвету в определённую колбу (цветные лужи под колбами), изначально в раскладке-задании 4 шарика одного цвета отсутствуют (4 пусто-места).
… схему тоже упростил по сравнению с пятнашками:

1 лайк

Добавил 6 “невидимых” сенсорных кнопок с полями активности:


Написал функцию игровой анимации шариков.

Спойлер
//игра колбы и шарики
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <XPT2046_Touchscreen.h>// Библиотека для работы с сенсорным экраном
#include <SPI.h>
#include "ris_.h"//файл хранения массивов фотокартинок
#define TFT_CS     17
#define TFT_DC     20
#define CS_PIN  14
//#define TFT_RST -1            // Пин подключения вывода RESET (ПРИ -1 СОЕДИНЯЕМ С +3,3В)
//#define TFT_MISO 16           // Пин подключения вывода дисплея SDO(MISO)
//#define TFT_MOSI 19           // Пин подключения вывода дисплея SDI(MOSI)
//#define TFT_CLK 18            // Пин подключения вывода дисплея SCK
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS,  TFT_DC);//
XPT2046_Touchscreen ts(CS_PIN);  // Param 2 - NULL - No interrupts
const struct { 
    const unsigned char *bitmap;
  }
 schar_kolba[]={ris_00,ris_01,ris_02,ris_03,ris_04,ris_05,ris_06};// картинки шариков на фоне колбы
const struct { 
    const unsigned char *bitmap;
  }
 schar_fon[]={ris_000,ris_001,ris_002,ris_003,ris_004,ris_005,ris_006};// картинки шариков на фоне игрового поля 
byte massiv_model[5][6]={ {0,0,0,0,0,0},//массив-модель игрового поля
                          {1,2,3,4,5,0},
                          {1,2,3,4,5,0},
                          {1,2,3,4,5,0},
                          {1,2,3,4,5,0},
};
bool fl_scharik=false;//флаг состояния - все шарики в колбах (false) или шарик вынут (true)

int x, y; // Переменные для работы с координатами нажатий
void setup() {
 //Serial.begin(9600);
  tft.begin();
  tft.setRotation(3);
  ts.begin();
  ts.setRotation(2);
// tft.fillScreen(tft.color565(0,0,0));
 drawFoto(0,0,fon_,320,240);//фоновая картинка игрового поля
 ris_model();//отрисовка массива-модели
 }
void loop() {
// 
  if(ts.touched()) { // Если имеются данные с сенсорного модуля
 TS_Point p = ts.getPoint();                      // Считываем с него данные
    y = map(p.y,220,3720,0,319);                  // Считываем и преобразуем координату нажатия Y
    x =map(p.x,275,3830,0,240);                   // Считываем и преобразуем  координату нажатия X           
    if((x!=-1) && (y!=-1))          // Если обе координаты в положительном диапазоне (т.е. если есть нажатие) 
     {
      x += 0;                      // Корректируем координату с учетом калибровочных данных
      y += 0;                      // Корректируем координату с учетом калибровочных данных
      // блок исполнения функций в игре 
      knopki();  
     }
  delay(20);   
 }
// 
}
/////////////////////////////////////////////////////////////////
void drawFoto(int x,int y, const uint8_t *bitmap,int w,int h) {//функция вывода фотокартинки
if(x<0||x+w>320||y<0||y+h>240){return;} 
// tft.setAddrWindow(x,y,w,h);
 SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));
  digitalWrite(TFT_DC, HIGH);
  digitalWrite(TFT_CS, LOW);
 tft.setAddrWindow(x,y,w,h);  
 for(int j=0; j<h; j++) {
    for(int i=0; i<2*w; i=i+2) {
    SPI.transfer(bitmap[i+1+j*2*w]);SPI.transfer(bitmap[i+j*2*w]);
  // SPI.transfer(pgm_read_byte(bitmap+i+1+j*2*w));SPI.transfer(pgm_read_byte(bitmap+i+j*2*w)); 
    }
  }
  digitalWrite(TFT_CS, HIGH);
  SPI.endTransaction();
 ///  
}
//////////////////////////////////////////////////////////
void ris_scharik(int stroka,int stolbez){// функция отрисовки шарика или пусто-места на игровом поле по его положению в массиве-модели
if(stroka==0){drawFoto(22+stolbez*50,30,schar_fon[massiv_model[stroka][stolbez]].bitmap,25,25);}//
else {drawFoto(22+stolbez*50,45+stroka*25,schar_kolba[massiv_model[stroka][stolbez]].bitmap,25,25);}  
}
//////////////////////////////////////////////////////////
void ris_model(){// функция отрисовки всей модели-массива
for(int i=0;i<5;i++){
  for(int j=0;j<6;j++){
    ris_scharik(i,j);
  }
 }
}
//////////////////////////////////////////////////////////
void knopki(){// функция отработки нажатия кнопок - игровая анимация шариков
 if(x>50&&x<190){
for(int w=0;w<6;w++){
 if(y>=25+50*w&&y<25+50*(w+1)){
//  massiv_model[0][0]=w+1;ris_scharik(0,0);
if(fl_scharik==false){
  for(int k=1;k<5;k++){//ищем и поднимаем шарик из колбы
    if(massiv_model[k][w]!=0){massiv_model[0][w]=massiv_model[k][w];massiv_model[k][w]=0;ris_scharik(0,w);ris_scharik(k,w);fl_scharik=true;goto metka;}
   }
}
else{
for(int k=0;k<6;k++){
  if(massiv_model[0][k]!=0){//если нашли поднятый над колбой шарик
     for(int s=4;s>0;s--){
    if(massiv_model[s][w]==0){if(w!=k){massiv_model[0][w]=massiv_model[0][k];massiv_model[0][k]=0;ris_scharik(0,w);ris_scharik(0,k);delay(200);}massiv_model[s][w]=massiv_model[0][w];massiv_model[0][w]=0;ris_scharik(s,w);ris_scharik(0,w);fl_scharik=false;goto metka;}//если в выбранной колбе оказалось место
   }
    for(int s=4;s>0;s--){
    if(massiv_model[s][k]==0){massiv_model[s][k]=massiv_model[0][k];massiv_model[0][k]=0;ris_scharik(s,k);ris_scharik(0,k);fl_scharik=false;goto metka;}//возврат шарика в исходную колбу если места в указанной не оказалось   
    }
    
    }
}
}
 }
}
 }
 metka:
 delay(150);
}
//////////////////////////////////////////////////////////////////

Доделал вариант готовой игры. Дальше можно усложнять, расширять, дополнять, но лень, просто поиграю :slight_smile:

Спойлер
//игра колбы и шарики
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <XPT2046_Touchscreen.h>// Библиотека для работы с сенсорным экраном
#include <SPI.h>
#include "ris_.h"//файл хранения массивов фотокартинок
#define TFT_CS     17
#define TFT_DC     20
#define CS_PIN  14
//#define TFT_RST -1            // Пин подключения вывода RESET (ПРИ -1 СОЕДИНЯЕМ С +3,3В)
//#define TFT_MISO 16           // Пин подключения вывода дисплея SDO(MISO)
//#define TFT_MOSI 19           // Пин подключения вывода дисплея SDI(MOSI)
//#define TFT_CLK 18            // Пин подключения вывода дисплея SCK
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS,  TFT_DC);//
XPT2046_Touchscreen ts(CS_PIN);  // Param 2 - NULL - No interrupts
const struct { 
    const unsigned char *bitmap;
  }
 schar_kolba[]={ris_00,ris_01,ris_02,ris_03,ris_04,ris_05,ris_06};// картинки шариков на фоне колбы
const struct { 
    const unsigned char *bitmap;
  }
 schar_fon[]={ris_000,ris_001,ris_002,ris_003,ris_004,ris_005,ris_006};// картинки шариков на фоне игрового поля 
byte massiv_model[5][6];////массив-модель игрового поля
/* 
byte massiv_model[5][6]={ {1,0,0,0,0,0},//массив-модель игрового поля
                          {1,2,0,0,0,0},
                          {1,2,3,0,0,0},
                          {1,2,3,4,0,0},
                          {1,2,3,4,5,0},
};
*/
bool fl_scharik=false;//флаг состояния - все шарики в колбах (false) или шарик вынут (true)
int st=0;//счётчик ходов в игре
int x, y; // Переменные для работы с координатами нажатий
void setup() {
 //Serial.begin(9600);
  tft.begin();
  tft.setRotation(3);
  ts.begin();
  ts.setRotation(2);
// tft.fillScreen(tft.color565(0,0,0));
tft.cp437(true);//для правильного отображения русского алфавита
 drawFoto(0,0,fon_,320,240);//фоновая картинка игрового поля
// ris_model();//отрисовка массива-модели
tft.setCursor(80, 5); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE);tft.println(utf8rus("ШАРИКИ И КОЛБЫ")); 
tft.setCursor(120, 210); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE);tft.println(utf8rus("ИГРАТЬ"));
 }
void loop() {
// 
  if(ts.touched()) { // Если имеются данные с сенсорного модуля
 TS_Point p = ts.getPoint();                      // Считываем с него данные
    y = map(p.y,220,3720,0,319);                  // Считываем и преобразуем координату нажатия Y
    x =map(p.x,275,3830,0,240);                   // Считываем и преобразуем  координату нажатия X           
    if((x!=-1) && (y!=-1))          // Если обе координаты в положительном диапазоне (т.е. если есть нажатие) 
     {
      x += 0;                      // Корректируем координату с учетом калибровочных данных
      y += 0;                      // Корректируем координату с учетом калибровочных данных
      // блок исполнения функций в игре 
      knopki();  
     }
  delay(20);   
 }
// 
}
/////////////////////////////////////////////////////////////////
void drawFoto(int x,int y, const uint8_t *bitmap,int w,int h) {//функция вывода фотокартинки
if(x<0||x+w>320||y<0||y+h>240){return;} 
// tft.setAddrWindow(x,y,w,h);
 SPI.beginTransaction(SPISettings(40000000, MSBFIRST, SPI_MODE0));
  digitalWrite(TFT_DC, HIGH);
  digitalWrite(TFT_CS, LOW);
 tft.setAddrWindow(x,y,w,h);  
 for(int j=0; j<h; j++) {
    for(int i=0; i<2*w; i=i+2) {
    SPI.transfer(bitmap[i+1+j*2*w]);SPI.transfer(bitmap[i+j*2*w]);
  // SPI.transfer(pgm_read_byte(bitmap+i+1+j*2*w));SPI.transfer(pgm_read_byte(bitmap+i+j*2*w)); 
    }
  }
  digitalWrite(TFT_CS, HIGH);
  SPI.endTransaction();
 ///  
}
//////////////////////////////////////////////////////////
void ris_scharik(int stroka,int stolbez){// функция отрисовки шарика или пусто-места на игровом поле по его положению в массиве-модели
if(stroka==0){drawFoto(22+stolbez*50,30,schar_fon[massiv_model[stroka][stolbez]].bitmap,25,25);}//
else {drawFoto(22+stolbez*50,45+stroka*25,schar_kolba[massiv_model[stroka][stolbez]].bitmap,25,25);}  
}
//////////////////////////////////////////////////////////
void ris_model(){// функция отрисовки всей модели-массива
for(int i=0;i<5;i++){
  for(int j=0;j<6;j++){
    ris_scharik(i,j);
  }
 }
}
//////////////////////////////////////////////////////////
void knopki(){// функция отработки нажатия кнопок - игровая анимация шариков
 if(x>50&&x<190){//область работы с колбами
for(int w=0;w<6;w++){
 if(y>=25+50*w&&y<25+50*(w+1)){
//  massiv_model[0][0]=w+1;ris_scharik(0,0);
if(fl_scharik==false){
  for(int k=1;k<5;k++){//ищем и поднимаем шарик из колбы
    if(massiv_model[k][w]!=0){massiv_model[0][w]=massiv_model[k][w];massiv_model[k][w]=0;ris_scharik(0,w);ris_scharik(k,w);fl_scharik=true;st++;stschet();goto metka;}
   }
}
else{
for(int k=0;k<6;k++){
  if(massiv_model[0][k]!=0){//если нашли поднятый над колбой шарик
     for(int s=4;s>0;s--){
    if(massiv_model[s][w]==0){if(w!=k){massiv_model[0][w]=massiv_model[0][k];massiv_model[0][k]=0;ris_scharik(0,w);ris_scharik(0,k);delay(250);}massiv_model[s][w]=massiv_model[0][w];massiv_model[0][w]=0;ris_scharik(s,w);ris_scharik(0,w);fl_scharik=false;goto metka;}//если в выбранной колбе оказалось место
   }
    for(int s=4;s>0;s--){
    if(massiv_model[s][k]==0){massiv_model[s][k]=massiv_model[0][k];massiv_model[0][k]=0;ris_scharik(s,k);ris_scharik(0,k);fl_scharik=false;goto metka;}//возврат шарика в исходную колбу если места в указанной не оказалось   
    }
    
    }
}
}
 }
}
 }
 else {zadanie_1();}// область работы с заданием-уровнем  
 metka:
 delay(150);
}
//////////////////////////////////////////////////////////////////
void zadanie_1(){
//предполагаемый результат выполнения задания виден несколько секунд
st=0;//обнуление счётчика ходов
for(int q=0;q<6;q++){  
for(int e=1;e<5;e++){
massiv_model[e][q]=q+1;
}
}    
for(int e=0;e<6;e++){
massiv_model[0][e]=0;
}
massiv_model[1][1]=0;
massiv_model[1][2]=0;
massiv_model[1][3]=0;
massiv_model[1][4]=0;
ris_model();
tft.fillRect(50,210,250,30,tft.color565(16,14,61));
tft.setCursor(120, 210); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE);tft.println(utf8rus("СОБРАТЬ"));
delay(4000);
//перемешивание шаров в колбах
for(int e=0;e<432;e++){
byte str_1=random(2,5);
byte sto_1=random(0,6);
byte c= massiv_model[str_1][sto_1];
byte str_2=random(2,5);
byte sto_2=random(0,6);
 massiv_model[str_1][sto_1] = massiv_model[str_2][sto_2];
 massiv_model[str_2][sto_2]=c;
}
ris_model();
stschet();
}
//////////////////////////////////////////////////////////////////
/* Функция перекодировки русских букв из UTF-8 в Win-1251 */
String utf8rus(String source)
{
  int i,k;
  String target;
  unsigned char n;
  char m[2] = { '0', '\0' };

  k = source.length(); i = 0;

  while (i < k) {
    n = source[i]; i++;

    if (n >= 0xC0) {
      switch (n) {
        case 0xD0: {
          n = source[i]; i++;
          if (n == 0x81) { n = 0xA8; break; }
          if (n >= 0x90 && n <= 0xBF) n = n + 0x30;
          break;
        }
        case 0xD1: {
          n = source[i]; i++;
          if (n == 0x91) { n = 0xB8; break; }
          if (n >= 0x80 && n <= 0x8F) n = n + 0x70;
          break;
        }
      }
    }
    m[0] = n; target = target + String(m);
  }
return target;
}
//////////////////////////////////////////////////////////////////
void stschet(){//функция счёта ходов
tft.fillRect(50,210,250,30,tft.color565(16,14,61));
tft.setCursor(90, 210); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE);tft.println(utf8rus("ХОД НОМЕР"));
tft.setCursor(210, 210); tft.println(st); 
}
//////////////////////////////////////////////////////////////////
1 лайк

Поигрался и понял, что генерация заданий - попарная взаимозамена мест шариков может быть рандомной, пазл всё равно соберётся, главное, что число пусто-мест должно совпадать с вместимостью колбы иначе шарик нижнего уровня никак не достать:)
… Вопрос возник, как написать такую структура для однотипных функций, а не массивов? Функции без аргументов и возврата результата.

const struct { 
    const unsigned char *bitmap;
  }
 schar_kolba[]={ris_00,ris_01,ris_02,ris_03,ris_04,ris_05,ris_06};// картинки шариков на фоне колбы

Не понял, что надо написать?

По типу такому:

massiv_fun[]={zadanie_1,zadanie_2,zadanie_3,...};
где определены
void zadanie_1(){}
void zadanie_2(){}
void zadanie_3(){}...

что б можно было вызывать их 
massiv_fun[0]; massiv_fun[1];massiv_fun[3];...
void zadanie_1(){}
void zadanie_2(){}
void zadanie_3(){}

typedef void (* TFucntion)(void);

TFucntion massiv_fun[]={zadanie_1,zadanie_2,zadanie_3};

void setup(void) {
	// Вызовы
	massiv_fun[0](); 
	massiv_fun[1]();
	int n = 2;
	massiv_fun[n]();
}

void loop(void) {}
1 лайк

Спасибо, попробую добавить в вариант скетча игры.

А вот должен ли я считать быдлокодером того кто отвечает на быдлокод?
Вопрос, конечно, интересный.)

Это тип такой или опечатка?

Спасибо, заработало, подправил буквы:

//массив функций
typedef void (* TFunction)(void);
TFunction massiv_fun[]={zadanie_1,zadanie_2,zadanie_3};
//

Нет. Максимум - сочувствующим.

1 лайк

Сочувствующий хотя бы мог исправить на инглиш. Тем самым показав пример.)

Это в строке №5 я ввожу “тип, определённый пользователем”. Видите там слово typedef – это один из способов определить “самогонный” тип, что я и сделал. Так что, да, это тип такой. Но, “самодельный”. Можно было и не определять тип, а написать прямо, но тогда строка №7 выглядела бы зубодробительно. Могу показать.

Ой! Только сейчас заметил переставленные буквы … но … раз уж я определил тип с переставленными буквами и потом так и использую, значит … такой вот тип :slight_smile:

Не знаю. Мне не должны, а другому кому – сами разбирайтесь.

1 лайк

Я не буду писать по английски и переводить гуглем слово задание на него, знаю, что это грязно.
… Наступит день, и мы напишем всё по русски :), и даже {} будут нашими.

А просто, всего лишь нужно признать, что запас английских слов, как у Эллочки-людоедочки. И этим нельзя гордиться.(
…Мечтать, конечно, не вредно. Особенно, не прилагая никаких усилий.

Я немецкий учил. И да, я не скрываю слабость в ин-язе. Дочь за меня с лихвой отомстила… да и сын тяготится к басурманскому.
… слово WHITE держится исключительно потому, что в голове оно ВХИТЕ, и не более.
… как и ЛОВ.
…гордости в этом нет, но и печали тоже.

2 лайка