Лес - моделируемая экосистема

19

НОТА

Эта проблема была взята из этой ветки reddit (оповещение о спойлере!), И я настроил ее так, чтобы она соответствовала формату этого сайта. Весь кредит переходит к пользователю Reddit "Coder_d00d".

В этой задаче мы будем моделировать лес.

Для этого смоделированного леса мы будем иметь дело с 3 аспектами.

  • Деревья, которые могут быть Саженцем, Древом или Старшим Деревом.
  • Дровосеки (он рубит деревья, обедает и идет на лаву)
  • Медведи (он травит дровосеков, которые пахнут блинами)

Предупреждение: эти правила, скорее всего, не идеальны. Рассматривайте их как ориентир, и если вам нужно немного подправить что-нибудь хорошее (нерестовые потоки были указаны как проблема, см. Ответ kuroi neko как пример этого).

Цикл времени:

Симуляция будет симулировать по месяцам. Вы будете продвигаться вперед во времени с «галочкой». Каждый «тик» представляет месяц. Каждые 12 «галочек» представляют год. Наш лес изменится и будет постоянно меняться. Мы запишем прогресс нашего леса и проанализируем, что с ним происходит.

Лес:

Лес будет двухмерным лесом. Нам потребуется ввод N для представления размера леса в сетке размером N x N. В каждом месте вы можете держать деревья, медведи или дровосеки. Они могут занимать одно и то же место, но часто события происходят, когда они занимают одно и то же место.

Наш лес будет порожден случайным образом в зависимости от размера. Например, если ваше значение N = 10. У вас будет 10 на 10 лесов и 100 мест.

  • 10% леса проведут дровосека в 10 случайных местах. (используя наш 100 точечный лес это должно быть 10 лесорубов)
  • 50% леса будут содержать деревья (деревья могут быть одного из 3-х видов и начинаются как середина дерева) в случайных местах.
  • 2% леса будут содержать медведей.

Как вы получите размер леса, зависит от вас (читайте из стандартного файла, файла или жестко запишите его). Я бы порекомендовал оставить N как 5 или выше. Небольшие леса не очень весело.

События:

Во время симуляции будут происходить события. События происходят на основе некоторой логики, которую я объясню ниже. Я опишу события ниже в каждом описании 3 элементов нашего леса.

События следуют за порядком деревьев сначала, лесорубами вторыми и медведями последними.

Деревья:

  • Каждый месяц у Дерева есть 10% шанс порождать новый «Саженец». В случайном открытом пространстве рядом с деревом у вас есть 10% шанс создать «Саженец».

  • Например, у дерева посреди леса вокруг него есть еще 8 пятен. Один из них (если они пусты) станет «Саженцем».

  • После 12 месяцев существования «Саженец» будет обновлен до «Дерева». «Саженец» не может порождать другие деревья, пока не превратится в «Дерево».

  • Как только "Саженец" становится деревом, он может порождать другие новые "Саженцы".

  • Когда «Дерево» существует в течение 120 месяцев (10 лет), оно становится «Древним деревом».

  • У Старших Деревьев с вероятностью 20% появляется новый «Саженец» вместо 10%.

  • Если рядом с деревом или старшим деревом нет открытых соседних мест, новые деревья не появятся.

Лесорубы:

Дровосеки рубят деревья, они прыгают и прыгают, им нравится давить полевые цветы.

  • Каждый месяц дровосеки будут бродить. Они будут перемещаться до 3 раз в случайно выбранную точку, смежную в любом направлении. Так, например, у Дровосека в середине вашей сетки есть 8 мест для перемещения. Он будет бродить в случайном месте. Тогда снова. И, наконец, в третий раз. NB: Это может быть любое место (так что они могут ходить в медведей, в результате чего мол).

  • Когда дровосек перемещается, если он встречает Дерево (не саженец), он останавливается, и его странствия на этот месяц заканчиваются. Затем он соберет дерево для пиломатериалов. Удалить дерево. Получите 1 кусок пиломатериалов.

  • Дровосеки не будут собирать «Саженцы».

  • Lumberacks также собирают Древние Деревья. Древние деревья стоят 2 штуки.

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

Каждые 12 месяцев количество заготовленной древесины сравнивается с количеством лесорубов в лесу.

  • Если количество собранных пиломатериалов равно или превышает количество лесорубов в лесу, то ряд новых лесорубов нанимаются и случайным образом появляются в лесу.

  • Определите количество лесорубов, которые можно взять на прокат: floor(lumber_collected / number_of_lumberjacks)

  • Однако, если после 12-месячного промежутка количество собранных пиломатериалов меньше количества лесорубов, то лесоруб отпускают, чтобы сэкономить деньги, и 1 случайный лесоруб удаляется из леса. Обратите внимание, что вы никогда не будете снижать свою рабочую силу дровосека ниже 0.

Медведи:

Медведи бродят по лесу, как дровосек. Однако вместо 3 пробелов Медведь будет бродить до 5 пробелов.

  • Если медведь встретит дровосека, он прекратит свое блуждание на месяц. (Например, после 2 ходов медведь приземляется на место с дровосеком, он не будет делать больше ходов в этом месяце)

  • Дровосеки пахнут блинами. Медведи любят блины. Поэтому Медведь, к сожалению, будет травмировать и повредить дровосека. Дровосека уберут из леса (по средам он будет ходить домой и делать покупки, а чай с маслом намазать маслом).

  • Мы будем отслеживать это как несчастный случай "Мол".

  • Обратите внимание, что популяция дровосека никогда не может опуститься ниже 1 - поэтому, если последний дровосек был размазан, просто возьмите другого.

Отслеживание Мол:

  • В течение 12 месяцев, если будет 0 несчастных случаев, связанных с «Молом», популяция медведей увеличится на 1. В случае несчастных случаев, связанных с «Молом», лесорубы наймут зоопарк, чтобы поймать и забрать медведя. Удалить 1 случайного медведя. Обратите внимание, что если ваша популяция медведей достигнет 0 медведей, то в следующем году не произойдет несчастных случаев "Мол", поэтому в следующем году вы получите 1 нового медведя.

  • Если в лесу есть только один лесоруб, и он получает Мол, он будет отправлен домой, но новый будет немедленно нанят и найден где-то еще в лесу. Популяция дровосека никогда не может опуститься ниже 1.

Время:

Моделирование происходит в течение 4800 месяцев (400 лет) или до тех пор, пока не появятся Саженцы, Деревья или Старшие Деревья.

Выход:

Каждый месяц вы будете распечатывать карту леса - возможно, используя карту ASCII или используя графику и цвета.

Дополнительные опции

  • Вы можете выводить популяции деревьев, дровосеков и медведей каждый тик.
  • Вы можете выводить всякий раз, когда происходит событие (например: «Медведь испортил дровосека».)

счет

Это конкурс популярности, поэтому большинство побед выигрывает!

РЕДАКТИРОВАТЬ - Люди указали на ряд недостатков в моих правилах, и, хотя вы можете свободно задавать мне вопросы, также можно немного изменить правила в соответствии с вашей собственной программой или ее интерпретацией.

Джеймс Уильямс
источник
О: Note that you will never reduce your Lumberjack labor force below 0в пункте 3 списка разделов дровосека, возможно, измените его на 1, чтобы соответствовать тому, что вы упоминаете в разделе медведя?
Теун Пронк
Хороший вопрос, отредактируем это сейчас.
Джеймс Уильямс
2
Можете ли вы обсудить сроки движения? Это "деревья двигаются / продвигаются", затем "лесорубы двигаются", затем "медведи двигаются"? Кроме того, медведи и деревья могут занимать те же самые места?
durron597
1
Что произойдет, если два медведя оба зайдут в одно и то же место? А как насчет двух лесорубов? Могут ли они быть в одном месте?
Джерри Иеремия
1
Он идентичен reddit.com/r/dailyprogrammer/comments/27h53e/… Вы должны отдать ему должное - по крайней мере, так, чтобы люди могли найти другие интересные решения.

Ответы:

15

Javascript + HTML - попробуйте

Обновлено в соответствии с популярным запросом

лес и граф

Общее поведение

Программа теперь несколько интерактивная.
Исходный код полностью параметризован, поэтому вы можете настроить еще несколько внутренних параметров в своем любимом текстовом редакторе.

Вы можете изменить размер леса.
Минимум 2 требуется, чтобы иметь достаточно места для размещения дерева, дровосека и медведя в 3 разных местах, а максимальное значение произвольно устанавливается равным 100 (что приведет к ползанию вашего среднего компьютера).

Вы также можете изменить скорость симуляции.
Дисплей обновляется каждые 20 мс, поэтому больший шаг по времени приведет к более точной анимации.

Кнопки позволяют остановить / запустить симуляцию или запустить ее в течение месяца или года.

Движение жителей леса теперь несколько оживлено. Также вырисовываются события рубки и вырубки деревьев.

Журнал некоторых событий также отображается. Еще несколько сообщений доступны, если вы измените уровень многословия, но это затопит вас уведомлениями «Боб режет еще одно дерево».
Я бы предпочел не делать этого на твоем месте, но я не так, так что ...

Рядом с игровой площадкой нарисован набор автоматически масштабируемой графики:

  • популяции медведей и дровосеков
  • общее количество деревьев, разделенных на саженцы, зрелые и старшие деревья

Легенда также отображает текущее количество каждого товара.

Стабильность системы

Графики показывают, что начальные условия не масштабируются так изящно. Если лес слишком велик, слишком много медведей уничтожают популяцию дровосеков, пока достаточное количество любителей блин не окажется за решеткой. Это вызывает первоначальный взрыв старших деревьев, что, в свою очередь, помогает вырастить популяции лесорубов.

Кажется, 15 - это минимальный размер для выживания леса. Лес размером 10 обычно разрушается через несколько сотен лет. Любой размер выше 30 создаст карту, почти полную деревьев. Между 15 и 30 вы можете видеть, что популяция деревьев значительно колеблется.

Некоторые дискуссионные правила

В комментариях к оригинальному сообщению кажется, что разные двуногие не должны занимать одно и то же место. Это как-то противоречит правилу о том, как быдло бродит в блин-любитель.
Во всяком случае, я не следовал этому руководству. Любая лесная клетка может содержать любое количество ингибиторов (и ровно ноль или одно дерево). Это может иметь некоторые последствия для эффективности дровосека: я подозреваю, что это позволяет им легче копаться в пучине старших деревьев. Что касается медведей, я не ожидаю, что это будет иметь большое значение.

Я также предпочел, чтобы в лесу всегда был хотя бы один лесоруб, несмотря на мысль о том, что популяция быдла может достичь нуля (запуск последнего лесоруба на карте, если урожай был действительно плохим, что никогда не произойдет, если лес не будет рубить до исчезновения).

доводка

Чтобы добиться стабильности, я добавил два параметра настройки:

1) темпы роста лесорубов

коэффициент, применяемый к формуле, который дает количество дополнительных лесорубов, нанятых при наличии достаточного количества древесины. Установите значение 1, чтобы вернуться к исходному определению, но я обнаружил, что значение около 0,5 позволяет лесу (особенно старшим деревьям) развиваться лучше.

2) критерий удаления медведя

коэффициент, определяющий минимальный процент разбитых дровосеков, чтобы отправить медведя в зоопарк. Установите 0, чтобы вернуться к исходному определению, но это радикальное устранение медведя в основном ограничит популяцию циклом колебаний 0-1. Я установил его на .15 (то есть медведь удаляется, только если в этом году было продано 15% или более лесорубов). Это учитывает умеренную популяцию медведей, достаточную для того, чтобы предотвратить быструю вытирание области чистыми местами, но в то же время позволяет рубить значительную часть леса.

В качестве примечания, симуляция никогда не останавливается (даже по прошествии 400 лет) Это легко можно сделать, но это не так.

Код

Код полностью содержится в одной HTML-странице.
Он должен быть в кодировке UTF-8 для отображения правильных символов Юникода для медведей и дровосеков.

Для систем с юникодом (например, Ubuntu): найдите следующие строки:

    jack   :{ pic: '🙎', color:'#bc0e11' },
    bear   :{ pic: '🐻', color:'#422f1e' }},

и изменить пиктограммы для символов легче дисплея ( #, *, что угодно)

<!doctype html>
<meta charset=utf-8>
<title>Of jacks and bears</title>
<body onload='init();'>
    <style>
    #log p { margin-top: 0; margin-bottom: 0; }
    </style>
    <div id='main'>

    </div>
    <table>
        <tr>
            <td><canvas id='forest'></canvas></td>
            <td>
                <table>
                    <tr>
                        <td colspan=2>
                            <div>Forest size     <input type='text' size=10 onchange='create_forest(this.value);'>     </div>
                            <div>Simulation tick <input type='text' size= 5 onchange='set_tick(this.value);'     > (ms)</div>
                            <div>
                                <input type='button' value='◾'       onclick='stop();'>
                                <input type='button' value='▸'       onclick='start();'>
                                <input type='button' value='1 month' onclick='start(1);'>
                                <input type='button' value='1 year'  onclick='start(12);'>
                            </div>
                        </td>
                    </tr>
                    <tr>
                        <td id='log' colspan=2>
                        </td>
                    </tr>
                    <tr>
                        <td><canvas id='graphs'></canvas></td>
                        <td id='legend'></td>
                    </tr>
                    <tr>
                        <td align='center'>evolution over 60 years</td>
                        <td id='counters'></td>
                    </tr>
                </table>
            </td>
        </tr>
    </table>
<script>
// ==================================================================================================
// Global parameters
// ==================================================================================================

var Prm = {
    // ------------------------------------
    // as defined in the original challenge
    // ------------------------------------

    // forest size
    forest_size: 45, // 2025 cells

    // simulation duration
    duration: 400*12, // 400 years

    // initial populations
    populate: { trees: .5, jacks:.1, bears:.02 },

    // tree ages
    age: { mature:12, elder:120 },

    // tree spawning probabilities
    spawn: { sapling:0, mature:.1, elder:.2 },

    // tree lumber yields
    lumber: { mature:1, elder:2 },

    // walking distances
    distance: { jack:3, bear:5 },

    // ------------------------------------
    // extra tweaks
    // ------------------------------------

    // lumberjacks growth rate
    // (set to 1 in original contest parameters)
    jacks_growth: 1, // .5,

    // minimal fraction of lumberjacks mauled to send a bear to the zoo
    // (set to 0 in original contest parameters)
    mauling_threshold: .15, // 0,

    // ------------------------------------
    // internal helpers
    // ------------------------------------

    // offsets to neighbouring cells
    neighbours: [ 
    {x:-1, y:-1}, {x: 0, y:-1}, {x: 1, y:-1},
    {x:-1, y: 0},               {x: 1, y: 0},
    {x:-1, y: 1}, {x: 0, y: 1}, {x: 1, y: 1}],

    // ------------------------------------
    // goodies
    // ------------------------------------

    // bear and people names
    names: 
    { bear: ["Art", "Ursula", "Arthur", "Barney", "Bernard", "Bernie", "Bjorn", "Orson", "Osborn", "Torben", "Bernadette", "Nita", "Uschi"],
     jack: ["Bob", "Tom", "Jack", "Fred", "Paul", "Abe", "Roy", "Chuck", "Rob", "Alf", "Tim", "Tex", "Mel", "Chris", "Dave", "Elmer", "Ian", "Kyle", "Leroy", "Matt", "Nick", "Olson", "Sam"] },

    // months
    month: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ],

    // ------------------------------------
    // graphics
    // ------------------------------------

    // messages verbosity (set to 2 to be flooded, -1 to have no trace at all)
    verbosity: 1,

     // pixel sizes
     icon_size: 100,
     canvas_f_size: 600,   // forest canvas size
     canvas_g_width : 400, // graphs canvas size
     canvas_g_height: 200,

     // graphical representation
     graph: { 
        soil: { color: '#82641e' },
        sapling:{ radius:.1, color:'#52e311', next:'mature'},
        mature :{ radius:.3, color:'#48b717', next:'elder' },
        elder  :{ radius:.5, color:'#8cb717', next:'elder' },
        jack   :{ pic: '🙎', color:'#2244ff' },
        bear   :{ pic: '🐻', color:'#422f1e' },
        mauling:{ pic: '★', color:'#ff1111' },
        cutting:{ pic: '●', color:'#441111' }},

    // animation tick
    tick:100 // ms
};

// ==================================================================================================
// Utilities
// ==================================================================================================

function int_rand (num)
{
    return Math.floor (Math.random() * num);
}

function shuffle (arr)
{
    for (
        var j, x, i = arr.length;
        i; 
        j = int_rand (i), x = arr[--i], arr[i] = arr[j], arr[j] = x);
}

function pick (arr)
{
    return arr[int_rand(arr.length)];
}

function message (str, level)
{
    level = level || 0;
    if (level <= Prm.verbosity)
    {
        while (Gg.log.childNodes.length > 10) Gg.log.removeChild(Gg.log.childNodes[0]);
        var line = document.createElement ('p');
        line.innerHTML = Prm.month[Forest.date%12]+" "+Math.floor(Forest.date/12)+": "+str;
        Gg.log.appendChild (line);
    }
}

// ==================================================================================================
// Forest
// ==================================================================================================

// --------------------------------------------------------------------------------------------------
// a forest cell
// --------------------------------------------------------------------------------------------------
function cell()
{
    this.contents = [];
}

cell.prototype = {

    add: function (elt)
    {
        this.contents.push (elt);
    },

    remove: function (elt)
    {
        var i = this.contents.indexOf (elt);
        this.contents.splice (i, 1);
    },

    contains: function (type)
    {
        for (var i = 0 ; i != this.contents.length ; i++)
        {
            if (this.contents[i].type == type)
            {
                return this.contents[i];
            }
        }
        return null;
    }
}

// --------------------------------------------------------------------------------------------------
// an entity (tree, jack, bear)
// --------------------------------------------------------------------------------------------------
function entity (x, y, type)
{
    this.age = 0;
    switch (type)
    {
        case "jack": this.name = pick (Prm.names.jack); break;
        case "bear": this.name = pick (Prm.names.bear); break;
        case "tree": this.name = "sapling"; Forest.t.low++; break;
    }

    this.x = this.old_x = x;
    this.y = this.old_y = y;
    this.type = type;
}

entity.prototype = {
    move: function ()
    {
        Forest.remove (this);
        var n = neighbours (this);
        this.x = n[0].x;
        this.y = n[0].y;
        return Forest.add (this);
    }
};

// --------------------------------------------------------------------------------------------------
// a list of entities (trees, jacks, bears)
// --------------------------------------------------------------------------------------------------
function elt_list (type)
{
    this.type = type;
    this.list = [];
}

elt_list.prototype = {
    add: function (x, y)
    {
        if (x === undefined) x = int_rand (Forest.size);
        if (y === undefined) y = int_rand (Forest.size);
        var e = new entity (x, y, this.type);
        Forest.add (e);
        this.list.push (e);
        return e;
    },

    remove: function (elt)
    {
        var i;
        if (elt) // remove a specific element (e.g. a mauled lumberjack)
        {
            i = this.list.indexOf (elt);
        }
        else // pick a random element (e.g. a bear punished for the collective pancake rampage)
        {           
            i = int_rand(this.list.length);
            elt = this.list[i];
        }
        this.list.splice (i, 1);
        Forest.remove (elt);
        if (elt.name == "mature") Forest.t.mid--;
        if (elt.name == "elder" ) Forest.t.old--;
        return elt;
    }
};

// --------------------------------------------------------------------------------------------------
// global forest handling
// --------------------------------------------------------------------------------------------------
function forest (size)
{
    // initial parameters
    this.size = size;
    this.surface = size * size;
    this.date = 0;
    this.mauling = this.lumber = 0;
    this.t = { low:0, mid:0, old:0 };

    // initialize cells
    this.cells = new Array (size);
    for (var i = 0 ; i != size ; i++)
    {
        this.cells[i] = new Array(size);
        for (var j = 0 ; j != size ; j++)
        {
            this.cells[i][j] = new cell;
        }
    }

    // initialize entities lists
    this.trees = new elt_list ("tree");
    this.jacks = new elt_list ("jack");
    this.bears = new elt_list ("bear");
    this.events = [];
}

forest.prototype = {
    populate: function ()
    {
        function fill (num, list)
        {
            for (var i = 0 ; i < num ; i++)
            {
                var coords = pick[i_pick++];
                list.add (coords.x, coords.y);
            }
        }

        // shuffle forest cells
        var pick = new Array (this.surface);
        for (var i = 0 ; i != this.surface ; i++)
        {
            pick[i] = { x:i%this.size, y:Math.floor(i/this.size)};
        }
        shuffle (pick);
        var i_pick = 0;

        // populate the lists
        fill (Prm.populate.jacks * this.surface, this.jacks);
        fill (Prm.populate.bears * this.surface, this.bears);
        fill (Prm.populate.trees * this.surface, this.trees);
        this.trees.list.forEach (function (elt) { elt.age = Prm.age.mature; });
    },

    add: function (elt)
    {
        var cell = this.cells[elt.x][elt.y];
        cell.add (elt);
        return cell;
    },

    remove: function (elt)
    {
        var cell = this.cells[elt.x][elt.y];
        cell.remove (elt);
    },

    evt_mauling: function (jack, bear)
    {
        message (bear.name+" sniffs a delicious scent of pancake, unfortunately for "+jack.name, 1);
        this.jacks.remove (jack);
        this.mauling++;
        Gg.counter.mauling.innerHTML = this.mauling;
        this.register_event ("mauling", jack);
    },

    evt_cutting: function (jack, tree)
    {
        if (tree.name == 'sapling') return; // too young to be chopped down
        message (jack.name+" cuts a "+tree.name+" tree: lumber "+this.lumber+" (+"+Prm.lumber[tree.name]+")", 2);
        this.trees.remove (tree);
        this.lumber += Prm.lumber[tree.name];
        Gg.counter.cutting.innerHTML = this.lumber;
        this.register_event ("cutting", jack);
    },

    register_event: function (type, position)
    {
        this.events.push ({ type:type, x:position.x, y:position.y});
    },

    tick: function()
    {
        this.date++;
        this.events = [];

        // monthly updates
        this.trees.list.forEach (b_tree);
        this.jacks.list.forEach (b_jack);
        this.bears.list.forEach (b_bear);

        // feed graphics
        Gg.graphs.trees.add (this.trees.list.length);
        Gg.graphs.jacks.add (this.jacks.list.length);
        Gg.graphs.bears.add (this.bears.list.length);
        Gg.graphs.sapling.add (this.t.low);
        Gg.graphs.mature .add (this.t.mid);
        Gg.graphs.elder  .add (this.t.old);

        // yearly updates
        if (!(this.date % 12))
        {
            // update jacks
            if (this.jacks.list.length == 0)
            {
                message ("An extra lumberjack is hired after a bear rampage");
                this.jacks.add ();
            }

            if (this.lumber >= this.jacks.list.length)
            {
                var extra_jacks = Math.floor (this.lumber / this.jacks.list.length * Prm.jacks_growth);
                message ("A good lumbering year. Lumberjacks +"+extra_jacks, 1);
                for (var i = 0 ; i != extra_jacks ; i++) this.jacks.add ();
            }
            else if (this.jacks.list.length > 1)
            {
                var fired = this.jacks.remove();
                message (fired.name+" has been chopped", 1);
            }

            // update bears
            if (this.mauling > this.jacks.list.length * Prm.mauling_threshold)
            {
                var bear = this.bears.remove();
                message (bear.name+" will now eat pancakes in a zoo", 1);
            }
            else
            {
                var bear = this.bears.add();
                message (bear.name+" starts a quest for pancakes", 1);
            }

            // reset counters
            this.mauling = this.lumber = 0;
        }
    }

}

function neighbours (elt)
{
    var ofs,x,y;
    var list = [];
    for (ofs in Prm.neighbours)
    {
        var o = Prm.neighbours[ofs];
        x = elt.x + o.x;
        y = elt.y + o.y;
        if (  x < 0 || x >= Forest.size
           || y < 0 || y >= Forest.size) continue;

        list.push ({x:x, y:y});
    }
    shuffle (list);
    return list;
}

// --------------------------------------------------------------------------------------------------
// entities behaviour
// --------------------------------------------------------------------------------------------------
function b_tree (tree)
{
    // update tree age and category
    if      (tree.age == Prm.age.mature) { tree.name = "mature"; Forest.t.low--; Forest.t.mid++; }
    else if (tree.age == Prm.age.elder ) { tree.name = "elder" ; Forest.t.mid--; Forest.t.old++; }
    tree.age++;

    // see if we can spawn something
    if (Math.random() < Prm.spawn[tree.name])
    {
        var n = neighbours (tree);
        for (var i = 0 ; i != n.length ; i++)
        {
            var coords = n[i];
            var cell = Forest.cells[coords.x][coords.y];
            if (cell.contains("tree")) continue;
            Forest.trees.add (coords.x, coords.y);
            break;
        }
    }
}

function b_jack (jack)
{
    jack.old_x = jack.x;
    jack.old_y = jack.y;

    for (var i = 0 ; i != Prm.distance.jack ; i++)
    {
        // move
        var cell = jack.move ();

        // see if we stumbled upon a bear
        var bear = cell.contains ("bear");
        if (bear)
        {
            Forest.evt_mauling (jack, bear);
            break;
        }

        // see if we reached an harvestable tree
        var tree = cell.contains ("tree");
        if (tree)
        {
            Forest.evt_cutting (jack, tree);
            break;
        }
    }
}

function b_bear (bear)
{
    bear.old_x = bear.x;
    bear.old_y = bear.y;

    for (var i = 0 ; i != Prm.distance.bear ; i++)
    {
        var cell = bear.move ();
        var jack = cell.contains ("jack");
        if (jack)
        {
            Forest.evt_mauling (jack, bear);
            break; // one pancake hunt per month is enough
        }
    }
}

// --------------------------------------------------------------------------------------------------
// Graphics
// --------------------------------------------------------------------------------------------------
function init()
{
    function create_counter (desc)
    {
        var counter = document.createElement ('span');
        var item = document.createElement ('p');
        item.innerHTML = desc.name+"&nbsp;";
        item.style.color = desc.color;
        item.appendChild (counter);
        return { item:item, counter:counter };
    }

    // initialize forest canvas
    Gf = { period:20, tick:0 };
    Gf.canvas = document.getElementById ('forest');
    Gf.canvas.width  =
    Gf.canvas.height = Prm.canvas_f_size;
    Gf.ctx = Gf.canvas.getContext ('2d');
    Gf.ctx.textBaseline = 'Top';

    // initialize graphs canvas
    Gg = { counter:[] };
    Gg.canvas = document.getElementById ('graphs');
    Gg.canvas.width  = Prm.canvas_g_width;
    Gg.canvas.height = Prm.canvas_g_height;
    Gg.ctx = Gg.canvas.getContext ('2d');

    // initialize graphs
    Gg.graphs = {
        jacks:   new graphic({ name:"lumberjacks" , color:Prm.graph.jack.color }),
        bears:   new graphic({ name:"bears"       , color:Prm.graph.bear.color, ref:'jacks' }),
        trees:   new graphic({ name:"trees"       , color:'#0F0' }),
        sapling: new graphic({ name:"saplings"    , color:Prm.graph.sapling.color, ref:'trees' }),
        mature:  new graphic({ name:"mature trees", color:Prm.graph.mature .color, ref:'trees' }),
        elder:   new graphic({ name:"elder trees" , color:Prm.graph.elder  .color, ref:'trees' })
    };
    Gg.legend = document.getElementById ('legend');
    for (g in Gg.graphs)
    {
        var gr = Gg.graphs[g];
        var c = create_counter (gr);
        gr.counter = c.counter;
        Gg.legend.appendChild (c.item);
    }

    // initialize counters
    var counters = document.getElementById ('counters');
    var def = [ "mauling", "cutting" ];
    var d; for (d in def)
    {
        var c = create_counter ({ name:def[d], color:Prm.graph[def[d]].color });
        counters.appendChild (c.item);
        Gg.counter[def[d]] = c.counter;
    }

    // initialize log
    Gg.log = document.getElementById ('log');

    // create our forest
    create_forest(Prm.forest_size);
    start();
}

function create_forest (size)
{
    if (size < 2) size = 2;
    if (size > 100) size = 100;
    Forest = new forest (size);
    Prm.icon_size = Prm.canvas_f_size / size;
    Gf.ctx.font = 'Bold '+Prm.icon_size+'px Arial';
    Forest.populate ();
    draw_forest();
    var g; for (g in Gg.graphs) Gg.graphs[g].reset();
    draw_graphs();
}

function animate()
{
    if (Gf.tick % Prm.tick == 0)
    {
        Forest.tick();
        draw_graphs();
    }
    draw_forest();
    Gf.tick+= Gf.period;
    if (Gf.tick == Gf.stop_date) stop();
}

function draw_forest ()
{
    function draw_dweller (dweller)
    {
        var type = Prm.graph[dweller.type];
        Gf.ctx.fillStyle = type.color;
        var x = dweller.x * time_fraction + dweller.old_x * (1 - time_fraction);
        var y = dweller.y * time_fraction + dweller.old_y * (1 - time_fraction);
        Gf.ctx.fillText (type.pic, x * Prm.icon_size, (y+1) * Prm.icon_size);
    }

    function draw_event (evt)
    {
        var gr = Prm.graph[evt.type];
        Gf.ctx.fillStyle = gr.color;
        Gf.ctx.fillText (gr.pic, evt.x * Prm.icon_size, (evt.y+1) * Prm.icon_size);
    }

    function draw_tree (tree)
    {
        // trees grow from one category to the next
        var type = Prm.graph[tree.name];
        var next = Prm.graph[type.next];
        var radius = (type.radius + (next.radius - type.radius) / Prm.age[type.next] * tree.age) * Prm.icon_size;
        Gf.ctx.fillStyle = Prm.graph[tree.name].color;
        Gf.ctx.beginPath();
        Gf.ctx.arc((tree.x+.5) * Prm.icon_size, (tree.y+.5) * Prm.icon_size, radius, 0, 2*Math.PI);
        Gf.ctx.fill();
    }

    // background
    Gf.ctx.fillStyle = Prm.graph.soil.color;
    Gf.ctx.fillRect (0, 0, Gf.canvas.width, Gf.canvas.height);

    // time fraction to animate displacements
    var time_fraction = (Gf.tick % Prm.tick) / (Prm.tick-Gf.period);

    // entities
    Forest.trees.list.forEach (draw_tree);
    Forest.jacks.list.forEach (draw_dweller);
    Forest.bears.list.forEach (draw_dweller);
    Forest.events.forEach (draw_event);
}

// --------------------------------------------------------------------------------------------------
// Graphs
// --------------------------------------------------------------------------------------------------
function graphic (prm)
{
    this.name  = prm.name  || '?';
    this.color = prm.color || '#FFF';
    this.size  = prm.size  || 720;
    this.ref   = prm.ref;
    this.values = [];
    this.counter = document.getElement
}

graphic.prototype = {
    draw: function ()
    {
        Gg.ctx.strokeStyle = this.color;
        Gg.ctx.beginPath();
        for (var i = 0 ; i != this.values.length ; i++)
        {
            var x = (i + this.size - this.values.length) / this.size * Gg.canvas.width;
            var y = (1-(this.values[i] - this.min) / this.rng)       * Gg.canvas.height;

            if (i == 0) Gg.ctx.moveTo (x, y);
            else        Gg.ctx.lineTo (x, y);
        }
        Gg.ctx.stroke();
    },

    add: function (value)
    {
        // store value
        this.values.push (value);
        this.counter.innerHTML = value;

        // cleanup history
        while (this.values.length > this.size) this.values.splice (0,1);

        // compute min and max
        this.min = Math.min.apply(Math, this.values);
        if (this.min > 0) this.min = 0;
        this.max = this.ref 
                 ? Gg.graphs[this.ref].max
                 : Math.max.apply(Math, this.values);
        this.rng = this.max - this.min;
        if (this.rng == 0) this.rng = 1;
    },

    reset: function()
    {
        this.values = [];
    }
}

function draw_graphs ()
{
    function draw_graph (graph)
    {
        graph.draw();
    }

    // background
    Gg.ctx.fillStyle = '#000';
    Gg.ctx.fillRect (0, 0, Gg.canvas.width, Gg.canvas.height);

    // graphs
    var g; for (g in Gg.graphs)
    {
        var gr = Gg.graphs[g];
        gr.draw();
    }
}

// --------------------------------------------------------------------------------------------------
// User interface
// --------------------------------------------------------------------------------------------------
function set_tick(value)
{
    value = Math.round (value / Gf.period);
    if (value < 2) value = 2;
    value *= Gf.period;
    Prm.tick = value;
    return value;
}

function start (duration)
{
    if (Prm.timer) stop();
    Gf.stop_date = duration ? Gf.tick + duration*Prm.tick : -1;
    Prm.timer = setInterval (animate, Gf.period);
}

function stop ()
{
    if (Prm.timer)
    {
        clearInterval (Prm.timer);
        Prm.timer = null;
    }
    Gf.stop_date = -1;
}

</script>
</body>

Что дальше?

Другие замечания все еще приветствуются.

NB: Я знаю, что количество саженцев / зрелых / старших деревьев все еще немного грязное, но черт с ним.

Кроме того, я считаю document.getElementById более читабельным, чем $, поэтому не нужно жаловаться на отсутствие jQueryisms. Это бесплатно для jQuery. Каждому свое, верно?


источник
+1, это выглядит круто, я хотел бы видеть интерактивность!
Уильям Барбоза
Я не вижу медведей и дровосеков. Что я должен сделать, чтобы увидеть их? Кстати, только максимум один медведь должен быть удален в год, верно?
Половина
@WilliamBarbosa Если vox populi кричит достаточно громко, это может меня уговорить добавить :)
@justhalf см. мои правки о юникод-символах. Что касается медведей, если я правильно понимаю спецификации, да: только один медведь, выбранный случайным образом, отправляется в зоопарк каждый год, если происходит какое-то увечье.
1
Растущие деревья чудесны. Молодец!
Blackhole
14

AngularJS

Вот моя версия , которая все еще находится в стадии разработки: код немного ... ну ... некрасиво. И довольно медленно. Я также планирую добавить дополнительные параметры для параметризации эволюции и анализа состояния леса. Комментарии и предложения по улучшению приветствуются!

Скриншот леса

демонстрация

<div ng-app="ForestApp" ng-controller="ForestController">
    <form name="parametersForm" ng-hide="evolutionInProgress" autocomplete="off" novalidate>
        <div class="line">
            <label for="forestSize">Size of the forest:</label>
            <input type="number" ng-model="Parameters.forestSize" id="forestSize" min="5" ng-pattern="/^[0-9]+$/" required />
        </div>
        <div class="line">
            <label for="simulationInterval">Number of milliseconds between each tick</label>
            <input type="number" ng-model="Parameters.simulationInterval" id="simulationInterval" min="10" ng-pattern="/^[0-9]+$/" required />
        </div>
        <div class="line">
            <label for="animationsEnabled">Animations enabled?
                <br /><small>(>= 300 ms between each tick is advisable)</small>

            </label>
            <input type="checkbox" ng-model="Parameters.animationsEnabled" id="animationsEnabled" />
        </div>
        <div class="line">
            <button ng-disabled="parametersForm.$invalid || evolutionInProgress" ng-click="launchEvolution()">Launch the evolution!</button>
        </div>
    </form>
    <div id="forest" ng-style="{width: side = (20*Parameters.forestSize) + 'px', height: side, 'transition-duration': transitionDuration = (Parameters.animationsEnabled ? 0.8*Parameters.simulationInterval : 0) + 'ms', '-webkit-transition-duration': transitionDuration}">
        <div ng-repeat="bear in Forest.bearsList" class="entity entity--bear" ng-style="{left: (20*bear.x) + 'px', top: (20*bear.y) + 'px'}"></div>
        <div ng-repeat="lumberjack in Forest.lumberjacksList" class="entity entity--lumberjack" ng-style="{left: (20*lumberjack.x) + 'px', top: (20*lumberjack.y) + 'px'}"></div>
        <div ng-repeat="tree in Forest.treesList" class="entity entity--tree" ng-class="'entity--tree--' + tree.stage" ng-style="{left: (20*tree.x) + 'px', top: (20*tree.y) + 'px'}"></div>
    </div>
    <div class="line"><em>Age of the forest:</em><samp>{{floor(Forest.age/12)}} year{{floor(Forest.age/12) > 1 ? 's' : ''}} and {{Forest.age%12}} month{{Forest.age%12 > 1 ? 's' : ''}}</samp></div>
    <div class="line"><em>Number of bears:</em><samp>{{Forest.bearsList.length}}</samp></div>
    <div class="line"><em>Number of lumberjacks:</em><samp>{{Forest.lumberjacksList.length}}</samp></div>
    <br />
    <div class="line"><em>Number of lumbers collected:</em><samp>{{Forest.numberOfLumbers}}</samp></div>
    <div class="line"><em>Number of mauls:</em><samp>{{Forest.numberOfMauls}}</samp></div>
</div>
/** @link http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array */
function shuffle(array) {
    var currentIndex = array.length,
        temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;

        // And swap it with the current element.
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }

    return array;
}

var forestApp = angular.module('ForestApp', ['ngAnimate']);

forestApp.value('Parameters', {
    /** @var int[] Maximal number of moves by species */
    speed: {
        bear: 5,
        lumberjack: 3,
    },

    /** @var int[] Initial percentage of each species in the forest */
    initialPercentage: {
        bear: 2,
        lumberjack: 10,
        tree: 50,
    },

    /** @var int[] Spawing rate, in percentage, of new saplings around an existing tree */
    spawningPercentage: {
        0: 0,
        1: 10,
        2: 20,
    },

    /** @var int[] Age of growth for an existing tree */
    ageOfGrowth: {
        sapling: 12,
        tree: 120,
    },

    /** @var int[] Lumber collected on an existing tree */
    numberOfLumbers: {
        tree: 1,
        elderTree: 2,
    },

    /** @var int Size of each side of the forest */
    forestSize: 20,

    /** @var int Number of milliseconds between each tick (month in the forest) */
    simulationInterval: 50,
});

forestApp.constant('TREE_STAGE', {
    SAPLING: 0,
    TREE: 1,
    ELDER_TREE: 2,
});

forestApp.factory('Tree', ['Forest', 'Parameters', 'TREE_STAGE', function (Forest, Parameters, TREE_STAGE) {
    // Classes which represents a tree
    var Tree = function (stage, x, y) {
        /** @var TREE_STAGE Current stage of the tree */
        this.stage = stage;

        /** @var int Current age of the tree, in month */
        this.age = 0;

        /** @var int X coordinates of the tree */
        this.x = x;

        /** @var int Y coordinates of the tree */
        this.y = y;

        this.tick = function () {
            if (Math.random() < Parameters.spawningPercentage[this.stage] / 100) {
                var freePositionsList = shuffle(Forest.getFreePositionsAround(this.x, this.y));
                if (freePositionsList.length > 0) {
                    var saplingPosition = freePositionsList[0];

                    Tree.create(TREE_STAGE.SAPLING, saplingPosition[0], saplingPosition[1]);
                }
            }

            ++this.age;

            if (this.stage === TREE_STAGE.SAPLING && this.age == Parameters.ageOfGrowth.sapling) {
                this.stage = TREE_STAGE.TREE;
            } else if (this.stage === TREE_STAGE.TREE && this.age == Parameters.ageOfGrowth.tree) {
                this.stage = TREE_STAGE.ELDER_TREE;
            }
        };

        /**
         * Remove the entity
         */
        this.remove = function () {
            var index = Forest.treesList.indexOf(this);
            Forest.treesList.splice(index, 1);
        };
    };

    Tree.create = function (stage, x, y) {
        Forest.add.tree(new Tree(stage, x, y));
    };

    return Tree;
}]);

forestApp.factory('Lumberjack', ['Forest', 'Parameters', 'TREE_STAGE', function (Forest, Parameters, TREE_STAGE) {
    // Classes which represents a lumberjack
    var Lumberjack = function (x, y) {
        /** @var int X coordinates of the lumberjack */
        this.x = x;

        /** @var int Y coordinates of the lumberjack */
        this.y = y;

        this.tick = function () {
            for (movement = Parameters.speed.lumberjack; movement > 0; --movement) {
                var positionsList = shuffle(Forest.getPositionsAround(this.x, this.y));
                var newPosition = positionsList[0];
                this.x = newPosition[0];
                this.y = newPosition[1];

                var tree = Forest.getTreeAt(this.x, this.y);
                if (tree !== null) {
                    if (tree.stage === TREE_STAGE.SAPLING) {
                        return;
                    } else if (tree.stage === TREE_STAGE.TREE) {
                        Forest.numberOfLumbers += Parameters.numberOfLumbers.tree;
                    } else {
                        Forest.numberOfLumbers += Parameters.numberOfLumbers.elderTree;
                    }

                    tree.remove();
                    movement = 0;
                };
            }
        };

        /**
         * Remove the entity
         */
        this.remove = function () {
            if (Forest.lumberjacksList.length === 1) {
                this.x = Math.floor(Math.random() * Parameters.forestSize);
                this.y = Math.floor(Math.random() * Parameters.forestSize);
            } else {
                var index = Forest.lumberjacksList.indexOf(this);
                Forest.lumberjacksList.splice(index, 1);
            }
        };
    };

    Lumberjack.create = function (x, y) {
        Forest.add.lumberjack(new Lumberjack(x, y));
    };

    return Lumberjack;
}]);

forestApp.factory('Bear', ['Forest', 'Parameters', function (Forest, Parameters) {
    // Classes which represents a bear
    var Bear = function (x, y) {
        /** @var int X coordinates of the bear */
        this.x = x;

        /** @var int Y coordinates of the bear */
        this.y = y;

        this.tick = function () {
            for (movement = Parameters.speed.bear; movement > 0; --movement) {
                var positionsList = shuffle(Forest.getPositionsAround(this.x, this.y));
                var newPosition = positionsList[0];
                this.x = newPosition[0];
                this.y = newPosition[1];

                angular.forEach(Forest.getLumberjacksListAt(this.x, this.y), function (lumberjack) {
                    lumberjack.remove();
                    ++Forest.numberOfMauls;
                    movement = 0;
                });
            }
        };

        /**
         * Remove the entity
         */
        this.remove = function () {
            var index = Forest.bearsList.indexOf(this);
            Forest.bearsList.splice(index, 1);
        };
    };

    Bear.create = function (x, y) {
        Forest.add.bear(new Bear(x, y));
    };

    return Bear;
}]);

forestApp.service('Forest', ['Parameters', function (Parameters) {
    var forest = this;

    this.age = 0;
    this.numberOfLumbers = 0;
    this.numberOfMauls = 0;

    this.bearsList = [];
    this.lumberjacksList = [];
    this.treesList = [];

    this.getEntitiesList = function () {
        return forest.bearsList.concat(forest.lumberjacksList, forest.treesList);
    };

    /**
     * Age the forest by one month
     */
    this.tick = function () {
        angular.forEach(forest.getEntitiesList(), function (entity) {
            entity.tick();
        });

        ++forest.age;
    };

    this.add = {
        bear: function (bear) {
            forest.bearsList.push(bear);
        },
        lumberjack: function (lumberjack) {
            forest.lumberjacksList.push(lumberjack);
        },
        tree: function (tree) {
            forest.treesList.push(tree);
        },
    };

    /**
     * @return Tree|null Tree at this position, or NULL if there is no tree.
     */
    this.getTreeAt = function (x, y) {
        var numberOfTrees = forest.treesList.length;
        for (treeId = 0; treeId < numberOfTrees; ++treeId) {
            var tree = forest.treesList[treeId];
            if (tree.x === x && tree.y === y) {
                return tree;
            }
        }

        return null;
    };

    /**
     * @return Lumberjack[] List of the lumberjacks at this position
     */
    this.getLumberjacksListAt = function (x, y) {
        var lumberjacksList = [];
        angular.forEach(forest.lumberjacksList, function (lumberjack) {
            if (lumberjack.x === x && lumberjack.y === y) {
                lumberjacksList.push(lumberjack);
            }
        });

        return lumberjacksList;
    };

    /**
     * @return int[] Positions around this position
     */
    this.getPositionsAround = function (x, y) {
        var positionsList = [
            [x - 1, y - 1],
            [x, y - 1],
            [x + 1, y - 1],
            [x - 1, y],
            [x + 1, y],
            [x - 1, y + 1],
            [x, y + 1],
            [x + 1, y + 1]
        ];

        return positionsList.filter(function (position) {
            return (position[0] >= 0 && position[1] >= 0 && position[0] < Parameters.forestSize && position[1] < Parameters.forestSize);
        });
    };

    /**
     * @return int[] Positions without tree around this position
     */
    this.getFreePositionsAround = function (x, y) {
        var positionsList = forest.getPositionsAround(x, y);

        return positionsList.filter(function (position) {
            return forest.getTreeAt(position[0], position[1]) === null;
        });
    };
}]);

forestApp.controller('ForestController', ['$interval', '$scope', 'Bear', 'Forest', 'Lumberjack', 'Parameters', 'Tree', 'TREE_STAGE', function ($interval, $scope, Bear, Forest, Lumberjack, Parameters, Tree, TREE_STAGE) {
    $scope.Forest = Forest;
    $scope.Parameters = Parameters;
    $scope.evolutionInProgress = false;
    $scope.floor = Math.floor;

    var positionsList = [];

    /**
     * Start the evolution of the forest
     */
    $scope.launchEvolution = function () {
        $scope.evolutionInProgress = true;

        for (var x = 0; x < Parameters.forestSize; ++x) {
            for (var y = 0; y < Parameters.forestSize; ++y) {
                positionsList.push([x, y]);
            }
        }

        shuffle(positionsList);
        var numberOfBears = Parameters.initialPercentage.bear * Math.pow(Parameters.forestSize, 2) / 100;
        for (var bearId = 0; bearId < numberOfBears; ++bearId) {
            Bear.create(positionsList[bearId][0], positionsList[bearId][1]);
        }

        shuffle(positionsList);
        var numberOfLumberjacks = Parameters.initialPercentage.lumberjack * Math.pow(Parameters.forestSize, 2) / 100;
        for (var lumberjackId = 0; lumberjackId < numberOfLumberjacks; ++lumberjackId) {
            Lumberjack.create(positionsList[lumberjackId][0], positionsList[lumberjackId][1]);
        }

        shuffle(positionsList);
        var numberOfTrees = Parameters.initialPercentage.tree * Math.pow(Parameters.forestSize, 2) / 100;
        for (var treeId = 0; treeId < numberOfTrees; ++treeId) {
            Tree.create(TREE_STAGE.TREE, positionsList[treeId][0], positionsList[treeId][1]);
        }

        $interval(function () {
            Forest.tick();

            if (Forest.age % 12 === 0) {
                // Hire or fire lumberjacks
                if (Forest.numberOfLumbers >= Forest.lumberjacksList.length) {
                    shuffle(positionsList);
                    var numberOfLumberjacks = Math.floor(Forest.numberOfLumbers / Forest.lumberjacksList.length);
                    for (var lumberjackId = 0; lumberjackId < numberOfLumberjacks; ++lumberjackId) {
                        Lumberjack.create(positionsList[lumberjackId][0], positionsList[lumberjackId][1]);
                    }
                } else {
                    shuffle(Forest.lumberjacksList);
                    Forest.lumberjacksList[0].remove();
                }

                // Hire or fire bears
                if (Forest.numberOfMauls === 0) {
                    shuffle(positionsList);
                    Bear.create(positionsList[0][0], positionsList[0][1]);
                } else {
                    Forest.bearsList[0].remove();
                }

                Forest.numberOfLumbers = 0;
                Forest.numberOfMauls = 0;
            }

        }, Parameters.simulationInterval);
    };
}]);
Черная дыра
источник
1
Также мне нравится, как мы используем тот же код для рандомизации массива случайных чисел
Кевин Л
Интересный. В первый раз, когда я запустил вашу симуляцию, она заканчивается всеми медведями и без дровосеков, что привело меня к мысли, что эта симуляция не сбалансирована. Но потом это больше никогда не повторится, поэтому я думаю, что это всего лишь «невезение», когда медведи повредили весь дровосек (потому что, если медведей так много, лесорубы почти наверняка умрут)
тридцать четвертое
getEntitiesAtпохоже на процессорную свинью! запуск системы с сеткой 50x50 занимает более одной секунды в месяц на моем ПК. Также есть случай, когда все деревья рубятся, тогда все лесорубы запускаются и карта медленно заполняется медведями :). Попробуйте маленький размер (10 или меньше), чтобы увидеть, как это происходит.
@kuroineko: Это потому, что есть ошибка (или, скорее, невыполненное правило), когда после того, как дровосек становится изуродованным и если он возрождается на медведя, его там не будет. Так что доходит до 0. Исправьте это проверкой после Forest.tick(), если Forest.lumberjackList.length == 0, тогда Lumberjack.create(<number>, <number>).
полугодие
1
Я пытался настроить ваш код, чтобы медведи предпочитали оставаться в лесу (и также включали критерии удаления медведя типа kuroineko, такие как добавление медведей, если уклонение составляет менее 10%, и удаление медведя, если забрасывание более 15% ). Интересно посмотреть, как меняется поведение =)
justhalf
3

Javascript

Я думаю, что это в основном работает. Есть некое странное поведение, когда я порождаю всех новых медведей / дровосеков синхронно и рядом друг с другом из-за лени во вставках.

Эта реализация не позволяет лесорубам стоять на саженцах, потому что вы знаете, вытаптывать саженцы плохо. Fiddle art использует цветные прямоугольники по умолчанию, измените вторую строку на false, чтобы использовать буквы для рисования.

играть на скрипке

HTML:

<canvas id="c" width="1" height="1"></canvas>
<div id="p1"></div>
<div id="p2"></div>
<div id="p3"></div>
<div id="p4"></div>

Js:

var n = 10; // Size of the grid
var drawUsingColor = true; // If true, draws colors for each entity instead :D
var intervalTime = 1000; // how often each tick happens, in milliseconds
var jackRatio = 0.1;
var treeRatio = 0.5;
var bearRatio = 0.02;
var size = 48; // Pixels allocated (in size x size) for each entity
var font = "30px Lucida Console"; // if drawUsingColor is false

var bearColor = '#8B4513'; // Saddlebrown
var elderColor = '#556B2F'; // DarkOliveGreen
var lumberjackColor = '#B22222'; // Firebrick
var treeColor = '#008000'; // Green
var saplingColor = '#ADFF2F'; // GreenYellow

// Game rules:
var spawnSaplingChance = 0.1;
var elderTreeAge = 120;
var elderSaplingChance = 0.2;
var treeAge = 12;
var lumberjackRange = 3;
var bearRange = 5;
var zooPeriod = 12; // If a maul happens within this period
var lumberPeriod = 12; // New lumberjacks hired in this period


var time = 1;

var world;
var n2 = n * n; //because one saved keystroke
var zooqueue = [];
var lumberqueue = [];
var canvas = document.getElementById('c'); // Needs more jquery
var context = canvas.getContext('2d');
context.font = font;

// various statistics
var treesAlive = 0;
var jacksAlive = 0;
var bearsAlive = 0;
var currentLumber = 0;
var lumberjacksMauled = 0;
var recentEvents = '';

// Entity is a bear, eldertree, lumberjack, tree, sapling, with age. aka belts.
function Entity(belts, birthday) {
    this.type = belts;
    this.age = 0;
    this.birthday = birthday;
}

function initWorld() {
    canvas.height = size * n;
    canvas.width = size * n;
    world = new Array(n2);

    // One pass spawning algorithm: numEntity = number of entity left to spawn
    // If rand() in range [0,numtrees), spawn tree
    // if rand() in range [numtrees, numtrees+numjacks), spawn lumberjack
    // if rand() in range [numtrees+numjacks, numtrees+numjacks+numbears), spawn bear

    var numTrees = treeRatio * n2;
    var numJacks = jackRatio * n2;
    var numBears = bearRatio * n2;

    var godseed = new Array(n2);
    for (var i = 0; i < n2; i++) {
        godseed[i] = i;
    }
    shuffle(godseed);

    for (var i = 0; i < n2; i++) {
        var god = godseed.pop();
        if (god < numTrees) {
            world[i] = new Entity('T', 0);
            treesAlive++;
        } else if (god < numTrees + numJacks) {
            world[i] = new Entity('L', 0);
            jacksAlive++;
        } else if (god < numTrees + numJacks + numBears) {
            world[i] = new Entity('B', 0);
            bearsAlive++;
        }
        // console.log(world, i);
    }

    // populate zoo array, lumber array
    for (var i = 0; i < zooPeriod; i++) {
        zooqueue.push(0);
    }

    for (var i = 0; i < lumberPeriod; i++) {
        lumberqueue.push(0);
    }
}

animateWorld = function () {
    recentEvents = '';
    computeWorld();
    drawWorld();
    time++;

    $('#p1').text(treesAlive + ' trees alive');
    $('#p2').text(bearsAlive + ' bears alive');
    $('#p3').text(jacksAlive + ' lumberjacks alive');
    $('#p4').text(recentEvents);
};

function computeWorld() {
    zooqueue.push(lumberjacksMauled);
    lumberqueue.push(currentLumber);

    // Calculate entity positions
    for (var i = 0; i < n2; i++) {
        if (world[i]) {
            switch (world[i].type) {
                case 'B':
                    bearStuff(i);
                    break;
                case 'E':
                    elderStuff(i);
                    break;
                case 'L':
                    lumberjackStuff(i);
                    break;
                case 'T':
                    treeStuff(i);
                    break;
                case 'S':
                    saplingStuff(i);
                    break;
            }
        }
    }

    // Pop the # mauls from zooPeriod's ago, if lumberjacksMauled > oldmauls, then someone was eaten.
    var oldmauls = zooqueue.shift();
    if (time % zooPeriod === 0) {
        if (lumberjacksMauled > oldmauls) {
            if (remove('B') == 1) {
                bearsAlive--;
                recentEvents += 'Bear sent to zoo! ';
            }
        } else {
            bearsAlive++;
            spawn('B');
            recentEvents += 'New bear appeared! ';
        }
    }

    var oldLumber = lumberqueue.shift();
    if (time % lumberPeriod === 0) {
        // # lumberjack to hire
        var hire = Math.floor((currentLumber - oldLumber) / jacksAlive);
        if (hire > 0) {
            recentEvents += 'Lumber jack hired! (' + hire + ') ';
            while (hire > 0) {
                jacksAlive++;
                spawn('L');
                hire--;
            }
        } else {
            if (remove('L') == 1) {
                recentEvents += 'Lumber jack fired!  ';
                jacksAlive--;
            }
            else {
            }
        }
    }

    // Ensure > 1 lumberjack
    if (jacksAlive === 0) {
        jacksAlive++;
        spawn('L');
        recentEvent += 'Lumberjack spontaneously appeared';
    }
}

// Not the job of spawn/remove to keep track of whatever was spawned/removed
function spawn(type) {
    var index = findEmpty(type);
    if (index != -1) {
        world[index] = new Entity(type, time);
    }
    // recentEvents += 'Spawned a ' + type + '\n';
}

function remove(type) {
    var index = findByType(type);
    if (index != -1) {
        world[index] = null;
        return 1;
    }
    return -1;
    // recentEvents += 'Removed a ' + type + '\n';
}

// Searches in world for an entity with type=type. Currently implemented as
// linear scan, which isn't very random
function findByType(type) {
    for (var i = 0; i < n2; i++) {
        if (world[i] && world[i].type == type) return i;
    }
    return -1;
}

// Also linear scan 
function findEmpty(type) {
    for (var i = 0; i < n2; i++) {
        if (!world[i]) {
            return i;
        }
    }
    return -1;
}

function bearStuff(index) {
    if (world[index].birthday == time) {
        return;
    }

    // Wander around
    var tindex = index;
    for (var i = 0; i < lumberjackRange; i++) {
        var neighbors = get8Neighbor(tindex);

        var mov = neighbors[Math.floor(Math.random() * neighbors.length)];
        if (world[mov] && world[mov].type == 'L') {
            recentEvents += 'Bear (' + index % 10 + ',' + Math.floor(index / 10) + ') mauled a Lumberjack (' + mov % 10 + ',' + Math.floor(mov / 10) + ') !';
            lumberjacksMauled++;
            jacksAlive--;
            world[mov] = new Entity('B', time);
            world[mov].age = ++world[index].age;
            world[index] = null;
            return;
        }
        tindex = mov;
    }

    if (!world[tindex]) {
        world[tindex] = new Entity('B', time);
        world[tindex].age = ++world[index].age;
        world[index] = null;
    }
}

function elderStuff(index) {
    if (world[index].birthday == time) {
        return;
    }
    neighbors = get8Neighbor(index);

    // spawn saplings
    for (var i = 0; i < neighbors.length; i++) {
        if (!world[neighbors[i]]) {
            if (Math.random() < elderSaplingChance) {
                world[neighbors[i]] = new Entity('S', time);
                treesAlive++;
            }
        }
    }

    // become older
    world[index].age++;
}

function lumberjackStuff(index) {
    if (world[index].birthday == time) {
        return;
    }

    // Wander around
    var tindex = index;
    for (var i = 0; i < lumberjackRange; i++) {
        var neighbors = get8Neighbor(tindex);

        var mov = neighbors[Math.floor(Math.random() * neighbors.length)];
        if (world[mov] && (world[mov].type == 'T' || world[mov].type == 'E')) {
            world[mov].type == 'T' ? currentLumber++ : currentLumber += 2;
            treesAlive--;
            world[mov] = new Entity('L', time);
            world[mov].age = ++world[index].age;
            world[index] = null;
            return;
        }
        tindex = mov;
    }

    if (!world[tindex]) {
        world[tindex] = new Entity('L', time);
        world[tindex].age = ++world[index].age;
        world[index] = null;
    }
}

function treeStuff(index) {
    if (world[index].birthday == time) {
        return;
    }
    neighbors = get8Neighbor(index);

    // spawn saplings
    for (var i = 0; i < neighbors.length; i++) {
        if (!world[neighbors[i]]) {
            if (Math.random() < spawnSaplingChance) {
                world[neighbors[i]] = new Entity('S', time);
                treesAlive++;
            }
        }
    }

    // promote to elder tree?
    if (world[index].age >= elderTreeAge) {
        world[index] = new Entity('E', time);
        return;
    }

    // become older
    world[index].age++;
}

function saplingStuff(index) {
    if (world[index].birthday == time) {
        return;
    }

    // promote to tree?
    if (world[index].age > treeAge) {
        world[index] = new Entity('T', time);
        return;
    }
    world[index].age++;
}

// Returns array containing up to 8 valid neighbors.
// Prolly gonna break for n < 3 but oh well
function get8Neighbor(index) {
    neighbors = [];

    if (index % n != 0) {
        neighbors.push(index - n - 1);
        neighbors.push(index - 1);
        neighbors.push(index + n - 1);
    }
    if (index % n != n - 1) {
        neighbors.push(index - n + 1);
        neighbors.push(index + 1);
        neighbors.push(index + n + 1);
    }
    neighbors.push(index - n);
    neighbors.push(index + n);
    return neighbors.filter(function (val, ind, arr) {
        return (0 <= val && val < n2)
    });
}

// Each entity allocated 5x5px for their art
function drawWorld() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    for (var i = 0; i < n2; i++) {
        if (world[i]) {
            var x = i % n;
            var y = Math.floor(i / n);
            switch (world[i].type) {
                case 'B':
                    drawBear(x, y);
                    break;
                case 'E':
                    drawElder(x, y);
                    break;
                case 'L':
                    drawJack(x, y);
                    break;
                case 'T':
                    drawTree(x, y);
                    break;
                case 'S':
                    drawSapling(x, y);
                    break;
            }
        }
    }
}

function drawBear(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, bearColor);
    } else {
        drawLetter(x * size, y * size, 'B');
    }
}

function drawElder(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, elderColor);
    } else {
        drawLetter(x * size, y * size, 'E');
    }
}

function drawJack(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, lumberjackColor);
    } else {
        drawLetter(x * size, y * size, 'J');
    }
}

function drawTree(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, treeColor);
    } else {
        drawLetter(x * size, y * size, 'T');
    }
}

function drawSapling(x, y) {
    if (drawUsingColor) {
        drawRect(x * size, y * size, size, size, saplingColor);
    } else {
        drawLetter(x * size, y * size, 'S');
    }
}

function drawLine(x1, y1, x2, y2, c) {
    context.beginPath();
    context.moveTo(x1, y1);
    context.lineTo(x2, y2);
    context.lineWidth = 3;
    context.strokeStyle = c;
    context.stroke();
}

function drawRect(x, y, w, h, c) {
    context.fillStyle = c;
    context.fillRect(x, y, w, h);
}

function drawLetter(x, y, l) {
    context.fillText(l, x, y);
}

$(document).ready(function () {
    initWorld();
    intervalID = window.setInterval(animateWorld, intervalTime);
    /*$('#s').click(function() {
        animateWorld();
    })*/
});

// http://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
function shuffle(array) {
    var currentIndex = array.length,
        temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {

        // Pick a remaining element...
        randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex -= 1;

        // And swap it with the current element. 
        temporaryValue = array[currentIndex];
        array[currentIndex] = array[randomIndex];
        array[randomIndex] = temporaryValue;
    }
    return array;
}
Кевин Л
источник
Что-то странное в лесах большого размера (например, попробуйте n = 50).
Blackhole
@ Чёрная дыра что-то странное = ____?
Кевин Л.
Я замечаю, что медведи застревают, и лес захватывает нижнюю часть карты, так как я порождаю дровосеков в верхней части карты
Кевин Л.
1
Что-то не так с тем, как появляются новые медведи и дровосеки. Я думаю, что размещение всего в верхнем левом углу немного меняет глобальный баланс системы.
3

питон

Ничего фантастического. Я продолжал добавлять вещи, так что рефакторинг может быть в порядке. (И я не делал unitest, поэтому ошибки все еще могут присутствовать).

Я дал случайные имена лесорубам и медведям. Деревья i, затем I, затем #, Лесорубы являются x, медведиo

import os

from random import randint, choice, shuffle
from time import sleep

NGRID = 15
SLEEPTIME = 0.0125
DURATION = 4800
VERBOSE = True

###init
grid = [[[] for _ in range(NGRID)] for _ in range(NGRID)]
#Money earned this year
n_lumbers = 0
#Lumberjacks killed this year
n_maul = 0

tick = 0
events = []
#total number of
d_total = {'trees':0,
           'lumberjacks': 0,
           'bears': 0,
           'cut': 0,
           'maul': 0,
           'capture': 0,
           'lumbers': 0,
           'fired': 0}

d_oldest = {'tree': 0,
            'lumberjack': (0, ""),
            'bear': (0, "")}

d_most = {'n_maul': (0, ""),
          'n_lumber': (0, ""),
          'n_cut': (0, "")}

d_year = {'n_maul': 0,
          'n_lumber': 0}

###Classes
class Tree(object):
    """Represent a Sapling, Tree, or Elder Tree"""
    def __init__(self, coords, m=0, t='Sapling'):
        self.months = m
        self.typeof = t
        self.coords = coords
    def grow(self, m=1):
        """the tree grows 1 month and its type might change"""
        self.months = self.months + m
        if self.months == 12:
            self.typeof = 'Tree'
        elif self.months == 480:
            self.typeof = 'Elder Tree'
    def __str__(self):
        if self.typeof == 'Sapling':
            return 'i'
        elif self.typeof == 'Tree':
            return 'I'
        else:
            return '#'

class Animated(object):
    """Animated beings can move"""
    def __init__(self, coords):
        self.coords = coords
        self.old_coords = None
        self.months = 0
    def where(self):
        return c_neighbors(self.coords)
    def choose_new_coords(self):
        self.old_coords = self.coords
        possible = self.where()
        if possible:
            direction = choice(self.where())
            self.coords = [(self.coords[i]+direction[i]) % NGRID for i in range(2)]
#    def __del__(self):
#        print "died at "+ str(self.coords)


class Lumberjack(Animated):
    """Lumberjacks chop down trees"""
    def __init__(self, coords):
        super(Lumberjack, self).__init__(coords)
        self.nb_cut = 0
        self.nb_lumber = 0
        self.name = gen_name("l")
    def __str__(self):
        return "x"

class Bear(Animated):
    """Bears maul"""
    def __init__(self, coords):
        super(Bear, self).__init__(coords)
        self.nb_maul = 0
        self.name = gen_name("b")
    def where(self):
        return c_land_neighbors(self.coords)
    def __str__(self):
        return "o"

###list of coords
def c_neighbors(coords):
    """returns the list of coordinates of adjacent cells"""
    return [[(coords[0] + i) % NGRID, (coords[1] + j) % NGRID] \
            for i in [-1, 0, 1] \
            for j in [-1, 0, 1] \
            if (i,j) != (0, 0)]

def c_empty_neighbors(coords):
    """returns the list of coordinates of adjacent cells that are empty """
    return [[i, j] for [i,j] in c_neighbors(coords) if grid[i][j] == []]

def c_land_neighbors(coords):
    """returns the list of coordinates of adjacent cells that contain not Trees
    for bears"""
    return [[i, j] for [i,j] in c_neighbors(coords)\
            if (grid[i][j] == []) or (not isinstance(grid[i][j][0], Tree))]

def c_empty_cells():
    """returns list of coords of empty cells in the grid"""
    return [[i, j] for i in range(NGRID) for j in range(NGRID) if grid[i][j] == []]

def c_not_bear_cells():
    """returns list of coords of cells without bear"""
    return [[i, j] for i in range(NGRID) for j in range(NGRID) \
            if not isinstance(grid[i][j], Bear)]

###one less
def maul(lumberjack):
    """a lumberjack will die"""
    global n_maul
    n_maul = n_maul + 1
    d_total['maul'] = d_total['maul'] + 1
    remove_from_grid(lumberjack.coords, lumberjack)
    return lumberjack.name + " is sent to hospital" + check_lumberjacks()

def capture_bear():
    """too many mauls, a Zoo traps a bear"""
    d_total['capture'] = d_total['capture'] + 1
    bear = choice(get_bears())
    remove_from_grid(bear.coords, bear)
    return bear.name + " has been captured"

def fire_lumberjack():
    """the job is not done correctly, one lumberjack is let go"""
    d_total['fired'] = d_total['fired'] + 1
    lumberjack = choice(get_lumberjacks())
    remove_from_grid(lumberjack.coords, lumberjack)
    return lumberjack.name + " has been fired" + check_lumberjacks()

def remove_from_grid(coords, item):
    """remove item from the grid at the coords"""
    grid[coords[0]][coords[1]].remove(item)
    del item

###one more
def new_animate(class_):
    """a new lumberjack or bear joins the forest"""
    if class_==Bear:
        d_total['bears'] = d_total['bears'] + 1
        x, y = choice(c_empty_cells())
    else:
        d_total['lumberjacks'] = d_total['lumberjacks'] + 1
        x, y = choice(c_not_bear_cells())
    new_being = class_([x,y])
    grid[x][y].append(new_being)
    return "a new " + class_.__name__ + " enters the forest: " + new_being.name

def check_lumberjacks():
    """we will never reduce our Lumberjack labor force below 0"""
    if len(get_lumberjacks())==0:
        return " - no more lumberjack, " + new_animate(Lumberjack)
    return ""

###movements
def move_on_grid(being):
    [x, y] = being.old_coords
    grid[x][y].remove(being)
    [x, y] = being.coords
    grid[x][y].append(being)

def move_lumberjack(lumberjack):
    """Lumberjacks move 3 times if they don't encounter a (Elder) Tree or a Bear"""
    global n_lumbers
    for _ in range(3):
        lumberjack.choose_new_coords()
        move_on_grid(lumberjack)
        [x, y] = lumberjack.coords
        #is there something at the new coordinate?
        #move append so this lumberjack is at the end
        if grid[x][y][:-1] != []:
            if isinstance(grid[x][y][0], Tree):
                the_tree = grid[x][y][0]
                price = worth(the_tree)
                if price > 0:
                    lumberjack.nb_cut = lumberjack.nb_cut + 1
                    d_most['n_cut'] = max((lumberjack.nb_cut, lumberjack.name), \
                                           d_most['n_cut'])
                    d_total['cut'] = d_total['cut'] + 1
                    n_lumbers = n_lumbers + price
                    d_total['lumbers'] = d_total['lumbers'] + 1
                    lumberjack.nb_lumber = lumberjack.nb_lumber + price
                    d_most['n_lumber'] = max(d_most['n_lumber'], \
                                             (lumberjack.nb_lumber, lumberjack.name))
                    remove_from_grid([x, y], the_tree)
                    return lumberjack.name + " cuts 1 " + the_tree.typeof
            #if there is a bear, all lumberjacks have been sent to hospital
            if isinstance(grid[x][y][0], Bear):
                #the first bear is the killer
                b = grid[x][y][0]
                b.nb_maul = b.nb_maul + 1
                d_most['n_maul'] = max((b.nb_maul, b.name), d_most['n_maul'])
                return maul(lumberjack)
    return None

def move_bear(bear):
    """Bears move 5 times if they don't encounter a Lumberjack"""
    for _ in range(5):
        bear.choose_new_coords()
        move_on_grid(bear)
        [x, y] = bear.coords
        there_was_something = (grid[x][y][:-1] != [])
        if there_was_something:
            #bears wander where there is no tree
            #so it's either a lumberjack or another bear
            #can't be both.
            if isinstance(grid[x][y][0], Lumberjack):
                bear.nb_maul = bear.nb_maul + 1
                d_most['n_maul'] = max((bear.nb_maul, bear.name), \
                                       d_most['n_maul'])
                return maul(grid[x][y][0])
    return None

###get objects
def get_objects(class_):
    """get a list of instances in the grid"""
    l = []
    for i in range(NGRID):
        for j in range(NGRID):
          if grid[i][j]:
              for k in grid[i][j]:
                  if isinstance(k, class_):
                      l.append(k)
    return l

def get_trees():
    """list of trees"""
    return get_objects(Tree)

def get_bears():
    """list of bears"""
    return get_objects(Bear)

def get_lumberjacks():
    """list of lumberjacks"""
    return get_objects(Lumberjack)

###utils
def gen_name(which="l"):
    """generate random name"""
    name = ""
    for _ in range(randint(1,4)):
        name = name + choice("bcdfghjklmnprstvwxz") + choice("auiey")
    if which == "b":
        name = name[::-1]
    return name.capitalize()

def worth(tree):
    """pieces for a tree"""
    if tree.typeof == 'Elder Tree':
        return 2
    if tree.typeof == 'Tree':
        return 1
    return 0

def one_month():
    """a step of one month"""
    events = []
    global tick
    tick = tick + 1
    #each Tree can spawn a new sapling
    for t in get_trees():
        l_empty_spaces = c_empty_neighbors(t.coords)
        percent = 10 if t.typeof == 'Tree' else \
                  20 if t.typeof == 'Elder Tree' else 0
        if (randint(1,100) < percent):
            if l_empty_spaces:
                [x, y] = choice(l_empty_spaces)
                grid[x][y] = [Tree([x,y])]
                d_total['trees'] = d_total['trees'] + 1
        t.grow()
        d_oldest['tree'] = max(t.months, d_oldest['tree'])
    #each lumberjack/bear moves
    for l in get_lumberjacks():
        l.months = l.months + 1
        d_oldest['lumberjack'] = max((l.months, l.name), \
                                     d_oldest['lumberjack'])
        event = move_lumberjack(l)
        if event:
            events.append(event)
    for b in get_bears():
        b.months = b.months + 1
        d_oldest['bear'] = max((b.months, b.name), d_oldest['bear'])
        event = move_bear(b)
        if event:
            events.append(event)
    return events

def print_grid():
    """print the grid
    if more than 1 thing is at a place, print the last.
    At 1 place, there is
    - at most a tree and possibly several lumberjack
    - or 1 bear
    """
    print "-" * 2 * NGRID
    print '\n'.join([' '.join([str(i[-1]) if i != [] else ' ' \
                               for i in line]) \
                     for line in grid])
    print "-" * 2 * NGRID

def clean():
    """clear the console"""
    os.system('cls' if os.name == 'nt' else 'clear')

def print_grid_and_events():
    """print grid and list of events"""
    clean()
    print_grid()
    if VERBOSE:
        print '\n'.join(events)
        print "-" * 2 * NGRID

###populate the forest
l = c_empty_cells()
shuffle(l)
for x, y in l[:((NGRID*NGRID) / 2)]:
    grid[x][y] = [Tree([x, y], 12, 'Tree')]
    d_total['trees'] = d_total['trees'] + 1

l = c_empty_cells()
shuffle(l)
for x, y in l[:((NGRID*NGRID) / 10)]:
    grid[x][y] = [Lumberjack([x, y])]
    d_total['lumberjacks'] = d_total['lumberjacks'] + 1

l = c_empty_cells()
shuffle(l)
for x, y in l[:((NGRID*NGRID) / 10)]:
    grid[x][y] = [Bear([x, y])]
    d_total['bears'] = d_total['bears'] + 1

###time goes on
while (tick <= DURATION and len(get_trees())>0):
    events = one_month()
    #end of the year
    if (tick % 12)==0:
        events.append("End of the year")
        #lumber tracking
        nlumberjacks = len(get_lumberjacks())
        events.append(str(n_lumbers) + " lumbers VS " +\
                      str(nlumberjacks) + " Lumberjacks")
        if n_lumbers >= nlumberjacks:
            n_hire = n_lumbers/nlumberjacks
            events.append("we hire " + str(n_hire) +\
                          " new Lumberjack" + ("s" if (n_hire > 1) else ""))
            for _ in range(n_hire):
                events.append(new_animate(Lumberjack))
        else:
            events.append(fire_lumberjack())
        d_year['n_lumber'] = max(d_year['n_lumber'], n_lumbers)
        n_lumbers = 0
        #maul tracking
        events.append("maul this year: " + str(n_maul))
        if n_maul == 0:
            events.append(new_animate(Bear))
        else:
            events.append(capture_bear())
        d_year['n_maul'] = max(d_year['n_maul'], n_maul)
        n_maul = 0
    print_grid_and_events()
    sleep(SLEEPTIME)

print "-"*70
print "End of the game"
print "-"*70
print "month:" + str(tick - 1)
print "number of trees still alive: " + str(len(get_trees()))
print "number of lumberjacks still alive: " + str(len(get_lumberjacks()))
print "number of bears still alive: " + str(len(get_bears()))

print "-"*70
print "oldest Tree ever is/was: " + str(d_oldest['tree'])
print "oldest Lumberjack ever is/was: " + str(d_oldest['lumberjack'][0]) + \
    " yo " + d_oldest['lumberjack'][1]
print "oldest Bear ever is/was: " + str(d_oldest['bear'][0]) + \
    " yo " + d_oldest['bear'][1]
print "-"*70
print "max cut by a Lumberjack: " + str(d_most['n_cut'][0]) + \
    " by " + str(d_most['n_cut'][1])
print "max lumber by a Lumberjack: " + str(d_most['n_lumber'][0]) + \
    " by " + str(d_most['n_lumber'][1])
print "max maul by a Bear: " + str(d_most['n_maul'][0]) + \
    " by " + str(d_most['n_maul'][1])
print "-"*70
print "max lumber in a year: " + str(d_year['n_lumber'])
print "max maul in a year: " + str(d_year['n_maul'])
print "-"*70
print "Total of:"
for i, j in d_total.items():
    print i, str(j)

Некоторые выводы:

------------------------------
          x



    I
    I
  x   i                     I

      i i i   i       I I x i
i   I   I i I I   i i     o
      i i I I I           i
    i I   x i
    I I   I I
      I     I i
            x
------------------------------
Dy is sent to hospital
Lehuniru cuts 1 Tree
------------------------------

Конец года

------------------------------
            x

    x

i I     I
    i     I               x
      I
                          i i
      x                   I I
  i I   i I     i       i
  I         i I
            i x i
    I         i I
          o
        x
------------------------------
Fuha cuts 1 Tree
Ka cuts 1 Tree
Ky is sent to hospital
End of the year
11 lumbers VS 4 Lumberjacks
we hire 2 new Lumberjacks
a new Lumberjack enters the forest: Di
a new Lumberjack enters the forest: Dy
maul this year: 6
Evykut has been captured
------------------------------

Конец игры

------------------------------
          x
  i
        x     x

          x                 x
                  x i     x
    i               I
    I i x x   I i           x
                    x   i   i
      x i i i i I
      i i I   I i I   i
I       i     i i
        i   x   i
            I   i I
    I I   x i   I I         x
------------------------------
Vanabixy cuts 1 Tree
Fasiguvy cuts 1 Tree
------------------------------
----------------------------------------------------------------------
End of the game
----------------------------------------------------------------------
month:4800
number of trees still alive: 36
number of lumberjacks still alive: 15
number of bears still alive: 0
----------------------------------------------------------------------
oldest Tree ever is/was: 129
oldest Lumberjack ever is/was: 308 yo Cejuka
oldest Bear ever is/was: 288 yo Ekyx
----------------------------------------------------------------------
max cut by a Lumberjack: 44 by Cejuka
max lumber by a Lumberjack: 44 by Cejuka
max maul by a Bear: 52 by Ekyx
----------------------------------------------------------------------
max lumber in a year: 84
max maul in a year: 86
----------------------------------------------------------------------
Total of:
bears 211
cut 5054
fired 67
capture 211
lumberjacks 1177
lumbers 5054
maul 1095
trees 5090
fredtantini
источник