2016年9月18日日曜日

開発環境

Eloquent JavaScript(Marijn Haverbeke 著、No Starch Press)のPart 1(Language)、Chapter 7(Project: Electronic Life)、Exercises(Artificial Stupidity)を取り組んでみる。

Exercises(Artificial Stupidity)

コード(Emacs)

HTML5

<pre>
  <div id="output0"></div>
</pre>
<label for="speed0">speed: </label>
<input id="speed0" type="number" min="1" step="1" value="500">

<script src="array.js"></script>
<script src="sample1.js"></script>

JavaScript

(function () {
    'use strict';
    var vector,
        grid,
        directions,
        bouncingCritter,
        elementFromChar,
        charFromElement,
        directionNames,
        world,
        wall,
        lifelikeWorld,
        view,
        valley,
        i,
        plan,
        dirPlus,
        wallFollower,
        actionTypes,
        plant,
        plantCount = 0,
        plantEater,
        smartPlantEater,
        div_output = document.querySelector('#output0'),
        input_speed = document.querySelector('#speed0'),
        intervalID;

    vector = function (x, y) {
        var that;

        that = {
            x: x,
            y: y,
            plus: function (other) {
                return vector(x + other.x, y + other.y);
            },
        };
        return that;
    };
    grid = function (width, height) {
        var that,
            space = new Array(width * height);

        that = {
            get width() { return width; },
            get height() { return height;},
            get space() { return space; },
            isInside: function(vector) {
                return vector.x >= 0 && vector.x < width &&
                    vector.y >= 0 && vector.y < height;
            },
            get: function (vector) {
                return space[vector.x + width * vector.y];
            },
            set: function (vector, value) {
                space[vector.x + width * vector.y] = value;
            },
            forEach: function (f, context) {
                var x,
                    y,
                    y_max = height,
                    x_max = width,
                    value;

                for (y = 0; y < height; y += 1) {
                    for (x = 0; x < width; x += 1) {
                        value = space[x + y * width];
                        if (value !== null) {
                            f(value, vector(x, y));
                        }
                    }
                }
            },
        };
        return that;
    };

    directions = {
        n: vector(0, -1),
        ne: vector(1, -1),
        e: vector(1, 0),
        se: vector(1, 1),
        s: vector(0, 1),
        sw: vector(-1, 1),
        w: vector(-1, 0),
        nw: vector(-1, -1),
    };
    directionNames = Object.keys(directions);
    bouncingCritter = function (view) {
        var that,
            direction = directionNames.random();;

        that = {
            act: function (view) {
                if (view.look(direction) !== ' ') {
                    direction = view.find(' ') || 's';
                }
                return {type: 'move', direction: direction};
            },
        };
        return that;
    };
    elementFromChar = function (legend, ch) {
        var elem;
        
        if (ch === ' ') {
            return null;
        }
        elem = legend[ch]();
        elem.originChar = ch;
        return elem;
    };
    charFromElement = function (elem) {
        if (elem === null) {
            return ' ';
        }
        return elem.originChar;
    };
    world = function (map, legend) {
        var that,
            grid0 = grid(map[0].length, map.length),
            letAct,
            turn,
            checkDestination;

        map.forEach(function (line, y) {
            var x,
                x_max = line.length;

            for (x = 0; x < x_max; x += 1) {
                grid0.set(vector(x, y), elementFromChar(legend, line[x]));
            }
        });
        checkDestination = function (action, vector) {
            var dest;
            
            if (directions.hasOwnProperty(action.direction)) {
                dest = vector.plus(directions[action.direction]);
                if (grid0.isInside(dest)) {
                    return dest;
                }
            }
        };
        letAct = function (critter, vector) {
            var action = critter.act(view(that, vector)),
                dest;

            if (action && action.type === 'move') {
                dest = checkDestination(action, vector);
                if (dest && grid0.get(dest) === null) {
                    grid0.set(vector, null);
                    grid0.set(dest, critter);
                }
            }
        };
        turn = function () {
            var acted = [];

            grid0.forEach(function (critter, vector) {
                if (critter.act && acted.indexOf(critter) === -1) {
                    acted.push(critter);
                    that.letAct(critter, vector);
                }
            });
        };
        that = {
            get legend() { return legend; },
            get grid() { return grid0; },
            letAct: letAct,
            toString: function () {
                var output = '',
                    y,
                    y_max = grid0.height,
                    x,
                    x_max = grid0.width,
                    elem;

                for (y = 0; y < y_max; y += 1) {
                    for (x = 0; x < x_max; x += 1) {
                        elem = grid0.get(vector(x, y));
                        output += charFromElement(elem);
                    }
                    output += '<br>';
                }
                return output;
            },
            turn: turn,
            checkDestination: checkDestination
        };
        return that;
    };
    wall = function () {
        var that = {};

        return that;
    };
    view = function (world, vector) {
        var that,
            look,
            findAll,
            find,
            pre;

        look = function (dir) {        
            var target = vector.plus(directions[dir]);

            if (world.grid.isInside(target)) {
                return charFromElement(world.grid.get(target));
            }
            return '#';
        };
        findAll =  function (ch) {
            var found = [];

            directionNames.forEach(function (dir) {
                if (look(dir) === ch) {
                    found.push(dir);
                }
            });
            return found;
        };
        find = function (ch) {
            var found = findAll(ch),
                t;

            if (found.length === 0) {
                return null;
            }
            for (t = found.random(); pre === t; t = found.random()) {
                ;
            }
            return t;
        };
        that = { look: look, findAll: findAll, find: find};
        return that;
    };
    dirPlus = function (dir, n) {
        var index = directionNames.indexOf(dir);

        return directionNames[(index + n + 8) % 8];
    };
    wallFollower = function () {
        var that,
            dir = 's';

        that = {
            dir: dir,
            act: function (view) {
                var start = dir;

                if (view.look(dirPlus(dir, -3)) !== ' ') {
                    start = dir = dirPlus(dir, -2);
                }
                for (; view.look(dir) !== ' '; ) {
                    dir = dirPlus(dir, 1);
                    if (dir === start) {
                        break;
                    }
                }
                return {type: 'move', direction: dir};
            },
        };
        return that;
    };

    lifelikeWorld = function (map, legend) {
        var that = world(map, legend);
        
        that.letAct = function (critter, vector) {
            var action = critter.act(view(that, vector)),
                handled;

            handled = action && action.type in actionTypes &&
                actionTypes[action.type](that, critter, vector, action);

            if (!handled) {
                critter.energy -= 0.2;
                if (critter.energy <= 0) {
                    that.grid.set(vector, null);
                }
            }
        };
        return that;
    };
    actionTypes = Object.create(null);
    actionTypes.grow = function (that, critter) {
        critter.energy += 0.5;
        return true;
    };
    actionTypes.move = function (that, critter, vector, action) {
        var dest = that.checkDestination(action, vector);

        if (dest === null || critter.energy <= 1 || that.grid.get(dest) !== null) {
            return false;
        }
        critter.energy -= 1;
        that.grid.set(vector, null);
        that.grid.set(dest, critter);
        return true;
    };
    actionTypes.stay = function () {
        return true;
    };
    actionTypes.eat = function (that, critter, vector, action) {
        var dest = that.checkDestination(action, vector),
            atDest = dest !== null && that.grid.get(dest);

        plantCount -= 1;
        if (!atDest || atDest.energy === null) {
            return false;
        }
        critter.energy += atDest.energy;
        that.grid.set(dest, null);
        return true;
    };

    actionTypes.reproduce = function (that, critter, vector, action) {    
        var baby = elementFromChar(that.legend, critter.originChar),
            dest = that.checkDestination(action, vector);

        if (dest === null || critter.energy <= 2 * baby.energy ||
            that.grid.get(dest) !== null) {
            return false;
        }    
        critter.energy -= 2 * baby.energy;
        that.grid.set(dest, baby);
        return true;
    };
    plant = function () {
        var that,
            energy = 3 + Math.random() * 4; 

        plantCount += 1;
        that = {
            energy: energy,
            act: function (context) {
                var space;

                if (this.energy > 15) {
                    space = context.find(' ');
                    if (space) {
                        return {type: 'reproduce', direction: space};
                    }
                }
                if (this.energy < 20) {
                    return {type: 'grow'};
                }
            },
        };

        return that;
    };
    plantEater = function () {
        var that,
            energy = 20;

        that = {
            energy: energy,
            act: function (context) {
                var space = context.find(' '),
                    plant;

                if (energy > 60 && space) {
                    return {type: 'reproduce', direction: space};
                }
                plant = context.find('*');
                if (plant) {
                    return {type: 'eat', direction: plant};
                }
                if (space) {
                    return {type: 'move', direction: space};
                }
            },
        };
        return that;
    };
    smartPlantEater = function () {
        var that = plantEater();

        that.act = function (context) {
            var space = context.find(' '),
                plant;

            if (that.energy > 60 && space) {
                return {type: 'reproduce', direction: space};
            }
            plant = context.find('*');
            if (plant) {
                if (plantCount === 5) {
                    return {type: 'stay'};
                }
                return {type: 'eat', direction: plant};
            }
            if (space) {
                return {type: 'move', direction: space};
            }
        };
        return that;
    };
    valley = lifelikeWorld(
        ["############################",
         '#####                  #####',
         '##   ***                **##',
         '#   *##**        **  O   *##',
         '#    ***     O   ##**     *#',
         '#       O        ##***     #',
         '#                ##**      #',
         '#  O        #*             #',
         '#*          #**       O    #',
         '#***        ##**   O     **#',
         '##****     ###***       *###',
         '############################'],
        {
            '#': wall,
            'O': smartPlantEater,
            '*': plant,
        }
    );
    intervalID = setInterval(function () {    
        valley.turn();
        div_output.innerHTML = valley.toString();
    }, parseInt(input_speed.value, 10));
    input_speed.onchange = function () {
        var t = parseInt(input_speed.value, 10);

        clearInterval(intervalID);
        intervalID = setInterval(function () {
            valley.turn();
            div_output.innerHTML = valley.toString();
        }, t);
    };
}());
  

0 コメント:

コメントを投稿