Re: Miscellaneous discussion of CA algorithms and software
Posted: June 6th, 2025, 9:04 am
Decide to share a rule table making interface which could been ran in browser. Currently only fully support Moore neighbourhood rules. Not very advanced, but supports variable, transitions, colors and names, or even creating multiple distinct variables with same list of states.
(Is there any more appriotiate thread to share such scripts?)
EDIT: this is a HTML + JS script. To run it, paste it into a text editor and save it as HTML file.
EDIT 2: It's indeed "revolutionary" (significantly lowering the barrier of ruletable golfing), but I'm not sure if this is worthy of a separate thread. Tell me if this is notable enough.
EDIT 3: GitHub repository at here.
Code: Select all
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RulegolfTable</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 10px; }
input, select, button { margin: 5px; font-size: 14px; }
button { padding: 5px 8px; }
.section { margin: 10px 0; border: 1px solid #ddd; padding: 8px; border-radius: 5px; }
.icon-btn { font-size: 18px; min-width: 30px; }
.var-list, .trans-list { margin: 5px 0; }
.dialog { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 15px; border: 1px solid #000; z-index: 100; max-width: 90%; max-height: 90%; overflow: auto; }
.dialog input, .dialog select { margin: 5px 0; width: 100%; }
.state-option { display: inline-block; width: 24px; height: 24px; text-align: center; line-height: 24px; margin: 2px; border: 1px solid #000; }
.trans-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 5px; margin: 10px 0; }
.trans-cell { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border: 1px solid #000; position: relative; }
.trans-center { grid-column: 3; grid-row: 2; background-color: #f0f0f0; }
.trans-arrow { grid-column: 5; grid-row: 2; display: flex; align-items: center; justify-content: center; font-size: 24px; }
.trans-result { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border: 1px solid #000; }
.var-options { display: flex; flex-wrap: wrap; margin: 5px 0; }
.var-option { margin: 2px; padding: 2px 5px; background: #eee; border-radius: 3px; cursor: pointer; }
.var-option:hover { background: #ddd; }
.multi-var { margin-top: 10px; }
.export-box { width: 100%; height: 300px; font-family: monospace; margin: 10px 0; }
.grid-pos-0 { grid-column: 3; grid-row: 1; } /* Top */
.grid-pos-1 { grid-column: 4; grid-row: 1; } /* Top-right */
.grid-pos-2 { grid-column: 4; grid-row: 2; } /* Right */
.grid-pos-3 { grid-column: 4; grid-row: 3; } /* Bottom-right */
.grid-pos-4 { grid-column: 3; grid-row: 3; } /* Bottom */
.grid-pos-5 { grid-column: 2; grid-row: 3; } /* Bottom-left */
.grid-pos-6 { grid-column: 2; grid-row: 2; } /* Left */
.grid-pos-7 { grid-column: 2; grid-row: 1; } /* Top-left */
.grid-pos-8 { grid-column: 3; grid-row: 2; } /* Center */
.grid-pos-9 { grid-column: 5; grid-row: 2; } /* Result */
</style>
</head>
<body>
<h1>RulegolfTable</h1>
<div class="section">
<div>📛 Rule name: <input type="text" id="ruleName" value="MyRule"></div>
<div>🔢 States: <input type="number" id="numStates" min="2" value="2"></div>
<div>🏘️ Neighborhood:
<select id="neighborhood">
<option value="moore">Moore</option>
<option value="vonNeumann">von Neumann</option>
<option value="hexagonal">Hexagonal</option>
</select>
</div>
<div>🔄 Symmetries:
<select id="symmetries">
<option value="none">None</option>
<option value="reflect_horizontal">Reflect Horizontal</option>
<option value="reflect_vertical">Reflect Vertical</option>
<option value="rotate4">Rotate 4</option>
<option value="rotate8">Rotate 8</option>
<option value="rotate4reflect">Rotate4 + Reflect</option>
<option value="permute">Permute</option>
</select>
</div>
</div>
<div class="section">
<button class="icon-btn" onclick="showVarDialog()">➕ Add Variable</button>
<button class="icon-btn" onclick="showTransDialog()">🔀 Add Transition</button>
<button class="icon-btn" onclick="showColorDialog()">🎨 Set Color</button>
<button class="icon-btn" onclick="showNameDialog()">🏷️ Set State Name</button>
<button class="icon-btn" onclick="showExportDialog()">📋 Export Rule</button>
</div>
<div class="section">
<h3>📊 Variables</h3>
<div id="varList" class="var-list"></div>
<h3>🔄 Transitions</h3>
<div id="transList" class="trans-list"></div>
</div>
<!-- Dialogs -->
<div id="varDialog" class="dialog">
<h3>➕ Add Variable</h3>
Name: <input type="text" id="varName" placeholder="Base name for multiple variables"><br>
<div class="var-options">
<div class="var-option" onclick="setVarType('states')">States</div>
<div class="var-option" onclick="setVarType('variable')">Same as Variable</div>
</div>
<div id="varStates"></div>
<div id="varVariable" style="display:none">
Variable: <select id="varRefVariable"></select>
</div>
<div class="multi-var">
Create multiple: <input type="number" id="varCount" min="1" value="1" style="width:50px">
<label><input type="checkbox" id="varAddSuffix"> Add numeric suffix</label>
</div>
<button onclick="addVariable()">Add</button>
<button onclick="hideDialog('varDialog')">Cancel</button>
</div>
<div id="transDialog" class="dialog">
<h3>🔀 Add Transition</h3>
<div class="trans-grid" id="transGrid">
<!-- Positions will be added dynamically in the correct order -->
<div class="trans-cell grid-pos-0" title="Top"></div>
<div class="trans-cell grid-pos-1" title="Top-right"></div>
<div class="trans-cell grid-pos-2" title="Right"></div>
<div class="trans-cell grid-pos-3" title="Bottom-right"></div>
<div class="trans-cell grid-pos-4" title="Bottom"></div>
<div class="trans-cell grid-pos-5" title="Bottom-left"></div>
<div class="trans-cell grid-pos-6" title="Left"></div>
<div class="trans-cell grid-pos-7" title="Top-left"></div>
<div class="trans-cell grid-pos-8 trans-center" title="Center"></div>
<div class="trans-arrow">→</div>
<div class="trans-cell grid-pos-9" id="transResultPreview" title="Result"></div>
</div>
<button onclick="addTransition()">Add</button>
<button onclick="hideDialog('transDialog')">Cancel</button>
</div>
<div id="editVarDialog" class="dialog">
<h3>✏️ Edit Variable</h3>
Name: <input type="text" id="editVarName"><br>
<div class="var-options">
<div class="var-option" onclick="setEditVarType('states')">States</div>
<div class="var-option" onclick="setEditVarType('variable')">Same as Variable</div>
</div>
<div id="editVarStates"></div>
<div id="editVarVariable" style="display:none">
Variable: <select id="editVarRefVariable"></select>
</div>
<button onclick="saveVariable()">Save</button>
<button onclick="hideDialog('editVarDialog')">Cancel</button>
</div>
<div id="editTransDialog" class="dialog">
<h3>✏️ Edit Transition</h3>
<div class="trans-grid" id="editTransGrid">
<!-- Positions will be added dynamically in the correct order -->
<div class="trans-cell grid-pos-0" title="Top"></div>
<div class="trans-cell grid-pos-1" title="Top-right"></div>
<div class="trans-cell grid-pos-2" title="Right"></div>
<div class="trans-cell grid-pos-3" title="Bottom-right"></div>
<div class="trans-cell grid-pos-4" title="Bottom"></div>
<div class="trans-cell grid-pos-5" title="Bottom-left"></div>
<div class="trans-cell grid-pos-6" title="Left"></div>
<div class="trans-cell grid-pos-7" title="Top-left"></div>
<div class="trans-cell grid-pos-8 trans-center" title="Center"></div>
<div class="trans-arrow">→</div>
<div class="trans-cell grid-pos-9" id="editTransResultPreview" title="Result"></div>
</div>
Sort order: <input type="number" id="editTransOrder" min="0" value="0"><br>
<button onclick="saveTransition()">Save</button>
<button onclick="hideDialog('editTransDialog')">Cancel</button>
</div>
<div id="colorDialog" class="dialog">
<h3>🎨 Set Color</h3>
State: <select id="colorState"></select><br>
Color: <input type="color" id="stateColor" value="#000000"><br>
<button onclick="saveColor()">Save</button>
<button onclick="hideDialog('colorDialog')">Cancel</button>
</div>
<div id="nameDialog" class="dialog">
<h3>🏷️ Set State Name</h3>
State: <select id="nameState"></select><br>
Name: <input type="text" id="stateName"><br>
<button onclick="saveName()">Save</button>
<button onclick="hideDialog('nameDialog')">Cancel</button>
</div>
<div id="exportDialog" class="dialog">
<h3>📋 Export Rule</h3>
<textarea id="exportBox" class="export-box" readonly></textarea>
<button onclick="copyToClipboard()">Copy to Clipboard</button>
<button onclick="hideDialog('exportDialog')">Close</button>
</div>
<script>
// Data storage
let variables = [];
let transitions = [];
let stateColors = {};
let stateNames = {};
let editingIndex = -1;
let currentVarType = 'states';
let currentEditVarType = 'states';
// Neighborhood positions in the correct order:
// Center (0), Top (1), Right (2), Bottom (3), Left (4),
// Top-right (5), Bottom-right (6), Bottom-left (7), Top-left (8)
const neighborhoodOrder = [8, 0, 1, 2, 3, 4, 5, 6, 7]; // Center first, then orthogonals, then diagonals
const displayOrder = [0,1,2,3,4,5,6,7,8]; // For visual display
// Initialize
document.addEventListener('DOMContentLoaded', function() {
updateNumStates();
document.getElementById('numStates').addEventListener('change', updateNumStates);
// Setup the transition grids
setupTransitionGrid('transGrid');
setupTransitionGrid('editTransGrid');
});
function setupTransitionGrid(gridId) {
const grid = document.getElementById(gridId);
// Clear only the select elements, keep the grid structure
const cells = grid.querySelectorAll('.trans-cell');
cells.forEach(cell => {
if (cell.id !== 'transResultPreview' && cell.id !== 'editTransResultPreview') {
cell.innerHTML = '';
const select = document.createElement('select');
select.id = `${gridId}N${cell.title.replace('-', '').toLowerCase()}`;
cell.appendChild(select);
}
});
}
function updateNumStates() {
const num = parseInt(document.getElementById('numStates').value);
// Initialize colors and names for states
for (let i = 0; i < num; i++) {
if (!stateColors[i]) stateColors[i] = getRandomColor();
if (!stateNames[i]) stateNames[i] = `State ${i}`;
}
updateVarOptions();
}
function getRandomColor() {
return '#' + Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
}
function updateVarOptions() {
const varSelect = document.getElementById('varRefVariable');
const editVarSelect = document.getElementById('editVarRefVariable');
varSelect.innerHTML = '';
editVarSelect.innerHTML = '';
variables.forEach((v, i) => {
varSelect.appendChild(new Option(v.name, i));
editVarSelect.appendChild(new Option(v.name, i));
});
}
function setVarType(type) {
currentVarType = type;
document.getElementById('varStates').style.display = type === 'states' ? 'block' : 'none';
document.getElementById('varVariable').style.display = type === 'variable' ? 'block' : 'none';
}
function setEditVarType(type) {
currentEditVarType = type;
document.getElementById('editVarStates').style.display = type === 'states' ? 'block' : 'none';
document.getElementById('editVarVariable').style.display = type === 'variable' ? 'block' : 'none';
}
// Dialog functions
function showVarDialog() {
const numStates = parseInt(document.getElementById('numStates').value);
let html = '';
for (let i = 0; i < numStates; i++) {
html += `<input type="checkbox" id="varState${i}" value="${i}">
<label for="varState${i}"><span class="state-option" style="background:${stateColors[i]}">${i}</span> ${stateNames[i]}</label><br>`;
}
document.getElementById('varStates').innerHTML = html;
document.getElementById('varName').value = '';
document.getElementById('varCount').value = 1;
document.getElementById('varAddSuffix').checked = false;
setVarType('states');
updateVarOptions();
showDialog('varDialog');
}
function createStateOption(state, prefix, selected) {
const option = document.createElement('option');
option.value = state;
option.textContent = state;
if (selected) option.selected = true;
const colorSpan = document.createElement('span');
colorSpan.className = 'state-option';
colorSpan.style.backgroundColor = stateColors[state];
colorSpan.textContent = state;
option.prepend(colorSpan.cloneNode(true));
return option;
}
function populateTransitionGrid(gridId, numStates, pattern) {
// Order for display: Center, Top, Right, Bottom, Left, Top-right, Bottom-right, Bottom-left, Top-left
const displayPositions = ['center', 'top', 'right', 'bottom', 'left', 'topright', 'bottomright', 'bottomleft', 'topleft'];
displayPositions.forEach((pos, index) => {
const select = document.getElementById(`${gridId}N${pos}`);
if (!select) return;
select.innerHTML = '';
const anyOption = document.createElement('option');
select.appendChild(anyOption);
for (let i = 0; i < numStates; i++) {
const selected = pattern && pattern[index] === i;
select.appendChild(createStateOption(i, gridId, selected));
}
// Add variables
variables.forEach((v, vi) => {
const varOption = document.createElement('option');
varOption.value = `var${vi}`;
varOption.textContent = v.name;
if (pattern && pattern[index] === `var${vi}`) varOption.selected = true;
select.appendChild(varOption);
});
});
// Handle result preview
if (gridId === 'transGrid') {
const resultPreview = document.getElementById('transResultPreview');
resultPreview.innerHTML = '';
const resultSelect = document.createElement('select');
resultSelect.id = 'transResult';
for (let i = 0; i < numStates; i++) {
resultSelect.appendChild(createStateOption(i, 'trans'));
}
resultSelect.addEventListener('change', function() {
updateResultPreview(resultPreview, this.value);
});
resultPreview.appendChild(resultSelect);
updateResultPreview(resultPreview, '0');
} else if (gridId === 'editTransGrid') {
const resultPreview = document.getElementById('editTransResultPreview');
resultPreview.innerHTML = '';
const resultSelect = document.createElement('select');
resultSelect.id = 'editTransResult';
for (let i = 0; i < numStates; i++) {
const selected = pattern && pattern.result === i;
resultSelect.appendChild(createStateOption(i, 'editTrans', selected));
}
resultSelect.addEventListener('change', function() {
updateResultPreview(resultPreview, this.value);
});
resultPreview.appendChild(resultSelect);
if (pattern) {
updateResultPreview(resultPreview, pattern.result);
} else {
updateResultPreview(resultPreview, '0');
}
}
}
function showTransDialog() {
const numStates = parseInt(document.getElementById('numStates').value);
// Populate the grid
populateTransitionGrid('transGrid', numStates);
showDialog('transDialog');
}
function updateResultPreview(previewEl, state) {
previewEl.style.backgroundColor = stateColors[state] || '#ffffff';
if (previewEl.querySelector('select')) {
previewEl.querySelector('select').value = state;
}
}
function showEditVarDialog(index) {
editingIndex = index;
const varData = variables[index];
document.getElementById('editVarName').value = varData.name;
if (varData.reference !== undefined) {
setEditVarType('variable');
document.getElementById('editVarRefVariable').value = varData.reference;
} else {
setEditVarType('states');
const numStates = parseInt(document.getElementById('numStates').value);
let html = '';
for (let i = 0; i < numStates; i++) {
const checked = varData.states.includes(i) ? 'checked' : '';
html += `<input type="checkbox" id="editVarState${i}" value="${i}" ${checked}>
<label for="editVarState${i}"><span class="state-option" style="background:${stateColors[i]}">${i}</span> ${stateNames[i]}</label><br>`;
}
document.getElementById('editVarStates').innerHTML = html;
}
updateVarOptions();
showDialog('editVarDialog');
}
function showEditTransDialog(index) {
editingIndex = index;
const trans = transitions[index];
const numStates = parseInt(document.getElementById('numStates').value);
// Convert the pattern to display order
const displayPattern = [
trans.pattern[0], // Center
trans.pattern[1], // Top
trans.pattern[2], // Right
trans.pattern[3], // Bottom
trans.pattern[4], // Left
trans.pattern[5], // Top-right
trans.pattern[6], // Bottom-right
trans.pattern[7], // Bottom-left
trans.pattern[8] // Top-left
];
// Populate the grid with existing pattern
populateTransitionGrid('editTransGrid', numStates, {
...trans,
pattern: displayPattern
});
document.getElementById('editTransOrder').value = trans.order || 0;
showDialog('editTransDialog');
}
function showColorDialog() {
const numStates = parseInt(document.getElementById('numStates').value);
let html = '';
for (let i = 0; i < numStates; i++) {
html += `<option value="${i}">${i}: ${stateNames[i]}</option>`;
}
document.getElementById('colorState').innerHTML = html;
showDialog('colorDialog');
}
function showNameDialog() {
const numStates = parseInt(document.getElementById('numStates').value);
let html = '';
for (let i = 0; i < numStates; i++) {
html += `<option value="${i}">${i}: ${stateNames[i]}</option>`;
}
document.getElementById('nameState').innerHTML = html;
showDialog('nameDialog');
}
function showExportDialog() {
const exportText = generateRuleText();
document.getElementById('exportBox').value = exportText;
showDialog('exportDialog');
}
function showDialog(id) {
document.getElementById(id).style.display = 'block';
}
function hideDialog(id) {
document.getElementById(id).style.display = 'none';
}
// Data manipulation functions
function addVariable() {
const baseName = document.getElementById('varName').value.trim();
if (!baseName) return;
const count = parseInt(document.getElementById('varCount').value);
const addSuffix = document.getElementById('varAddSuffix').checked;
for (let c = 0; c < count; c++) {
const name = addSuffix ? `${baseName}${c || ''}` : baseName;
let varData;
if (currentVarType === 'variable') {
const refIndex = parseInt(document.getElementById('varRefVariable').value);
if (isNaN(refIndex)) return;
varData = { name, reference: refIndex };
} else {
const numStates = parseInt(document.getElementById('numStates').value);
const states = [];
for (let i = 0; i < numStates; i++) {
if (document.getElementById(`varState${i}`).checked) {
states.push(i);
}
}
varData = { name, states };
}
variables.push(varData);
}
updateVarList();
updateVarOptions();
hideDialog('varDialog');
}
function saveVariable() {
if (editingIndex === -1) return;
const name = document.getElementById('editVarName').value.trim();
if (!name) return;
let varData;
if (currentEditVarType === 'variable') {
const refIndex = parseInt(document.getElementById('editVarRefVariable').value);
if (isNaN(refIndex)) return;
varData = { name, reference: refIndex };
} else {
const numStates = parseInt(document.getElementById('numStates').value);
const states = [];
for (let i = 0; i < numStates; i++) {
if (document.getElementById(`editVarState${i}`).checked) {
states.push(i);
}
}
varData = { name, states };
}
variables[editingIndex] = varData;
updateVarList();
updateVarOptions();
hideDialog('editVarDialog');
editingIndex = -1;
}
function addTransition() {
// Get values in display order: Center, Top, Right, Bottom, Left, Top-right, Bottom-right, Bottom-left, Top-left
const displayPositions = ['center', 'top', 'right', 'bottom', 'left', 'topright', 'bottomright', 'bottomleft', 'topleft'];
const pattern = displayPositions.map(pos => {
const val = document.getElementById(`transGridN${pos}`).value;
if (val.startsWith('var')) {
return val;
} else {
return val === '' ? null : parseInt(val);
}
});
const result = parseInt(document.getElementById('transResult').value);
transitions.push({
pattern: [
pattern[0], // Center
pattern[1], // Top
pattern[2], // Right
pattern[3], // Bottom
pattern[4], // Left
pattern[5], // Top-right
pattern[6], // Bottom-right
pattern[7], // Bottom-left
pattern[8] // Top-left
],
result,
order: 0
});
updateTransList();
hideDialog('transDialog');
}
function saveTransition() {
if (editingIndex === -1) return;
// Get values in display order: Center, Top, Right, Bottom, Left, Top-right, Bottom-right, Bottom-left, Top-left
const displayPositions = ['center', 'top', 'right', 'bottom', 'left', 'topright', 'bottomright', 'bottomleft', 'topleft'];
const pattern = displayPositions.map(pos => {
const val = document.getElementById(`editTransGridN${pos}`).value;
if (val.startsWith('var')) {
return val;
} else {
return val === '' ? null : parseInt(val);
}
});
const result = parseInt(document.getElementById('editTransResult').value);
const order = parseInt(document.getElementById('editTransOrder').value);
transitions[editingIndex] = {
pattern: [
pattern[0], // Center
pattern[1], // Top
pattern[2], // Right
pattern[3], // Bottom
pattern[4], // Left
pattern[5], // Top-right
pattern[6], // Bottom-right
pattern[7], // Bottom-left
pattern[8] // Top-left
],
result,
order
};
updateTransList();
hideDialog('editTransDialog');
editingIndex = -1;
}
function saveColor() {
const state = parseInt(document.getElementById('colorState').value);
const color = document.getElementById('stateColor').value;
stateColors[state] = color;
hideDialog('colorDialog');
}
function saveName() {
const state = parseInt(document.getElementById('nameState').value);
const name = document.getElementById('stateName').value.trim();
stateNames[state] = name || `State ${state}`;
hideDialog('nameDialog');
updateVarList();
updateTransList();
}
function deleteVariable(index) {
variables.splice(index, 1);
updateVarList();
updateVarOptions();
}
function deleteTransition(index) {
transitions.splice(index, 1);
updateTransList();
}
// UI update functions
function updateVarList() {
const container = document.getElementById('varList');
container.innerHTML = '';
variables.forEach((varData, index) => {
const btn = document.createElement('button');
btn.className = 'icon-btn';
let displayText;
if (varData.reference !== undefined) {
const refVar = variables[varData.reference];
displayText = `${varData.name} = ${refVar.name}`;
} else {
displayText = `${varData.name} = {${varData.states.join(',')}}`;
}
btn.innerHTML = '✏️ ' + displayText;
btn.onclick = () => showEditVarDialog(index);
const delBtn = document.createElement('button');
delBtn.className = 'icon-btn';
delBtn.innerHTML = '❌';
delBtn.onclick = (e) => { e.stopPropagation(); deleteVariable(index); };
const div = document.createElement('div');
div.appendChild(btn);
div.appendChild(delBtn);
container.appendChild(div);
});
}
function updateTransList() {
const container = document.getElementById('transList');
container.innerHTML = '';
// Sort transitions by order
transitions.sort((a, b) => (a.order || 0) - (b.order || 0));
transitions.forEach((trans, index) => {
const patternStr = [
trans.pattern[0], // Center
trans.pattern[1], // Top
trans.pattern[2], // Right
trans.pattern[3], // Bottom
trans.pattern[4], // Left
trans.pattern[5], // Top-right
trans.pattern[6], // Bottom-right
trans.pattern[7], // Bottom-left
trans.pattern[8] // Top-left
].map(s => {
if (s === null) return '*';
if (typeof s === 'string' && s.startsWith('var')) {
const varIndex = parseInt(s.substring(3));
return variables[varIndex].name;
}
return s;
}).join(',');
const resultStr = stateNames[trans.result] || 'State '+trans.result;
const btn = document.createElement('button');
btn.className = 'icon-btn';
btn.innerHTML = '✏️ ' + patternStr + ' → ' + resultStr;
btn.onclick = () => showEditTransDialog(index);
const delBtn = document.createElement('button');
delBtn.className = 'icon-btn';
delBtn.innerHTML = '❌';
delBtn.onclick = (e) => { e.stopPropagation(); deleteTransition(index); };
const div = document.createElement('div');
div.appendChild(btn);
div.appendChild(delBtn);
container.appendChild(div);
});
}
// Export function
function generateRuleText() {
const ruleName = document.getElementById('ruleName').value;
const numStates = document.getElementById('numStates').value;
const neighborhood = document.getElementById('neighborhood').value;
const symmetries = document.getElementById('symmetries').value;
let ruleText = `@RULE ${ruleName}\n\n`;
ruleText += `@TABLE\n`;
ruleText += `n_states:${numStates}\n`;
ruleText += `neighborhood:${neighborhood}\n`;
ruleText += `symmetries:${symmetries}\n\n`;
// Add variables
variables.forEach(varData => {
if (varData.reference !== undefined) {
const refVar = variables[varData.reference];
ruleText += `var ${varData.name} = ${refVar.name}\n`;
} else {
ruleText += `var ${varData.name} = {${varData.states.join(',')}}\n`;
}
});
if (variables.length > 0) ruleText += '\n';
// Add transitions
transitions.forEach(trans => {
// Format pattern in the correct order: Center, Top, Right, Bottom, Left, Top-right, Bottom-right, Bottom-left, Top-left
const pattern = [
trans.pattern[0], // Center
trans.pattern[1], // Top
trans.pattern[5], // Right
trans.pattern[2], // Bottom
trans.pattern[6], // Left
trans.pattern[3], // Top-right
trans.pattern[7], // Bottom-right
trans.pattern[4], // Bottom-left
trans.pattern[8] // Top-left
];
const patternStr = pattern.map(s => {
if (s === null) return '*';
if (typeof s === 'string' && s.startsWith('var')) {
const varIndex = parseInt(s.substring(3));
return variables[varIndex].name;
}
return s;
}).join(',');
ruleText += patternStr + ',';
ruleText += trans.result + '\n\n';
});
// Add colors
ruleText += `@COLORS\n`;
for (const [state, color] of Object.entries(stateColors)) {
const rgb = hexToRgb(color);
ruleText += `${state} ${rgb.r} ${rgb.g} ${rgb.b}\n`;
}
return ruleText;
}
function copyToClipboard() {
const exportText = document.getElementById('exportBox').value;
navigator.clipboard.writeText(exportText).then(() => {
alert('Rule copied to clipboard!');
}).catch(err => {
console.error('Failed to copy: ', err);
});
}
function hexToRgb(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
}
</script>
</body>
</html>EDIT: this is a HTML + JS script. To run it, paste it into a text editor and save it as HTML file.
EDIT 2: It's indeed "revolutionary" (significantly lowering the barrier of ruletable golfing), but I'm not sure if this is worthy of a separate thread. Tell me if this is notable enough.
EDIT 3: GitHub repository at here.