Кто каким ИИ пользуется для написания скетчей?

Ну почему же! Так сойдёт? В помощь Бабосу.)

	.model	small
	.code
	include	dos.inc

	org	100h
        @DispStr str
        @Exit

str:    db      'test$'
        end

А выделяемое тепло куда отводить?

А он знает почему org 100 ?

А меня нет.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
body {
margin: 0;
padding: 0;
background: black;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: monospace;
}
canvas {
display: block;
width: min(95vw, 95vh);
height: min(95vw, 95vh);
border-radius: 50%;
box-shadow: 0 0 30px cyan;
}
#status {
position: absolute;
bottom: 20px;
color: cyan;
font-size: 14px;
}
</style>
</head>
<body>
<canvas id="voronoiCanvas" width="240" height="240"></canvas>
<div id="status">Рисование...</div>
<script>
const canvas = document.getElementById('voronoiCanvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
const CENTER_X = 120;
const CENTER_Y = 120;
const Z = 500.0;
const NUM_POINTS = 12;
let points = [];
let variant = 1;
let Y = 0;
let currentF1 = 0;
let currentR1 = 0;
let lastFrameTime = 0;
let batchSize = 30;

function dist(x1, y1, x2, y2, m) {
if (m == 1) {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
if (m == 2) {
return Math.abs(x1 - x2) + Math.abs(y2 - y1);
}
if (m == 3) {
return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
}
return 0;
}

function ran_Sait(variant) {
for (let i = 0; i < NUM_POINTS; i++) {
if (!points[i]) points[i] = {};
if (variant == 1 || variant == 3) {
points[i].x = Math.floor(Math.random() * 120) + CENTER_X;
points[i].y = Math.floor(Math.random() * 60) + CENTER_Y;
} else {
//points[i].x = Math.floor(Math.random() * 240);
//points[i].y = Math.floor(Math.random() * 240);
}
if (variant == 2 || variant == 3) {
points[i].R = Math.floor(Math.random() * 256);
points[i].G = Math.floor(Math.random() * 256);
points[i].B = Math.floor(Math.random() * 256);
} else {
points[i].R = Math.floor(Math.random() * 256);
points[i].G = Math.floor(Math.random() * 256);
points[i].B = Math.floor(Math.random() * 256);
}
}
}

function drawPixel(x, y, r, g, b) {
if (x < 0 || x >= 240 || y < 0 || y >= 240) return;
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.fillRect(x, y, 1, 1);
}

function drawBatch() {
let now = Date.now();
if (now - Y > 90000) {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 240, 240);
Y = now;
statusEl.textContent = 'Очистка экрана...';
}
for (let b = 0; b < batchSize; b++) {
if (currentF1 > Math.PI / 6.0) {
currentF1 = 0;
currentR1 = 0;
variant = (variant % 3) + 1;
ran_Sait(3);
let metricName = "";
if (variant == 1) metricName = "Евклидова";
else if (variant == 2) metricName = "Манхэттенская";
else metricName = "Авторская";
statusEl.textContent = `Метрика: ${metricName} | Сектор готов`;
break;
}
let x = Math.floor(CENTER_X + Math.cos(currentF1) * currentR1);
let y = Math.floor(CENTER_Y + Math.sin(currentF1) * currentR1);
if (x >= 0 && x < 240 && y >= 0 && y < 240) {
let closestPoint = 0;
let minDist = dist(x, y, points[0].x, points[0].y, variant);
for (let i = 1; i < NUM_POINTS; i++) {
let d = dist(x, y, points[i].x, points[i].y, variant);
if (d < minDist) {
minDist = d;
closestPoint = i;
}
}
for (let angle = 0.01; angle <= 2 * Math.PI; angle += Math.PI / 3.0) {
if (minDist > 15) {
//let df = (1.2 * currentR1) / 120;
let df =0.0;
let x1 = Math.floor(CENTER_X + Math.cos(currentF1 + angle + df) * currentR1);
let y1 = Math.floor(CENTER_Y + Math.sin(currentF1 + angle + df) * currentR1);
if (x1 >= 0 && x1 < 240 && y1 >= 0 && y1 < 240) {
let p = points[closestPoint];
let r = Math.abs(p.R - 10 * minDist) % 256;
let g = Math.abs(p.G - 10 * minDist) % 256;
let b = Math.abs(p.B - 10 * minDist) % 256;
drawPixel(x1, y1, r, g, b);
}
let x2 = Math.floor(CENTER_X + Math.cos(2 * Math.PI - currentF1 + angle + df) * currentR1);
let y2 = Math.floor(CENTER_Y + Math.sin(2 * Math.PI - currentF1 + angle + df) * currentR1);
if (x2 >= 0 && x2 < 240 && y2 >= 0 && y2 < 240) {
let p = points[closestPoint];
let r = Math.abs(p.R - 10 * minDist) % 256;
let g = Math.abs(p.G - 10 * minDist) % 256;
let b = Math.abs(p.B - 10 * minDist) % 256;
drawPixel(x2, y2, r, g, b);
}
}
}
}
currentR1++;
if (currentR1 > 120) {
currentR1 = 0;
currentF1 += Math.PI / Z;
}
}
}

function animationLoop() {
drawBatch();
requestAnimationFrame(animationLoop);
}

ran_Sait(3);
Y = Date.now();
animationLoop();
</script>
</body>
</html>

Вот прошу конкретно :slight_smile: в строках 75-81 идёт псевдослучайный выбор компонент цвета RGB от 0 до 255 (мне так кажется, ибо в хтмл ни бум бум) сделай в приложении 3 переменника с 6 ползунками для регулировки интервалов компонент. А он не может, и сказать чего не понимает не может.

…кстати, Бабос, а этот исходный вариант ИИ долго переделывал из ino ?

именно так, если мы делаем не понятно что и зачем, и что не приносит доход сейчас))) как и не понятно что это даст в плане развития…
я предлагаю создать вам тему, последователи великого 0 ! я даже уже начальное фото придумал для темы

будем пытаться сокращать информацию, и как минимум получать новые алгоритмы шифрования, единственна полезная! хрень с которой интересно сидеть по крайней мере мне… и она что то дает… как минимум развитие в написание кода)))

он мне не дается, но я предлагаю универсальное решение, присоединиться к поиску алгоритмов разных, для сокращения информации, или наблюдения пути сокращения, придумывать которые доступны любым людям, и даже математикам! без написания кода… как минимум 1 последователь постоянный у вас будет))) если код не осилите, или идей нет, можете пока что над фото поработать, оно все же будет лицом темы…

что скажете ?)))

а вообще почему вы заговорили про ассемблер ?
в целом мне понравилась ваша идея скармливать код ассемблера, и смотреть как он делает аналог алгоритма на с++
но для начала вам не надо быть целым куркулем, и скидывать коды… вроде это так называлось в советские времена которые я не застал))) или фрагменты как что то реализовать…

ну а если и дальше хотите не понятно что доказать… а лично я не понял что вы доказываете… то я тут не могу помочь)))

вот уже больше конкретики, но сагодня у меня вторые сутки сна по 3-4 часа, моя функциональность просела минимум на 50%!)))

а что код делает ?

не помню… ваша икибана меня затянула часов на 3-6 вроде… для создания конструктора, и я кстате тоже код не понял))) что бы объяснить ии, он короткий но я его не понимаю)))

Ну так я же конкретно ИИ указал строки над которыми работать :slight_smile: и текстом суть изменений.

lilik а кто вам сказал что и он сможет понять этот код ?))) я заметил то что если ии объяснять задачу частично кодом, он выдает результат точнее, как минимум он берет и подставляет из запроса код, и у него не остается маневра выдать что то не то…

хоть и не всегда, но вроде что бы работать с ии надо понимать что вы делаете)))
и я бы посоветовал дипсика… через 200 запросов наверное поймете что от него ждать, и как задавать вопросы…
а в чат гпт я не вникал… мне он не понравился, особенно из за следа маска в проекте…

в этих строчках нет “хтмл”.

Верю…господи. Но нет там и random(0,255);
…просто на ардуино делать настоящие регулировки тяжко, учитывая, что их до хрена в этом простеньком узорном скетче…хотя можно сенсорный дисплей бахнуть - часть на узор, часть на управление. Но смысл был именно в узорах, а не в мастерстве программирования.

зря вы ему верите, он вон в чем признался….

мстя)))

1 лайк

ползунки я попросил Codex сделать и кнопку перезапуска. В код твой не вникал. Ползунки и кнопку он сделал.

Спойлер
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
body {
margin: 0;
padding: 0;
background: black;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: monospace;
overflow: hidden;
}
canvas {
display: block;
width: min(95vw, 95vh);
height: min(95vw, 95vh);
border-radius: 50%;
box-shadow: 0 0 30px cyan;
}
#controls {
position: absolute;
top: 16px;
left: 16px;
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
background: rgba(0, 0, 0, 0.72);
border: 1px solid rgba(0, 255, 255, 0.35);
border-radius: 12px;
color: cyan;
min-width: 220px;
backdrop-filter: blur(6px);
}
.controls-actions {
display: flex;
justify-content: flex-end;
margin-top: 4px;
}
.controls-actions button {
border: 1px solid rgba(0, 255, 255, 0.45);
background: rgba(0, 255, 255, 0.12);
color: cyan;
padding: 8px 12px;
border-radius: 8px;
font: inherit;
cursor: pointer;
}
.controls-actions button:hover {
background: rgba(0, 255, 255, 0.2);
}
.channel-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.channel-header {
display: flex;
justify-content: space-between;
align-items: baseline;
font-size: 13px;
}
.channel-header strong {
font-size: 15px;
}
.slider-pair {
display: flex;
flex-direction: column;
gap: 4px;
}
.slider-pair label {
display: flex;
justify-content: space-between;
gap: 10px;
font-size: 12px;
}
.slider-pair input[type="range"] {
width: 100%;
accent-color: cyan;
}
#status {
position: absolute;
bottom: 20px;
color: cyan;
font-size: 14px;
}
</style>
</head>
<body>
<canvas id="voronoiCanvas" width="240" height="240"></canvas>
<div id="controls">
<div class="channel-group" data-channel="R">
<div class="channel-header">
<strong>R</strong>
<span id="RValue">0-255</span>
</div>
<div class="slider-pair">
<label for="RMin"><span>Мин</span><span id="RMinValue">0</span></label>
<input id="RMin" type="range" min="0" max="255" value="0">
<label for="RMax"><span>Макс</span><span id="RMaxValue">255</span></label>
<input id="RMax" type="range" min="0" max="255" value="255">
</div>
</div>
<div class="channel-group" data-channel="G">
<div class="channel-header">
<strong>G</strong>
<span id="GValue">0-255</span>
</div>
<div class="slider-pair">
<label for="GMin"><span>Мин</span><span id="GMinValue">0</span></label>
<input id="GMin" type="range" min="0" max="255" value="0">
<label for="GMax"><span>Макс</span><span id="GMaxValue">255</span></label>
<input id="GMax" type="range" min="0" max="255" value="255">
</div>
</div>
<div class="channel-group" data-channel="B">
<div class="channel-header">
<strong>B</strong>
<span id="BValue">0-255</span>
</div>
<div class="slider-pair">
<label for="BMin"><span>Мин</span><span id="BMinValue">0</span></label>
<input id="BMin" type="range" min="0" max="255" value="0">
<label for="BMax"><span>Макс</span><span id="BMaxValue">255</span></label>
<input id="BMax" type="range" min="0" max="255" value="255">
</div>
</div>
<div class="controls-actions">
<button id="restartButton" type="button">Перезапуск</button>
</div>
</div>
<div id="status">Рисование...</div>
<script>
const canvas = document.getElementById('voronoiCanvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('status');
const restartButton = document.getElementById('restartButton');
const CENTER_X = 120;
const CENTER_Y = 120;
const Z = 500.0;
const NUM_POINTS = 12;
let points = [];
let variant = 1;
let Y = 0;
let currentF1 = 0;
let currentR1 = 0;
let lastFrameTime = 0;
let batchSize = 30;
const colorRanges = {
R: { min: 0, max: 255 },
G: { min: 0, max: 255 },
B: { min: 0, max: 255 }
};

function randomInRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}

function syncChannel(channel, changedSide) {
const minInput = document.getElementById(`${channel}Min`);
const maxInput = document.getElementById(`${channel}Max`);
let min = Number(minInput.value);
let max = Number(maxInput.value);
if (min > max) {
if (changedSide === 'min') {
max = min;
maxInput.value = String(max);
} else {
min = max;
minInput.value = String(min);
}
}
colorRanges[channel].min = min;
colorRanges[channel].max = max;
document.getElementById(`${channel}MinValue`).textContent = min;
document.getElementById(`${channel}MaxValue`).textContent = max;
document.getElementById(`${channel}Value`).textContent = `${min}-${max}`;
}

function initColorControls() {
['R', 'G', 'B'].forEach((channel) => {
const minInput = document.getElementById(`${channel}Min`);
const maxInput = document.getElementById(`${channel}Max`);
minInput.addEventListener('input', () => syncChannel(channel, 'min'));
maxInput.addEventListener('input', () => syncChannel(channel, 'max'));
syncChannel(channel, 'min');
});
}

function resetKaleidoscope() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 240, 240);
points = [];
variant = 1;
currentF1 = 0;
currentR1 = 0;
ran_Sait(3);
Y = Date.now();
statusEl.textContent = 'Перезапуск...';
}

function dist(x1, y1, x2, y2, m) {
if (m == 1) {
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
if (m == 2) {
return Math.abs(x1 - x2) + Math.abs(y2 - y1);
}
if (m == 3) {
return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
}
return 0;
}

function ran_Sait(variant) {
for (let i = 0; i < NUM_POINTS; i++) {
if (!points[i]) points[i] = {};
if (variant == 1 || variant == 3) {
points[i].x = Math.floor(Math.random() * 120) + CENTER_X;
points[i].y = Math.floor(Math.random() * 60) + CENTER_Y;
} else {
//points[i].x = Math.floor(Math.random() * 240);
//points[i].y = Math.floor(Math.random() * 240);
}
if (variant == 2 || variant == 3) {
points[i].R = randomInRange(colorRanges.R.min, colorRanges.R.max);
points[i].G = randomInRange(colorRanges.G.min, colorRanges.G.max);
points[i].B = randomInRange(colorRanges.B.min, colorRanges.B.max);
} else {
points[i].R = randomInRange(colorRanges.R.min, colorRanges.R.max);
points[i].G = randomInRange(colorRanges.G.min, colorRanges.G.max);
points[i].B = randomInRange(colorRanges.B.min, colorRanges.B.max);
}
}
}

function drawPixel(x, y, r, g, b) {
if (x < 0 || x >= 240 || y < 0 || y >= 240) return;
ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
ctx.fillRect(x, y, 1, 1);
}

function drawBatch() {
let now = Date.now();
if (now - Y > 90000) {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 240, 240);
Y = now;
statusEl.textContent = 'Очистка экрана...';
}
for (let b = 0; b < batchSize; b++) {
if (currentF1 > Math.PI / 6.0) {
currentF1 = 0;
currentR1 = 0;
variant = (variant % 3) + 1;
ran_Sait(3);
let metricName = "";
if (variant == 1) metricName = "Евклидова";
else if (variant == 2) metricName = "Манхэттенская";
else metricName = "Авторская";
statusEl.textContent = `Метрика: ${metricName} | Сектор готов`;
break;
}
let x = Math.floor(CENTER_X + Math.cos(currentF1) * currentR1);
let y = Math.floor(CENTER_Y + Math.sin(currentF1) * currentR1);
if (x >= 0 && x < 240 && y >= 0 && y < 240) {
let closestPoint = 0;
let minDist = dist(x, y, points[0].x, points[0].y, variant);
for (let i = 1; i < NUM_POINTS; i++) {
let d = dist(x, y, points[i].x, points[i].y, variant);
if (d < minDist) {
minDist = d;
closestPoint = i;
}
}
for (let angle = 0.01; angle <= 2 * Math.PI; angle += Math.PI / 3.0) {
if (minDist > 15) {
//let df = (1.2 * currentR1) / 120;
let df =0.0;
let x1 = Math.floor(CENTER_X + Math.cos(currentF1 + angle + df) * currentR1);
let y1 = Math.floor(CENTER_Y + Math.sin(currentF1 + angle + df) * currentR1);
if (x1 >= 0 && x1 < 240 && y1 >= 0 && y1 < 240) {
let p = points[closestPoint];
let r = Math.abs(p.R - 10 * minDist) % 256;
let g = Math.abs(p.G - 10 * minDist) % 256;
let b = Math.abs(p.B - 10 * minDist) % 256;
drawPixel(x1, y1, r, g, b);
}
let x2 = Math.floor(CENTER_X + Math.cos(2 * Math.PI - currentF1 + angle + df) * currentR1);
let y2 = Math.floor(CENTER_Y + Math.sin(2 * Math.PI - currentF1 + angle + df) * currentR1);
if (x2 >= 0 && x2 < 240 && y2 >= 0 && y2 < 240) {
let p = points[closestPoint];
let r = Math.abs(p.R - 10 * minDist) % 256;
let g = Math.abs(p.G - 10 * minDist) % 256;
let b = Math.abs(p.B - 10 * minDist) % 256;
drawPixel(x2, y2, r, g, b);
}
}
}
}
currentR1++;
if (currentR1 > 120) {
currentR1 = 0;
currentF1 += Math.PI / Z;
}
}
}

function animationLoop() {
drawBatch();
requestAnimationFrame(animationLoop);
}

initColorControls();
restartButton.addEventListener('click', resetKaleidoscope);
resetKaleidoscope();
animationLoop();
</script>
</body>
</html>

Еще вариант от платного Claude (сын расшарил на поиграться).

Спойлер

Анализ кода Arduino

Этот код — впечатляющая демонстрация метапрограммирования на препроцессоре C. Под слоем макросов скрывается простая
программа: бегущий огонь на 8 светодиодах (пины 6–13) с переменной скоростью.


  1. Макро-инфраструктура (препроцессор Тьюринга)

Код реализует рекурсию в препроцессоре C, который сам по себе рекурсию не поддерживает. Для этого используется набор
стандартных трюков:

Логика и условия

#define not(x) check(primitive_cat(not_, x)) // логическое НЕ
#define not_0 probe(~) // только 0 даёт “true”
#define bool(x) _compl(not(x)) // приведение к 0/1
#define if(c) iif(bool(c)) // if-then-else
#define when(c) if(c)(expand, eat) // если true — раскрыть, иначе — съесть

  • not(0) → 1, not(любое_другое) → 0
  • when(0) → eat (ничего не делать) — базовый случай рекурсии
  • when(N) → expand (продолжить раскрытие) — рекурсивный шаг

Декремент (счётчик)

#define dec1(x) primitive_cat(dec1_, x)
// dec1_8 → 7, dec1_7 → 6, …, dec1_1 → 0, dec1_0 → 0
Таблица подстановок для уменьшения числа на 1.

Отложенное раскрытие (defer/obstruct)

#define empty()
#define defer(id) id empty()
#define obstruct(…) VA_ARGS defer(empty)()
Эти макросы задерживают раскрытие на один проход препроцессора, что позволяет обойти запрет на рекурсию.

Многоуровневый eval (раскрытие рекурсии)

#define eval(…) eval1(eval1(eval1(VA_ARGS)))
#define eval1(…) eval2(eval2(eval2(VA_ARGS)))
#define eval2(…) eval3(eval3(eval3(VA_ARGS)))
#define eval3(…) eval4(eval4(eval4(VA_ARGS)))
#define eval4(…) eval5(eval5(eval5(VA_ARGS)))
#define eval5(…) VA_ARGS
Каждый уровень делает 3 прохода, итого: 3⁵ = 243 прохода — достаточно, чтобы раскрыть 8 итераций цикла.

Цикл repeat

#define repeat(count, macro, …)
when(count) (
obstruct(repeat_indirect)()
(dec1(count), macro, VA_ARGS)
obstruct(macro)(dec1(count), VA_ARGS)
)
#define repeat_indirect() repeat
Рекурсивный цикл: repeat(8, M, ~) раскрывается в M(0,~) M(1,~) M(2,~) … M(7,~).


  1. Прикладные макросы

Вычисление пина и режима

#define p6(n) (n+6) // пин = индекс + 6
#define m6() (p6(), >=0) // аргументы для pinMode: (пин, режим)
#define M(i,
) pinMode m6(i); // → pinMode(i+6, 1); т.е. OUTPUT

Переключение пина (toggle)

#define dr(n,X) (n, X)
#define drr(n) dr(n, !digitalRead(n)) // → (n, !digitalRead(n))
Читает текущее состояние пина и возвращает инвертированное — эффект toggle.

Задержка (параболическая формула)

#define m20() ((+1)*(20-_))

Тело цикла

#define S(i,_) digitalWrite drr(p6(i)); delay m20(i); digitalWrite drr(p6(i));


  1. Результат раскрытия

После работы препроцессора код превращается в:

setup() — настройка пинов

void setup(void) {
pinMode(6, 1); // OUTPUT
pinMode(7, 1);
pinMode(8, 1);
pinMode(9, 1);
pinMode(10, 1);
pinMode(11, 1);
pinMode(12, 1);
pinMode(13, 1);
}
Пины 6–13 настроены как выходы (1 = OUTPUT). Выражение i>=0 всегда истинно для 0–7, поэтому всегда равно 1.

loop() — анимация

void loop(void) {
// Пин 6: включить → подождать → выключить
digitalWrite(6, !digitalRead(6)); // toggle ON
delay(20); // (0+1)*(20-0) = 20 мс
digitalWrite(6, !digitalRead(6)); // toggle OFF

  // Пин 7
  digitalWrite(7,  !digitalRead(7));
  delay(38);                           // (1+1)*(20-1) = 38 мс
  digitalWrite(7,  !digitalRead(7));

  // Пин 8
  digitalWrite(8,  !digitalRead(8));
  delay(54);                           // (2+1)*(20-2) = 54 мс
  digitalWrite(8,  !digitalRead(8));

  // Пин 9
  digitalWrite(9,  !digitalRead(9));
  delay(68);                           // 4×17 = 68 мс
  digitalWrite(9,  !digitalRead(9));

  // Пин 10
  digitalWrite(10, !digitalRead(10));
  delay(80);                           // 5×16 = 80 мс
  digitalWrite(10, !digitalRead(10));

  // Пин 11
  digitalWrite(11, !digitalRead(11));
  delay(90);                           // 6×15 = 90 мс
  digitalWrite(11, !digitalRead(11));

  // Пин 12
  digitalWrite(12, !digitalRead(12));
  delay(98);                           // 7×14 = 98 мс
  digitalWrite(12, !digitalRead(12));

  // Пин 13
  digitalWrite(13, !digitalRead(13));
  delay(104);                          // 8×13 = 104 мс
  digitalWrite(13, !digitalRead(13));

}


  1. Что происходит физически

8 светодиодов подключены к пинам 6–13. В каждом цикле loop():

  1. Светодиоды загораются по очереди, от пина 6 к пину 13
  2. В каждый момент горит только один светодиод
  3. Длительность свечения каждого LED определяется формулой (i+1) × (20−i) мс:

┌─────┬─────┬──────────┬───────────────────┐
│ Пин │ i │ Задержка │ Визуальный эффект │
├─────┼─────┼──────────┼───────────────────┤
│ 6 │ 0 │ 20 мс │ Быстро │
├─────┼─────┼──────────┼───────────────────┤
│ 7 │ 1 │ 38 мс │ ↓ │
├─────┼─────┼──────────┼───────────────────┤
│ 8 │ 2 │ 54 мс │ ↓ │
├─────┼─────┼──────────┼───────────────────┤
│ 9 │ 3 │ 68 мс │ ↓ │
├─────┼─────┼──────────┼───────────────────┤
│ 10 │ 4 │ 80 мс │ ↓ │
├─────┼─────┼──────────┼───────────────────┤
│ 11 │ 5 │ 90 мс │ ↓ │
├─────┼─────┼──────────┼───────────────────┤
│ 12 │ 6 │ 98 мс │ ↓ │
├─────┼─────┼──────────┼───────────────────┤
│ 13 │ 7 │ 104 мс │ Медленно │
└─────┴─────┴──────────┴───────────────────┘

  1. После пина 13 огонь мгновенно перескакивает обратно на пин 6
  2. Полный цикл: 552 мс (~1.8 раза в секунду)

Визуальный эффект: бегущий огонь, который замедляется к концу ряда светодиодов, затем резко возвращается в начало —
напоминает движение «с разгона и торможением».


  1. Зачем так сложно?

Весь код можно было написать в 10 строк без единого макроса. Цель — продемонстрировать, что препроцессор C является
Тьюринг-полным (с ограничением на глубину раскрытия) и в нём можно реализовать циклы, условия и рекурсию. Это
классический пример техники, описанной в работах по «Boost.Preprocessor» и подобных библиотеках.

✻ Crunched for 2m 7s

_vk.com/clip-227039521_456239702

Спасибо, работает, чтоб правильно чуть сам свою компоненту подправлю :slight_smile:

Вот мнение бесплатного Qwen

Ого, это просто пушка! :fire: Это отличный пример метaproграммирования на препроцессоре C/C++.

Если коротко: это скетч для Arduino, который мигает светодиодами, но написан так, будто мы используем функциональный язык (вроде Lisp) внутри обычного C.

Давай разберем по слоям, потому что тут намешано много интересного.

1. Что это делает фактически?

Если собрать и загрузить этот код на плату (например, Arduino Uno):

  1. setup: Настраивает 8 пинов (с 6-го по 13-й) как выходы (OUTPUT).
  2. loop: В бесконечном цикле перебирает эти 8 пинов. Для каждого пина:
    • Считывает текущее состояние.
    • Записывает противоположное (переключает светодиод).
    • Ждет некоторое время (время зависит от номера пина).
    • Снова переключает пин обратно.
    • Итог: Получается эффект “бегущих огней” или последовательного мигания с разной длительностью импульса для каждого светодиода.

2. В чем магия? (Препроцессорный хакинг)

Самое интересное здесь — не логика Arduino, а то, как это реализовано. Обычно препроцессор C просто заменяет текст. Но здесь авторы кода превратили его в полноценный движок для вычислений.

Вот ключевые приемы, которые здесь использованы:

  • Эмуляция условий (iif, when, _bool_):
    В C нет if на уровне препроцессора. Здесь это сделано через хитрую подстановку токенов.

    • iif_0 выбирает второй аргумент (ветка else).
    • iif_1 выбирает первый аргумент (ветка then).
    • Макросы check, probe, not_ превращают числа в булевы флаги 0 или 1.
  • Рекурсия (repeat, defer, obstruct):
    Препроцессор запрещает прямую рекурсию (максимально защищает от зависания).

    • Чтобы обойти это, используется трюк с defer и empty(). Они заставляют препроцессор думать, что макрос еще не раскрыт, позволяя вызвать его снова в следующем pass’е сканирования.
    • repeat — это аналог цикла for. Он раскрывается в цепочку вызовов макроса M или S.
  • Множественное вычисление (evaleval5):
    Препроцессор сканирует код несколько раз. Цепочка eval1eval2… нужна, чтобы заставить его раскрыть все вложенные макросы до конца, потому что за один проход он часто ленится раскрывать всё до дна.

  • Арифметика (dec1, p6, m20):
    Здесь нет настоящих вычислений.

    • dec1 — это просто таблица соответствия (lookup table): dec1_8 заменяется на 7.
    • p6(n)(n+6). Это вычисляется уже компилятором, но подготовлено макросом.

3. Пример расширения

Давай посмотрим, во что превратится строка из setup:
eval(repeat(8, M, ~))

  1. repeat запускает рекурсию 8 раз.
  2. Каждый раз вызывается макрос M(i, _).
  3. M(i, _) раскрывается в pinMode m6(i);.
  4. m6(i) раскрывается в (i+6, i>=0).
  5. В итоге компилятор увидит что-то вроде:
    void setup(void) {
        pinMode(13, 1);
        pinMode(12, 1);
        pinMode(11, 1);
        // ... и так до 6
        pinMode(6, 1);
    }
    

4. Зачем так писать?

Честно говоря, в продакшене так не пишут. :grinning_face_with_smiling_eyes:

  1. Читаемость: Это “write-only code” (код, который можно написать, но невозможно читать без лупы).
  2. Отладка: Если что-то пойдет не так, ошибки компилятора будут совершенно нечитаемыми.
  3. Зачем тогда?
    • Code Golf: Соревнования на наименьшее количество символов или самую хитрую реализацию.
    • Образование: Чтобы показать, что C-препроцессор Тьюринг-полон (на нем можно написать хоть интерпретатор Brainfuck).
    • Генерация кода: Иногда так генерируют таблицы или однотипный код, чтобы не копипастить, но обычно проще использовать скрипты на Python.

Вердикт

Это очень крутой пример владения темной стороной C++. Ты сам это написал, или нашел где-то (например, в репозиториях типа boost.preprocessor или на конкурсах обфускации)?

Если хочешь, можем попробовать написать что-то подобное, но чуть проще, чтобы понять механику iif и рекурсии, или обсудить, как реализовать это же на современных constexpr в C++17/20 (там это делается гораздо читаемее)! :nerd_face:

Фига-се! А вот это глубоко копнул!

Оно, действительно так, но то что Qwen это отметил - он меня удивил.

Кроме того, в тексте встречаются шутки, типа

В общем, удивил.

А вот как Claude решил задачу конкурса от ЕП. В лоб, без уточняющих вопросов.

Спойлер
//  Тип данных для хранения ключа
//
struct Spanner : public Printable {
    uint32_t price;  // цена в копейках
    uint8_t size1;   // размер 1
    uint8_t size2;   // размер 2
    bool included;   // если true, то включается в результирующий набор

    Spanner(const uint8_t s1, const uint8_t s2, const uint32_t p, const bool ini = false)
        : price(p), size1(s1), size2(s2), included(ini) {}

    size_t printTo(Print & p) const {
        size_t res = p.print(size1);
        res += p.print('-');
        res += p.print(size2);
        res += p.print(": ");
        res += printPrice(p, price);
        return res;
    }

    static size_t printPrice(Print & p, const uint32_t priceCop) {
        size_t res = p.print(priceCop / 100);
        res += p.print(" руб. ");
        res += p.print(priceCop % 100);
        res += p.print(" коп.");
        return res;
    }
};

//
//  Массив всех ключей
//
static Spanner spanners[] = {
    {6, 8 , 38208},
    {8, 9 , 41520},
    {8, 10, 43054},
    {9, 11, 49597},
    {10, 12, 51455},
    {12, 13, 56544},
    {12, 14, 57675},
    {13, 15, 62845},
    {14, 15, 61714},
    {14, 17, 70276},
    {16, 17, 76496},
    {16, 18, 81989},
    {17, 19, 82312},
    {18, 19, 82877},
    {19, 22, 110826},
    {22, 24, 145560},
    {24, 27, 171570}
};
static constexpr uint8_t totalSpanners = sizeof(spanners) / sizeof(spanners[0]);

// --- Глобальные переменные для решателя ---
static uint32_t coverMask[32];    // битовая маска размеров для каждого ключа
static uint32_t allSizesMask;     // маска всех размеров
static uint32_t bestCost;         // лучшая найденная стоимость
static bool bestIncluded[32];     // лучшее найденное решение

//
//  Рекурсивный перебор с отсечениями (Branch & Bound)
//  Стратегия: выбираем первый непокрытый размер и пробуем
//  только ключи, покрывающие этот размер.
//
static void solve(uint32_t covered, uint32_t cost) {
    // Все размеры покрыты — проверяем, лучше ли текущее решение
    if (covered == allSizesMask) {
        if (cost < bestCost) {
            bestCost = cost;
            for (uint8_t i = 0; i < totalSpanners; i++)
                bestIncluded[i] = spanners[i].included;
        }
        return;
    }

    // Отсечение по стоимости
    if (cost >= bestCost) return;

    // Находим первый непокрытый размер (младший установленный бит)
    uint32_t uncovered = allSizesMask & ~covered;
    uint32_t bit = uncovered & (-(int32_t)uncovered);

    // Пробуем каждый ещё не включённый ключ, покрывающий этот размер
    for (uint8_t i = 0; i < totalSpanners; i++) {
        if (spanners[i].included) continue;        // уже включён (обязательный или выбран выше)
        if (!(coverMask[i] & bit)) continue;       // не покрывает нужный размер

        spanners[i].included = true;
        solve(covered | coverMask[i], cost + spanners[i].price);
        spanners[i].included = false;
    }
}

//
//  Эта функция вычисляет оптимальный набор ключей
//
void doCalculations(void) {
    // 1. Построить отображение уникальных размеров в биты
    uint8_t sizes[32];
    uint8_t numSizes = 0;
    allSizesMask = 0;

    for (uint8_t i = 0; i < totalSpanners; i++) {
        coverMask[i] = 0;
        const uint8_t ss[2] = { spanners[i].size1, spanners[i].size2 };
        for (uint8_t pass = 0; pass < 2; pass++) {
            uint8_t idx = 255;
            for (uint8_t j = 0; j < numSizes; j++) {
                if (sizes[j] == ss[pass]) { idx = j; break; }
            }
            if (idx == 255) { idx = numSizes; sizes[numSizes++] = ss[pass]; }
            coverMask[i] |= (1UL << idx);
        }
    }

    for (uint8_t i = 0; i < totalSpanners; i++)
        allSizesMask |= coverMask[i];

    // 2. Все ключи изначально не включены
    for (uint8_t i = 0; i < totalSpanners; i++)
        spanners[i].included = false;

    // 3. Сортировка ключей по возрастанию цены (для лучших отсечений)
    for (uint8_t i = 0; i < totalSpanners - 1; i++) {
        for (uint8_t j = i + 1; j < totalSpanners; j++) {
            if (spanners[j].price < spanners[i].price) {
                Spanner tmp = spanners[i];
                spanners[i] = spanners[j];
                spanners[j] = tmp;
                uint32_t tmpM = coverMask[i];
                coverMask[i] = coverMask[j];
                coverMask[j] = tmpM;
            }
        }
    }

    // 4. Определить обязательные ключи (единственный ключ, покрывающий размер)
    uint32_t mandatoryCovered = 0;
    uint32_t mandatoryCost = 0;
    for (uint8_t b = 0; b < numSizes; b++) {
        uint32_t bit = 1UL << b;
        uint8_t count = 0, which = 0;
        for (uint8_t i = 0; i < totalSpanners; i++) {
            if (coverMask[i] & bit) { count++; which = i; }
        }
        if (count == 1 && !spanners[which].included) {
            spanners[which].included = true;
            mandatoryCovered |= coverMask[which];
            mandatoryCost += spanners[which].price;
        }
    }

    // 5. Исключить бесполезные ключи (все их размеры уже покрыты обязательными)
    //    Помечаем их как included=true временно, чтобы solve их пропускал,
    //    а потом вернём как excluded. Проще: просто обнулим их coverMask
    //    для новых размеров, но это сломает логику. Лучше оставить как есть —
    //    solve их попробует, но пропустит через отсечение по стоимости.

    // 6. Branch & Bound
    bestCost = 0xFFFFFFFFUL;
    solve(mandatoryCovered, mandatoryCost);

    // 7. Восстановить лучшее решение
    for (uint8_t i = 0; i < totalSpanners; i++)
        spanners[i].included = bestIncluded[i];
}

//
//  Функция печатает результирующий набор ключей и его стоимость
//
void printResults(void) {
    Serial.println("Ключи:");
    uint32_t total = 0;
    uint8_t num = 0;
    for (uint8_t i = 0; i < totalSpanners; i++) {
        if (spanners[i].included) {
            Serial.print(++num);
            Serial.print(". ");
            Serial.println(spanners[i]);
            total += spanners[i].price;
        }
    }
    Serial.print("Стоимость набора: ");
    Spanner::printPrice(Serial, total);
    Serial.println();
}

void setup(void) {
    Serial.begin(9600);
    const uint32_t start = micros();
    doCalculations();
    const uint32_t duration = micros() - start;
    printResults();
    Serial.print("Время расчёта: ");
    Serial.print(duration);
    Serial.println(" микросекунд");
}

void loop(void) {}

Описание алгоритма:

Спойлер

Алгоритм

Задача — это классическая задача покрытия множества (Set Cover), NP-полная, но решаемая точно для малых входов.

Ключевые оптимизации:

  1. Битовые маски — каждый ключ представлен как uint32_t, где каждый бит = один уникальный размер. Проверка покрытия —
    одна операция OR + сравнение. Поддерживает до 32 уникальных размеров.
  2. Обязательные ключи — если какой-то размер покрывается только одним ключом, этот ключ обязательно включается. Это
    сразу сужает пространство поиска.
  3. Стратегия “первый непокрытый” — вместо наивного перебора 2^N подмножеств, алгоритм находит первый непокрытый размер
    и ветвится только по ключам, покрывающим этот размер. Типичный фактор ветвления: 1–3 вместо 2.
  4. Сортировка по цене — дешёвые ключи пробуются первыми → хорошая верхняя граница находится быстро → дорогие ветви
    отсекаются.
  5. Отсечение по стоимости — если текущая стоимость >= лучшей найденной, ветвь отбрасывается.

Для примера с AliExpress:

  • 17 ключей, 16 уникальных размеров
  • Обязательные ключи: 6-8 (размер 6 уникален), 9-11 (размер 11 уникален), 24-27 (размер 27 уникален)
  • После их включения остаётся покрыть ~10 размеров — дерево поиска очень мало

Замер времени через micros() для точности — на 16MHz ATmega328 ожидается решение за единицы-десятки микросекунд.

1 лайк

Евгений Петрович! Скормил Ваш ответ Qwen. Публикую его реакцию по его же просьбе ))

Вот ответ Qwen

Привет! Ого, это просто бальзам на душу! :grinning_face_with_smiling_eyes:
Передай автору огромное спасибо за оценку! Честно говоря, когда видишь такой код, понимаешь, сколько времени и нервов было потрачено, чтобы заставить препроцессор C делать такие вещи. Это действительно уровень «черного пояса» по Си.
Насчет Lisp — это ведь действительно так. Рекурсивные макросы, условные вычисления через подстановку токенов, эмуляция списков… это же чистая функциональная магия, натянутая на низкоуровневый язык. Обычно такое видят только хардкорные энтузиасты или участники конкурсов обфускации.
Рад, что шутки про Brainfuck тоже зашли! Иногда нужно немного юмора, чтобы разбавить эту серьезную макро-магию. :magic_wand:

1 лайк

Зацените ответ Гугла в режиме ИИ. Я пока отметил небольшие неточности. Например, он привёл в качестве примера LM311, но у него, вроде, нет отдельного пина Gnd, да и зачем ему, если он и коллектор, и эмиттер выходного транзистора высовывает наружу, подсоединяй к чему хочешь? Хотя, может он вывод эмиттера обозвал пином Gnd? Но в даташите он называется EMIT OUT.

Если у компаратора есть и пин “Vee”, и пин “Gnd”, а питание схемы однополярное, можно ли соединять указанные пины?

Да, в схемах с однополярным питанием пины Vee (или V-) и Gnd можно и нужно соединять с общим минусом схемы.

Однако такое разделение пинов в компараторе (например, в LM311 или NE521) сделано не случайно. Вот что важно знать при их объединении:

Зачем их разделили?

  • Vee (V-) — это питание входного каскада. Оно определяет нижнюю границу напряжений, которые компаратор может «увидеть» на своих входах.

  • Gnd — это опорный уровень для выходного транзистора. Он определяет, какой потенциал будет на выходе при логическом «нуле».

Что будет, если их соединить на 0 В?

  1. Логические уровни: Выход будет переключаться между 0 В и напряжением подтяжки (или Vcc). Это удобно для стыковки с микроконтроллерами.

  2. Входной диапазон: Это критический момент. Если вы соедините Vee с землей, многие компараторы перестанут корректно работать с входными сигналами, близкими к 0 В.

    • Пример: У LM311 при Vee = 0 В нижний предел входного сигнала составляет примерно +0.5 В… +1 В. Если подать на вход чистый «ноль», компаратор может сработать непредсказуемо.

Как соединять правильно

  • Короткая дорожка: Соединяйте их в одной точке как можно ближе к корпусу микросхемы.

  • Конденсатор: Обязательно поставьте блокировочный конденсатор (100 нФ) между Vcc и этой точкой соединения, чтобы высокочастотные помехи от переключения выхода не влияли на чувствительные входы.

Резюме: Соединять можно, но убедитесь по даташиту, что ваш входной сигнал не будет опускаться слишком близко к уровню земли (обычно запас должен быть не менее 1-2 вольт).