Skip to content

Instantly share code, notes, and snippets.

@rikonor
Last active April 7, 2025 01:09
Show Gist options
  • Save rikonor/7de9b95e1e89e068600ec91c10916b2d to your computer and use it in GitHub Desktop.
Save rikonor/7de9b95e1e89e068600ec91c10916b2d to your computer and use it in GitHub Desktop.
dots
import React, { useState, useEffect } from 'react';
const DotGridEncoder = () => {
// State variables for customization
const [text, setText] = useState('life is war');
const [dotSize, setDotSize] = useState(4);
const [gridSpacing, setGridSpacing] = useState(8);
const [encoding, setEncoding] = useState('binary'); // 'binary', 'ascii', 'morse'
const [colorScheme, setColorScheme] = useState('monochrome'); // 'monochrome', 'gradient', 'rainbow'
// Calculate grid dimensions for a more square layout
const totalBits = text.length * 8; // Approximate bits needed for encoding
const gridWidth = Math.ceil(Math.sqrt(totalBits)); // Square root for square layout
const gridHeight = Math.ceil(totalBits / gridWidth);
// Function to encode text into dot grid
const encodeText = () => {
let encodedBits = [];
// Handle empty input
if (!text || text.length === 0) {
return Array(8).fill().map(() => Array(8).fill(false));
}
// Reset grid dimensions based on encoding method and text length
let estimatedBits;
if (encoding === 'morse') {
// Morse encoding tends to generate more bits
estimatedBits = text.length * 15;
} else {
estimatedBits = text.length * 8;
}
// Adjust gridWidth and gridHeight for square layout
const squareSide = Math.ceil(Math.sqrt(estimatedBits));
switch(encoding) {
case 'binary':
// Convert text to binary representation
for (let i = 0; i < text.length; i++) {
const charCode = text.charCodeAt(i);
const binaryStr = charCode.toString(2).padStart(8, '0');
for (let j = 0; j < binaryStr.length; j++) {
encodedBits.push(binaryStr[j] === '1');
}
}
break;
case 'ascii':
// Use ASCII values directly
for (let i = 0; i < text.length; i++) {
const value = text.charCodeAt(i);
for (let j = 0; j < 8; j++) {
encodedBits.push(Boolean((value >> j) & 1));
}
}
break;
case 'morse':
// Simple Morse code mapping
const morseMap = {
'a': '.-', 'b': '-...', 'c': '-.-.', 'd': '-..', 'e': '.', 'f': '..-.',
'g': '--.', 'h': '....', 'i': '..', 'j': '.---', 'k': '-.-', 'l': '.-..',
'm': '--', 'n': '-.', 'o': '---', 'p': '.--.', 'q': '--.-', 'r': '.-.',
's': '...', 't': '-', 'u': '..-', 'v': '...-', 'w': '.--', 'x': '-..-',
'y': '-.--', 'z': '--..', ' ': '/'
};
for (let i = 0; i < text.length; i++) {
const char = text[i].toLowerCase();
if (morseMap[char]) {
for (let j = 0; j < morseMap[char].length; j++) {
encodedBits.push(morseMap[char][j] === '.' || morseMap[char][j] === '-');
// Add a spacer bit between morse symbols
if (j < morseMap[char].length - 1) {
encodedBits.push(false);
}
}
// Add three spacer bits between characters
if (i < text.length - 1) {
encodedBits.push(false);
encodedBits.push(false);
}
}
}
break;
}
// Fill the grid row by row, left to right
const grid = Array(gridHeight).fill().map(() => Array(gridWidth).fill(false));
let bitIndex = 0;
for (let row = 0; row < gridHeight && bitIndex < encodedBits.length; row++) {
for (let col = 0; col < gridWidth && bitIndex < encodedBits.length; col++) {
grid[row][col] = encodedBits[bitIndex];
bitIndex++;
}
}
return grid;
};
// Get dot color based on position and color scheme
const getDotColor = (row, col, active) => {
if (!active) return '#eaeaea';
switch(colorScheme) {
case 'monochrome':
return '#333333';
case 'gradient':
// Creates a gradient from blue to red
const progress = (row * gridWidth + col) / (gridWidth * gridHeight);
return `rgb(${Math.round(progress * 255)}, 0, ${Math.round(255 - progress * 255)})`;
case 'rainbow':
// Creates a rainbow effect
const hue = ((row * gridWidth + col) / (gridWidth * gridHeight)) * 360;
return `hsl(${hue}, 70%, 50%)`;
default:
return '#333333';
}
};
// Calculate proper dimensions for a square-like grid
let estimatedBits;
// Handle empty input case
if (!text || text.length === 0) {
estimatedBits = 64; // Default to an 8x8 grid for empty input
} else if (encoding === 'morse') {
estimatedBits = text.length * 15; // Morse code is variable length but generally longer
} else {
estimatedBits = text.length * 8; // Binary and ASCII use 8 bits per character
}
// Calculate grid dimensions for a more square layout
const squareSide = Math.ceil(Math.sqrt(estimatedBits));
const adjustedWidth = squareSide;
const adjustedHeight = Math.ceil(estimatedBits / adjustedWidth);
// Create the encoded grid
const encodedGrid = encodeText();
// Ensure we have valid dimensions even with empty input
const displayWidth = encodedGrid[0]?.length || 8;
const displayHeight = encodedGrid.length || 8;
return (
<div className="flex flex-col items-center max-w-4xl mx-auto p-4 bg-gray-50 rounded-lg shadow">
<h2 className="text-2xl font-bold mb-4">"{text}" Encoded as Dots</h2>
<div className="mb-6 w-full">
<div className="flex flex-wrap gap-4 mb-4">
<div className="flex-1">
<label className="block text-sm font-medium mb-1">Text to Encode</label>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
className="w-full p-2 border rounded"
/>
</div>
<div className="w-32">
<label className="block text-sm font-medium mb-1">Dot Size</label>
<input
type="range"
min="2"
max="6"
value={dotSize}
onChange={(e) => setDotSize(parseInt(e.target.value))}
className="w-full"
/>
</div>
<div className="w-32">
<label className="block text-sm font-medium mb-1">Grid Spacing</label>
<input
type="range"
min="6"
max="12"
value={gridSpacing}
onChange={(e) => setGridSpacing(parseInt(e.target.value))}
className="w-full"
/>
</div>
</div>
<div className="flex flex-wrap gap-4">
<div className="flex-1">
<label className="block text-sm font-medium mb-1">Encoding Method</label>
<select
value={encoding}
onChange={(e) => setEncoding(e.target.value)}
className="w-full p-2 border rounded"
>
<option value="binary">Binary</option>
<option value="ascii">ASCII</option>
<option value="morse">Morse Code</option>
</select>
</div>
<div className="flex-1">
<label className="block text-sm font-medium mb-1">Color Scheme</label>
<select
value={colorScheme}
onChange={(e) => setColorScheme(e.target.value)}
className="w-full p-2 border rounded"
>
<option value="monochrome">Monochrome</option>
<option value="gradient">Blue-Red Gradient</option>
<option value="rainbow">Rainbow</option>
</select>
</div>
</div>
</div>
<div className="relative bg-white p-4 border rounded">
<svg
width={displayWidth * gridSpacing}
height={displayHeight * gridSpacing}
className="overflow-visible"
>
{encodedGrid.map((row, rowIndex) =>
row.map((isActive, colIndex) => (
<circle
key={`${rowIndex}-${colIndex}`}
cx={colIndex * gridSpacing + gridSpacing/2}
cy={rowIndex * gridSpacing + gridSpacing/2}
r={isActive ? dotSize : dotSize/2}
fill={getDotColor(rowIndex, colIndex, isActive)}
className="transition-all duration-300"
/>
))
)}
</svg>
</div>
<div className="mt-4 text-sm text-gray-700">
<p>
{text ? `Encoding "${text}" using ${encoding} encoding.` : 'Enter text to see it encoded as dots.'}
</p>
<p className="italic mt-1">
{encoding === 'binary' && 'Each character is represented by its 8-bit binary ASCII value.'}
{encoding === 'ascii' && 'Each bit in the ASCII value is represented by a dot.'}
{encoding === 'morse' && 'Dots and dashes of Morse code are represented by dots.'}
</p>
</div>
</div>
);
};
export default DotGridEncoder;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment