CelAut v3

Материал из Department of Theoretical and Applied Mechanics
Перейти к: навигация, поиск
Виртуальная лаборатория > Игра "Жизнь" > Клеточный автомат - версии > CelAut v3

Справа вы видите легенду геномов, а также можете выбрать, какие клетки вы хотите "рисовать" мышкой.

Скачать программу: CelAut_v3_release.zip Текст программы на языке JavaScript (разработчик Цветков Денис): <toggledisplay status=hide showtext="Показать↓" hidetext="Скрыть↑" linkstyle="font-size:default"> Файл "CelAut_v3_release.js"

function Main_CelAut(canvas) {

    // Предварительные установки

    var context = canvas.getContext("2d");                  // на context происходит рисование
    canvas.oncontextmenu = function() {return false;};      // блокировка контекстного меню
    canvas.onselectstart = function() {return false;};      // запрет выделения canvas

    // *** Задание физических параметров ***

    var mutate_chance = 1 / 50;     // шанс клетки мутировать при рождении

    // *** Задание вычислительных параметров ***

    var fps = 5;             	    // frames per second - число кадров в секунду

    // Выполнение программы

    var w = canvas.width;		    // ширина окна
    var h = canvas.height;		// высота окна
    var N = 50;                   // количество клеток по горизонтали (желательно, делитель ширины окна)
    var M = 50;                   // количество клеток по горизонтали (желательно, делитель высоты окна)
    var cell_w = w / N;           // ширина клетки
    var cell_h = h / M;           // высота клетки

    var pause = true;               // остановлен ли клеточный автомат
    var interval_ID;                // для управления работой автомата

    // "Жизнь" Конвея: [B = 000100000, L = 001100000]
    var B = "001000000";          // геном рождения
    var   L = "000001100";          // геном выживания

    this.set_L = function(n) {      // присваеваем нужный геном (n - положение первой единицы в геноме)
        while (L[0] != '1') L = L.substring(1) + "0";
        for (var i = 0; i < n; i++) L = "0" + L.substring(0, L.length - 1);
    };

    // Работа с мышью

    canvas.onmousedown = function(e){           // функция при нажатии клавиши мыши
        var life;
        if (e.which == 1) life = true;          // при нажатии левой клавиши мыши клетка рождается
        else if (e.which == 3) life = false;    // при нажатии правой клавиши мыши клетка умирает
        else return;

        set_cell(e, life, B, L);
        canvas.onmousemove = function(e) {set_cell(e, life, B, L);};   // функция, выполняющаяся при перемещении курсора мыши
    };

    document.onmouseup = function(e){           // функция при отпускании клавиши мыши
        canvas.onmousemove = null;              // когда клавиша отпущена - функции перемещения нету
    };

    function set_cell(e, is_alive, B, L){       // придать клетке определенное состояние с нажатия клавиши мыши
        var m = mouse_coords(e);                // обновить координаты в переменных mouseX, mouseY
        if (m.x < 0 || m.x >= w || m.y < 0 || m.y >= h) return;         // проверка на ошибочные координаты
        var i = Math.floor(m.x / cell_w);       // получаем ячейку по горизонтали
        var j = Math.floor(m.y / cell_h);       // получаем ячейку по вертикали
        // везде прибавляем 1 - из за периодических условий массив сдвинут на 1
        cells[i + 1][j + 1].alive = is_alive;
        cells[i + 1][j + 1].B = B;
        cells[i + 1][j + 1].L = L;
        draw();
    }

    function mouse_coords(e) {                  // функция возвращает экранные координаты курсора мыши
        var m = [];
        var rect = canvas.getBoundingClientRect();
        m.x = e.clientX - rect.left;
        m.y = e.clientY - rect.top;
        return m;
    }

    // Работа с массивом

    var cells;                                          // массив клеток
    var cells_buf = [];                                 // буфер для расчета следующего шага
    for (var i1 = 0; i1 < N + 2; i1++) {
        cells_buf[i1] = [];
        for (var j1 = 0; j1 < M + 2; j1++) {
            cells_buf[i1][j1] = [];
        }
    }
    function generate_random_field() {              // каждая клетка жива с вероятностью 50%
        cells = [];
        var i, j;
        for (i = 0; i < N + 2; i++) {
            cells[i] = [];
            for (j = 0; j < M + 2; j++) {
                if (i != 0 && i != (N + 1) && j != 0 && j != (M + 1))
                    cells[i][j] = {B:B, L:L, alive:Math.random() >= 0.5};
                else
                    cells[i][j] = [];
            }
        }
    }

    this.clear = function() {                           // очистить поле
        for (var i = 1; i < N + 1; i++)
            for (var j = 1; j < M + 1; j++)
                cells[i][j].alive = false;
        draw();
        stop_system();
    };

    // Управление работой автомата

    function step() {
        tick();
        draw();
    }

    this.change_pause_state = function() {              // кнопка паузы
        if (!pause) stop_system();
        else start_system()
    };

    this.next_step = function(){                        // кнопка "Следующий шаг"
        stop_system();
        step();
    };

    function start_system() {
        pause = false;
        interval_ID = setInterval(step, 1000/fps);
        document.getElementById('pause').value = "Остановить";
    }

    function stop_system() {
        pause = true;
        clearInterval(interval_ID);
        document.getElementById('pause').value = "Запустить";
    }

    // Расчетная часть программы

    function tick() {                            // то, что происходит каждый шаг времени

        // периодичность - копируем боковые клетки
        for (i = 1; i < N + 1; i++) {
            cells[i][0].B = cells[i][M].B;      cells[i][0].L = cells[i][M].L;      cells[i][0].alive = cells[i][M].alive;
            cells[i][M + 1].B = cells[i][1].B;  cells[i][M + 1].L = cells[i][1].L;  cells[i][M + 1].alive = cells[i][1].alive;
        }
        for (j = 0; j < M + 2; j++) {
            cells[0][j].B = cells[N][j].B;      cells[0][j].L = cells[N][j].L;      cells[0][j].alive = cells[N][j].alive;
            cells[N + 1][j].B = cells[1][j].B;  cells[N + 1][j].L = cells[1][j].L;  cells[N + 1][j].alive = cells[1][j].alive;
        }

        // цикл по всем клеткам
        for (var i = 1; i < N + 1; i++) {
            for (var j = 1; j < M + 1; j++) {
                // подсчет количества живых клеток вокруг рассматриваемой клетки
                var near = [];
                var cell;
                // сначала содержимое cells[][] присваевается переменной cell
                // потом проверяется cell.alive, и если оно true - клетка добавляется в список живых клеток
                if ((cell = cells[i - 1]    [j - 1] ).alive)   near.push(cell);
                if ((cell = cells[i - 1]    [j]     ).alive)   near.push(cell);
                if ((cell = cells[i - 1]    [j + 1] ).alive)   near.push(cell);
                if ((cell = cells[i]        [j - 1] ).alive)   near.push(cell);
                if ((cell = cells[i]        [j + 1] ).alive)   near.push(cell);
                if ((cell = cells[i + 1]    [j - 1] ).alive)   near.push(cell);
                if ((cell = cells[i + 1]    [j]     ).alive)   near.push(cell);
                if ((cell = cells[i + 1]    [j + 1] ).alive)   near.push(cell);

                if (cells[i][j].alive) {                            // рассматриваемая клетка жива
                    if (cells[i][j].L[near.length] == '1') {        // проверка условия выживания по биному L
                        cells_buf[i][j].alive = true;
                        cells_buf[i][j].B = cells[i][j].B;
                        cells_buf[i][j].L = cells[i][j].L;
                    } else {
                        cells_buf[i][j].alive = false;
                    }
                } else {                                            // рассматриваемая клетка мертва
                    //выясним, сколько видов удовлетворяют условиям рождения новой клетки
                    var good_types = [];
                    for (var k = 0; k < near.length; k++) {
                        if (near[k].B[near.length] != "1") continue;
                        var bad_type = false;
                        for (var gt = 0; gt < good_types.length; gt++) {
                            if (good_types[gt].B == near[k].B && good_types[gt].L == near[k].L) bad_type = true;
                        }
                        if (!bad_type) good_types.push(near[k]);
                    }

                    if (good_types.length > 0) {                    // видов, условия которых удовлетворены - больше 0
                        // если нужных видов больше одного - выбираем случайно
                        var type = good_types[Math.floor(Math.random() * good_types.length)];

                        // здесь происходит мутация
                        if (Math.random() < mutate_chance) cells_buf[i][j].L = genome_shift(type.L);
                        else cells_buf[i][j].L = type.L;
                        cells_buf[i][j].B = type.B;
                        cells_buf[i][j].alive = true;
                    } else {
                        cells_buf[i][j].alive = false;
                    }
                }
            }
        }

        // копирование посчитанных значений следующего шага (cells_buf) в cells
        for (var i0 = 1; i0 < N + 1; i0++) {
            for (var j0 = 1; j0 < M + 1; j0++) {
                cells[i0][j0].B = cells_buf[i0][j0].B;
                cells[i0][j0].L = cells_buf[i0][j0].L;
                cells[i0][j0].alive = cells_buf[i0][j0].alive;
            }
        }
    }

    // функция сдвига. Принимает строку вида 001110000 (количество единиц любое), и случайно сдвигает единицы влево/вправо
    // Пример_1: 01100 -> 00110 или 01100 -> 11000
    // Пример_2: 00001 -> 00010 (в другую сторону сдвига быть не может)
    function genome_shift(genome) {
        var new_genome = genome;
        // если первый или последний элемент генома - единичка, сдвиг может быть произведен только в одну сторону с шансом 50%
        if (Math.random() >= 0.5) {
            if (genome[0] != '1') new_genome = genome.substring(1) + "0";
        } else {
            if (genome[genome.length - 1] != '1') new_genome = "0" + genome.substring(0, genome.length - 1)
        }
        return new_genome;
    }

    // Рисование

    var colors = ["#ff0000", "#ffaa00", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#7f00ff", "#ff40ff", "#7f0000"];

    function draw(){
        context.fillStyle = "#000000";              // цвет клетки
        context.fillRect(0, 0, w, h);               // очистить экран
        for (var i = 1; i < N + 1; i++) {
            for (var j = 1; j < M + 1; j++) {
                if (cells[i][j].alive) {
                    context.fillStyle = colors[cells[i][j].L.indexOf("1")];  // цвет клетки
                    context.beginPath();
                    context.rect((i - 1) * cell_w, (j - 1) * cell_h, cell_w, cell_h);
                    context.closePath();
                    context.fill();
                }
            }
        }
    }

    // Интерфейс

    // запишем все возможные состояния генома L (все возможные мутации)
    var L_test = L;
    while (L_test[0] != '1') L_test = L_test.substring(1) + "0";        // сдвинем единицы в геноме максимально влево
    var variants = [L_test];
    while (L_test[L_test.length - 1] != '1') {                          // теперь сдвигаем единицы вправо и запоминаем вариант
        L_test = "0" + L_test.substring(0, L_test.length - 1);
        variants.push(L_test);
    }

    // нарисуем таблицу геномов
    for (var i0 = 0; i0 < variants.length; i0++) {
        var tr = document.createElement('tr');
        tr.innerHTML =
            "<td><input type='radio' id='radio_" + i0 +"' name='colorTable' onclick='app.set_L(" + i0 + ")'/></td>" +
            "<td style='width:30px; height:30px; background-color:" + colors[i0] + "; border: 1px solid black;'></td>" +
            "<td>" + variants[i0] +  "</td>";
        document.getElementById("type_table").appendChild(tr);
    }

    // Запуск системы

    generate_random_field();                    // сгенероровать поле
    start_system();

    document.getElementById("radio_0").checked = true;
    this.set_L(0);                              // после запуска системы, т.к. изменяет переменную L
}

Файл "CelAut_v3_release.html"

<!DOCTYPE html>
<html>
<head>
    <title>Cellular automaton</title>
    <script src="CelAut_v3_release.js"></script>
</head>
<body>
    <table>
        <tr>
            <td><canvas id="canvas_CelAut" width="600" height="600" style="border:1px solid #000000;"></canvas></td>
            <td valign="top"><table id="type_table"></table></td>
        </tr><tr>
            <td><input id="pause" type="button" name="" style="width: 100px" onclick="app.change_pause_state();return false;"/>
            <input type="button" name="" onclick="app.next_step();return false;" value="Следующий шаг"/>
            <input type="button" name="" onclick="app.clear();return false;" value="Очистить поле"/></td>
        </tr>
    </table>

    <script type="text/javascript">var app = new Main_CelAut(document.getElementById('canvas_CelAut'));</script>
</body>
</html>

</toggledisplay>