前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >js模块化例子

js模块化例子

作者头像
黒之染
发布2018-10-19 14:29:50
4.6K0
发布2018-10-19 14:29:50
举报

最近在看一本书,里面提到js的模块化,觉得很有必要,所以记录下来 Game.js

代码语言:javascript
复制
/**
 * This is the main class that handles the game life cycle. It initializes
 * other components like Board and BoardModel, listens to the DOM events and
 * translates clicks to coordinates.
 * @param canvas the canvas object to use for drawing
 */
function Game(canvas) {
    this._boardRect = null;
    this._canvas = canvas;
    this._ctx = canvas.getContext("2d");
    this._boardModel = new BoardModel();//实例化类

    this._boardRenderer = new boardRenderer(this._ctx, this._boardModel);
    this.handleResize();
}

_h = Game.prototype;

/**
 * Handles the click (or tap) on the Canvas. Translates the canvas coordinates
 * into the column of the game board and makes the next turn in that column
 * @param x the x coordinate of the click or tap
 * @param y the y coordinate of the click or tap
 */
_h.handleClick = function(x, y) {
    // 获取列的索引
    var column = Math.floor((x - this._boardRect.x)/this._boardRect.cellSize);

    //生成回合并检查结果
    var turn = this._boardModel.makeTurn(column);

    // 如果回合有效,更新游戏盘并绘制新球
    if (turn.status != BoardModel.ILLEGAL_TURN) {
        this._boardRenderer.drawToken(turn.x, turn.y);
    }

    // Do we have a winner after the last turn?
    if (turn.status == BoardModel.WIN) {
        // Tell the world about it and reset the board for the next game
        alert((turn.piece == BoardModel.RED ? "red" : "green") + " won the match!");
        this._reset();
    }

    // If we have the draw, do the same
    if (turn.status == BoardModel.DRAW) {
        alert("It is a draw!");
        this._reset();
    }
};

/**
 * Reset the _boardModel and redraw the board.
 */
_h._reset = function() {
    this._clearCanvas();
    this._boardModel.reset();
    this._boardRenderer.repaint();
};

/**
 * Called when the screen has resized. In this case we need to calculate
 * new size and position for the game board and repaint it.
 */
_h.handleResize = function() {
    this._clearCanvas();
    this._boardRect = this._getBoardRect();//拿到画棋盘的3个重要参数
    this._boardRenderer.setSize(this._boardRect.x, this._boardRect.y, this._boardRect.cellSize);
    this._boardRenderer.repaint();
};

/**
 * Get the optimal position and the size of the board
 * @return the object that describes the optimal position and
 * size for the board:
 * {
 *      x: the x of the top-left corner of the board
 *      y: the y of the top-left corner of the board
 *      cellSize: the optimal size of the cell (in pixels)
 * }
 */
_h._getBoardRect = function() {
    var cols = this._boardModel.getCols();//这个游戏的列数
    var rows = this._boardModel.getRows();//这个游戏的行数
    var cellSize = Math.floor(Math.min(this._canvas.width/cols, this._canvas.height/rows));//每个单元格的长

    var boardWidth = cellSize*cols;
    var boardHeight = cellSize*rows;

    return {
        x: Math.floor((this._canvas.width - boardWidth)/2),
        y: Math.floor((this._canvas.height - boardHeight)/2),
        cellSize: cellSize
    }
};

/**
 * 清除画布的方法居然是直接在画布上面画一个白色的矩形!不需要使用clearRect()...
 * 但是如果背景是一张图片,直接就可以用这个方法
 */
_h._clearCanvas = function() {
    this._ctx.fillStyle = "white";
    this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
};

boardRenderer.js

代码语言:javascript
复制
/**
 * 这个类负责绘制,棋盘,球
 * @param context the 2d context to draw at
 * @param model the BoardModel to take data from
 */
function boardRenderer(context,model){
    this._ctx = context;
    this._model = model;

    //为方便保存
    this._cols = model.getCols();
    this._rows = model.getRows();

    //游戏盘左上角的位置
    this._x = 0;
    this._y = 0;

    //游戏盘矩形的宽度和高度
    this.width = 0;
    this.height = 0;

    //游戏盘单元格的最佳大小
    this._cellSize = 0;
}

_h = boardRenderer.prototype;

/**
 * 重新绘制整个画板Repaints the whole board.
 */
_h.repaint = function() {
    this._ctx.save();
    this._ctx.translate(this._x, this._y);
    this._drawBackground();
    this._drawGrid();
    this._ctx.restore();

    for (var i = 0; i < this._cols; i++) {
        for (var j = 0; j < this._rows; j++) {
            this.drawToken(i, j);
        }
    }
};

/**
 * 画背景
 *
 */
_h._drawBackground = function(){
    var ctx = this._ctx;

    //背景
    var gradient = ctx.createLinearGradient(0,0,0,this._height);
    gradient.addColorStop(0,"#fffbb3");
    gradient.addColorStop(1,"#f6f6b2");
    ctx.fillStyle = gradient;
    ctx.fillRect(0,0,this._width,this._height);

    //绘制曲线
    var co = this.width/6;
    ctx.strokeStyle = "#dad7ac";
    ctx.fillStyle = "#f6f6b2";

    //第一条曲线
    ctx.beginPath();
    ctx.moveTo(co, this._height);
    ctx.bezierCurveTo(this._width + co*3, -co, -co*3, -co, this._width - co, this._height);
    ctx.fill();

    //第二条曲线
    ctx.beginPath();
    ctx.moveTo(co, 0);
    ctx.bezierCurveTo(this._width + co*3, this._height + co, -co*3, this._height + co, this._width - co, 0);
    ctx.fill();
}

/**
 * 画网格.
 */
_h._drawGrid = function() {
    var ctx = this._ctx;
    ctx.beginPath();
    //画横线 Drawing horizontal lines
    for (var i = 0; i <= this._cols; i++) {
        ctx.moveTo(i*this._cellSize + 0.5, 0.5);
        ctx.lineTo(i*this._cellSize + 0.5, this._height + 0.5)
    }

    //画竖线 Drawing vertical lines
    for (var j = 0; j <= this._rows; j++) {
        ctx.moveTo(0.5, j*this._cellSize + 0.5);
        ctx.lineTo(this._width + 0.5, j*this._cellSize + 0.5);
    }

    //给这些线描边
    ctx.strokeStyle = "#CCC";
    ctx.stroke();
};

/**
 * 在指定的地方画上指定颜色的标记
 * @param cellX 单元格的x坐标
 * @param cellY 单元格的y坐标
 */
_h.drawToken = function(cellX, cellY) {
    var ctx = this._ctx;
    var cellSize = this._cellSize;
    var tokenType = this._model.getPiece(cellX, cellY);

    //如果单元格为空
    if (!tokenType)
        return;


    var colorCode = "black";
    switch(tokenType) {
        case BoardModel.RED:
            colorCode = "red";
        break;
        case BoardModel.GREEN:
            colorCode = "green";
        break;
    }

    //标记的圆心位置
    var x = this._x + (cellX + 0.5)*cellSize;
    var y = this._y + (cellY + 0.5)*cellSize;
    ctx.save();
    ctx.translate(x, y);

    //标记的半径
    var radius = cellSize*0.4;

    //渐变的中心
    var gradientX = cellSize*0.1;
    var gradientY = -cellSize*0.1;

    var gradient = ctx.createRadialGradient(
        gradientX, gradientY, cellSize*0.1, // 内圆 (炫光)
        gradientX, gradientY, radius*1.2); // 外圆

    gradient.addColorStop(0, "yellow"); // “光线”的颜色
    gradient.addColorStop(1, colorCode); // 标记的颜色
    ctx.fillStyle = gradient;

    ctx.beginPath();
    ctx.arc(0, 0, radius, 0, 2*Math.PI, true);
    ctx.fill();
    ctx.restore();
};

/**
 * Sets the new position and size for the board. Should call repaint to
 * see the changes
 * @param x the x coordinate of the top-left corner
 * @param y the y coordinate of the top-left corner
 * @param cellSize optimal size of the cell in pixels
 */
_h.setSize = function(x, y, cellSize)  {
    this._x = x;
    this._y = y;
    this._cellSize = cellSize;
    this._width = this._cellSize*this._cols;
    this._height = this._cellSize*this._rows;
};

boardModel.js

代码语言:javascript
复制
/**
 * 这个类是负责保存/验证/返回当前游戏的状态
 * 如当前的玩家是谁、每个单元格放的是什么球、
 * 是不是谁赢了
 * @param cols number of columns in the board
 * @param rows number of rows in the board
 */
function BoardModel(cols, rows) {
    this._cols = cols || 7;
    this._rows = rows || 6;
    this._data = [];//用于记录游戏当前的游戏状态——每个格子有什么球
    this._currentPlayer = BoardModel.RED;
    this._totalTokens = 0;

    this.reset();
}

/**
 * 0代表单元格为空,1代表单元格有红色球,2代表单元格有绿色球
 * 因为怕以后忘记这些数字代表什么,干脆把数字存到常量里,代码看起来易懂,
 * 但是这么多字,前端的js不是应该越短越好吗!?

 * ps.变量名全大写表示这是常量,这是一个js程序员之间的约定,表达为 CAPITAL_CASED。
 * 另外一个与变量(函数)有关的约定是:变量名(函数名)前加"_"下横杠,表示这是私有变量(函数),表达为 _underlinePrivateVariables
 */
BoardModel.EMPTY = 0;
BoardModel.RED = 1;
BoardModel.GREEN = 2;

/**
 * Game state after the turn
 */
BoardModel.NONE = 0; // 没有赢也没有平局
BoardModel.WIN = 1; //刚刚着棋的玩家赢了
BoardModel.DRAW = 2; // 棋盘满了,且平局
BoardModel.ILLEGAL_TURN = 3; // 刚刚着棋的玩家,走棋无效,再走一次

_h = BoardModel.prototype;

/**
 * Resets the game board into the initial state: the
 * board is empty and the starting player is RED.
 */
_h.reset = function() {
    this._data = [];
    for (var i = 0; i < this._rows; i++) {
        this._data[i] = [];
        for (var j = 0; j < this._cols; j++) {
            this._data[i][j] = BoardModel.EMPTY;
        }
    }

    this._currentPlayer = BoardModel.RED;
    this._totalTokens = 0;
};

/**
 * 把球放到指定列。 Board model itself takes care of
 * tracking the current player.
 * @param column the index of the column
 * @param piece the ID of the piece (RED or YELLOW)
 * @return the object {
 *      status: win condition
 *      x: the x coordinate of the new turn (undefined if turn was illegal)
 *      y: the y coordinate of the new turn (undefined if turn was illegal)
 *      piece: piece id (RED or GREEN)
 * }
 */
_h.makeTurn = function(column) {

    //正在放的球的颜色 The color of the piece that we're dropping
    var piece = this._currentPlayer;

    //检查这一列是否可以放球
    if (column < 0 || column >= this._cols) {
        return {
            status: BoardModel.ILLEGAL_TURN
        }
    }

    //检查这一列上有空行没,如果没有,则这回合无效
    var row = this._getEmptyRow(column);
    if (row == -1) {
        return {
            status: BoardModel.ILLEGAL_TURN
        }
    }

    //放置球
    this._totalTokens++;
    this._data[row][column] = piece;

    // 更换玩家
    this._toggleCurrentPlayer();

    // 返回回合成功的消息,包括新的游戏状态:none——可以继续游戏、win、draw
    return {
        status: this._getGameState(column, row),
        x: column,
        y: row,
        piece: piece
    }
};

_h.getPiece = function(col, row) {
    return this._data[row][col];
};

/**
 * 返回这个游戏有多少列
 */
_h.getCols = function() {
    return this._cols;
};

/**
 * 返回这个游戏有多少行
 */
_h.getRows = function() {
    return this._rows;
};

/**
 * 返回指定列是否有空行,如果没有 return -1.
 * @param column the column index
 */
_h._getEmptyRow = function(column) {
    for (var i = this._rows - 1; i >= 0; i--) {
        if (!this.getPiece(column, i)) {
            return i;
        }
    }
    return -1;
};


/**
 * Checks for the win condition, checks how many pieces of the same color are in each
 * possible row: horizontal, vertical or both diagonals.
 * @param column the column of the last move
 * @param row the row of the last move
 * @return the game state after this turn:
 *  NONE if the state wasn't affected
 *  WIN if current player won the game with the last turn
 *  DRAW if there's no emty cells in the board left
 */
_h._getGameState = function(column, row) {
    if (this._totalTokens == this._cols*this._rows)
        return BoardModel.DRAW;

    for (var deltaX = -1; deltaX < 2; deltaX++) {
        for (var deltaY = -1; deltaY < 2; deltaY++) {
            if (deltaX == 0 && deltaY == 0)
                continue;
            var count = this._checkWinDirection(column, row, deltaX, deltaY)
                    + this._checkWinDirection(column, row, -deltaX, -deltaY) + 1;
            if (count >= 4) {
                return BoardModel.WIN;
            }
        }
    }
    return BoardModel.NONE;
};

/**
 * Calculates the number of pieces of the same color in the given direction, starting
 * fromt the given point (last turn)
 * @param column starting column
 * @param row starting row
 * @param deltaX the x direction of the check
 * @param deltaY the y direction of the check
 */
_h._checkWinDirection = function(column, row, deltaX, deltaY) {
    var pieceColor = this.getPiece(column, row);
    var tokenCounter = 0;
    var c = column + deltaX;
    var r = row + deltaY;
    while(c >= 0 && r >= 0 && c < this._cols && r < this._rows &&
            this.getPiece(c, r) == pieceColor) {
        c += deltaX;
        r += deltaY;
        tokenCounter++;
    }
    return tokenCounter;
};

/**
 * 切换当前玩家 - from red to green and back.
 */
_h._toggleCurrentPlayer = function() {
    this._currentPlayer = (this._currentPlayer==BoardModel.RED)?BoardModel.GREEN:BoardModel.RED;
    /*if (this._currentPlayer == BoardModel.RED)
        this._currentPlayer = BoardModel.GREEN;
    else
        this._currentPlayer = BoardModel.RED;*/
};
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档