Это туториал по созданию интерактивного приложения для решения задачи о ходе коня на языках processing и p5.js.
Посмотреть саму программу можно здесь. Для управления «конём» используется метод mouseDragged(); пример программы, использующей этот метод здесь. Отмена хода осуществляется нажатием на квадратную кнопку в левом нижнем углу.
Вот здесь пример программы, иллюстрирующий создание класса, отвечающего за отрисовку эллипсов на языке processing, а вот здесь этот же пример на языке p5.js.
Online редактор p5.js-кода здесь https://editor.p5js.org/.
В этом примере атрибутами класса Module являются координаты экземпляров класса mods.
Создадим поле, разлинованное по горизонтали и вертикали. Теперь атрибутами/свойствами класса будут координаты левого верхнего угла клетки rect(x, y, size, size). Будем передавать цвет k в функцию заливки fill():
Добавим метод mouseClick() для изменения цвета (от тёмного к светлому) по клику на прямоугольник rect():
Теперь на холсте можно рисовать различные рисунки, например, вот:
Проверить программу можно здесь, но в браузере программа работает не очень быстро.
Создадим коня — прямоугольник rect(). Коня обозначим серым кругом
Пускай конь закрашивает все клетки по которым проходит, вот как здесь.
Далее, пусть конь притягивается к центру клетки при отпускании кнопки mouseRealised().
Добавим переменные storX и storY, которые будут хранить координаты клетки, на которой находится курсор. Также добавим переменную bool_mouseReleased:
Если нажата левая кнопка и курсор находится над конём, то сохраняем координаты текщей клетки mod в переменные storX и storY.
При отпускании кнопки координаты storX и storY загружаются в координаты коня knightX и knightY.
Если для состояния «нажатая кнопка» существует стандартная булевая переменная mousePressed, то для состояния «ненажатая кнопка» такой переменной не существует — создадим её сами:
Вообще-то клетка закрашивается не каждый раз, когда происходит нажатие на кнопку (т.е. когда bool_mouseReleased=true) и, наверное, следует добавить таймеры, отмеряющие время после нажатия/отпускания кнопки для того, чтобы отделить эти события друг от друга. Программа требует доработки.
Проверить можно здесь
Добавим кнопку отмены хода. Пример, иллюстрирующий работу кнопок.
Сперва создадим списки IntList координат клеток, по которым прошел конь; нарисуем саму кнопку в левом нижнем углу:
По клику на клетку с конём добавляем в список (стек) координаты этой клетки:
Переменную и списки выводим в консоль в основном цикле программы:
Создадим булевую функцию overButton(), которая возвращает true, если курсор мыши находится над кнопкой и функцию buttonUpdate(), которая обновляет переменную boolButton
Добавим функцию прыжка на предыдущую клетку при нажатии на кнопку.
Если списки не пустые, то при отпускании кнопки mouseReleased() извлекаем из списков/стеков координат последние значения и загружаем их в координаты клетки с конём.
Проверить можно здесь.
Пока что при отмене хода предыдущая клетка так и остаётся закрашенной серым цветом. Пусть при отмене хода клетка возвращает первоначальный (черный) цвет. Добавим в наш класс метод knightReturn(). В этом методе проверяем, что кнопка отмены хода нажата и список координат не пуст, и тогда, если координате на вершине списка/стека соответствует координата текущей клетки класса, возвращаем текущей клетке первоначальный цвет
Сейчас, если нажата левая кнопка и курсор находится над конём, то сохраняются координаты текщей клетки mod в переменные storX и storY.
Пусть теперь сохранение происходит, если нажата левая кнопка и центр коня находится в текущей клетке mod.
Добавляем метод knightReturn() в основной цикл программы
Ссылка на github с текстами программ, представленных в статье.
Вот несколько статей о языках processing и p5.js
Processing.js
Знакомство с p5.js
Разработка игры на Processing с управлением через плату Arduino Uno
Web-Drawing библиотеки: Paper.js Vs. Processing.js Vs. Raphael.js
Программирование для начинающих. Моё знакомство с Processing
Processing 1.0 и почти закон всемирного тяготения
Посмотреть саму программу можно здесь. Для управления «конём» используется метод mouseDragged(); пример программы, использующей этот метод здесь. Отмена хода осуществляется нажатием на квадратную кнопку в левом нижнем углу.
Преамбула
Вот здесь пример программы, иллюстрирующий создание класса, отвечающего за отрисовку эллипсов на языке processing, а вот здесь этот же пример на языке p5.js.
Online редактор p5.js-кода здесь https://editor.p5js.org/.
В этом примере атрибутами класса 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;
}
}
}
Теперь на холсте можно рисовать различные рисунки, например, вот:
Проверить программу можно здесь, но в браузере программа работает не очень быстро.
Код программы на языке 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);
Пускай конь закрашивает все клетки по которым проходит, вот как здесь.
Далее, пусть конь притягивается к центру клетки при отпускании кнопки 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; } // то закрашиваем клетку
}
}
}
Если нажата левая кнопка и курсор находится над конём, то сохраняем координаты текщей клетки mod в переменные storX и storY.
При отпускании кнопки координаты storX и storY загружаются в координаты коня knightX и knightY.
knightX=storX;
knightY=storY;
Если для состояния «нажатая кнопка» существует стандартная булевая переменная 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; }
}
}
}
Сейчас, если нажата левая кнопка и курсор находится над конём, то сохраняются координаты текщей клетки mod в переменные storX и storY.
Пусть теперь сохранение происходит, если нажата левая кнопка и центр коня находится в текущей клетке mod.
void mouseClick() {
//if (mouseX >= x && mouseX <= x+100 &&
// mouseY >= y && mouseY <= y+100) {
if (knightX+50 >= x && knightX+50 <= x+100 &&
knightY+50 >= y && knightY+50 <= y+100) {
if (overKnight && mousePressed && (mouseButton == LEFT)) {
storX=x;
storY=y;
if(bool_mouseReleased ) {modColor=200;}
}
}
}
Добавляем метод 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 (knightX+50 >= x && knightX+50 <= x+100 &&
knightY+50 >= y && knightY+50 <= 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 с текстами программ, представленных в статье.
Вот несколько статей о языках processing и p5.js
Processing.js
Знакомство с p5.js
Разработка игры на Processing с управлением через плату Arduino Uno
Web-Drawing библиотеки: Paper.js Vs. Processing.js Vs. Raphael.js
Программирование для начинающих. Моё знакомство с Processing
Processing 1.0 и почти закон всемирного тяготения