let simulationStates = []; let singleCharMode = true; let simulationStepDuration = 500; function simulate(word) { if(activeDocument === null) { return false; } let steps = []; if (singleCharMode) { steps = word.split(''); } else { steps = word.split(/ /g); } simulationStates.forEach(state => { state.isActive = false; }); simulationStates.splice(); const startConnections = documents[activeDocument].connections.filter(conn => conn instanceof StartConnection); startConnections.forEach(connection => animations.push(new ConnectionAnimation(connection))); simulationStates = startConnections.map(conn => conn.state); // TODO: Check if there are no connections and throw error message to user setTimeout(() => { simulationStates.forEach(state => { state.isActive = true; state.activeTime = Date.now(); }); let stepIndex = 0; const stepInterval = setInterval(() => { if (stepIndex >= steps.length) { clearInterval(stepInterval); return; } // TODO: Check if there are any possibilities left or stop run simulationStep(steps[stepIndex]); stepIndex++; }, simulationStepDuration * 4); }, simulationStepDuration); } function simulationStep(input) { const len = simulationStates.length; for (let i = 0; i < len; i++) { const state = simulationStates[i]; const possibilities = getOutgoingConnections(state); simulationStates.splice(0, 1); state.isActive = false; state.activeTime = Date.now(); possibilities.forEach(conn => { const conditions = conn.text.split(/, ?/g); if (input.match(new RegExp(conditions.join('|'), 'g'))) { if (conn instanceof Connection) { animations.push(new ConnectionAnimation(conn)); setTimeout(() => { simulationStates.push(conn.stateB); conn.stateB.isActive = true; conn.stateB.activeTime = Date.now(); }, simulationStepDuration); } else if (conn instanceof SelfConnection) { animations.push(new SelfConnectionAnimation(conn)); setTimeout(() => { simulationStates.push(conn.state); conn.state.isActive = true; conn.state.activeTime = Date.now(); }, simulationStepDuration); } } }); } } function getOutgoingConnections(state) { if(activeDocument === null) return []; return documents[activeDocument].connections.filter(connection => connection instanceof SelfConnection ? connection.state === state : connection.stateA === state); } class ConnectionAnimation { constructor(connection) { this.connection = connection; this.startTime = Date.now(); } getSize(percent) { if (percent < .33) { return easeInOutCubic(percent * 3) * 10; } else if (percent < .66) { return 10; } else { return easeInOutCubic(1 - (percent - .8) * 3) * 10; } } getPosLinear(percent, points) { } getPosCircular(percent, points) { const distanceSquared = (points.end.x - points.start.x) ** 2 + (points.end.y - points.start.y) ** 2; let phi = Math.acos(1 - (distanceSquared / (2 * points.circle.radius ** 2))); if (distanceSquared > points.circle.radius ** 2) { phi = 2 * Math.PI - phi; } let deltaPhi = phi * percent; const startX = points.start.x - points.circle.x; const startY = points.start.y - points.circle.y; if (!points.isReversed) { deltaPhi *= -1; } const x = startX * Math.cos(deltaPhi) + startY * Math.sin(deltaPhi) + points.circle.x; const y = -startX * Math.sin(deltaPhi) + startY * Math.cos(deltaPhi) + points.circle.y; return {x, y}; } draw() { const deltaTime = Date.now() - this.startTime; const percent = Math.min(Math.max(deltaTime / simulationStepDuration, 0), 1); let curX = 0; let curY = 0; const endPoints = this.connection instanceof StartConnection ? this.connection.getEndPoints() : this.connection.getEndPointsAndCircle(); if (endPoints.isCircle) { const pos = this.getPosCircular(percent, endPoints); curX = pos.x; curY = pos.y; } else { const deltaX = endPoints.end.x - endPoints.start.x; const deltaY = endPoints.end.y - endPoints.start.y; curX = endPoints.start.x + deltaX * percent; curY = endPoints.start.y + deltaY * percent; } ctx.fillStyle = '#0f0'; ctx.beginPath(); ctx.arc(curX, curY, this.getSize(percent), 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); if (percent >= 1) { animations.splice(animations.findIndex(anim => anim === this), 1); } } } class SelfConnectionAnimation { constructor(connection) { this.connection = connection; this.startTime = Date.now(); } draw() { const deltaTime = Date.now() - this.startTime; const percent = Math.min(Math.max(deltaTime / simulationStepDuration, 0), 1); const endPoints = this.connection.getEndPointsAndCircle(); const distanceSquared = (endPoints.end.x - endPoints.start.x) ** 2 + (endPoints.end.y - endPoints.start.y) ** 2; let phi = Math.acos(1 - (distanceSquared / (2 * endPoints.circle.radius ** 2))); if (distanceSquared > endPoints.circle.radius ** 2) { phi = 2 * Math.PI - phi; } let deltaPhi = phi * percent; const startX = endPoints.start.x - endPoints.circle.x; const startY = endPoints.start.y - endPoints.circle.y; if (!endPoints.isReversed) { deltaPhi *= -1; } let x = startX * Math.cos(deltaPhi) + startY * Math.sin(deltaPhi) + endPoints.circle.x; let y = -startX * Math.sin(deltaPhi) + startY * Math.cos(deltaPhi) + endPoints.circle.y; ctx.fillStyle = '#0f0'; ctx.beginPath(); ctx.arc(x, y, 10, 0, 2 * Math.PI); ctx.fill(); ctx.closePath(); if (percent >= 1) { animations.splice(animations.findIndex(anim => anim === this), 1); } } } function easeInOutCubic(t) { return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 }