Это туториал по созданию интерактивного прилодения для решения задачи о ходе коня на языках processing и p5.js.
Посмотреть саму программу можно здесь. Для управления «конём» используется метод mouseDragged(); пример программы, использующей этот метод здесь. Отмена хода осуществляется нажатием на квадратную кнопку в левом нижнем углу.
Преамбула
Вот здесь пример программы, иллюстрирующей создание класса, отвечающего за отрисовку эллипсов на языке processing, а вот здесь этот же пример на языке 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;
}
}
}
Теперь на холсте можно рисовать различные рисунки, например, вот:
Проверить программу можно здесь, но в браузере программа работает не очень быстро.
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);
Пускай конь закрашивает все клетки по которым проходит, вот как здесь.
Далее, пусть конь притягивается к центру клетки при отпускании кнопки mouseRealised().
Добавим переменные 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, то для состояния «ненажатая кнопка» такой переменной не существует — создадим её сами:
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;
}
Проверить можно здесь
Часть II
Добавим кнопку отмены хода. Пример, иллюстрирующий работу кнопок.
Сперва создадим списки IntList координат клеток, по которым прошел конь; нарисуем саму кнопку в левом нижнем углу:
// 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;
}
}
Проверить можно здесь.
Часть 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;
}
}
Ссылка на github с текстами программ, представленных в статье.
Online редактор p5.js-кода здесь https://editor.p5js.org/.
Автор: Дмитрий