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

/**
 * 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

/**
 * 这个类负责绘制,棋盘,球
 * @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

/**
 * 这个类是负责保存/验证/返回当前游戏的状态
 * 如当前的玩家是谁、每个单元格放的是什么球、
 * 是不是谁赢了
 * @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;*/
};


黒之染
3.1k 声望47 粉丝

两年半个人练习生,喜欢ctrl+c/ctrl+v/delete