XNA — это инфраструктура создания игр высокого уровня для устройств Microsoft, включая ПК с Windows, Xbox 360 и совершенно новую операционную систему Windows Phone 7. В предыдущем уроке мы рассмотрели основы инфраструктуры XNA, включая обработку ввода и отображение спрайтов. В этой статье вы узнаете, как объединить эти навыки с собственной игровой идеей, чтобы создать что-то интересное и легкое в освоении.
Обзор проекта
В этом уроке вы создадите простую игру в крестики-нолики, в которую можно играть с друзьями. Крестики-нолики — это простая игра, в которой два игрока поочередно размещают персонажей на сетке три на три. Первый игрок использует O, а второй игрок использует X. Чтобы выиграть игру, игрок должен расположить трех своих персонажей в ряд, столбец или диагональ.
Хотя эта игра проста, для ее создания требуется несколько различных навыков. Во-первых, вы будете использовать XNA и его графические возможности для создания своей игры. Таким образом, вы должны быть знакомы с отображением спрайтов. Во-вторых, вам нужно знать, как обрабатывать прикосновения к сенсорной панели телефона. К счастью, XNA предоставляет высокоуровневый API для доступа к этим штрихам. Наконец, вам нужно применить немного знаний программирования и логики, чтобы определить победителя. В этом уроке многое из этого будет вам дано. При создании игр в будущем вам придется придумывать свои собственные идеи и логику.
Создание вашего проекта
Для начала убедитесь, что вы установили новейшие средства разработки для Windows Phone 7. Если вы не обновляли свои инструменты со времени последнего учебника WP7 по MobileTuts, вам следует посетить Центр загрузки Microsoft и получить версию RTW . Эта последняя версия поставляется с финальным эмулятором, который демонстрирует, как ваши приложения будут работать на аппаратном обеспечении в день выпуска.
Убедившись, что ваши инструменты обновлены, откройте Visual Studio 2010 и нажмите ссылку «Новый проект…» на левой боковой панели. В появившемся диалоговом окне выберите «XNA Game Studio 4» в левом столбце и убедитесь, что шаблон «Windows Phone Game (4.0)» выбран справа. Дайте вашему проекту соответствующее имя, например «TicTacToe», и убедитесь, что установлен флажок «Создать каталог для решения». Если вы все сделали правильно, ваше диалоговое окно должно соответствовать следующему изображению:
Нажмите «ОК», чтобы создать новый проект. Visual Studio 2010 сгенерирует необходимые файлы в указанном вами каталоге и откроет Game1.cs
для редактирования.
Импорт медиа-контента
Поскольку вы будете использовать спрайты для всей игровой графики в этом проекте, вам необходимо импортировать необходимые элементы в ваш проект. В загрузке, сопровождающей это руководство, вы найдете каталог Media
содержащий множество изображений. В обозревателе решений в правой части экрана найдите проект Content (называемый TicTacToeContent) и щелкните его правой кнопкой мыши. В контекстном меню выберите «Добавить> Существующий элемент…». Когда откроется диалоговое окно, перейдите в папку « Media
», разархивированную из загружаемой учебной программы, и выберите все содержащиеся в ней изображения. Вы должны быть в состоянии сказать по именам изображений, что именно содержит каждый элемент.
После импорта мультимедиа обозреватель решений должен выглядеть следующим образом:
Загрузка и назначение контента
Теперь, когда ваши медиафайлы были импортированы в проект Content, прикрепленный к вашему решению, вам нужно загрузить каждое изображение в виде отдельной текстуры. Поскольку вы будете использовать эти текстуры на протяжении всей игры, вы будете хранить их в полях внутри своего игрового класса. Откройте файл Game1.cs и добавьте следующие поля в начало вашего объявления класса:
Texture2D gridTexture; Прямоугольник gridRectangle; Texture2D resetButton; Rectangle resetButtonPosition; Texture2D oPiece; Texture2D xPiece; Texture2D OWinner; Texture2D xWinner; Texture2D noWinner; Texture2D oTurn; Texture2D xTurn;
Вы видите, что каждое импортированное вами изображение имеет поле для его резервного копирования. Кроме того, основная игровая сетка и текстура кнопки сброса имеют поля типа Rectangle
которые будут использоваться для позиционирования этих элементов. Они хранятся в виде полей, потому что они не будут меняться в течение игры.
Теперь, когда у вас есть соответствующие поля, пришло время создать экземпляры объектов Texture2D
и Rectangle
, которые назначены полям. Прокрутите файл Game1.cs
вниз, пока не дойдете до метода LoadContent
. Внутри этого метода вставьте следующий код после строки, которая читает spriteBatch = new SpriteBatch(GraphicsDevice);
:
gridTexture = Content.Load <Texture2D> ("TicTacToe_Grid"); gridRectangle = new Rectangle (0, 0, spriteBatch.GraphicsDevice.Viewport.Width, spriteBatch.GraphicsDevice.Viewport.Height); oPiece = Content.Load <Texture2D> ("TicTacToe_O"); xPiece = Content.Load <Texture2D> ("TicTacToe_X"); resetButton = Content.Load <Texture2D> ("TicTacToe_Reset"); resetButtonPosition = new Rectangle (spriteBatch.GraphicsDevice.Viewport.Width / 2 - (resetButton.Width / 2), spriteBatch.GraphicsDevice.Viewport.Height - 95, resetButton.Width, resetButton.Height); oWinner = Content.Load <Texture2D> ("TicTacToe_O_Winner"); xWinner = Content.Load <Texture2D> ("TicTacToe_X_Winner"); noWinner = Content.Load <Texture2D> ("TicTacToe_Draw"); oTurn = Content.Load <Texture2D> ("TicTacToe_O_Turn"); xTurn = Content.Load <Texture2D> ("TicTacToe_X_Turn");
Определение рабочего процесса игры
После загрузки спрайтов, которые будут использоваться игрой, вам нужно придумать рабочий процесс, на котором будет запускаться игра. Для целей Tic-Tac-Toe это довольно просто и выглядит примерно так:
- Нарисуйте игровую сетку
- Нарисуйте пьесы, сыгранные в данный момент
- Нарисуйте текущий статус игры (чей ход или если есть победитель)
- Если игра выиграна или больше не выигрывается, нарисуйте кнопку «Сброс»
- Если никто не выиграл, а игра все еще выигрышная, обработайте касания по сетке и проверьте победителя, если сделано касание
- Если игра выиграна или больше не выигрывается, нажмите кнопку сброса
- Если нажать кнопку сброса, перезагрузите игру
Как вы, вероятно, можете сказать, эти шаги попадают в одну из двух основных категорий, Draw или Update. Сканируя файл Game1.cs
вы увидите, что у вас есть два метода, Draw
и Update
которые являются идеальными контейнерами для кода, необходимого для описания рабочего процесса.
Сохранение состояния игры
Глядя на рабочий процесс, вы можете заметить, что есть четыре различных элемента, которые нужно отслеживать для управления состоянием игры. Во-первых, вы должны отслеживать состояние каждой точки сетки на игровом поле. Вы сделаете это, используя пользовательское перечисление и многомерный массив. Далее, вы должны следить за тем, была ли игра выиграна и кем. В-третьих, вы должны отслеживать, чья очередь. Наконец, вы должны отслеживать, является ли игра выигрышной. Этот предмет инициализируется как true
и пересчитывается после каждого хода игрока. Когда все точки сетки заполнены, это становится false
.
Вы начнете с определения пользовательского перечисления, описывающего игрока для вашей игры. Вверху вашего файла класса, над объявлением класса, добавьте следующий код:
public enum TicTacToePlayer {Нет, PlayerO, PlayerX}
Это перечисление описывает трех игроков (None, PlayerO и PlayerX) и будет использоваться для отслеживания как состояния сетки, так и победителя игры. Теперь добавьте переменные экземпляра, которые помогут вам отслеживать состояние игры. Эти элементы должны быть добавлены в начале вашего объявления класса:
bool winnable; Победитель TicTacToePlayer; TicTacToePlayer current; TicTacToePlayer [,] grid;
Вы храните четыре вещи здесь. Сначала вы определяете, можно ли выиграть в игре. Во-вторых, вы объявляете победителя. Если победителем является None, игра продолжается. После этого вы сохраняете текущий плеер. Наконец, вы сохраняете состояние сетки. На этом этапе вам все еще нужно инициализировать каждую из этих переменных. Забегая наперед, вы знаете, что вам нужно будет повторно инициализировать их всякий раз, когда кто-то нажимает кнопку «Сброс», и поэтому мы можем создать новый метод, который будет выполнять эту инициализацию. Добавьте новый метод в ваш класс под методом Initialize
следующим образом:
частный сброс () { winnable = true; winner = TicTacToePlayer.None; grid = новый TicTacToePlayer [3, 3]; для (int i = 0; i <3; i ++) { для (int j = 0; j <3; j ++) { grid [i, j] = TicTacToePlayer.None; } } }
Теперь вызовите этот новый метод из метода Initialize
, изменив его следующим образом:
защищенное переопределение void Initialize () { Сброс настроек(); base.Initialize (); }
На данный момент вы храните все необходимые данные, поэтому начать рисование интерфейса должно быть просто.
Рисование интерфейса игры
Прежде чем вы начнете что-либо рисовать, вам нужно установить предпочитаемую ширину и высоту для вашего устройства. Вы будете делать это внутри конструктора Game1
. Измените его следующим образом:
общедоступная Game1 () { graphics = new GraphicsDeviceManager (this); Content.RootDirectory = "Content"; graphics.PreferredBackBufferWidth = 480; graphics.PreferredBackBufferHeight = 800; // Частота кадров по умолчанию составляет 30 кадров в секунду для Windows Phone. TargetElapsedTime = TimeSpan.FromTicks (333333); }
Вы добавили операторы, заявляющие, что вы хотите, чтобы ширина заднего буфера была 480 пикселей, а высота — 800 пикселей. Это значительно упрощает прорисовку остальных компонентов игры.
Для простоты вы будете выполнять каждый шаг рисования внутри отдельного метода. Эти методы затем будут вызываться из базового метода Draw
который уже существует. Давайте начнем с обдумывания некоторых хороших имен для каждого из этапов рабочего процесса игры, создания методов с таким именем и добавления вызовов к этим методам из Draw
. На мой взгляд, следующие имена методов адекватно описывают то, что они будут делать, и описанные шаги рабочего процесса:
- DrawGrid — охватывает первый шаг рабочего процесса и рисует игровую сетку.
- DrawPieces — охватывает второй шаг рабочего процесса и рисует фигуры, где бы они ни были сыграны
- DrawStatus — охватывает третий шаг рабочего процесса и отображает текущее состояние игры («Ход О», «Ход Х», «Побед О!», «Побед Х!» Или «Рисовать!»)
- DrawResetButton — охватывает рабочий процесс четвертого шага и рисует кнопку сброса
Создайте эти методы сейчас, вставив следующий код под ваш метод Draw
:
приватная пустота DrawGrid () { } приватные пустые DrawPieces () { } личная пустота DrawStatus () { } закрытое пространство DrawResetButton () { }
Вскоре вы напишите код для этих методов, но сейчас вам нужно добавить их в ваш метод Draw
, изменив его следующим образом:
защищенное переопределение void Draw (GameTime gameTime) { GraphicsDevice.Clear (Color.Black); spriteBatch.Begin (); DrawGrid (); DrawPieces (); DrawStatus (); DrawResetButton (); spriteBatch.End (); base.Draw (GameTime); }
Вы заметите, что окружили вызовы своих вспомогательных методов вызовами spriteBatch
Begin
и End
объекта spriteBatch
. Вы делаете это, потому что все ваши вспомогательные методы будут рисовать спрайты, и гораздо эффективнее вызывать пару Begin
и End
один раз внутри Draw
а не внутри каждого вспомогательного метода.
Теперь давайте поработаем над тем, чтобы показать игровую сетку. Сетка воспроизведения представляет собой изображение шириной 480 пикселей и высотой 800 пикселей с прозрачным фоном и белой сеткой из квадратов шириной 150 пикселей, расположенной в центре. Вы импортировали это ранее. Рисование на экране телефона не может быть проще. Вы берете загруженную и сохраненную gridTexture
переменную gridTexture
и рисуете ее, позиционируя ее с помощью предварительно gridRectangle
переменной gridRectangle
, передавая оба элемента в метод spriteBatch
объекта spriteBatch
. Измените метод DrawGrid
следующим образом:
приватная пустота DrawGrid () { spriteBatch.Draw (gridTexture, gridRectangle, Color.White); }
Сохраните файл, в котором вы работаете, и нажмите F5, чтобы скомпилировать и запустить ваш проект. Эмулятор Windows Phone 7 должен отображаться и отображать сетку крестики-нолики на черном фоне, как показано на следующем рисунке:
Теперь, когда сетка на месте, давайте нарисуем игровые фигуры. На этом этапе мы можем применить некоторую простую логику, чтобы показать кусочки, но ничего не будет отображаться, пока мы не добавим код, чтобы фактически обрабатывать касания и играть в игру. Измените ваш метод DrawPieces
следующим образом:
приватные пустые DrawPieces () { для (int i = 0; i <3; i ++) { для (int j = 0; j <3; j ++) { if (grid [i, j]! = TicTacToePlayer.None) { Texture2D texture = grid [i, j] == TicTacToePlayer.PlayerO? oPiece: xPiece; Положение прямоугольника = GetGridSpace (i, j, texture.Width, texture.Height); spriteBatch.Draw (текстура, позиция, цвет. белый); } } } }
Если у вас острый взгляд (или, что более вероятно, Visual Studio показывает красные волнистые линии), вы увидите, что метод GetGridSpace
отсутствует. GetGridSpace
— это удобный метод, который помогает сохранить часть кода в методе DrawPieces
а также пригодится при дальнейшей обработке касаний. Добавьте его в конец вашего объявления класса следующим образом:
закрытый прямоугольник GetGridSpace (столбец int, строка int, ширина int, высота int) { int centerX = spriteBatch.GraphicsDevice.Viewport.Width / 2; int centerY = spriteBatch.GraphicsDevice.Viewport.Height / 2; int x = centerX + ((столбец - 1) * 150) - (ширина / 2); int y = centerY + ((строка - 1) * 150) - (высота / 2); вернуть новый прямоугольник (x, y, ширина, высота); }
Теперь давайте посмотрим на остальные DrawPieces
. Этот метод немного сложнее, чем DrawGrid
но все же он должен быть довольно простым для понимания. Вы перебираете каждую строку и столбец игрового поля, сохраненные в переменной grid
, и проверяете, чтобы увидеть состояние этого пространства сетки. Если пространство сетки содержит игрока, отличного от «Нет», тогда рисуем соответствующую текстуру. Вы используете троичный оператор, чтобы получить правильную текстуру на основе состояния пространства сетки, а затем нарисовать ее, используя прямоугольник, полученный из GetGridSpace
.
Следующая проблема, с которой нужно разобраться — это рисование текущего статуса. Статус показывает, чья это очередь, кто выиграл игру или является ли игра ничьей. Заполните метод следующим образом:
личная пустота DrawStatus () { Texture2D текстура; if (winner! = TicTacToePlayer.None) { текстура = победитель == TicTacToePlayer.PlayerO? oWinner: xWinner; } иначе если (! winnable) { текстура = не победитель; } еще { текстура = текущая == TicTacToePlayer.PlayerO? oTurn: xTurn; } Положение прямоугольника = новый прямоугольник (spriteBatch.GraphicsDevice.Viewport.Width / 2 - (texture.Width / 2), 15, texture.Width, texture.Height); spriteBatch.Draw (текстура, позиция, цвет. белый); }
Этот метод не сильно отличается от других методов рисования, которые вы создали до сих пор. У вас есть текстура и позиция, и вам нужно нарисовать текстуру на экране. Интересной частью этого метода является определение текстуры для рисования. Сначала вы проверяете, есть ли победитель. Если есть, вы выбираете правильную текстуру победителя и рисуете это. Затем вы проверяете, все ли ячейки сетки заняты, и игра больше не выигрывает. Если это так, то вы выбираете текстуру без победителя и рисуете ее. Если ни одно из этих условий не выполняется, вы проверяете, какой ход игрока, выбираете правильную текстуру хода и рисуете. Если вы скомпилируете и запустите свой проект в этот момент (нажав F5), вы увидите, что интерфейс показывает, что настала очередь О:
Наконец, вы собираетесь создать код, который рисует кнопку сброса. Это довольно просто. Если есть победитель или игра больше не выигрывает, вы рисуете текстуру кнопки сброса. Измените метод DrawResetButton
так, чтобы он DrawResetButton
следующим образом:
закрытое пространство DrawResetButton () { if (winner! = TicTacToePlayer.None ||! winnable) { spriteBatch.Draw (resetButton, resetButtonPosition, Color.White); } }
На данный момент вы создали весь код, необходимый для рисования вашего интерфейса и всех его компонентов. Теперь вам просто нужно справиться с обновлением игры на основе касаний игроков.
Обработка касаний и состояние обновления
Если вы следовали последнему учебнику по XNA , большая часть следующего кода будет выглядеть знакомо. Обработка прикосновений на экране — это что-то довольно стандартное, и только в коде обработки прикосновений у вас будет логика игры. Для начала давайте поместим базовый код обработки касаний в метод Update
. Найдите Update
в своем классе Game1
и измените его следующим образом:
Обновление защищенного переопределения void (GameTime gameTime) { if (GamePad.GetState (PlayerIndex.One) .Buttons.Back == ButtonState.Pressed) { this.Exit (); } TouchCollection касается = TouchPanel.GetState (); if (! touch && touch.Count> 0) { прикосновение = правда; TouchLocation touch = touch.First (); HandleBoardTouch (сенсорный); HandleResetTouch (сенсорный); } еще если (трогает. Количество == 0) { прикосновение = ложь; } base.Update (GameTime); }
В этом методе есть несколько вещей, на которые следует обратить внимание. Во-первых, вы заметите новую touching
переменную, которая не существует. Эта переменная хранит информацию о том, касался ли игрок доску во время предыдущего вызова на Update
и предотвращает многократное воспроизведение затянувшегося пальца, не отпуская его с экрана. Добавьте touching
в качестве переменной экземпляра в верхней части объявления вашего класса.
касание бул;
Вы также заметите, что вы делаете два вызова метода внутри метода Update
для методов, которые еще не существуют. Добавьте эти методы в объявление класса прямо под методом Update
:
Частное пространство HandleBoardTouch (TouchLocation Touch) { } частное пространство HandleResetTouch (TouchLocation Touch) { }
Теперь, пройдя по методу Update
вы увидите, что проверяете наличие текущих касаний на экране. Если есть прикосновения, и игрок ранее не касался экрана, тогда вы берете первое касание и передаете его методам, которые обрабатывают касания доски и сбрасывают касания. Если игрок не касается экрана и они были ранее, тогда вы touching
переменную touching
на false.
На данный момент, давайте заполнить методы HandleBoardTouch
и HandleResetTouch
. Они соответствуют шагам 5 и 6 рабочего процесса соответственно.
Обработка досок
Когда пользователь касается экрана, игра должна сделать несколько вещей:
- Проверьте, находится ли касание в игровой зоне
- Если он находится в игровой зоне, проверьте, занято ли уже затронутое место
- Если место не занято, поместите фигуру туда и проверьте, выиграл ли текущий игрок
- Если сыгранная часть была победителем, обновите статус игры как таковой. Если сыгранная фигура заняла последнее доступное место, отметьте игру как не выигранную
- Наконец, поменяйте местами текущего игрока
Обновите HandleBoardTouch
следующим образом, HandleBoardTouch
все вышеперечисленные шаги:
Частное пространство HandleBoardTouch (TouchLocation Touch) { if (winner == TicTacToePlayer.None) { для (int i = 0; i <3; i ++) { для (int j = 0; j <3; j ++) { Прямоугольник = GetGridSpace (i, j, 150, 150); if (grid [i, j] == TicTacToePlayer.None && box.Contains ((int) touch.Position.X, (int) touch.Position.Y)) { сетка [i, j] = текущая; CheckForWin (ток); CheckForWinnable (); current = current == TicTacToePlayer.PlayerO? TicTacToePlayer.PlayerX: TicTacToePlayer.PlayerO; } } } } }
Как вы можете видеть, этот метод следует основной схеме выше. Если нет победителя, он выполняет итерацию по каждому пространству сетки, проверяет, находится ли касание в этом пространстве, проверяет, открыто ли пространство, и затем помещает туда часть. Проверка на победителя и уверенность в том, что игра все еще можно выиграть, обрабатываются другими методами. Давайте заглушим эти методы сейчас. Под методом HandleBoardTouch
добавьте следующий код:
приватный void CheckForWin (проигрыватель TicTacToePlayer) { } частный void CheckForWinnable () { }
Пока оставьте эти методы пустыми, скомпилируйте и запустите вашу игру. Попробуйте прикоснуться к доске, щелкнув по эмулятору, и просмотрите сообщение об изменении состояния и отображение играющих фигур. Вы уже на пути к полноценной игре!
Условия выигрыша
В этот момент вы попадаете в настоящую суть игры, выигрышную логику. Как обсуждалось ранее, выигрышное состояние возникает, когда фигуры одного игрока занимают один из рядов, один из столбцов или одну из двух диагоналей. Игра становится невозможной, если не объявлен победитель и не осталось свободных мест. В этой игре мы используем многомерный массив для хранения состояния сетки, и C # не предоставляет методов для проверки строк, столбцов или диагоналей. К счастью, язык поддерживает замечательную функцию, называемую методами расширения, которые вы будете использовать здесь, чтобы сделать свой код чище.
Создание помощников
Чтобы создать методы расширения, вы должны сначала создать новый класс. Нажмите на название вашего проекта в обозревателе решений и нажмите «Добавить> Класс…». Назовите свой класс MultiDimensionalArrayExtensions и нажмите «Добавить». Когда ваш новый файл откроется для редактирования, измените его так, чтобы объявление вашего класса выглядело следующим образом:
открытый статический класс MultiDimensionalArrayExtensions { }
Вы увидите, что вы добавили модификаторы public
и static
в объявление класса. Это необходимо для создания методов расширения. Теперь мы собираемся создать пару методов, которые нам понадобятся, каждый из которых возвращает IEnumerable
для удобного запроса:
- Строка — возвращает все элементы в определенной строке
- Столбец — возвращает все элементы в определенном столбце.
- Диагональ — возвращает все элементы по диагонали
- Все — Возвращает все элементы в многомерном массиве
Снова измените ваш класс, добавив методы следующим образом:
открытый статический класс MultiDimensionalArrayExtensions { открытый статический IEnumerable <T> Row <T> (это массив T [,], int row) { var columnLower = array.GetLowerBound (1); var columnUpper = array.GetUpperBound (1); for (int i = columnLower; i <= columnUpper; i ++) { yield return array [row, i]; } } открытый статический IEnumerable <T> столбец <T> (это массив T [,], столбец int) { var rowLower = array.GetLowerBound (0); var rowUpper = array.GetUpperBound (0); for (int i = rowLower; i <= rowUpper; i ++) { yield return array [i, column]; } } открытый статический IEnumerable <T> Diagonal <T> (это массив T [,], Направление DiagonalDirection) { var rowLower = array.GetLowerBound (0); var rowUpper = array.GetUpperBound (0); var columnLower = array.GetLowerBound (1); var columnUpper = array.GetUpperBound (1); for (int row = rowLower, column = columnLower; row <= rowUpper && column <= columnUpper; строка ++, колонка ++) { int realColumn = column; if (direction == DiagonalDirection.DownLeft) realColumn = columnUpper - columnLower - column; yield return array [row, realColumn]; } } общедоступное перечисление DiagonalDirection { Downright, DownLeft } открытый статический IEnumerable <T> All <T> (этот массив T [,]) { var rowLower = array.GetLowerBound (0); var rowUpper = array.GetUpperBound (0); var columnLower = array.GetLowerBound (1); var columnUpper = array.GetUpperBound (1); for (int row = rowLower; row <= rowUpper; row ++) { for (int column = columnLower; column <= columnUpper; column ++) { yield return array [строка, столбец]; } } } }
Вы можете видеть, что в этих методах нет ничего особенного. Для каждого из них определенная часть многомерного массива повторяется, и эта часть массива возвращается как часть IEnumerable
. Единственной действительно сложной задачей может быть использование ключевого слова yield
. Объяснение поведения этого ключевого слова выходит за рамки данной статьи, но ссылка C # на MSDN для ключевого слова yield может помочь, если вы хотите узнать больше. Как примечание, большая часть работы над этими методами расширения взята из пользовательского вклада в StackOverflow, который вы можете найти здесь
Реализация логики Win
Теперь, когда необходимые методы расширения реализованы, должно быть довольно легко реализовать логику выигрыша. Вернемся к методу CheckForWin
и реализуем его. Обновите метод следующим образом:
приватный void CheckForWin (проигрыватель TicTacToePlayer) { Func <TicTacToePlayer, bool> checkWinner = b => b == player; если ( grid.Row (0) .All (checkWinner) || grid.Row (1) .All (checkWinner) || grid.Row (2) .All (checkWinner) || grid.Column (0) .All (checkWinner) || grid.Column (1) .All (checkWinner) || grid.Column (2) .All (checkWinner) || grid.Diagonal (MultiDimensionalArrayExtensions.DiagonalDirection.DownRight) .All (checkWinner) || grid.Diagonal (MultiDimensionalArrayExtensions.DiagonalDirection.DownLeft) .Все (checkWinner) ) { победитель = игрок; } }
Учитывая то, что вы уже знаете по созданию методов расширения, это должно быть довольно просто расшифровать. Сначала вы создаете "Func":http://msdn.microsoft.com/en-us/library/bb549151.aspx
объект, который действует делегатом и позволяет вам использовать лямбда-оператор (который b => b == player
часть b => b == player
) запросить IEnumerable
(например, возвращенный из метода расширения) и вернуть bool
. Затем вы применяете этот объект Func
каждой строке, столбцу и диагонали, используя метод IEnumerable.All
. Если какой-либо из этих случаев верен, то вы назначаете переменную экземпляра winner
параметру player
. Если ни один из этих случаев не соответствует действительности, то ничего не происходит.
Теперь измените ваш метод CheckForWinnable
:
частный void CheckForWinnable () { if (winner == TicTacToePlayer.None) { Func <TicTacToePlayer, bool> checkNone = b => b == TicTacToePlayer.None; if (! grid.All (). Any (checkNone)) { winnable = false; } } }
Этот метод очень похож на CheckForWin
. Сначала вы проверяете, есть ли в игре победитель. Если это не так, вы создаете объект Func
, который проверяет, равен ли элемент TicTacToePlayer
None
. Затем вы применяете этот Func
ко всем пробелам в сетке, проверяя, свободны ли какие-либо пробелы. Если их нет, игра больше не выигрывает, и вы переключаете переменную экземпляра winnable
.
Заканчивать
На данный момент ваша игра готова к работе. Вы можете скомпилировать и запустить свой проект, нажав F5 и начать играть (либо самостоятельно, либо с партнером). По очереди размещайте фигуры на доске, следите за изменением сообщения о статусе и смотрите, что произойдет, когда вы выиграете. После того, как вы выиграете или сыграете вничью, нажмите кнопку сброса и посмотрите, как игра вернется в исходное состояние.
На данный момент есть множество разных вещей, которые вы могли бы сделать. Вы можете реализовать подсчет выигрышей, который показывает, сколько раз выиграл каждый игрок. Вы можете изменить способ отображения фигур или добавить классную анимацию, когда объявлен победитель. Вы могли бы сделать игру более интересной, возможно, противопоставив Альянсу повстанцев Галактическую Империю?
На данный момент все зависит от вас, чтобы расширять и развивать то, что вы хотите. Я надеюсь, что вам понравилось читать этот урок так же, как и мне, и я с нетерпением жду ваших комментариев.