diff --git a/brainfuck/templates/brainfuck/index.html b/brainfuck/templates/brainfuck/index.html index a46db285..f6093b6a 100644 --- a/brainfuck/templates/brainfuck/index.html +++ b/brainfuck/templates/brainfuck/index.html @@ -30,15 +30,13 @@ display: inline-block; padding: 0.75rem 1.5rem; background: var(--primary); color: #fff; text-decoration: none; border: none; border-radius: 0.375rem; font-weight: 600; cursor: pointer; transition: background 0.2s ease; - margin-top: 1rem; } .btn:hover:not([disabled]) { background: var(--primary-light); } .btn-stop { background: #ef4444; color: #fff; font-weight: 600; cursor: pointer; transition: background 0.2s ease; - margin-top: 1rem; padding: 0.75rem 1.5rem; border: none; border-radius: 0.375rem; + padding: 0.75rem 1.5rem; border: none; border-radius: 0.375rem; display: inline-block; text-decoration: none; - margin-left: 1rem; } .btn-stop:hover:not([disabled]) { background: #dc2626; } .btn[disabled] { opacity: 0.6; cursor: not-allowed; } @@ -46,13 +44,32 @@ border: 2px solid #f3f3f3; border-top: 2px solid #fff; border-radius: 50%; - width: 16px; - height: 16px; + width: 14px; + height: 14px; display: inline-block; vertical-align: middle; margin-right: 0.5rem; animation: spin 1s linear infinite; } + .spinner::before, + .spinner::after { + content: ""; + box-sizing: border-box; + position: absolute; + inset: 0; + border-radius: 50%; + } + .spinner::before { + border: 4px solid rgba(0,0,0,0.1); + } + .spinner::after { + border: 4px solid transparent; + border-top-color: #3498db; + border-right-color: #8e44ad; + box-shadow: 0 0 8px rgba(52, 152, 219, 0.6), + 0 0 8px rgba(142, 68, 173, 0.6) inset; + animation: spin 1s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite; + } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .flex { display: flex; gap: 1rem; } .flex > div { flex: 1; } @@ -79,12 +96,46 @@ font-size: 0.8rem; color: var(--text); margin-top: 1rem; - line-height: 2em; + line-height: 1.5rem; + } + .styled-select { + padding: 0.75rem; + border: 1px solid var(--border); + border-radius: 0.375rem; + background-color: var(--card); + color: var(--text); + font-family: 'Inter', sans-serif; + font-size: 0.9rem; + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 1rem; + padding-right: 2rem; + padding-left: 1rem; + } + + .styled-select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2); + } + .align-center { + display: flex; + align-items: center; } @media screen and (max-width: 768px) { .flex { flex-direction: column; } .flex > div { width: 100%; } + .align-center { + align-items: normal; + } + .styled-select { + width: 100%; + max-width: 500px !important; + } } + @@ -109,8 +160,15 @@ - - +
+ + + +
@@ -129,6 +187,7 @@ let abortSignal = false; const outElement = document.getElementById('output'); const lastStateElement = document.getElementById('last_state'); + const speedSelect = document.getElementById('speed'); class Interpreter { constructor({ commandsPerSecond = 1_000_000_000, debug = false} = {}) { @@ -146,6 +205,7 @@ this.userInput = ""; this.userInputIndex = 0; this.savedOutput = ""; + this.steps = 0; } ourI32(value) { @@ -277,11 +337,32 @@ } async execute({ codeString, userInput = '', callback } = {}) { + let waitTime = 0; + let breath = 0; + const speed = speedSelect.value; + if (speed === 'slllooow') { + this.commandsPerSecond = 1; + waitTime = 1 / this.commandsPerSecond; + breath = 1; + } else if (speed === 'med') { + this.commandsPerSecond = 10; + waitTime = 1 / this.commandsPerSecond; + breath = 1 + } else if (speed === 'fast') { + this.commandsPerSecond = 1_000_000_000_000; + waitTime = 0; + breath = 100000; + } + this.loadCodeFromString(codeString); this.userInput = userInput; let steps = 0; - let breath = 100; while (this.pc < this.code.length) { + if (abortSignal) { + console.log("Execution aborted"); + this.savedOutput = "Execution aborted"; + break; + } const com = this.code[this.pc]; switch (com) { case '+': this.handlePlus(); break; @@ -295,16 +376,15 @@ } this.pc++; steps++; - if (steps % breath === 0) { - await new Promise(resolve => setTimeout(resolve, 0)); - } - if (abortSignal) { - console.log("Execution aborted"); - this.savedOutput = "Execution aborted"; - break; - } + this.steps = steps; outElement.textContent = this.savedOutput; lastStateElement.textContent = this.getState(); + // auto-scroll to the bottom + outElement.scrollTop = outElement.scrollHeight; + lastStateElement.scrollTop = lastStateElement.scrollHeight; + if (steps % breath === 0) { + await new Promise(resolve => setTimeout(resolve, waitTime * 1000)); + } } callback(this.savedOutput, this.getState()); return this.savedOutput; diff --git a/brainfuck/views.py b/brainfuck/views.py index ef7d5b5d..e2339f3c 100644 --- a/brainfuck/views.py +++ b/brainfuck/views.py @@ -1,6 +1,5 @@ from django.template.response import TemplateResponse from django.http import JsonResponse as JSONResponse -from .interpreter import Interpreter from urllib.parse import parse_qsl, unquote_plus, unquote from typing import Dict