606 lines
16 KiB
JavaScript
606 lines
16 KiB
JavaScript
// Author: Mykolas Ruth
|
|
// Create a stage for the animation
|
|
var stage = new Konva.Stage({
|
|
container: 'container',
|
|
width: 1200,
|
|
height: 700
|
|
});
|
|
|
|
// Create layer
|
|
var layer = new Konva.Layer();
|
|
|
|
// Variables to track animation state
|
|
var stack = [];
|
|
var lastPopped = "";
|
|
var currentString = "";
|
|
var currentIndex = 0;
|
|
var pc = 0; // Program counter for animation steps
|
|
var isComplete = false;
|
|
|
|
// Stack size and positioning
|
|
var stack_size = 10;
|
|
var stack_x = 1000;
|
|
var stack_y = 100;
|
|
var stack_width = 120;
|
|
var stack_height = 40;
|
|
|
|
// Initialize the visualization
|
|
function init() {
|
|
// Reset layer
|
|
layer.destroyChildren();
|
|
|
|
// Create code display
|
|
createCodeDisplay();
|
|
|
|
// Create stack visualization
|
|
createStack();
|
|
|
|
// Create animation controls area
|
|
createNextStepButton();
|
|
|
|
// Add the layer to the stage
|
|
stage.add(layer);
|
|
layer.draw();
|
|
}
|
|
|
|
// Create the code display
|
|
function createCodeDisplay() {
|
|
var codeRect = new Konva.Rect({
|
|
x: 50,
|
|
y: 50,
|
|
width: 450,
|
|
height: 600,
|
|
stroke: '#555',
|
|
strokeWidth: 2,
|
|
fill: '#f5f5f5',
|
|
shadowColor: 'black',
|
|
shadowBlur: 10,
|
|
shadowOffsetX: 5,
|
|
shadowOffsetY: 5,
|
|
shadowOpacity: 0.2,
|
|
cornerRadius: 10,
|
|
id: 'code_rect'
|
|
});
|
|
layer.add(codeRect);
|
|
|
|
// Add the code text
|
|
var codeLines = [
|
|
"bool isValid(string s) {",
|
|
" std::stack<char> myStack;",
|
|
" int len = s.length();",
|
|
" char c;",
|
|
" for(int i=0; i<len; i++) {",
|
|
" // Push opening brackets to stack",
|
|
" if(s[i]=='(' || s[i]=='{' || s[i]=='[') {",
|
|
" myStack.push(s[i]);",
|
|
" } else {",
|
|
" // Check for closing brackets",
|
|
" if(myStack.empty()) {",
|
|
" return false;",
|
|
" }",
|
|
" c = myStack.top();",
|
|
" myStack.pop();",
|
|
" // Check bracket matching",
|
|
" if(s[i]==')' && c!='(') return false;",
|
|
" if(s[i]=='}' && c!='{') return false;",
|
|
" if(s[i]==']' && c!='[') return false;",
|
|
" }",
|
|
" }",
|
|
" // Check if all brackets are matched",
|
|
" return myStack.empty();",
|
|
"}"
|
|
];
|
|
|
|
for (var i = 0; i < codeLines.length; i++) {
|
|
makeText(60, 60 + i * 20, codeLines[i], 'code_line_' + i);
|
|
}
|
|
}
|
|
|
|
// Create the stack boxes
|
|
function createStack() {
|
|
makeText(stack_x, stack_y - 50, "Stack", "stack_title");
|
|
|
|
// rectangles on stack
|
|
for (var i = 0; i < stack_size; i++) {
|
|
var rect = new Konva.Rect({
|
|
x: stack_x,
|
|
y: stack_y + i * stack_height,
|
|
id: "stack_rec" + i,
|
|
stroke: '#343434',
|
|
strokeWidth: 2,
|
|
fill: '#f0f0f0',
|
|
width: stack_width,
|
|
height: stack_height,
|
|
});
|
|
layer.add(rect);
|
|
}
|
|
|
|
var base = new Konva.Rect({
|
|
x: stack_x,
|
|
y: stack_y + stack_size * stack_height,
|
|
width: stack_width,
|
|
height: 5,
|
|
fill: 'black',
|
|
});
|
|
layer.add(base);
|
|
}
|
|
|
|
// create next step button
|
|
function createNextStepButton() {
|
|
|
|
var nextStepButton = new Konva.Label({
|
|
x: 530,
|
|
y: 400,
|
|
|
|
});
|
|
|
|
nextStepButton.add(new Konva.Tag({
|
|
fill: 'blue',
|
|
cornerRadius: 3,
|
|
shadowColor: 'black',
|
|
}));
|
|
|
|
nextStepButton.add(new Konva.Text({
|
|
text: 'Next Step',
|
|
fontSize: 18,
|
|
padding: 10,
|
|
fill: 'white'
|
|
}));
|
|
|
|
nextStepButton.on('click', function() {
|
|
nextStep();
|
|
});
|
|
|
|
|
|
layer.add(nextStepButton);
|
|
}
|
|
|
|
// Create the input string display
|
|
function createInputDisplay(str) {
|
|
// Clear previous displays
|
|
var prevDisplay = stage.find('#input_display');
|
|
prevDisplay.each(function(node) {
|
|
node.destroy();
|
|
});
|
|
|
|
// Input string display title
|
|
makeText(520, 50, "Input: string s = \"" + str + "\"", "input_title");
|
|
|
|
// Create boxes for each character
|
|
var boxWidth = 40;
|
|
var boxSpacing = 10;
|
|
var startX = 520;
|
|
var startY = 100;
|
|
|
|
var group = new Konva.Group({
|
|
id: 'input_display'
|
|
});
|
|
|
|
for (var i = 0; i < str.length; i++) {
|
|
var charBox = new Konva.Rect({
|
|
x: startX + i * (boxWidth + boxSpacing),
|
|
y: startY,
|
|
width: boxWidth,
|
|
height: boxWidth,
|
|
fill: '#f0f0f0',
|
|
stroke: '#555',
|
|
strokeWidth: 1,
|
|
cornerRadius: 4,
|
|
id: 'char_box_' + i
|
|
});
|
|
|
|
var charText = new Konva.Text({
|
|
x: startX + i * (boxWidth + boxSpacing) + 12,
|
|
y: startY + 10,
|
|
text: str[i],
|
|
fontSize: 20,
|
|
fontFamily: 'monospace',
|
|
fill: 'black',
|
|
id: 'char_text_' + i
|
|
});
|
|
|
|
group.add(charBox);
|
|
group.add(charText);
|
|
}
|
|
|
|
// Create the character pointer (initially at first character)
|
|
var pointer = new Konva.RegularPolygon({
|
|
x: startX + boxWidth/2,
|
|
y: startY - 15,
|
|
sides: 3,
|
|
radius: 12,
|
|
fill: 'red',
|
|
rotation: 180,
|
|
id: 'char_pointer'
|
|
});
|
|
|
|
group.add(pointer);
|
|
layer.add(group);
|
|
}
|
|
|
|
// Create the result and explanations
|
|
function createResultDisplay() {
|
|
var prevDisplay = stage.find('#result_display');
|
|
prevDisplay.each(function(node) {
|
|
node.destroy();
|
|
});
|
|
|
|
var group = new Konva.Group({
|
|
id: 'result_display'
|
|
});
|
|
|
|
// Result display
|
|
var resultBox = new Konva.Rect({
|
|
x: 520,
|
|
y: 150,
|
|
width: 250,
|
|
height: 40,
|
|
fill: '#e0e0e0',
|
|
stroke: '#555',
|
|
strokeWidth: 1,
|
|
cornerRadius: 5,
|
|
id: 'result_box'
|
|
});
|
|
|
|
var resultText = new Konva.Text({
|
|
x: 530,
|
|
y: 160,
|
|
text: 'Output: Code Not Finished',
|
|
fontSize: 16,
|
|
fontFamily: 'Arial',
|
|
fill: 'black',
|
|
id: 'result_text'
|
|
});
|
|
|
|
// Explanation box
|
|
var explanationBox = new Konva.Rect({
|
|
x: 520,
|
|
y: 200,
|
|
width: 250,
|
|
height: 150,
|
|
fill: '#f0f0f0',
|
|
stroke: '#555',
|
|
strokeWidth: 1,
|
|
cornerRadius: 5,
|
|
id: 'explanation_box'
|
|
});
|
|
|
|
var explanationText = new Konva.Text({
|
|
x: 530,
|
|
y: 210,
|
|
text: 'Click "Next Step" to begin checking the brackets.',
|
|
fontSize: 16,
|
|
fontFamily: 'Arial',
|
|
fill: 'black',
|
|
width: 240,
|
|
id: 'explanation_text'
|
|
});
|
|
|
|
group.add(resultBox);
|
|
group.add(resultText);
|
|
group.add(explanationBox);
|
|
group.add(explanationText);
|
|
|
|
layer.add(group);
|
|
}
|
|
|
|
// Utility function to create text
|
|
function makeText(x, y, str, id, color) {
|
|
var text = new Konva.Text({
|
|
x: x,
|
|
y: y,
|
|
text: str,
|
|
id: 'text_' + id,
|
|
fontSize: 16,
|
|
fontFamily: 'Arial',
|
|
fill: color || 'black',
|
|
width: 1000,
|
|
});
|
|
layer.add(text);
|
|
return text;
|
|
}
|
|
|
|
// highlight current line in code
|
|
function highlightCodeLine(lineNum) {
|
|
for (var i = 0; i < 25; i++) {
|
|
var line = stage.find('#text_code_line_' + i);
|
|
if (line.length > 0) {
|
|
line[0].fontStyle('normal');
|
|
line[0].fill('black');
|
|
}
|
|
}
|
|
|
|
// highlight current line
|
|
var currentLine = stage.find('#text_code_line_' + lineNum);
|
|
if (currentLine.length > 0) {
|
|
currentLine[0].fontStyle('bold');
|
|
currentLine[0].fill('blue');
|
|
}
|
|
|
|
layer.draw();
|
|
}
|
|
|
|
// Move the character pointer
|
|
function movePointer(index) {
|
|
if (index < 0 || index >= currentString.length) return;
|
|
|
|
var charBox = stage.find('#char_box_' + index)[0];
|
|
var pointer = stage.find('#char_pointer')[0];
|
|
|
|
if (charBox && pointer) {
|
|
pointer.x(charBox.x() + charBox.width()/2);
|
|
}
|
|
|
|
layer.draw();
|
|
}
|
|
|
|
|
|
// Reset character highlight
|
|
function resetCharHighlight(index) {
|
|
if (index < 0 || index >= currentString.length) return;
|
|
|
|
var charBox = stage.find('#char_box_' + index)[0];
|
|
if (charBox) {
|
|
charBox.fill('#f0f0f0');
|
|
}
|
|
|
|
layer.draw();
|
|
}
|
|
|
|
// Push a character onto the stack
|
|
function pushToStack(char) {
|
|
stack.push(char);
|
|
var stackIndex = stack_size - stack.length;
|
|
|
|
// Add the character to the stack visual
|
|
var charText = new Konva.Text({
|
|
x: stack_x + stack_width/2 - 10,
|
|
y: stack_y + stackIndex * stack_height + 10,
|
|
text: char,
|
|
fontSize: 24,
|
|
fontFamily: 'monospace',
|
|
fill: 'black',
|
|
id: 'stack_item_' + stackIndex
|
|
});
|
|
|
|
layer.add(charText);
|
|
layer.draw();
|
|
}
|
|
|
|
// Pop a character from the stack
|
|
function popFromStack() {
|
|
if (stack.length === 0) return null;
|
|
|
|
var poppedChar = stack.pop();
|
|
var stackIndex = stack_size - stack.length - 1;
|
|
|
|
// Remove the character from the stack visual
|
|
stage.find('#stack_item_' + stackIndex).destroy();
|
|
|
|
// Reset the stack slot color
|
|
stage.find('#stack_rec' + stackIndex)[0].fill('#f0f0f0');
|
|
|
|
layer.draw();
|
|
return poppedChar;
|
|
}
|
|
|
|
|
|
// Update the result text
|
|
function updateResult(text, color) {
|
|
var resultText = stage.find('#result_text')[0];
|
|
if (resultText) {
|
|
resultText.text('Output: ' + text);
|
|
resultText.fill(color || 'black');
|
|
}
|
|
layer.draw();
|
|
}
|
|
|
|
// Update the explanation text
|
|
function updateExplanation(text) {
|
|
var explanationText = stage.find('#explanation_text')[0];
|
|
if (explanationText) {
|
|
explanationText.text(text);
|
|
}
|
|
layer.draw();
|
|
}
|
|
|
|
// Reset the animation state for a new case
|
|
function resetAnimation(str) {
|
|
// Reset state variables
|
|
currentString = str;
|
|
currentIndex = 0;
|
|
stack = [];
|
|
pc = 1;
|
|
isComplete = false;
|
|
|
|
// Reset visual elements
|
|
init();
|
|
createInputDisplay(str);
|
|
createResultDisplay();
|
|
|
|
// Reset all stack slots
|
|
for (var i = 0; i < stack_size; i++) {
|
|
stage.find('#stack_rec' + i)[0].fill('#f0f0f0');
|
|
}
|
|
|
|
// Initialize first step
|
|
highlightCodeLine(0);
|
|
updateResult('Code Not Finished', 'black');
|
|
updateExplanation('Click "Next Step" to begin checking the brackets: ' + str);
|
|
|
|
layer.draw();
|
|
}
|
|
|
|
// Process the next step of the animation
|
|
function nextStep() {
|
|
if (isComplete) return;
|
|
|
|
switch (pc) {
|
|
case 1:
|
|
// Initialize stack
|
|
highlightCodeLine(1);
|
|
updateExplanation("Creating an empty stack to store opening brackets");
|
|
pc++;
|
|
break;
|
|
|
|
case 2:
|
|
// Get length
|
|
highlightCodeLine(2);
|
|
updateExplanation("Getting the length of the string of brackets: " + currentString.length);
|
|
pc++;
|
|
break;
|
|
|
|
case 3:
|
|
// Initialize for loop
|
|
highlightCodeLine(4);
|
|
updateExplanation("Starting the loop to process each character");
|
|
pc++;
|
|
break;
|
|
|
|
case 4:
|
|
// Check if done with all characters
|
|
if (currentIndex >= currentString.length) {
|
|
// Final check if stack is empty
|
|
highlightCodeLine(21);
|
|
updateExplanation("All characters processed. Checking if the stack is empty...");
|
|
|
|
if (stack.length === 0) {
|
|
updateResult("True");
|
|
updateExplanation("The stack is empty, which means all opening brackets were matched with their closing brackets in the correct order. The string is valid.");
|
|
} else {
|
|
updateResult("False", "red");
|
|
updateExplanation("The stack still has " + stack.length + " unmatched opening bracket(s). This means some opening brackets were never matched with closing brackets. The string is invalid.");
|
|
}
|
|
|
|
isComplete = true;
|
|
break;
|
|
}
|
|
|
|
// Process current character
|
|
movePointer(currentIndex);
|
|
var currentChar = currentString[currentIndex];
|
|
updateExplanation("Processing character at index " + currentIndex + ": '" + currentChar + "'");
|
|
|
|
// Check if opening bracket
|
|
if (currentChar === '(' || currentChar === '{' || currentChar === '[') {
|
|
highlightCodeLine(6);
|
|
pc = 5; // Go to opening bracket logic
|
|
} else {
|
|
highlightCodeLine(8);
|
|
pc = 6; // Go to closing bracket logic
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
// Process opening bracket
|
|
var currentChar = currentString[currentIndex];
|
|
highlightCodeLine(7);
|
|
updateExplanation("Found opening bracket '" + currentChar + "'. Pushing it onto the stack to remember it.");
|
|
pushToStack(currentChar);
|
|
|
|
// Move to next character
|
|
resetCharHighlight(currentIndex);
|
|
currentIndex++;
|
|
pc = 4; // Return to character processing
|
|
break;
|
|
|
|
case 6:
|
|
// Process closing bracket - check if stack is empty
|
|
var currentChar = currentString[currentIndex];
|
|
highlightCodeLine(10);
|
|
|
|
if (stack.length === 0) {
|
|
updateExplanation("Found closing bracket '" + currentChar + "' but the stack is empty. This means there's no matching opening bracket, so the string is invalid.");
|
|
updateResult("false", "red");
|
|
isComplete = true;
|
|
break;
|
|
}
|
|
|
|
updateExplanation("Found closing bracket '" + currentChar + "'. Checking the top of the stack for a matching opening bracket.");
|
|
pc++;
|
|
break;
|
|
|
|
case 7:
|
|
// Get top of stack
|
|
var currentChar = currentString[currentIndex];
|
|
var topChar = stack[stack.length - 1];
|
|
highlightCodeLine(12);
|
|
updateExplanation("The top of the stack contains: '" + topChar + "'");
|
|
pc++;
|
|
break;
|
|
|
|
case 8:
|
|
// Pop from stack
|
|
var currentChar = currentString[currentIndex];
|
|
var topChar = stack[stack.length - 1];
|
|
highlightCodeLine(13);
|
|
updateExplanation("Popping '" + topChar + "' from the stack to compare with '" + currentChar + "'");
|
|
lastPopped = popFromStack();
|
|
pc++;
|
|
break;
|
|
|
|
case 9:
|
|
// Check if brackets match
|
|
var currentChar = currentString[currentIndex];
|
|
var topChar = lastPopped;
|
|
if (currentChar === ')') {
|
|
highlightCodeLine(15);
|
|
} else if (currentChar === '}') {
|
|
highlightCodeLine(16);
|
|
} else if (currentChar === ']') {
|
|
highlightCodeLine(17);
|
|
}
|
|
|
|
// Check if match failed
|
|
if ((currentChar === ')' && topChar !== '(') ||
|
|
(currentChar === '}' && topChar !== '{') ||
|
|
(currentChar === ']' && topChar !== '[')) {
|
|
|
|
updateExplanation("Mismatch! Closing bracket '" + currentChar + "' doesn't match with the opening bracket '" + topChar + "'. The string is invalid.");
|
|
updateResult("False");
|
|
isComplete = true;
|
|
break;
|
|
}
|
|
|
|
// Match succeeded
|
|
updateExplanation("Match found! Closing bracket '" + currentChar + "' correctly matches with opening bracket '" + topChar + "'." );
|
|
|
|
// Move to next character
|
|
resetCharHighlight(currentIndex);
|
|
currentIndex++;
|
|
pc = 4; // Return to character processing
|
|
break;
|
|
}
|
|
|
|
layer.draw();
|
|
}
|
|
|
|
// Case 1: "()"
|
|
function case1() {
|
|
resetAnimation("()");
|
|
}
|
|
|
|
// Case 2: "()[]{}"
|
|
function case2() {
|
|
resetAnimation("()[]{}");
|
|
}
|
|
|
|
// Case 3: "(]"
|
|
function case3() {
|
|
resetAnimation("(]");
|
|
}
|
|
|
|
// Case 4: "([])"
|
|
function case4() {
|
|
resetAnimation("([])");
|
|
}
|
|
|
|
// Case 5: "({[{[()]}]})"
|
|
function case5() {
|
|
resetAnimation("{[([])]}");
|
|
}
|
|
|
|
// Initialize the animation
|
|
init();
|
|
case1(); // Start with Case 1 by default
|