const animationTime = 100; let animationFinish = 0; class StackVisualizer { constructor(canvas, direction, blockSize, blockNum, margin) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.direction = direction; this.blockSize = blockSize; this.blockNum = blockNum; this.margin = margin; this.canvas.width = blockSize * blockNum + 2 * margin; this.canvas.height = blockSize + 2 * margin; this.elements = []; this.ctx.font = `${blockSize * 0.6}px monospace`; this.ctx.textAlign = "center"; this.ctx.textBaseline = "middle"; this.drawShape(); } drawBlock(x, y, text, opacity = 1.0) { this.ctx.lineWidth = 2; this.ctx.strokeStyle = `rgba(0, 0, 0, ${opacity})`; this.ctx.fillStyle = `rgba(127, 255, 127, ${opacity})`; this.ctx.beginPath(); this.ctx.moveTo(x, y); this.ctx.lineTo(x, y + this.blockSize); this.ctx.lineTo(x + this.blockSize, y + this.blockSize); this.ctx.lineTo(x + this.blockSize, y); this.ctx.closePath(); this.ctx.fill(); this.ctx.stroke(); this.ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`; this.ctx.fillText(text, x + this.blockSize / 2, y + this.blockSize / 2); } drawShape(curElements = this.elements) { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (this.direction == 'right') { for (let j = 0; j < curElements.length; j++) { this.drawBlock(this.margin + j * this.blockSize, this.margin, curElements[j]); } this.ctx.lineWidth = 4; this.ctx.strokeStyle = `#000`; this.ctx.beginPath(); this.ctx.moveTo(this.margin + this.blockNum * this.blockSize, this.margin); this.ctx.lineTo(this.margin, this.margin); this.ctx.lineTo(this.margin, this.margin + this.blockSize); this.ctx.lineTo(this.margin + this.blockNum * this.blockSize, this.margin + this.blockSize); this.ctx.stroke(); } else if (this.direction == 'left') { for (let j = 0; j < curElements.length; j++) { this.drawBlock(this.margin + (this.blockNum - j - 1) * this.blockSize, this.margin, curElements[j]); } this.ctx.lineWidth = 4; this.ctx.strokeStyle = `#000`; this.ctx.beginPath(); this.ctx.moveTo(this.margin, this.margin); this.ctx.lineTo(this.margin + this.blockNum * this.blockSize, this.margin); this.ctx.lineTo(this.margin + this.blockNum * this.blockSize, this.margin + this.blockSize); this.ctx.lineTo(this.margin, this.margin + this.blockSize); this.ctx.stroke(); } } setPushAnimation(curElements, x) { setTimeout(() => { let start = Date.now(); let timer = setInterval(() => { let t = Date.now() - start; if (t >= animationTime) { clearInterval(timer); curElements.push(x); this.drawShape(curElements); return; } let completion = t / animationTime; this.drawShape(curElements); if (this.direction == 'right') { this.drawBlock(this.margin + (curElements.length + 1 - completion) * this.blockSize, this.margin, x, completion); } else if (this.direction == 'left') { this.drawBlock(this.margin + (this.blockNum - curElements.length - 1 - 1 + completion) * this.blockSize, this.margin, x, completion); } }, 20); }, Math.max(Date.now(), animationFinish) - Date.now()); } push(x) { if (this.elements.length >= this.blockNum) return; this.setPushAnimation([...this.elements], x); this.elements.push(x); } setPopAnimation(curElements, x) { setTimeout(() => { let start = Date.now(); let timer = setInterval(() => { let t = Date.now() - start; if (t >= animationTime) { clearInterval(timer); this.drawShape(curElements); return; } let completion = t / animationTime; this.drawShape(curElements); if (this.direction == 'right') { this.drawBlock(this.margin + (curElements.length + completion) * this.blockSize, this.margin, x, 1 - completion); } else if (this.direction == 'left') { this.drawBlock(this.margin + (this.blockNum - curElements.length - 1 - completion) * this.blockSize, this.margin, x, 1 - completion); } }, 20); }, Math.max(Date.now(), animationFinish) - Date.now()); } pop() { if (this.elements.length <= 0) return; let x = this.elements[this.elements.length - 1]; this.elements.pop(); this.setPopAnimation([...this.elements], x); } top() { return this.elements[this.elements.length - 1]; } setAssignAnimation(curElements) { setTimeout(() => { let start = Date.now(); let timer = setInterval(() => { let t = Date.now() - start; if (t >= animationTime) { clearInterval(timer); this.drawShape(curElements); return; } let completion = t / animationTime; this.drawShape([]); if (this.direction == 'right') { for (let j = 0; j < curElements.length; j++) { this.drawBlock(this.margin + (j + 1 - completion) * this.blockSize, this.margin, curElements[j], completion); } } else if (this.direction == 'left') { for (let j = 0; j < curElements.length; j++) { this.drawBlock(this.margin + (this.blockNum - j - 1 - 1 + completion) * this.blockSize, this.margin, curElements[j], completion); } } }, 20); }, Math.max(Date.now(), animationFinish) - Date.now()); } setExtractAnimation(curElements) { setTimeout(() => { let start = Date.now(); let timer = setInterval(() => { let t = Date.now() - start; if (t >= animationTime) { clearInterval(timer); this.drawShape([]); return; } let completion = t / animationTime; this.drawShape([]); if (this.direction == 'right') { for (let j = 0; j < curElements.length; j++) { this.drawBlock(this.margin + (j + completion) * this.blockSize, this.margin, curElements[j], 1 - completion); } } else if (this.direction == 'left') { for (let j = 0; j < curElements.length; j++) { this.drawBlock(this.margin + (this.blockNum - j - 1 - completion) * this.blockSize, this.margin, curElements[j], 1 - completion); } } }, 20); }, Math.max(Date.now(), animationFinish) - Date.now()); } } window.onload = (e) => { let stack_l = new StackVisualizer(document.getElementById("stack_l"), "left", 70, 10, 5); let stack_r = new StackVisualizer(document.getElementById("stack_r"), "right", 70, 10, 5); let stack_ll = new StackVisualizer(document.getElementById("stack_ll"), "left", 70, 10, 5); let stack_rc = new StackVisualizer(document.getElementById("stack_rc"), "right", 70, 10, 5); let stack_s = new StackVisualizer(document.getElementById("stack_s"), "left", 70, 10, 5); let stack_rrc = new StackVisualizer(document.getElementById("stack_rrc"), "right", 70, 10, 5); const button_push = document.getElementById("button-push"); const button_pop = document.getElementById("button-pop"); let i = 1; let normalMode = true; let toCopy = 0, toRCopy = 0; function recopy() { if (!normalMode) { if (toCopy > 0) { toCopy--; stack_s.push(stack_r.top()); stack_r.pop(); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; } else if (stack_l.elements.length > 0) { stack_r.push(stack_l.top()); stack_rrc.push(stack_l.top()); stack_l.pop(); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; } else if (stack_s.elements.length > 0) { if (toRCopy > 0) { toRCopy--; stack_r.push(stack_s.top()); stack_rrc.push(stack_s.top()); } stack_s.pop(); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; } if (toCopy == 0 && toRCopy == 0 && stack_s.elements.length == 0 && stack_l.elements.length == 0) { normalMode = true; let elementsL = [...stack_l.elements]; let elementsLL = [...stack_ll.elements]; let elementsRC = [...stack_rc.elements]; let elementsRRC = [...stack_rrc.elements]; stack_l.elements = []; stack_l.setExtractAnimation(elementsL); stack_ll.elements = []; stack_ll.setExtractAnimation(elementsLL); stack_rc.elements = []; stack_rc.setExtractAnimation(elementsRC); stack_rrc.elements = []; stack_rrc.setExtractAnimation(elementsRRC); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; stack_l.elements = elementsLL; stack_l.setAssignAnimation(elementsLL); stack_ll.elements = elementsL; stack_ll.setAssignAnimation(elementsL); stack_rc.elements = elementsRRC; stack_rc.setAssignAnimation(elementsRRC); stack_rrc.elements = elementsRC; stack_rrc.setAssignAnimation(elementsRC); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; } } else { if (stack_rrc.elements.length > 0) { stack_rrc.pop(); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; } } } function push() { if (Date.now() > animationFinish) { if (normalMode) { stack_l.push(i); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; i++; if (stack_l.elements.length > stack_r.elements.length) { normalMode = false; toCopy = stack_r.elements.length; toRCopy = stack_r.elements.length; } } else { stack_ll.push(i); animationFinish = Math.max(Date.now(), animationFinish) + animationTime * 1.5; i++; } recopy(); recopy(); recopy(); } } function pop() { if (Date.now() > animationFinish) { if (!normalMode && toRCopy > 0) { toRCopy--; } else if (!normalMode) { stack_r.pop(); stack_rrc.pop(); } else { stack_r.pop(); if (stack_l.elements.length > stack_r.elements.length) { normalMode = false; toCopy = stack_r.elements.length; toRCopy = stack_r.elements.length; } } stack_rc.pop(); recopy(); recopy(); recopy(); } } button_push.onclick = push; button_pop.onclick = pop; };