O MVC foi abordado em muitos lugares, portanto, não deve haver muito o que reiterar aqui. Essencialmente, você deseja que seu gráfico, auxiliares e lógica de objetos estejam contidos na camada do modelo. As visualizações serão as telas empurradas para preencher a parte dinâmica da página (e podem conter uma quantidade pequena de lógica e auxiliares). E o controlador, que é uma implementação leve para atender às telas com base no que estava disponível nos gráficos de objetos, auxiliares e lógica.
Modelo
Deve ser onde fica a carne do aplicativo. Ele pode ser dividido em camadas em uma camada de serviço, uma camada lógica e uma camada de entidade. O que isso significa para o seu exemplo?
Camada de entidade
Isso deve incluir as definições dos modelos e comportamentos internos do seu jogo. Por exemplo, se você tivesse um jogo para o caça-minas, seria aqui que as definições do tabuleiro e do quadrado estavam junto com a forma como elas mudam seu estado interno.
function Location(x,y){
this.x = x;
this.y = y;
}
function MineTile(x,y){
this.flagged = false;
this.hasMine = false;
this.pristine = true;
this.location = new Location(x,y);
}
MineTile.prototype.expose = function(){
if( this.hasMine ) return false;
this.pristine = false;
return this.location;
};
Portanto, o MineTile conhecerá seu estado interno, como se ele está sendo exibido ou foi examinado ( this.pristine
), se foi um dos blocos que possui uma mina ( this.hasMine
), mas não determinará se deveria ter uma mina. Isso depende da camada lógica. (Para ir ainda mais longe no OOP, o MineTile pode herdar de um Tile genérico).
Camada lógica
Isso deve abrigar as formas complexas pelas quais o aplicativo irá interagir com os modos de mudança, mantendo o estado, etc. Portanto, seria nesse local que um padrão mediador seria implementado para manter o estado do jogo atual. É aí que reside a lógica do jogo para determinar o que acontece durante um game over, por exemplo, ou para definir quais MineTiles terão uma mina. Ele faria chamadas para a camada Entidade para obter níveis instanciados com base em parâmetros determinados logicamente.
var MineSweeperLogic = {
construct: function(x,y,difficulty){
var mineSet = [];
var bombs = 7;
if( difficulty === "expert" ) bombs = 15;
for( var i = 0; i < x; i++ ){
for( var j = 0; i j < y; j++ ){
var mineTile = new MineTile(i,j);
mineTile.hasMine = bombs-- > 0;
mineSet.push(mineTile);
}
}
return mineSet;
},
mineAt: function(x,y,mineSet){
for( var i = 0; i < mineSet.length; i++ )
if( mineSet[i].x === x && mineSet[i].y === y ) return mineSet[i];
}
};
Camada de serviço
É aqui que o controlador tem acesso. Ele terá acesso à camada lógica para a construção dos jogos. Uma chamada de alto nível pode ser feita na camada de serviço para recuperar um jogo totalmente instanciado ou um estado de jogo modificado.
function MineSweeper(x,y,difficulty){
this.x = x;
thix.y = y;
this.difficulty = difficulty;
this.mineSet = MineSweeperLogic.construct(x,y,difficulty);
}
MineSweeper.prototype.expose = function(x,y){
return MineSweeperLogic.mineAt(x,y,this.mineSet).expose();
}
Controlador
Os controladores devem ser leves, essencialmente é isso que é exposto como cliente ao modelo. Haverá muitos controladores, portanto, estruturá-los se tornará importante. As chamadas de função do controlador serão as chamadas javascript atingidas com base nos eventos da interface do usuário. Eles devem expor os comportamentos disponíveis na camada de serviço e, em seguida, preencher ou, neste caso, modificar as visualizações do cliente.
function MineSweeperController(ctx){
var this.context = ctx;
}
MineSweeperController.prototype.Start = function(x,y,difficulty){
this.game = new MineSweeper(x,y,difficulty);
this.view = new MineSweeperGameView(this.context,this.game.x,this.game.y,this.game.mineSet);
this.view.Update();
};
MineSweeperController.prototype.Select = function(x,y){
var result = this.game.expose(x,y);
if( result === false ) this.GameOver();
this.view.Select(result);
};
MineSweeperController.prototype.GameOver = function(){
this.view.Summary(this.game.FinalScore());
};
Visão
As visualizações devem ser organizadas em relação aos comportamentos do controlador. Eles provavelmente serão a parte mais intensiva do seu aplicativo, pois trata da tela.
function MineSweeperGameView(ctx,x,y,mineSet){
this.x = x;
this.y = y;
this.mineSet = mineSet;
this.context = ctx;
}
MineSweeperGameView.prototype.Update = function(){
//todo: heavy canvas modification
for(var mine in this.mineSet){}
this.context.fill();
}
Então agora você tem toda a sua configuração do MVC para este jogo. Ou pelo menos, um exemplo simples, escrever o jogo inteiro seria excessivo.
Quando tudo estiver pronto, será necessário haver um escopo global para o aplicativo em algum lugar. Isso manterá a vida útil do seu controlador atual, que é o gateway para toda a pilha MVC neste cenário.
var currentGame;
var context = document.getElementById("masterCanvas").getContext('2d');
startMineSweeper.click = function(){
currentGame = new MineSweeperController(context);
currentGame.Start(25,25,"expert");
};
O uso de padrões MVC é muito poderoso, mas não se preocupe muito em aderir a todas as nuances deles. No final, é a experiência do jogo que determinará se o aplicativo é um sucesso :)
Para consideração: Não deixe que os astronautas da arquitetura o assuste Joel Spolsky