最近在看一本书,里面提到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;*/
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。