вторник, 31 августа 2010 г.

Реализация шаблона MVC на примере игры типа Packman – Часть 4

В этойстатье мы опишем самые главные компоненты игры, это модель самой игры и представление игры. После чего мы, я надеюсь, с удовольствием сможем поиграть в нашу игру типа Packman полностью основанную на паттерне MVC.

Модель в шаблоне Model-View-Controller

Начнём с программирования модели игры. Модель является «сутью» нашей игры и отвечает за непосредственные алгоритмы, расчёты и тому подобное внутреннее устройство игры. Обычно под Моделью понимается, часть содержащая в себе функциональную логику приложения, иными словами то, что мы обычно называем «Business Layer», «Бизнес-слой» или «Слой бизнес логики». Как именно организован этот слой, по большому счету не важно, однако есть ряд ключевых моментов, на которых мы остановимся позднее. Основная цель паттерна - сделать так, чтобы Модель была полностью независима от остальных частей и практически ничего не знала об их существовании, что позволило бы менять и Контроллер и Представление модели, не трогая саму Модель и даже позволить функционирование нескольких экземпляров Представлений и Контроллеров с одной Моделью одновременно. Вследствие чего, Модель ни при каких условиях не может содержать ссылок на объекты Представления или Контроллера.

Обычно различают несколько типов паттернов в зависимости от роли модели.

Passive Model (пассивная модель) - Модель не имеет вообще никаких способов воздействовать на Представление или Контроллер и только используется ими в качестве источника данных для отображения. Все изменения модели отслеживаются Контроллером и он же отвечает за перерисовку Представления, если это необходимо.

Active Model (активная модель) - Модель имеет возможность оповестить Представление о том, что в ней произошли некие изменения, и Представление может эти изменения отобразить. Как правило, механизм оповещения реализуется на основе паттерна Observer (обозреватель), Модель просто бросает сообщение, а Представления, которые заинтересованы в оповещении, подписываются на эти сообщения, что позволяет сохранить независимость Модели, как от Контроллера так и от Представления, не нарушая тем самым основного свойства паттерна. Классической реализацией паттерна MVC принято считать версию именно с активной Моделью.

Программирование модели игры типа Packman

Мы будем программировать модель нашей игры как активную модель. Для начала создадим массивы объектов игры, таких как яблоки, танки, стены и объект пакмена. Все классы этих сущностей описаны в статьях ранне. В техническом задании было сказано, что каждый объект должен существовать в своём потоке. Поэтому далее мы создаём для каждого движущегося объекта свой поток. Затем наконец-то пишем самый главный метод Start(). Также нам необходимо написать метод проверки на пересечение объектов, и обработчик события перемещение объекта в заданные координаты, соответственно нам нужно подписать каждый элемент с каждым на пересечение и написать. Размещение стен происходит в методе PlaceWalls() в соответствии с картой, заданной массивом GameForm.map. А теперь посмотрим на сам класс.

namespace PackMan

{

/// <summary>

/// Класс игры.

/// </summary>

class Game

{

#region Объекты елементов и их свойства

/// <summary>

/// Список танков

/// </summary>

List<Tank> arrTank = new List<Tank>();

/// <summary>

/// Список стен

/// </summary>

List<Wall> arrWall = new List<Wall>();

/// <summary>

/// Список яблок

/// </summary>

List<Apple> arrApple = new List<Apple>();

/// <summary>

/// Пакмен.

/// </summary>

PackMan packMan;

/// <summary>

/// Свойство списка яблок,доступное только для чтения

/// </summary>

public List<Apple> ArrApple

{

get { return arrApple; }

}

/// <summary>

/// Свойство списка стен,доступное только для чтения

/// </summary>

public List<Wall> ArrWall

{

get { return arrWall; }

}

/// <summary>

/// Свойство пакмена,доступное только для чтения

/// </summary>

public PackMan PackMan

{

get { return packMan; }

}

/// <summary>

/// Свойство списка танков,доступное только для чтения

/// </summary>

public List<Tank> ArrTank

{

get { return arrTank; }

}

#endregion

#region Вспомогательные поля класса

/// <summary>

/// Список потоков,в которых запускаются объекты

/// </summary>

List<Thread> arrThread = new List<Thread>();

/// <summary>

/// Поток в котором запускается пакмен.

/// </summary>

private Thread packManThread;

/// <summary>

/// Очки.

/// </summary>

private static int score = 0;

/// <summary>

/// Свойство очков.

/// </summary>

public static int Score

{

get { return Game.score; }

set { Game.score = value; }

}

#endregion

/// <summary>

/// Конструктор

/// </summary>

public Game()

{

//Создание стен

for (int i = 0; i < GameForm.countWall; i++)

{

Rectangle rect = new Rectangle();

arrWall.Add(new Wall(rect.Location));

}

PlaceWalls();

//Создание танков

for (int i = 0; i < GameForm.countTank; i++)

{

Rectangle rect = new Rectangle();

do

{

rect = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new Tank()).Width, (new Tank()).Height);

} while (Collides(rect));

arrTank.Add(new Tank(rect.Location));

Thread thread = new Thread(new ThreadStart(arrTank[i].Run));

arrThread.Add(thread);

}

//Создание яблок

for (int i = 0; i < GameForm.countApples; i++)

{

Rectangle rect = new Rectangle();

do

{

rect = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new Apple()).Width, (new Apple()).Height);

} while (Collides(rect));

arrApple.Add(new Apple(rect.Location));

}

Rectangle rect2 = new Rectangle();

do

{

rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX), GameForm.rand.Next(0, GameForm.MAXY), (new PackMan()).Width, (new PackMan()).Height);

} while (Collides(rect2));

//Создание пакмена.

packMan = new PackMan(rect2.Location);

packManThread = new Thread(new ThreadStart(PackMan.Run));

SubscribePos();

}

/// <summary>

/// Начало игры

/// </summary>

public void Start()

{

foreach (Thread thread in arrThread)

{

thread.Start();

}

packManThread.Start();

}

/// <summary>

/// Размещение стен в соответствии с картой,заданной массивом GameForm.map

/// </summary>

private void PlaceWalls()

{

int cur = 0;

for (int i = 0; i < GameForm.map.Length; i++)

for (int j = 0; j < GameForm.map[0].Length; j++)

if (GameForm.map[i][j] == '*')

{

arrWall[cur].Position = new Point(i * arrWall[cur].Width, j * arrWall[cur].Height);

cur++;

}

}

/// <summary>

/// Проверка на пересечение объектов

/// </summary>

/// <param name="rect"></param>

/// <returns></returns>

private bool Collides(Rectangle rect)

{

if (rect.Left < 0 || rect.Right >= GameForm.MAXX || rect.Top < 0 || rect.Bottom >= GameForm.MAXY)

return true;

for (int i = 0; i < arrTank.Count; i++)

{

if (arrTank[i].CollidesWith(rect)) return true;

}

for (int i = 0; i < arrWall.Count; i++)

{

if (arrWall[i].CollidesWith(rect)) return true;

}

for (int i = 0; i < arrApple.Count; i++)

{

if (arrApple[i].CollidesWith(rect)) return true;

}

if (packMan != null && packMan.CollidesWith(rect)) return true;

return false;

}

/// <summary>

/// Уничтожение объектов

/// </summary>

public void Dispose()

{

for (int i = 0; i < arrThread.Count; i++)

{

arrThread[i].Abort();

}

packManThread.Abort();

}

/// <summary>

/// Подписка каждого элемента с каждым на пересечение

/// </summary>

public void SubscribePos()

{

for (int i = 0; i < arrTank.Count; i++)

{

for (int j = 0; j < arrTank.Count; j++)

{

if (i != j)

{

arrTank[i].CheckPosition += new EventHandler(arrTank[j].OnCheckPosition);

}

}

}

for (int i = 0; i < arrTank.Count; i++)

{

for (int j = 0; j < arrWall.Count; j++)

{

arrTank[i].CheckPosition += new EventHandler(arrWall[j].OnCheckPosition);

}

}

for (int j = 0; j < arrWall.Count; j++)

{

packMan.CheckPosition += new EventHandler(arrWall[j].OnCheckPosition);

}

for (int j = 0; j < arrTank.Count; j++)

{

packMan.CheckPosition += new EventHandler(arrTank[j].OnCheckPosition);

}

for (int j = 0; j < arrTank.Count; j++)

{

arrTank[j].CheckPosition += new EventHandler(packMan.OnCheckPosition);

}

for (int j = 0; j < arrApple.Count; j++)

{

packMan.CheckPosition += new EventHandler(arrApple[j].OnCheckPosition);

}

packMan.ReplaceNeeded += new EventHandler(packMan_ReplaceNeeded);

for (int j = 0; j < arrApple.Count; j++)

{

arrApple[j].ReplaceNeeded += new EventHandler(apple_ReplaceNeeded);

}

}

/// <summary>

/// Обработчик события перемещение объекта в заданные координаты

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

void apple_ReplaceNeeded(object sender, EventArgs e)

{

Rectangle rect2 = new Rectangle();

do

{

rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX - (sender as MapObject).Width - 2), GameForm.rand.Next(GameForm.MAXY - (sender as MapObject).Height - 2), (new Apple()).Width, (new Apple()).Height);

} while (Collides(rect2));

(sender as Apple).Position = rect2.Location;

}

/// <summary>

/// Обработчик события перемещение объекта в заданные координаты

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

void packMan_ReplaceNeeded(object sender, EventArgs e)

{

Rectangle rect2 = new Rectangle();

do

{

rect2 = new Rectangle(GameForm.rand.Next(0, GameForm.MAXX - (sender as MapObject).Width - 2), GameForm.rand.Next(GameForm.MAXY - (sender as MapObject).Height - 2), (new PackMan()).Width, (new PackMan()).Height);

} while (Collides(rect2));

packMan.Position = rect2.Location;

}

/// <summary>

/// Отписка каждого с каждым от событий пересечения

/// </summary>

private void UnSubscribePos()

{

for (int i = 0; i < arrTank.Count; i++)

{

for (int j = 0; j < arrTank.Count; j++)

{

if (i != j)

{

arrTank[i].CheckPosition -= new EventHandler(arrTank[i].OnCheckPosition);

}

}

}

}

}

}

Теперь создадим представление игры. Для этого в классе создаём представление для каждого объекта нашей игры и связываем ссылки представлений объектов с реальными объектами игры. А также создаём событие нажатия на клавишу, описываем его обработчик и подписываем контроллер на событие нажатие клавиши.

namespace PackMan

{

class ViewGame:View<Game>

{

#region Создание представлений и их свойств

//Game model = new Game();

//internal Game Model

//{

// get { return model; }

// set { model = value; }

//}

/// <summary>

/// Спмсок представлений танков

/// </summary>

List<ViewTank> arrViewTank = new List<ViewTank>();

/// <summary>

/// Представление пакмена

/// </summary>

ViewPackMan viewPackMan;

/// <summary>

/// Список представлений стен

/// </summary>

List<ViewWall> viewWall = new List<ViewWall>();

/// <summary>

/// Список представлений яблок

/// </summary>

List<ViewApple> viewApple = new List<ViewApple>();

/// <summary>

/// Контроллер пакмена

/// </summary>

PackManController controller = new PackManController();

/// <summary>

/// Поле на котором происходит игра

/// </summary>

Panel panelMap;

/// <summary>

/// Очки

/// </summary>

Label point;

#endregion

/// <summary>

/// Конструктор

/// </summary>

/// <param name="panelMap"></param>

public ViewGame(Panel panelMap,Label point)

{

this.panelMap = panelMap;

this.point = point;

panelMap.Controls.Add(point);

}

#region Событие нажатие на клавишу

/// <summary>

/// Событие нажатия на клавишу

/// </summary>

private event KeyEventHandler keyPress;

/// <summary>

/// Обработчик нажатия на клавишу

/// </summary>

/// <param name="key">Клавиша,которая нажата</param>

public virtual void OnKeyPress(Keys key)

{

if (keyPress != null)

keyPress(viewPackMan.Model, new KeyEventArgs(key));

}

/// <summary>

/// Подписка на событие нажатие клавиши

/// </summary>

public void SubscribeKeyPress()

{

this.keyPress += new KeyEventHandler(controller.OnKeyPress);

controller.OnKeyPress(viewPackMan, new KeyEventArgs(Keys.Right));

}

/// <summary>

/// Отписка на событие нажатие клавиши

/// </summary>

public void UnsubscribeKeyPress()

{

this.keyPress -= new KeyEventHandler(controller.OnKeyPress);

}

#endregion

/// <summary>

/// Обновить.

/// </summary>

protected override void Update()

{

Refresh();

}

/// <summary>

/// Создание всех элементов игры,

/// связывание ссылок представлений объектоа с реальными объектами игры

/// </summary>

private void Refresh()

{

arrViewTank = new List<ViewTank>();

viewWall = new List<ViewWall>();

viewApple = new List<ViewApple>();

viewPackMan = new ViewPackMan(panelMap);

viewPackMan.Model = Model.PackMan;

viewPackMan.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewPackMan.Subscribe();

for (int i = 0; i < Model.ArrTank.Count; i++)

{

ViewTank viewTanktemp = new ViewTank(panelMap);

viewTanktemp.Model = Model.ArrTank[i];

viewTanktemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewTanktemp.Subscribe();

arrViewTank.Add(viewTanktemp);

}

for (int i = 0; i < Model.ArrWall.Count; i++)

{

ViewWall viewWalltemp = new ViewWall(panelMap);

viewWalltemp.Model = Model.ArrWall[i];

viewWalltemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewWall.Add(viewWalltemp);

}

for (int i = 0; i < Model.ArrApple.Count; i++)

{

ViewApple viewAppletemp = new ViewApple(panelMap);

viewAppletemp.Model = Model.ArrApple[i];

viewAppletemp.Model.MapSize = new Point(panelMap.Width, panelMap.Height);

viewAppletemp.Subscribe();

viewApple.Add(viewAppletemp);

}

}

}

Следующая статья будет заключающей, где мы опишем контроллер игры и создадим модель и представление формы.

Комментариев нет:

Отправить комментарий