- PVSM.RU - https://www.pvsm.ru -

Создание графического приложения для решения задачи о ходе коня

Это туториал по созданию интерактивного прилодения для решения задачи о ходе коня [1] на языках processing и p5.js.

Посмотреть саму программу можно здесь [2]. Для управления «конём» используется метод mouseDragged() [3]; пример программы, использующей этот метод здесь [4]. Отмена хода осуществляется нажатием на квадратную кнопку в левом нижнем углу.

Преамбула

Вот здесь [5] пример программы, иллюстрирующей создание класса, отвечающего за отрисовку эллипсов [6] на языке processing, а вот здесь [7] этот же пример на языке p5.js.

В этом примере атрибутами класса Module являюся координаты экземпляров класса mods.

Создадим поле, разлинованное по горизонтали и вертикали. Теперь атрибутами/свойствами класса будут координаты левого верхнего угла клетки rect(x, y, size, size). Будем передавать цвет k в функцию заливки fill():

void update() {
  fill(k);
  rect(x, y, 25, 25); 
    }
}

Добавим метод mouseClick() для изменения цвета (от тёмного к светлому) по клику на прямоугольник rect():

void mouseClick() {
   if (mouseX >= x && mouseX <= x+25 && 
      mouseY >= y && mouseY <= y+25) {
   if (mousePressed && (mouseButton == LEFT)) {
    k=k+10; 
        if(k>255) k=255;  
         } 
        }   
 }

Теперь на холсте можно рисовать различные рисунки, например, вот:

Создание графического приложения для решения задачи о ходе коня - 1

Проверить программу можно здесь [8], но в браузере программа работает не очень быстро.

Код программы на языке processing

int unit = 15;
int count;
Module[] mods;
void setup() {
  size(1000, 1500);
    stroke(10);
 int wideCount = width / unit;
  int highCount = height / unit;
  count = wideCount * highCount;
  mods = new Module[count];

  int index = 0;
  for (int y = 0; y < highCount; y++) {
    for (int x = 0; x < wideCount; x++) {
      mods[index++] = new Module(x*unit, y*unit);
    }
  }
}
void draw() {
  background(0);
  for (Module mod : mods) {
    mod.mouseClick();
    mod.update();
  }
}
class Module {
  int x;
  int y;
  int k=0;
  // Contructor
  Module(int xT, int yT){
    x = xT;
    y = yT;
  }
   void mouseClick() {    
   if (mouseX >= x && mouseX <= x+25 && 
      mouseY >= y && mouseY <= y+25) {
   if (mousePressed && (mouseButton == LEFT)) {
    k=k+10; 
      if(k>255) k=255; 
            } 
          }
 }
 void update() {
  fill(k);
  rect(x, y, 25, 25);   
  }
} 

Часть I

Создадим коня — прямоугольник rect(). Коня обозначим серым кругом

rect(bx, by, boxSize, boxSize);
  fill(50);
  ellipse(bx+50,by+50,20,20);

Пускай конь закрашивает все клетки по которым проходит, вот как здесь [9].

Далее, пусть конь притягивается к центру клетки при отпускании кнопки mouseRealised() [10].

Добавим переменные storX и storY, которые будут хранить координаты клетки, на
которой находится курсор. Также добавим переменную bool_mouseReleased:

  void mouseClick() {
   if (mouseX >= x && mouseX <= x+100 && 
      mouseY >= y && mouseY <= y+100) {
   if (overBox && mousePressed && (mouseButton == LEFT)) {
    storX=x; // сохраняем x
    storY=y; // сохраняем y
    if(bool_mouseReleased ){ // если кнопка отжата
    modColor=255; } // то закрашиваем клетку
            } 
       }
  }

Если нажата левая кнопка и курсор находится над конём, то сохраняем координаты курсора x и y в переменные storX и storY.

При отпускании кнопки координаты storX и storY загружаются в координаты коня knightX и knightY.
Если для состояния «нажатая кнопка» существует стандартная булевая переменная mousePressed [11], то для состояния «ненажатая кнопка» такой переменной не существует — создадим её сами:

void mouseReleased() {
  bool_mouseReleased=true;
  locked = false;
  knightX=storX;
  knightY=storY;
}

Вообще-то клетка закрашивается не каждый раз, когда происходит нажатие на кнопку (т.е. когда bool_mouseReleased=true) и, наверное, следует добавить таймеры, отмеряющие время после нажатия/отпускания кнопки для того, чтобы отделить эти события друг от друга. Программа требует доработки.

Код. Притягивание коня к центру клетки

// объявляем bool_mouseReleased; storX; storY;
boolean bool_mouseReleased;
float storX;
float storY;

float knightX;
float knightY;
// size of canvas 600*600
int edgeOfCanvas=600;
int knightSize = 100;
boolean overKnight = false;
boolean locked = false;
float xOffset = 0.0; 
float yOffset = 0.0; 
int unit = 100; // -> width / unit;
int unitSize=100; 
int count;
         
Module[] mods;

void setup() {
size(600, 600);
 knightX = 0;
  knightY = 0;
  rectMode(CORNER);
 stroke(100);
 
 int wideCount = edgeOfCanvas / unit;
  int highCount = edgeOfCanvas / unit;
  count = wideCount * highCount;
  mods = new Module[count];
  int index = 0;
  for (int y = 0; y < highCount; y++) {
    for (int x = 0; x < wideCount; x++) {
      mods[index++] = new Module(x*unit, y*unit);
     }
   }
}
void draw() {
  background(0); 
  for (Module mod : mods) {
    mod.mouseClick();
     mod.update();
  }
 //      //       //       //
 // Test if the cursor is over the box 
 fill(200);
  if (mouseX > knightX && mouseX < knightX+knightSize && 
      mouseY > knightY && mouseY < knightY+knightSize) {
    overKnight = true;  
  } else {
     overKnight = false;  
        }
  fill(200);
  rect(0,0,100,100); 
  rect(knightX, knightY, knightSize, knightSize);
  fill(50);
  ellipse(knightX+50,knightY+50,20,20);
 }
  
class Module {
  int x;
  int y;
 int modColor=0;
  // Contructor
  Module(int xT, int yT){
    x = xT;
    y = yT;
  }
  void mouseClick() {
   if ((mouseX >= x && mouseX <= x+100 && 
      mouseY >= y && mouseY <= y+100)&& 
    (overKnight && mousePressed && (mouseButton == LEFT))) {
    storX=x;
    storY=y; 
    if(bool_mouseReleased ){ modColor=200; } 
            }        
  }
 void update() {
  fill(modColor);
  rect(x, y, unitSize, unitSize); 
   }
}

void mousePressed() {
  if(overKnight) { 
    locked = true;   
  } else {
    locked = false;
  }
  xOffset = mouseX-knightX; 
  yOffset = mouseY-knightY; 
}
void mouseDragged() {
  if(locked) {
    bool_mouseReleased=false;
    knightX = mouseX-xOffset; 
    knightY = mouseY-yOffset; 
  }
}
void mouseReleased() {
  bool_mouseReleased=true;
  locked = false;
  knightX=storX;
  knightY=storY;
}

Проверить можно здесь [12]

Часть II

Добавим кнопку отмены хода. Пример [13], иллюстрирующий работу кнопок.

Сперва создадим списки IntList [14] координат клеток, по которым прошел конь; нарисуем саму кнопку в левом нижнем углу:

// list
IntList listOfCoordinatesX;
IntList listOfCoordinatesY;
//button
int buttonX=25, buttonY=525; 
int buttonSize = 50;     
boolean boolButton = false;

По клику на клетку с конём добавляем в список (стек) координаты этой клетки:

    listOfCoordinatesX.append(int(knightX));
    listOfCoordinatesY.append(int(knightY)); 

Переменную и списки выводим в консоль в основном цикле программы:

  println(boolButton);
  println(listOfCoordinatesX);
  println(listOfCoordinatesY);

Создадим булевую функцию overButton(), которая возвращает true, если курсор мыши находится над кнопкой и функцию buttonUpdate(), которая обновляет переменную boolButton

Программа целиком

  
// list
IntList listOfCoordinatesX;
IntList listOfCoordinatesY;
//button
int buttonX=25, buttonY=525; 
int buttonSize = 50;     
boolean boolButton = false;
//mouse
boolean bool_mouseReleased;
// jump to rect center on release button
float storX, storY;

float knightX, knightY;
// size of canvas 
int edgeOfCanvas=500;
int knightSize = 100;
boolean overKnight = false;
boolean locked = false;
float xOffset = 0.0; 
float yOffset = 0.0; 
int unit = 100; // -> width / unit;
int count;
         
Module[] mods;

void setup() {
size(500, 600);
 stroke(100);
  knightX = 0;
  knightY = 0;
 rectMode(CORNER);
 listOfCoordinatesX = new IntList();
 listOfCoordinatesY = new IntList(); 
 int wideCount = edgeOfCanvas / unit;
  int highCount = edgeOfCanvas / unit;
  count = wideCount * highCount;
  mods = new Module[count];
  int index = 0;
  for (int y = 0; y < highCount; y++) {
    for (int x = 0; x < wideCount; x++) {
      mods[index++] = new Module(x*unit, y*unit);
     }
   }
}
void draw() {
  background(0);
buttonUpdate();
   for (Module mod : mods) {
    mod.mouseClick();
     mod.update();
  }
 //  //  //  //  //  //
 // Test if the cursor is over the box 
 fill(200);
  if (mouseX > knightX && mouseX < knightX+knightSize && 
      mouseY > knightY && mouseY < knightY+knightSize) {
    overKnight = true;  
  } else {
     overKnight = false;  
        }
 fill(200);
  rect(0,0,100,100);
 
  rect(knightX, knightY, knightSize, knightSize);
  fill(50);
  ellipse(knightX+50,knightY+50,20,20);
  // draw button
  rect(buttonX,buttonY,buttonSize,buttonSize);  
  println();
  println(boolButton);
  println(listOfCoordinatesX);
  println(listOfCoordinatesY);  
 }


class Module {
  int x;
  int y;
 int modColor=0;
  // Contructor
  Module(int xT, int yT){
    x = xT;
    y = yT;
  }
  void mouseClick() {
       if (mouseX >= x && mouseX <= x+100 && 
      mouseY >= y && mouseY <= y+100) {
   if (overKnight && mousePressed && (mouseButton == LEFT)) {
    storX=x;
    storY=y; 
    if(bool_mouseReleased ) {modColor=200;} 
            } 
       }
  }
 void update() {
  fill(modColor);
  rect(x, y, knightSize, knightSize); 
   }
}

void mousePressed() {
  if(overKnight) { 
    locked = true; 
    listOfCoordinatesX.append(int(knightX));
    listOfCoordinatesY.append(int(knightY)); 
    } else {
    locked = false;
  }
  xOffset = mouseX-knightX; 
  yOffset = mouseY-knightY; 
}
void mouseDragged() {
  if(locked) {
    bool_mouseReleased=false;
    knightX = mouseX-xOffset; 
    knightY = mouseY-yOffset; 
  }
}
void mouseReleased() {
  bool_mouseReleased=true;
  locked = false;
  knightX=storX;
  knightY=storY;
}
// button
 void buttonUpdate() {
  if ( overButton(buttonX, buttonY, buttonSize, buttonSize) ) {
    boolButton = true;
  } else {
   boolButton = false;
  }
}
boolean overButton(int x, int y, int width, int height)  {
  if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
    return true;
  } else {
    return false;
  }
}

Добавим функцию прыжка на предыдущую клетку при нажатии на кнопку.

Если списки не пустые, то при отпускании кнопки mouseReleased() извлекаем из списков (стеков) координат последние значения и загружаем их в координаты клетки с конём.

 if(listOfCoordinatesX.length != 0){
   knightX=listOfCoordinatesX.pop();
   knightY=listOfCoordinatesY.pop();   
} 

Код прграммы целиком

// list
IntList listOfCoordinatesX;
IntList listOfCoordinatesY;
//button
int buttonX=25, buttonY=525; 
int buttonSize = 50;     
boolean boolButton = false;
//mouse
boolean bool_mouseReleased;
// jump to rect center on button release
float storX;
float storY;

float knightX;
float knightY;
// size of canvas 
int edgeOfCanvas=500;
int knightSize = 100;
boolean overKnight = false;
boolean locked = false;
float xOffset = 0.0; 
float yOffset = 0.0; 
int unit = 100; // -> width / unit;
int unitSize=100; 
int count;
         
Module[] mods;

void setup() {
size(500, 600);
stroke(100);
  knightX = 0;
  knightY = 0;
  rectMode(CORNER);  
 listOfCoordinatesX = new IntList();
 listOfCoordinatesY = new IntList(); 
 
 int wideCount = edgeOfCanvas / unit;
  int highCount = edgeOfCanvas / unit;
  count = wideCount * highCount;
  mods = new Module[count];
  int index = 0;
  for (int y = 0; y < highCount; y++) {
    for (int x = 0; x < wideCount; x++) {
      mods[index++] = new Module(x*unit, y*unit);
     }
    }
}
void draw() {
  background(0);
   buttonUpdate();
  for (Module mod : mods) {
    mod.mouseClick();
     mod.update();
  }
 //      //      //      //
 // Test if the cursor is over the box 
 fill(200);
  if (mouseX > knightX && mouseX < knightX+knightSize && 
      mouseY > knightY && mouseY < knightY+knightSize) {
    overKnight = true;  
  } else {
     overKnight = false;  
        }
 fill(200);
 rect(0,0,100,100); 
  rect(knightX, knightY, knightSize, knightSize);
  fill(50);
  ellipse(knightX+50,knightY+50,20,20);
  // draw button
  rect(buttonX,buttonY,buttonSize,buttonSize);
  if(boolButton && mousePressed) { fill(200);
   rect(buttonX,buttonY,buttonSize,buttonSize); }
 }

class Module {
  int x;
  int y;
 int modColor=0;
  // Contructor
  Module(int xT, int yT){
    x = xT;
    y = yT;
  }  
  void mouseClick() {
    if (mouseX >= x && mouseX <= x+100 && 
      mouseY >= y && mouseY <= y+100) {
   if (overKnight && mousePressed && (mouseButton == LEFT)) {
    storX=x;
    storY=y; 
    if(bool_mouseReleased ) {modColor=200;} 
            } 
          }
  }
 void update() {
  fill(modColor);
  rect(x, y, unitSize, unitSize); 
   }
}

void mousePressed() {
  if(overKnight) { 
    locked = true; 
     listOfCoordinatesX.append(int(knightX));
      listOfCoordinatesY.append(int(knightY));    
  } else {
    locked = false;
   }
  xOffset = mouseX-knightX; 
  yOffset = mouseY-knightY; 
}
void mouseDragged() {
  if(locked) {
    bool_mouseReleased=false;
    knightX = mouseX-xOffset; 
    knightY = mouseY-yOffset; 
  }
}
void mouseReleased() {
  bool_mouseReleased=true;
  locked = false;
  if(!boolButton){
  knightX=storX;
  knightY=storY; }
 else if(boolButton){
   //if list not emty
   if(listOfCoordinatesX.size()!=0){
  knightX=listOfCoordinatesX.get(listOfCoordinatesX.size()-1);
  knightY=listOfCoordinatesY.get(listOfCoordinatesY.size()-1);
   /// remove last element of list
       listOfCoordinatesX.remove(listOfCoordinatesX.size()-1);
       listOfCoordinatesY.remove(listOfCoordinatesY.size()-1);
       }
     }  
}
// button
 void buttonUpdate() {
  if ( overButton(buttonX, buttonY, buttonSize, buttonSize) ) {
    boolButton = true;
  } else {
   boolButton = false;
  }
}
boolean overButton(int x, int y, int width, int height)  {
  if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
    return true;
  } else {
    return false;
  }
}

Проверить можно здесь [15].

Часть III

Пока что при отмене хода предыдущая клетка так и остаётся закрашенной серым цветом. Пусть при отмене хода клетка возвращает первоначальный (черный) цвет. Добавим в наш класс метод knightReturn(). В этом методе проверяем, что кнопка отмены хода нажата и список координат не пуст, и тогда, если координате на вершине списка/стека соответствует координата текущей клетки класса, возвращаем текущей клетке первоначальный цвет

 void knightReturn(){
    if(buttonOver&& mousePressed){
    if(listOfCoordinateX.size()!=0){
   if(int(x)==listOfCoordinateX.get(listOfCoordinateX.size()-1) &&  
    int(y)==listOfCoordinateY.get(listOfCoordinateY.size()-1) )   
      { modColor=30; }           
       }
     }
  }

Добавляем метод knightReturn() в основной цикл программы

for (Module mod : mods) {
     mod.mouseClick();
     mod.update();
     mod.knightReturn();
  }

Код программы целиком

// list
IntList listOfCoordinateX;
IntList listOfCoordinateY;
//button
int buttonX=25, buttonY; 
int buttonSize = 50;    
boolean buttonOver = false;
//mouse
boolean bool_mouseReleased;
// jump to rect center on mouse release
float storX;
float storY;
// Knight cootdinates 
float knightX;
float knightY;
// size of canvas 
int edgeOfCanvas=500;
int knightSize = 100;
boolean overKnight = false;
boolean locked = false;
float xOffset = 0.0; 
float yOffset = 0.0;
int unit = 100; // -> width / unit;
int unitSize=100; 
int count;
         
Module[] mods;

void setup() {
size(500, 600);
  knightX = 0;
  knightY = 0;
   buttonY=edgeOfCanvas+25;
   rectMode(CORNER);
 listOfCoordinateX = new IntList();
 listOfCoordinateY = new IntList();
stroke(100); //color of the net of edges
 int wideCount = edgeOfCanvas / unit;
  int highCount = edgeOfCanvas / unit;
  count = wideCount * highCount;
  mods = new Module[count];
  int index = 0;
  for (int y = 0; y < highCount; y++) {
    for (int x = 0; x < wideCount; x++) {
      mods[index++] = new Module(x*unit, y*unit);
    }
   }
}
void draw() {
background(0);
buttonUpdate();  
  for (Module mod : mods) {
     mod.mouseClick();
     mod.update();
     mod.knightReturn();
  }
 // Test if the cursor is over the box 
 if (mouseX > knightX && mouseX < knightX+knightSize && 
      mouseY > knightY && mouseY < knightY+knightSize) {
    overKnight = true;  
  } else {
     overKnight = false;  
     }      
  fill(200);
  rect(0,0,100,100);
 // draw Knight
  rect(knightX, knightY, knightSize, knightSize);
  fill(50);
  ellipse(knightX+50,knightY+50,20,20);
  // draw button
  rect(buttonX,buttonY,buttonSize,buttonSize);
  if(buttonOver && mousePressed) { 
     fill(200);
     rect(buttonX,buttonY,buttonSize,buttonSize);  
     }
 }

class Module {
  int x;
  int y;
 int modColor=30;
  // Contructor
  Module(int xT, int yT){
    x = xT;
    y = yT;
  }
  // Custom method for drawing the object
  void mouseClick() {
  if (mouseX >= x && mouseX <= x+100 && 
      mouseY >= y && mouseY <= y+100) {
   if (overKnight && mousePressed && (mouseButton == LEFT)) {
    storX=x;
    storY=y; 
    if(bool_mouseReleased ) {modColor=200;} 
            }
       }
  }
  void knightReturn(){
    if(buttonOver&& mousePressed){
    if(listOfCoordinateX.size()!=0){
   if(int(x)==listOfCoordinateX.get(listOfCoordinateX.size()-1) &&  
    int(y)==listOfCoordinateY.get(listOfCoordinateY.size()-1) )   
      {modColor=30;} } }
  }
 void update() {
   fill(modColor);
    rect(x, y, unitSize, unitSize); 
   }
}

void mousePressed() {
  if(overKnight) { 
    locked = true; 
     listOfCoordinateX.append(int(knightX));
     listOfCoordinateY.append(int(knightY));        
  } else {
    locked = false;
   }
  xOffset = mouseX-knightX; 
  yOffset = mouseY-knightY; 
}
void mouseDragged() {
  if(locked) {
    bool_mouseReleased=false;
    knightX = mouseX-xOffset; 
    knightY = mouseY-yOffset; 
  }
}
void mouseReleased() {
  bool_mouseReleased=true;
  locked = false;
  if(!buttonOver){
  knightX=storX;
  knightY=storY; }
 else if(buttonOver){
   //if list not emty
  if(listOfCoordinateX.size()!=0){
   knightX=listOfCoordinateX.get(listOfCoordinateX.size()-1);
   knightY=listOfCoordinateY.get(listOfCoordinateY.size()-1);   
   /// remove last element of list
       listOfCoordinateX.remove(listOfCoordinateX.size()-1);
       listOfCoordinateY.remove(listOfCoordinateY.size()-1);         
        }
     }    
}
// button
 void buttonUpdate() {
  if ( overButton(buttonX, buttonY, buttonSize, buttonSize) ) {
    buttonOver = true;
  } else {
   buttonOver = false;
  }
}
boolean overButton(int x, int y, int width, int height)  {
  if (mouseX >= x && mouseX <= x+width && 
      mouseY >= y && mouseY <= y+height) {
    return true;
  } else {
    return false;
  }
}

Ссылка [16] на github с текстами программ, представленных в статье.

Online редактор p5.js-кода здесь https://editor.p5js.org/ [17].

Автор: Дмитрий

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/313827

Ссылки в тексте:

[1] задачи о ходе коня: https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%85%D0%BE%D0%B4%D0%B5_%D0%BA%D0%BE%D0%BD%D1%8F

[2] здесь: https://demsp.github.io/knight/

[3] mouseDragged(): https://www.processing.org/reference/mouseDragged_

[4] здесь: https://processing.org/examples/mousefunctions.html

[5] Вот здесь: https://processing.org/examples/arrayobjects.html

[6] эллипсов: https://www.processing.org/reference/ellipse_

[7] вот здесь: https://p5js.org/examples/arrays-array-objects.html

[8] здесь: https://demsp.github.io/pixelart.html

[9] здесь: https://codepen.io/dmitrij-potapow/pen/YMWOxy

[10] mouseRealised(): https://www.processing.org/reference/mouseReleased_.html

[11] mousePressed: https://www.processing.org/reference/mousePressed.html

[12] здесь: https://codepen.io/dmitrij-potapow/pen/NmRWmR

[13] Пример: https://processing.org/examples/button.html

[14] IntList: https://www.processing.org/reference/IntList.html

[15] здесь: https://codepen.io/dmitrij-potapow/pen/bJwdbv?editors=1111

[16] Ссылка: https://github.com/demsp/miniGames.pde/tree/master/knightTurn

[17] https://editor.p5js.org/: https://editor.p5js.org/

[18] Источник: https://habr.com/ru/post/444512/?utm_source=habrahabr&utm_medium=rss&utm_campaign=444512