某島

… : "…アッカリ~ン . .. . " .. .
April 28, 2020

Untrusted 魔改 —— 大地圖與地圖滾動

完成品

var Game = {
    display: null,
    map: {},
    width: 0,
    height: 0,
    camera_x: 0,
    camera_y: 0,
    offset_x: 0,
    offset_y: 0,
    engine: null,
    player: null,
    pedro: null,
    ananas: null,
    
    init: function() {
        this.display = new ROT.Display({
        	width: 40,
        	height: 20,
        	spacing:1.1
        });

        document.body.appendChild(this.display.getContainer());
        
        this._generateMap();
        
        var scheduler = new ROT.Scheduler.Simple();
        scheduler.add(this.player, true);
        scheduler.add(this.pedro, true);

        this.engine = new ROT.Engine(scheduler);
        this.engine.start();
    },

    _adjustCamera() {
    	let o = this.display.getOptions();
        let w = o.width;
        let h = o.height;
    	
    	if (this.camera_x - this.offset_x < 0) this.offset_x += this.camera_x - this.offset_x;    	
    	else if (this.width - this.camera_x + this.offset_x < w) this.offset_x -= (this.width - this.camera_x + this.offset_x) - w + 1;
    	
    	if (this.camera_y - this.offset_y < 0) this.offset_y += this.camera_y - this.offset_y;
    	else if (this.height - this.camera_y + this.offset_y < h) this.offset_y -= (this.height - this.camera_y + this.offset_y) - h + 1;
    },

    _moveCamera: function(dx, dy) {        
    	this.camera_x += dx; this.camera_y += dy;
        let o = this.display.getOptions();
        let w = o.width, h = o.height; 
        let ww = Math.floor(w/2);
        let hh = Math.floor(h/2);

        console.log(dx, this.camera_x, ww);

        if (dx > 0 && this.camera_x < ww || dx < 0 && this.camera_x > this.width - ww) {
        	this.offset_x += dx; 
        } else if (dy > 0 && this.camera_y < hh || dy < 0 && this.camera_y > this.height - ww){
        	this.offset_y += dy;
        } else {
        	this._adjustCamera();	
        }
    },

    _generateMap: function() {    	
    	this.width = 64;
    	this.height = 32;
        var digger = new ROT.Map.Digger(this.width, this.height);
        //var digger = new ROT.Map.Arena(this.width, this.height); 
        var freeCells = [];
        
        var digCallback = function(x, y, value) {
            if (value) { return; }            
            var key = x+","+y;
            this.map[key] = ".";
            freeCells.push(key);
        }
        digger.create(digCallback.bind(this));
        
        this._generateBoxes(freeCells);
        this._drawWholeMap();
        
        this.player = this._createBeing(Player, freeCells);               
        this.pedro = this._createBeing(Pedro, freeCells);                
        let o = this.display.getOptions();
        let w = o.width;
        let h = o.height;        
		
		this.camera_x = this.player._x;
        this.camera_y = this.player._y;
        this.offset_x = Math.floor(w/2);
        this.offset_y = Math.floor(h/2);
        this._adjustCamera();
        this._drawWholeMap();
    },
    
    _createBeing: function(what, freeCells) {
        var index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
        var key = freeCells.splice(index, 1)[0];
        var parts = key.split(",");
        var x = parseInt(parts[0]);
        var y = parseInt(parts[1]);        
        return new what(x, y);
    },
    
    _generateBoxes: function(freeCells) {
        for (var i=0;i<10;i++) {
            var index = Math.floor(ROT.RNG.getUniform() * freeCells.length);
            var key = freeCells.splice(index, 1)[0];
            this.map[key] = "*";
            if (!i) { this.ananas = key; } /* first box contains an ananas */
        }
    },

    _drawWholeMap: function() {
        let o = this.display.getOptions();
        let w = o.width;
        let h = o.height; 
  	
        for (let x=0;x<w;++x) {
        	for (let y=0;y<h;++y) {
        		let xx = x + this.camera_x - this.offset_x;
        		let yy = y + this.camera_y - this.offset_y;
        		let key = xx+','+yy;        		
        		this.display.draw(x, y, this.map[key]);        		
        	}
        }
        if (this.player) this.player._draw();
        if (this.pedro) this.pedro._draw();
    }
};

var Player = function(x, y) {
    this._x = x;
    this._y = y;
    this._draw();
}
    
Player.prototype.getSpeed = function() { return 100; }
Player.prototype.getX = function() { return this._x; }
Player.prototype.getY = function() { return this._y; }

Player.prototype.act = function() {
    Game.engine.lock();
    window.addEventListener("keydown", this);
}


    
Player.prototype.handleEvent = function(e) {
    var code = e.keyCode;
    if (code == 13 || code == 32) {
        this._checkBox();
        return;
    }

    var keyMap = {};
    keyMap[38] = 0;
    keyMap[33] = 1;
    keyMap[39] = 2;
    keyMap[34] = 3;
    keyMap[40] = 4;
    keyMap[35] = 5;
    keyMap[37] = 6;
    keyMap[36] = 7;

    /* one of numpad directions? */
    if (!(code in keyMap)) { return; }

    /* is there a free space? */
    var dir = ROT.DIRS[8][keyMap];
    var newX = this._x + dir[0];
    var newY = this._y + dir[1];

    var newKey = newX + "," + newY;
    if (!(newKey in Game.map)) { return; }

    this._x = newX;
    this._y = newY;
    Game._moveCamera(dir[0], dir[1]);
    Game._drawWholeMap();
    window.removeEventListener("keydown", this);
    Game.engine.unlock();
}

Player.prototype._draw = function() {
    Game.display.draw(this._x - Game.camera_x + Game.offset_x, this._y - Game.camera_y + Game.offset_y, "@", "#ff0");
}
    
Player.prototype._checkBox = function() {
    var key = this._x + "," + this._y;
    if (Game.map[key] != "*") {
        alert("There is no box here!");
    } else if (key == Game.ananas) {
        alert("Hooray! You found an ananas and won this game.");
        Game.engine.lock();
        window.removeEventListener("keydown", this);
    } else {
        alert("This box is empty :-(");
    }
}
    
var Pedro = function(x, y) {
    this._x = x;
    this._y = y;
    this._draw();
}
    
Pedro.prototype.getSpeed = function() { return 100; }
    
Pedro.prototype.act = function() {
    var x = Game.player.getX();
    var y = Game.player.getY();
    return;


    var passableCallback = function(x, y) {
        return (x+","+y in Game.map);
    }
    var astar = new ROT.Path.AStar(x, y, passableCallback, {topology:4});

    var path = [];
    var pathCallback = function(x, y) {
        path.push([x, y]);
    }
    astar.compute(this._x, this._y, pathCallback);

    path.shift();
    if (path.length == -1) {
        Game.engine.lock();
        alert("Game over - you were captured by Pedro!");
    } else {
        x = path[0][0];
        y = path[0][1];
        //Game.display.draw(this._x, this._y, Game.map[this._x+","+this._y]);
        this._x = x;
        this._y = y;
        //this._draw();
        Game._drawWholeMap();
    }
}
    
Pedro.prototype._draw = function() {
    Game.display.draw(this._x - Game.camera_x + Game.offset_x, this._y - Game.camera_y + Game.offset_y, "P", "red");
}    


Game.init();

map.js

    this.getWidth = function () { return __game._dimensions.width; };
    this.getHeight = function () { return __game._dimensions.height; };
        if (x < 0 || x >= __game._dimensions.width || y < 0 || y >= __game._dimensions.height) {
            return false;
        }

game.js

    this._dimensions = {
        width: 50,
        height: 25
    };

display.js

        this.offset = i + 3;
        this.drawAll(map);
ROT.Display.prototype.drawAll = function(map) {
    if (!this.offset) {this.offset = 0;}

    var game = this.game;

    // _initialize grid
    var grid = new Array(game._dimensions.width);
    for (var x = 0; x < game._dimensions.width; x++) {
        grid[x] = new Array(game._dimensions.height);
        for (var y = 0; y < game._dimensions.height; y++) {
            grid[x][y] = {
                type: 'empty',
                bgColor: 'black'
            };
        }
    }

    // place static objects
    for (var x = 0; x < game._dimensions.width; x++) {
        for (var y = 0; y < game._dimensions.height; y++) {
            grid[x][y] = {
                type: map._getGrid()[x][y].type,
                bgColor: map._getGrid()[x][y].bgColor
            };
        }
    }

    // place dynamic objects
    for (var i = 0; i < map.getDynamicObjects().length; i++) {
        var obj = map.getDynamicObjects()[i];
        grid[obj.getX()][obj.getY()] = {
            type: obj.getType(),
            bgColor: map._getGrid()[obj.getX()][obj.getY()].bgColor
        };
    }

    // place player
    if (map.getPlayer()) {
        var player = map.getPlayer();
        grid[player.getX()][player.getY()] = {
            type: 'player',
            color: player.getColor(),
            bgColor: map._getGrid()[player.getX()][player.getY()].bgColor
        }
    }

    // draw grid
    for (var x = 0; x < game._dimensions.width; x++) {
        for (var y = Math.max(0, this.offset - map.getHeight()); y < game._dimensions.height; y++) {
            this.drawObject(map, x, y + this.offset, grid[x][y]);
        }
    }

    // write error messages, if any
    if (this.errors && this.errors.length > 0) {
        for (var i = 0; i < this.errors.length; i++) {
            var y = this.game._dimensions.height - this.errors.length + i;
            this.drawText(0, y, this.errors[i]);
        }
    }

    // store for potential later use
    this.grid = grid;
};

查了一下這個 ROT 居然是一個專門用於 Roguelike 遊戲的包。。
Github, ROguelike Toolkit

python -m http.server