Compare commits
5 commits
Author | SHA1 | Date | |
---|---|---|---|
577557146d | |||
c6d280dfd8 | |||
4cb534838a | |||
7f0b77327e | |||
ac29780e6b |
11 changed files with 503 additions and 0 deletions
0
brainfuck/__init__.py
Normal file
0
brainfuck/__init__.py
Normal file
3
brainfuck/admin.py
Normal file
3
brainfuck/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
brainfuck/apps.py
Normal file
6
brainfuck/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BrainfuckConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'brainfuck'
|
0
brainfuck/migrations/__init__.py
Normal file
0
brainfuck/migrations/__init__.py
Normal file
3
brainfuck/models.py
Normal file
3
brainfuck/models.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
469
brainfuck/templates/brainfuck/index.html
Normal file
469
brainfuck/templates/brainfuck/index.html
Normal file
|
@ -0,0 +1,469 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Brainfuck Compiler</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #f5f7fa;
|
||||||
|
--card: #ffffff;
|
||||||
|
--primary: #4f46e5;
|
||||||
|
--primary-light: #6366f1;
|
||||||
|
--text: #374151;
|
||||||
|
--border: #e5e7eb;
|
||||||
|
--highlight: #e0e7ff;
|
||||||
|
--fade: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { font-family: 'Inter', sans-serif; background: var(--bg); color: var(--text); }
|
||||||
|
.container { max-width: 1100px; margin: 2rem auto; padding: 1rem; }
|
||||||
|
h1 { text-align: center; margin-bottom: 1.5rem; font-size: 2rem; font-weight: 700; }
|
||||||
|
.card { background: var(--card); border: 1px solid var(--border); border-radius: 0.5rem; padding: 1.5rem; box-shadow: 0 2px 4px rgba(0,0,0,0.05); margin-bottom: 1.5rem; }
|
||||||
|
label { display: block; margin-bottom: 0.5rem; font-weight: 600; }
|
||||||
|
textarea, input[type="text"] {
|
||||||
|
width: 100%; padding: 0.75rem; border: 1px solid var(--border); border-radius: 0.375rem; font-family: monospace; font-size: 0.9rem; margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
textarea { resize: vertical; height: 200px; }
|
||||||
|
.btn {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.btn:hover:not([disabled]) { background: var(--primary-light); }
|
||||||
|
.btn-stop {
|
||||||
|
background: #ef4444; color: #fff; font-weight: 600; cursor: pointer;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
padding: 0.75rem 1.5rem; border: none; border-radius: 0.375rem;
|
||||||
|
display: inline-block; text-decoration: none;
|
||||||
|
}
|
||||||
|
.btn-stop:hover:not([disabled]) { background: #dc2626; }
|
||||||
|
.btn[disabled] { opacity: 0.6; cursor: not-allowed; }
|
||||||
|
.spinner {
|
||||||
|
border: 2px solid #f3f3f3;
|
||||||
|
border-top: 2px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
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; }
|
||||||
|
#output, #last_state { background: #1e293b; color: #d1d5db; height: 150px; overflow-y: auto; font-family: monospace; }
|
||||||
|
.drop-zone {
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 2px dashed var(--border);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.drop-zone.active {
|
||||||
|
background: var(--highlight);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
.drop-zone input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; width: 100%; height: 100%; opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.author {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text);
|
||||||
|
margin-top: 1rem;
|
||||||
|
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,<svg fill='%23374151' height='20' viewBox='0 0 24 24' width='20' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Brainfuck Compiler for M&M</h1>
|
||||||
|
<form id="bf-form" class="card">
|
||||||
|
<div class="flex">
|
||||||
|
<div>
|
||||||
|
<label for="code">Code</label>
|
||||||
|
<textarea id="code" placeholder="Enter Brainfuck code..."></textarea>
|
||||||
|
<div id="code-drop" class="drop-zone">
|
||||||
|
<p id="code-drop-text">Drag & drop code file here or click to select</p>
|
||||||
|
<input type="file" id="code-file" accept=".bf, .in, .txt">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="input">Input</label>
|
||||||
|
<textarea id="input" placeholder="Program input..."></textarea>
|
||||||
|
<div id="input-drop" class="drop-zone">
|
||||||
|
<p id="input-drop-text">Drag & drop input file here or click to select</p>
|
||||||
|
<input type="file" id="input-file" accept=".txt, .in">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex align-center" style="margin-top: 1rem; flex-wrap: wrap; gap: 1rem;">
|
||||||
|
<select id="speed" class="styled-select">
|
||||||
|
<option value="slllooow">pretty, pretty slow</option>
|
||||||
|
<option value="med">medium</option>
|
||||||
|
<option value="fast" selected>blazingly fast</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" id="run-btn" class="btn">Run</button>
|
||||||
|
<button type="button" id="stop-btn" class="btn-stop" style="display: none;">Stop</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<label for="output">Output</label>
|
||||||
|
<pre id="output"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<label for="last_state">State</label>
|
||||||
|
<pre id="last_state"></pre>
|
||||||
|
</div>
|
||||||
|
<div class="author">Created by TicVac 2025<br>vaclav.tichy.mam@gmail.com</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
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} = {}) {
|
||||||
|
this.debug = debug;
|
||||||
|
this.commands = ['+', '-', '>', '<', '.', ',', '[', ']'];
|
||||||
|
this.mp = 0; // memory pointer
|
||||||
|
this.pc = 0; // program counter
|
||||||
|
this.data = [0, 0];
|
||||||
|
this.dataEnd = [0, 0]; // tail reversed
|
||||||
|
this.isAtEnd = false;
|
||||||
|
this.code = "";
|
||||||
|
this.commandsPerSecond = commandsPerSecond;
|
||||||
|
this.MAX = 2 ** 32 - 1;
|
||||||
|
|
||||||
|
this.userInput = "";
|
||||||
|
this.userInputIndex = 0;
|
||||||
|
this.savedOutput = "";
|
||||||
|
this.steps = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ourI32(value) {
|
||||||
|
if (value === -1) return this.MAX;
|
||||||
|
if (value === this.MAX + 1) return 0;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCodeFromString(code) {
|
||||||
|
const expandNumbers = (match, count, char) => {
|
||||||
|
return char.repeat(parseInt(count, 10));
|
||||||
|
};
|
||||||
|
let cleaned = code.trim();
|
||||||
|
cleaned = cleaned.replace(/(\d+)(.)/g, expandNumbers);
|
||||||
|
cleaned = cleaned.split('').filter(c => this.commands.includes(c)).join('');
|
||||||
|
this.code = cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValueAtMp() {
|
||||||
|
if (!this.isAtEnd) {
|
||||||
|
return this.data[this.mp];
|
||||||
|
} else {
|
||||||
|
return this.dataEnd[this.MAX - this.mp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setValueAtMp(value) {
|
||||||
|
if (!this.isAtEnd) {
|
||||||
|
this.data[this.mp] = value;
|
||||||
|
} else {
|
||||||
|
this.dataEnd[this.MAX - this.mp] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePlus() {
|
||||||
|
this.setValueAtMp(this.ourI32(this.getValueAtMp() + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMinus() {
|
||||||
|
this.setValueAtMp(this.ourI32(this.getValueAtMp() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGreater() {
|
||||||
|
const temp = this.mp;
|
||||||
|
this.mp = this.ourI32(this.mp + 1);
|
||||||
|
if (this.mp > this.MAX - this.dataEnd.length) {
|
||||||
|
this.isAtEnd = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (temp > this.mp) {
|
||||||
|
this.isAtEnd = false;
|
||||||
|
}
|
||||||
|
if (this.mp >= this.data.length) {
|
||||||
|
this.data = this.data.concat(new Array(this.data.length).fill(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLess() {
|
||||||
|
const temp = this.mp;
|
||||||
|
this.mp = this.ourI32(this.mp - 1);
|
||||||
|
if (this.mp < this.data.length) {
|
||||||
|
this.isAtEnd = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (temp < this.mp) {
|
||||||
|
this.isAtEnd = true;
|
||||||
|
}
|
||||||
|
if (this.MAX - this.mp >= this.dataEnd.length) {
|
||||||
|
this.dataEnd = this.dataEnd.concat(new Array(this.dataEnd.length).fill(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDot() {
|
||||||
|
const ch = String.fromCharCode(this.getValueAtMp());
|
||||||
|
this.savedOutput += ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleComma() {
|
||||||
|
let inputChar;
|
||||||
|
if (this.commaStandardInput) {
|
||||||
|
// Standard input ignored in this variant
|
||||||
|
inputChar = null;
|
||||||
|
} else {
|
||||||
|
if (this.userInputIndex < this.userInput.length) {
|
||||||
|
inputChar = this.userInput[this.userInputIndex++];
|
||||||
|
} else {
|
||||||
|
inputChar = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!inputChar) {
|
||||||
|
this.setValueAtMp(0);
|
||||||
|
} else {
|
||||||
|
this.setValueAtMp(inputChar.charCodeAt(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLeftBracket() {
|
||||||
|
if (this.getValueAtMp() === 0) {
|
||||||
|
let openBrackets = 1;
|
||||||
|
while (openBrackets > 0) {
|
||||||
|
this.pc++;
|
||||||
|
if (this.pc >= this.code.length) {
|
||||||
|
throw new SyntaxError('Unmatched "["');
|
||||||
|
}
|
||||||
|
if (this.code[this.pc] === '[') openBrackets++;
|
||||||
|
if (this.code[this.pc] === ']') openBrackets--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRightBracket() {
|
||||||
|
if (this.getValueAtMp() !== 0) {
|
||||||
|
let closeBrackets = 1;
|
||||||
|
while (closeBrackets > 0) {
|
||||||
|
this.pc--;
|
||||||
|
if (this.pc < 0) {
|
||||||
|
throw new SyntaxError('Unmatched "]"');
|
||||||
|
}
|
||||||
|
if (this.code[this.pc] === ']') closeBrackets++;
|
||||||
|
if (this.code[this.pc] === '[') closeBrackets--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getState() {
|
||||||
|
const dataMain = this.data.join(' ');
|
||||||
|
const dataTail = [...this.dataEnd].reverse().join(' ');
|
||||||
|
return `step: ${this.steps} mp: ${this.mp} | ${dataMain} | end: | ${dataTail} |`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
case '-': this.handleMinus(); break;
|
||||||
|
case '>': this.handleGreater(); break;
|
||||||
|
case '<': this.handleLess(); break;
|
||||||
|
case '.': this.handleDot(); break;
|
||||||
|
case ',': this.handleComma(); break;
|
||||||
|
case '[': this.handleLeftBracket(); break;
|
||||||
|
case ']': this.handleRightBracket(); break;
|
||||||
|
}
|
||||||
|
this.pc++;
|
||||||
|
steps++;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopBtn = document.getElementById('stop-btn');
|
||||||
|
|
||||||
|
// Setup drop zones with filename display
|
||||||
|
function setupDropZone(dropZoneId, textareaId, textId) {
|
||||||
|
const dropZone = document.getElementById(dropZoneId);
|
||||||
|
const fileInput = dropZone.querySelector('input');
|
||||||
|
const textarea = document.getElementById(textareaId);
|
||||||
|
const dropText = document.getElementById(textId);
|
||||||
|
|
||||||
|
['dragenter', 'dragover'].forEach(evt => {
|
||||||
|
dropZone.addEventListener(evt, e => {
|
||||||
|
e.preventDefault(); dropZone.classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
['dragleave', 'drop'].forEach(evt => {
|
||||||
|
dropZone.addEventListener(evt, e => {
|
||||||
|
e.preventDefault(); dropZone.classList.remove('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
dropZone.addEventListener('drop', e => {
|
||||||
|
const file = e.dataTransfer.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
dropText.textContent = file.name;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => textarea.value = reader.result;
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
fileInput.addEventListener('change', e => {
|
||||||
|
const file = e.target.files[0]; if (!file) return;
|
||||||
|
dropText.textContent = file.name;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => textarea.value = reader.result;
|
||||||
|
reader.readAsText(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDropZone('code-drop', 'code', 'code-drop-text');
|
||||||
|
setupDropZone('input-drop', 'input', 'input-drop-text');
|
||||||
|
|
||||||
|
function random_js(out, state) {
|
||||||
|
console.log("random_js");
|
||||||
|
const btn = document.getElementById('run-btn');
|
||||||
|
btn.removeChild(btn.firstChild);
|
||||||
|
document.getElementById('output').textContent = out;
|
||||||
|
document.getElementById('last_state').textContent = state;
|
||||||
|
btn.disabled = false;
|
||||||
|
stopBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run button handler with spinner prepend
|
||||||
|
document.getElementById('run-btn').addEventListener('click', () => {
|
||||||
|
const btn = document.getElementById('run-btn');
|
||||||
|
const code = document.getElementById('code').value;
|
||||||
|
const input = document.getElementById('input').value;
|
||||||
|
if (btn.disabled) return;
|
||||||
|
|
||||||
|
outElement.textContent = "";
|
||||||
|
lastStateElement.textContent = "";
|
||||||
|
abortSignal = false;
|
||||||
|
stopBtn.style.display = 'inline-block';
|
||||||
|
btn.disabled = true;
|
||||||
|
const spinner = document.createElement('div'); spinner.className = 'spinner';
|
||||||
|
btn.insertBefore(spinner, btn.firstChild);
|
||||||
|
|
||||||
|
// execution start
|
||||||
|
const interpreter = new Interpreter({ commandsPerSecond: 1_000_000_000, debug: false });
|
||||||
|
let out = interpreter.execute({ codeString: code, userInput: input, callback: random_js });
|
||||||
|
});
|
||||||
|
|
||||||
|
stopBtn.addEventListener('click', () => {
|
||||||
|
console.log("Stop button clicked");
|
||||||
|
abortSignal = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
3
brainfuck/tests.py
Normal file
3
brainfuck/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
7
brainfuck/urls.py
Normal file
7
brainfuck/urls.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.index),
|
||||||
|
]
|
||||||
|
|
8
brainfuck/views.py
Normal file
8
brainfuck/views.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.http import JsonResponse as JSONResponse
|
||||||
|
from urllib.parse import parse_qsl, unquote_plus, unquote
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
args = {}
|
||||||
|
return TemplateResponse(request, 'brainfuck/index.html', args)
|
|
@ -152,6 +152,7 @@ INSTALLED_APPS = (
|
||||||
'vyroci',
|
'vyroci',
|
||||||
'sifrovacka',
|
'sifrovacka',
|
||||||
'novinky',
|
'novinky',
|
||||||
|
'brainfuck',
|
||||||
|
|
||||||
# Admin upravy:
|
# Admin upravy:
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,9 @@ urlpatterns = [
|
||||||
|
|
||||||
# Miniapka na šifrovačku
|
# Miniapka na šifrovačku
|
||||||
path('sifrovacka/', include('sifrovacka.urls')),
|
path('sifrovacka/', include('sifrovacka.urls')),
|
||||||
|
|
||||||
|
# tematku brainfuck
|
||||||
|
path('brainfuck/', include('brainfuck.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
# This is only needed when using runserver.
|
# This is only needed when using runserver.
|
||||||
|
|
Loading…
Reference in a new issue