Skip to content

Commit

Permalink
Last Ai autofiling + Tutorial Menu + DeleteAllPoly (#28)
Browse files Browse the repository at this point in the history
* Last AI using autofiling + Tutorial Menu + DeleteAllPolyo

* Last Ai autofiling + Tutorial Menu + DeleteAllPoly

* Add files via upload

* update

* Update polyomino.js

---------

Co-authored-by: Viet Nguyen <77735678+Viet281101@users.noreply.github.com>
  • Loading branch information
D-TheProgrammer and Viet281101 authored Jun 13, 2024
1 parent 18ce5f6 commit f7d3411
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 7 deletions.
81 changes: 81 additions & 0 deletions 2D/js/ai.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,3 +239,84 @@ export function randomBacktrackingTiling(polyominoes, gridBoard, placePolyomino,
};
placeNextPolyomino();
};


/**
* Automatic tiling the grid based on polyomino placed on the field by the user.
*
* @param {GridBoard} gridBoard - The grid board object representing the grid on which the polyominoes will be placed.
* @param {Array} polyominoes - The array of polyominoes to be placed on the grid board.
* @param {Function} placePolyomino - The function to place a polyomino on the grid board.
* @param {Function} removePolyomino - The function to remove a polyomino from the grid board.
* @param {Function} redraw - The function to redraw the grid board after placing or removing a polyomino.
* @param {Function} duplicatePolyomino - The function to duplicate a polyomino.
* @param {Function} message - An optional callback function to be called when the tiling is complete.
* @return {void} This function does not return anything.
*/

export function fullAutoTiling(gridBoard, polyominoes, placePolyomino, removePolyomino, redraw, duplicatePolyomino, message) {
function canPlace(polyomino, x, y) {
const originalX = polyomino.x;
const originalY = polyomino.y;
polyomino.x = x;
polyomino.y = y;
if (gridBoard.isInBounds(polyomino) && !gridBoard.isOverlapping(polyomino)) {
polyomino.x = originalX;
polyomino.y = originalY;
return true;
}
polyomino.x = originalX;
polyomino.y = originalY;
return false;
};

function placeAllPolyominoes(index) {
if (index >= polyominoes.length) { return true; }
const polyomino = polyominoes[index];

for (let row = 0; row < gridBoard.rows; row++) {
for (let col = 0; col < gridBoard.cols; col++) {
for (let rotation = 0; rotation < 4; rotation++) {
const x = col * gridBoard.gridSize + gridBoard.gridOffsetX;
const y = row * gridBoard.gridSize + gridBoard.gridOffsetY;

if (canPlace(polyomino, x, y)) {
const originalX = polyomino.x;
const originalY = polyomino.y;

polyomino.x = x;
polyomino.y = y;
duplicatePolyomino(polyomino);
placePolyomino(polyomino);
polyomino.isPlaced = true;

if (placeAllPolyominoes(index + 1)) { return true; }

polyomino.x = originalX;
polyomino.y = originalY;
polyomino.isPlaced = false;
gridBoard.removePolyomino(polyomino);
}
polyomino.rotateRight();
}
}
}
return placeAllPolyominoes(index + 1);
};

const originalStates = polyominoes.map(p => ({ x: p.x, y: p.y, isPlaced: p.isPlaced }));
if (placeAllPolyominoes(0)) {
console.log("placement ok");
redraw();
if (message) { message(); }
} else {
polyominoes.forEach((p, i) => {
p.x = originalStates[i].x;
p.y = originalStates[i].y;
p.isPlaced = originalStates[i].isPlaced;
});
console.log("rien trouve");
redraw();
}
}

30 changes: 27 additions & 3 deletions 2D/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { GridBoard } from './board.js';
import { Polyomino, getRandomColor } from './polyomino.js';
import { GUIController } from './gui.js';
import { Toolbar } from './toolbar.js';
import { backtrackingAutoTiling, bruteForceTiling, randomTiling, randomBacktrackingTiling } from './ai.js';
import { backtrackingAutoTiling, bruteForceTiling, randomTiling, randomBacktrackingTiling, fullAutoTiling } from './ai.js';

class MainApp {
constructor() {
Expand Down Expand Up @@ -135,7 +135,7 @@ class MainApp {
};

handleMouseMove(mousePos) {
if (this.isBlackening) this.canvas.style.cursor = 'url("../assets/cursor_blacken.png"), auto';
if (this.isBlackening) this.canvas.style.cursor = 'url("../assets/cursor_blackend.png"), auto';
this.polyominoes.forEach(polyomino => polyomino.onMouseMove(mousePos));
if (this.tooltipPolyomino && !this.selectedPolyomino?.isDragging) {
let found = false;
Expand Down Expand Up @@ -198,7 +198,8 @@ class MainApp {

deleteAllPolyominos() {
this.polyominoes = [];
this.selectedPolyomino = null;
this.selectedPoyomino = null;
this.autoWhitening() ;
this.redraw();
};

Expand Down Expand Up @@ -378,6 +379,7 @@ class MainApp {
this.placePolyomino.bind(this),
this.gridBoard.removePolyomino.bind(this),
this.redraw.bind(this),

() => { this.showMessageBox(messageBox); }
);
}, 1000);
Expand Down Expand Up @@ -417,6 +419,28 @@ class MainApp {
);
}, 1000);
};

fullAutoTiling() {
this.resetBoard();

const messageBox = this.createMessageBox(2);

setTimeout(() => {
fullAutoTiling(
this.gridBoard,
this.polyominoes,
this.placePolyomino.bind(this),
this.gridBoard.removePolyomino.bind(this.gridBoard),
this.redraw.bind(this),
this.duplicatePolyomino.bind(this),
() => {
this.showMessageBox(messageBox);
}
);
}, 1000);

console.log("Auto Tiling process initiated.");
};
};

const main_app = new MainApp();
9 changes: 5 additions & 4 deletions 2D/js/popup/solve.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ export function showSolvePopup(toolbar) {
{ label: '2) Brute force method :', underline: true, icon: '../assets/ic_solution.png', description: 'Brute force tries all possible combinations of polyominoes on the grid to find a solution. It is guaranteed to find a solution if one exists but is computationally expensive and slow for large grids.' },
{ label: '3) Random method :', underline: true, icon: '../assets/ic_solution.png', description: 'The random method places polyominoes randomly on the grid. It is fast but does not guarantee a solution or full coverage. It is useful for generating quick and varied patterns.' },
{ label: '4) Random backtracking :', underline: true, icon: '../assets/ic_solution.png', description: 'Random backtracking combines random placement with backtracking to find a solution. It is more efficient than brute force but less predictable than pure backtracking.' },
{ label: '5) Genetic Algorithm :', underline: true, icon: '../assets/ic_solution.png', description: 'Genetic Algorithm (GA) uses principles of natural selection and genetics to find the best arrangement of polyominoes. It starts with a population of random solutions and improves them over generations through crossover and mutation.' },
{ label: '6) Simulated Annealing :', underline: true, icon: '../assets/ic_solution.png', description: 'Simulated Annealing is an optimization algorithm inspired by the process of heating and cooling metal. It allows accepting worse solutions under certain conditions to escape local optima, aiming to find a better overall solution.' },
{ label: '7) Ant Colony Optimization :', underline: true, icon: '../assets/ic_solution.png', description: 'Ant Colony Optimization (ACO) is inspired by the foraging behavior of ants. It uses a group of artificial ants to explore solutions and leave pheromones, guiding other ants to follow the best paths found.' },
{ label: '8) Greedy Algorithm :', underline: true, icon: '../assets/ic_solution.png', description: 'The Greedy Algorithm makes the best choice at each step without considering the whole problem. It can quickly find a valid solution but does not guarantee a globally optimal solution.' }
{ label: '5) Full Automatic Tiling :', underline: true, icon: '../assets/ic_solution.png', description: 'Automatically fills the grid using user-selected pieces from outside the grid, such as filling the grid entirely with monominoes if only one monomino is chosen.' }
];


const startY = 60;
const rowHeight = 60;
const colX = 30;
Expand Down Expand Up @@ -160,6 +158,9 @@ function attachSolveClickEvent(toolbar, popup, row, y) {
case '4) Random backtracking :':
toolbar.mainApp.randomBacktrackingTiling();
break;
case '5) Full Automatic Tiling :':
toolbar.mainApp.fullAutoTiling();
break;
}
if (toolbar.isMobile) toolbar.closePopup('solve');
}
Expand Down
133 changes: 133 additions & 0 deletions 2D/js/popup/tutorial.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,137 @@ export function showTutorialPopup(toolbar) {

ctx.fillStyle = '#a0a0a0';
ctx.fillRect(0, 0, popup.width, popup.height);

const rows = [
{ label: 'Auto Tiling the Polyominoes Blocks', title: true },
{ label: '1) Create Any Polyomino :', underline: true, icon: '../assets/ic_plus.png', description: 'Click this icon to open a menu. Select the polyomino you want to spawn it on the field. Drag and drop it onto the grid.' },
{ label: '2) Manipulate the Polyominoes :', underline: true, icon: '../assets/ic_solution.png', description: 'Each polyomino has an interactive menu. Click on the polyomino to : \n - rotate left or rotate right \n - flip \n - duplicate \n(the clone will appear below \n the piece selected in another color) \n - or delete.' },
{ label: '3) Grid Settings :', underline: true, icon: '../assets/ic_table.png', description: 'This menu lets you create a new board by setting the grid\'s length and height. Click to create the grid. \n Options include : \n - deleting the grid \n - blocking cells to prevent polyomino placement \n - creating automatically random \n black cells \n - Clear all black cells \n - and swapping black and empty cells. ' },
{ label: '4) Solving Grid :', underline: true, icon: '../assets/ic_solving.png', description: 'Click this menu to use various AI solvers. Descriptions are available for each solver; just click to read them.' },
{ label: '5) Use Settings :', underline: true, icon: '../assets/ic_solution.png', description: 'This section facilitates user testing. In this menu, you can: \n - Reset the position \n of the polyominoes to their original spawn points.\n - Shuffle the positions \n of the polyominoes on the field, useful if duplicated pieces overlap.\n - Delete all polyominoes \n from the field and the grid.' }
];


const subIcons = [
{ path: '../assets/ic_rotate_right.png', x: 245, y: 339 },
{ path: '../assets/ic_rotate_left.png', x: 285, y: 339},
{ path: '../assets/ic_flip.png', x: 95, y: 357},
{ path: '../assets/ic_duplicate.png', x: 135, y: 375},
{ path: '../assets/ic_trash.png', x: 135, y: 440},
{ path: '../assets/ic_draw.png', x: 80, y: 560},
{ path: '../assets/ic_trash.png', x: 182, y: 597},
{ path: '../assets/ic_blacken_cell.png', x: 310, y: 620},
{ path: '../assets/ic_random_blacken_cell.png', x: 275, y: 655},
{ path: '../assets/ic_whiten.png', x: 220, y: 697},
{ path: '../assets/ic_invert_blacken.png', x: 320, y: 717},
{ path: '../assets/ic_reset.png', x: 200, y: 975},
{ path: '../assets/ic_shuffle.png', x: 210, y: 1037},
{ path: '../assets/ic_trash.png', x: 250, y: 1107},
];


const startY = 60;
const rowHeight = 60;
const colX = 25;
const maxWidth = 280;

const dropdowns = {};
let clickAreas = [];

rows.forEach((row, index) => {
const y = startY + index * rowHeight;
ctx.font = '21px Pixellari';
ctx.fillStyle = '#000';
ctx.fillText(row.label, colX, y + 20);


if (row.description) {
dropdowns[index] = { description: row.description, expanded: true, y: y + 40 };
}
});


function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
const paragraphs = text.split('\n');
let totalLines = 0;

paragraphs.forEach(paragraph => {
const words = paragraph.split(' ');
let line = '';
const lines = [];

for (let n = 0; n < words.length; n++) {
let testLine = line + words[n] + ' ';
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
lines.push(line);
line = words[n] + ' ';
} else {
line = testLine;
}
}
lines.push(line);
lines.forEach((line, index) => {
ctx.fillText(line, x, y + totalLines * lineHeight + index * lineHeight);
});
totalLines += lines.length;
});

return totalLines;
}


function redrawPopup() {
ctx.clearRect(0, 0, popup.width, popup.height);
ctx.fillStyle = '#a0a0a0';
ctx.fillRect(0, 0, popup.width, popup.height);

let yOffset = 0;
clickAreas = [];
rows.forEach((row, index) => {
const y = startY + index * rowHeight + yOffset;
ctx.font = '21px Pixellari';
ctx.fillStyle = '#000';
ctx.fillText(row.label, colX, y + 20);
if (row.underline) {
ctx.beginPath();
ctx.moveTo(colX, y + 25);
ctx.lineTo(colX + ctx.measureText(row.label).width, y + 25);
ctx.stroke();
}

if (row.icon) {
const icon = new Image();
icon.src = row.icon;
icon.onload = () => {
ctx.drawImage(icon, popup.width - 64, y - 14, 50, 50);
};
clickAreas.push({ index, rect: { x: popup.width - 94, y: y - 14, width: 50, height: 50 }, type: 'icon' });
}

subIcons.forEach(subIcon => {
if (subIcon.path ) {
const subIconImage = new Image();
subIconImage.src = subIcon.path;
subIconImage.onload = () => {
ctx.drawImage(subIconImage, subIcon.x, subIcon.y, 25, 25);
};
}
});


clickAreas.push({ index, rect: { x: colX, y, width: popup.width - colX - 100, height: rowHeight }, type: 'label' });

if (dropdowns[index] && dropdowns[index].expanded) {
ctx.font = '16px Pixellari';
ctx.fillStyle = '#000';
const linesCount = wrapText(ctx, dropdowns[index].description, colX + 15, y + 55, maxWidth, 20);
yOffset += linesCount * 20;
}
});
};

redrawPopup();
};

0 comments on commit f7d3411

Please sign in to comment.