Windows Phone 7 Game Development Skapa Tic-Tac-Toe med XNA

XNA är en högkonjunktur för skapande av spel för Microsoft-enheter, inklusive Windows-datorer, Xbox 360 och det helt nya Windows Phone 7-operativsystemet. I en tidigare handledning täckte vi grunderna i XNA-ramverket, inklusive hanteringsinmatning och visning av sprites. I den här artikeln lär du dig att kombinera dessa färdigheter med en egen spelide för att skapa något roligt att spela och enkelt att hämta.

Projekt Överblick

I denna handledning kommer du att skapa ett enkelt Tic-Tac-Toe-spel som kan spelas med vänner. Tic-Tac-Toe är ett enkelt spel där två spelare tar växlande svängar placera tecken på ett tre i tre rutor. Den första spelaren använder en O och den andra spelaren använder en X. För att vinna spelet måste en spelare ordna tre av sina karaktärer i en rad, kolumn eller diagonal.

Medan det här spelet är enkelt krävs det ett par olika färdigheter att bygga. Först använder du XNA och dess grafikfunktioner för att bygga ditt spel. Som sådan måste du vara bekant med att visa sprites. För det andra behöver du veta hur man hanterar beröring på telefonens pekpanel. Lyckligtvis tillhandahåller XNA ett högt API för att komma åt dessa berör. Slutligen måste du tillämpa lite programmering och logisk know-how för att bestämma vinnaren. För denna handledning kommer mycket av det att ges till dig. När du bygger spel i framtiden måste du komma med dina egna idéer och logik.

Skapa ditt projekt

För att börja, se till att du har installerat de senaste utvecklingsverktygen för Windows Phone 7. Om du inte har uppdaterat dina verktyg sedan den senaste WP7-handledningen på MobileTuts, bör du besöka Microsoft Download Center och få RTW-versionen. Den senaste versionen kommer med den slutliga emulatorn som visar hur dina appar ska fungera för att släppa ut daghårdvara.

När du har visat att dina verktyg är uppdaterade öppnar du Visual Studio 2010 och klickar på länken "Ny projekt ..." i vänster sidofält. I dialogrutan som dyker upp väljer du "XNA Game Studio 4" i den vänstra kolumnen och kontrollerar att "Windows Phone Game (4.0)" mallen är vald till höger. Ge ditt projekt ett lämpligt namn som "TicTacToe" och bekräfta att kryssrutan "Skapa katalog för lösning" är markerad. Om du har gjort allt detta korrekt bör din dialogruta matcha följande bild:

Klicka på "OK" för att skapa ditt nya projekt. Visual Studio 2010 kommer att generera nödvändiga filer i din angivna katalog och öppna Game1.cs för redigering.

Importera mediainnehåll

Eftersom du använder sprites för all spelgrafik i det här projektet måste du importera de nödvändiga objekten till ditt projekt. I nedladdningen som följer med denna handledning hittar du en Media katalog som innehåller en mängd bilder. I lösningsutforskaren på höger sida av skärmen, leta reda på innehållsprojektet (kallat TicTacToeContent) och högerklicka på det. Från snabbmenyn väljer du "Lägg till> Befintlig föremål ...". När dialogrutan öppnas, bläddra till Media mapp som du släpper ut från handledningen och väljer alla bilder som finns i den. Du bör kunna berätta från bildnamnen exakt vad varje objekt innehåller.

Efter att ha importerat ditt media ska din lösningsutforskare likna följande:

Laddar och tilldela innehåll

Nu när dina medier har importerats till innehållsprojektet som bifogas din lösning måste du ladda varje bild som en separat textur. Eftersom du använder dessa texturer i hela ditt spel kommer du att lagra dem i fält inuti din spelklass. Öppna filen Game1.cs och lägg till följande fält högst upp i din klassdeklaration:

 Texture2D gridTexture; Rektangel gridRectangle; Texture2D resetButton; Rektangel resetButtonPosition; Texture2D oPiece; Texture2D xPiece; Texture2D oWinner; Texture2D xWinner; Texture2D noWinner; Texture2D oTurn; Texture2D xTurn; 

Du ser att alla bilder du importerat har ett fält för att säkerhetskopiera det. Dessutom har det huvudsakliga spelruten och återställningsknappens textur fält av typ Rektangel som kommer att användas för att placera dessa objekt. Dessa lagras som fält eftersom de inte kommer att förändras under spelet.

Nu när du har rätt fält skapade är det dags att instansera Texture2D och Rektangel objekt som tilldelas fälten. Bläddra ner till din Game1.cs filen tills du når LoadContent metod. Inuti denna metod, sätt in följande kod efter den linje som läser spriteBatch = ny SpriteBatch (GraphicsDevice);:

 gridTexture = Content.Load( "TicTacToe_Grid"); gridRectangle = ny rektangel (0, 0, spriteBatch.GraphicsDevice.Viewport.Width, spriteBatch.GraphicsDevice.Viewport.Height); oPiece = Content.Load( "TicTacToe_O"); xPiece = Content.Load( "TicTacToe_X"); resetButton = Content.Load( "TicTacToe_Reset"); resetButtonPosition = ny rektangel (spriteBatch.GraphicsDevice.Viewport.Width / 2 - (resetButton.Width / 2), spriteBatch.GraphicsDevice.Viewport.Height - 95, resetButton.Width, resetButton.Height); oWinner = Content.Load( "TicTacToe_O_Winner"); xWinner = Content.Load( "TicTacToe_X_Winner"); noWinner = Content.Load( "TicTacToe_Draw"); oTurn = Content.Load( "TicTacToe_O_Turn"); xTurn = Content.Load( "TicTacToe_X_Turn"); 

Definiera spelarbetet

Efter att ha laddat sprites som kommer att användas av spelet, måste du komma överens med det arbetsflöde som spelet kommer att springa på. För Tic-Tac-Toe är det här ganska enkelt och ser något ut som följande:

  1. Rita spelningsnätet
  2. Rita de för närvarande spelade bitarna
  3. Rita nuvarande spelstatus (vars tur det är eller om det finns en vinnare)
  4. Om spelet har vunnits eller inte längre är winnable, rita återställningsknappen
  5. Om ingen har vunnit och spelet fortfarande är winnable, ta hand om handen på rutnätet och kolla efter en vinnare om en beröring görs
  6. Om spelet har vunnits eller inte längre är winnable, ta hand om handen på återställningsknappen
    • Om nollställningsknappen berörs, återställ spelet

Som du säkert kan berätta, faller dessa steg i en av två grundläggande kategorier, Draw eller Update. Skanna igenom Game1.cs filen ser du att du har två metoder, Dra och Uppdatering Det är de perfekta behållarna för koden som krävs för att beskriva arbetsflödet.

Lagring av spelstatus

Om man tittar på arbetsflödet kan man skilja att det finns fyra olika saker att hålla reda på för att hantera spelstaten. Först måste du spåra tillståndet för varje rutnät på spelplanen. Du gör detta med hjälp av en anpassad uppräkning och en mångdimensionell matris. Därefter måste du hålla reda på om spelet har vunnits och av vem. För det tredje måste du spåra vars vänd det är. Slutligen måste du spåra är om spelet är winnable. Denna artikel initialiseras som Sann och omräknas efter varje spelare har en tur. När alla rutor är fyllda ändras detta till falsk.

Du börjar med att definiera den anpassade uppräkningen som beskriver en spelare för ditt spel. Överst i din klassfil, ovanför din klassdeklaration, lägg till följande kod:

 allmänhet TicTacToePlayer Ingen, PlayerO, PlayerX 

Denna uppräkning avgränsar tre spelare (Ingen, PlayerO och PlayerX) och kommer att användas för att spåra både nätstatus och spelvinnaren. Lägg nu till instansvariablerna som hjälper dig att spåra spelets tillstånd. Dessa objekt ska läggas högst upp i din klassdeklaration:

 bool winnable; TicTacToePlayer vinnare; TicTacToePlayer current; TicTacToePlayer [,] rutnät; 

Du lagrar fyra saker här. Först avgränsar du om spelet fortfarande är winnable. För det andra förklarar du vinnaren. Om vinnaren är Ingen, fortsätter spelningen. Därefter lagrar du den aktuella spelaren. Slutligen lagrar du rutnätet. Vid denna tidpunkt behöver du fortfarande initiera alla dessa variabler. Tänk på att du vet att du måste återinitiera dem när någon klickar på Återställ-knappen och som sådan kan vi skapa en ny metod som hanterar den initialiseringen. Lägg till den nya metoden i din klass under initiera metod enligt följande:

 privat tomt återställning () winnable = true; vinnare = TicTacToePlayer.None; rutnät = nytt TicTacToePlayer [3, 3]; för (int i = 0; i < 3; i++)  for (int j = 0; j < 3; j++)  grid[i, j] = TicTacToePlayer.None;    

Nu, ring den här nya metoden från initiera metod genom att ändra den för att läsa enligt följande:

 skyddad åsidosättningsfel Initialisera () Reset (); base.Initialize ();  

Vid denna tidpunkt lagrar du all information du behöver, så det borde vara enkelt att börja granska gränssnittet.

Ritning av spelgränssnittet

Innan du börjar skriva något måste du ange önskad bredd och höjd för din enhet. Du gör det här inuti game1 konstruktör. Ändra det för att läsa enligt följande:

 offentliga Game1 () graphics = new GraphicsDeviceManager (this); Content.RootDirectory = "Innehåll"; graphics.PreferredBackBufferWidth = 480; graphics.PreferredBackBufferHeight = 800; // Ramhastighet är 30 fps som standard för Windows Phone. TargetElapsedTime = TimeSpan.FromTicks (333333);  

Du har lagt till uttalanden som anger att du vill att bakbuffertbredden ska vara 480 pixlar och höjden ska vara 800 pixlar. Detta underlättar väldigt att dra resten av komponenterna till spelet.

För att hålla sakerna enkla, ska du utföra varje ritningssteg inuti en separat metod. Dessa metoder kommer då att ringas från basen Dra metod som redan finns. Låt oss börja med att tänka på några bra namn för var och en av arbetsflödet, skapa metoder för det namnet och lägga till samtal till dessa methos från Dra. Enligt min mening beskriver följande metodnamn tillräckligt både vad de ska göra och de arbetsflödessteg som omfattas:

  • DrawGrid - Omfattar arbetsflödet steg ett och drar spelningsnätet
  • DrawPieces - Omsluter arbetsflöde steg två och drar bitar varhelst en bit har spelats
  • DrawStatus - Omfattar arbetsflöde steg tre och drar den aktuella spelstatusen ("O s Turn", "X's Turn", "O Wins!", "X Wins!" Eller "Draw!")
  • DrawResetButton - Omsluter arbetsflöde steg fyra och drar återställningsknappen

Skapa dessa metoder nu genom att infoga följande kod under din Dra metod:

 private void DrawGrid ()  privat void DrawPieces ()  privat void DrawStatus ()  privat void DrawResetButton ()  

Du skriver koden för dessa metoder på ett ögonblick, men för tillfället måste du lägga till dem i din Dra metod genom att ändra den för att läsa enligt följande:

 skyddad åsidosätt rubbning (GameTime gameTime) GraphicsDevice.Clear (Color.Black); spriteBatch.Begin (); DrawGrid (); DrawPieces (); DrawStatus (); DrawResetButton (); spriteBatch.End (); base.Draw (gametime);  

Du kommer märka att du har omringat samtalen till dina hjälpmetoder med samtal till spriteBatch objekt Börja och Slutet metoder. Du gör det här eftersom dina hjälpar metoder kommer alla att dra sprites och det är mycket effektivare att ringa Börja och Slutet par en gång inuti Dra snarare än inuti varje hjälparmetod.

Nu, låt oss arbeta med att få spelningsnätet att dyka upp. Spelningsnätet är en 480 pixlar bred vid 800 pixlar lång bild med en genomskinlig bakgrund och ett vitt rutnät av kvadrater 150 pixlar bred i mitten. Du importerade det tidigare. Att dra det till telefonens skärm kunde inte vara enklare. Du tar den textur du laddat och lagrade i gridTexture variabel och rita den genom att placera den med hjälp av prevoiusly instantiated gridRectangle variabel, passerar båda objekten till spriteBatch objekt Dra metod. Ändra din DrawGrid metod att läsa enligt följande:

 privat tomt DrawGrid () spriteBatch.Draw (gridTexture, gridRectangle, Color.White);  

Spara filen du arbetar med och klicka på F5 för att kompilera och köra ditt projekt. Windows Phone 7 Emulator ska dyka upp och visa ditt Tic-Tac-Toe-rutnät på en svart bakgrund precis som följande bild:

Nu när gallret är på plats, låt oss rita spelbitarna. Vid denna tidpunkt kan vi tillämpa en enkel logik för att visa bitarna men ingenting kommer att visas tills vi lägger till koden för att faktiskt hantera berör och spela spelet. Ändra din DrawPieces metod enligt följande:

 private void DrawPieces () for (int i = 0; i < 3; i++)  for (int j = 0; j < 3; j++)  if (grid[i, j] != TicTacToePlayer.None)  Texture2D texture = grid[i, j] == TicTacToePlayer.PlayerO ? oPiece : xPiece; Rectangle position = GetGridSpace(i, j, texture.Width, texture.Height); spriteBatch.Draw(texture, position, Color.White);     

Om du har ett skarpt öga (eller mer sannolikt visar Visual Studio dig röda squiggly linjer) ser du att GetGridSpace metod saknas. GetGridSpace är en bekväm metod som hjälper till att hålla lite kod ut ur DrawPieces metod och kommer också att vara till nytta när man försöker hantera beröring senare. Lägg till det i slutet av din klassdeklaration enligt följande:

 privat rektangel GetGridSpace (int kolumn, int rad, int bredd, int höjd) int centerX = spriteBatch.GraphicsDevice.Viewport.Width / 2; int centerY = spriteBatch.GraphicsDevice.Viewport.Height / 2; int x = centerX + ((kolumn - 1) * 150) - (bredd / 2); int y = centerY + ((rad - 1) * 150) - (höjd / 2); returnera ny rektangel (x, y, bredd, höjd);  

Nu ska vi titta på resten av DrawPieces. Denna metod är lite mer komplicerad än DrawGrid men det borde fortfarande vara ganska lätt att förstå. Du repeterar över varje rad och kolumn i spelplanen, lagrad i rutnät variabel och kolla för att se tillståndet för det nätutrymmet. Om rutnätet innehåller en annan spelare än "Ingen" än att rita rätt textur. Du använder den ternära operatören för att ta tag i rätt textur baserat på rutnätets tillstånd och dra sedan det med rektangeln som erhållits från GetGridSpace.

Nästa problem att hantera drar nuvarande status. Statusen visar vars tur det är, vem vann spelet, eller om spelet är oavgjort. Fyll i metoden enligt följande:

 privat tomt DrawStatus () Texture2D texture; om (vinnare! = TicTacToePlayer.None) textur = vinnare == TicTacToePlayer.PlayerO? oWinner: xWinner;  annars om (! winnable) texture = noWinner;  else texture = current == TicTacToePlayer.PlayerO? oTurn: xTurn;  Rektangelposition = ny rektangel (spriteBatch.GraphicsDevice.Viewport.Width / 2 - (texture.Width / 2), 15, texture.Width, texture.Hight); spriteBatch.Draw (textur, position, Color.White);  

Denna metod skiljer sig inte mycket från de andra ritningsmetoderna du hittills har skapat. Du har en konsistens och en position och behöver rita texturen på skärmen. Den intressanta delen av denna metod är att bestämma vilken textur som ska ritas. Du först kontrollera om det finns en vinnare. Om det finns, väljer du rätt vinnare textur och ritar det. Därefter kontrollerar du om alla rutnät är upptagna och spelet är inte längre winnable. Om så är fallet väljer du ingen vinnare textur och ritar den. Om ingen av dessa villkor är sanna, kontrollera vilken spelarens tur det är, välj rätt svängtextur och dra den. Om du sammanställer och kör ditt projekt vid denna tidpunkt (genom att trycka på F5) ser du att gränssnittet visar att det är O: s tur:

Slutligen kommer du att skapa koden som drar återställningsknappen. Detta är ganska enkelt. Om det finns en vinnare eller spelet inte längre är winnable, drar du återställningsknappens textur. Ändra DrawResetButton metod så det lyder som följer:

 privat tomt DrawResetButton () om (vinnare! = TicTacToePlayer.None ||! winnable) spriteBatch.Draw (resetButton, resetButtonPosition, Color.White);  

Vid denna tidpunkt har du skapat all kod som behövs för att rita ditt gränssnitt och alla dess delar. Nu behöver du bara hantera uppdateringen av ditt spel baserat på beröring från spelarna.

Hantering av tuffar och uppdateringsstatus

Om du följde den sista handledningen på XNA kommer mycket av följande kod att bli bekant. Hantering av beröring på skärmen är något som är ganska vanligt och det är bara inuti den beröringshanteringskod som du har din spellogik. För att börja, låt oss sätta den grundläggande beröringshanteringskoden i Uppdatering metod. Hitta Uppdatering i din game1 klass och ändra det för att läsa enligt följande:

 Uppdaterad skyddad överstyrning (GameTime gameTime) if (GamePad.GetState (PlayerIndex.One) .Buttons.Back == ButtonState.Pressed) this.Exit ();  TouchCollection berör = TouchPanel.GetState (); om (! rörande && berör.Count> 0) touching = true; TouchLocation touch = touches.First (); HandleBoardTouch (touch); HandleResetTouch (touch);  annars om (berör.Count == 0) touching = false;  base.Update (gameTime);  

Det finns några saker i denna metod att uppmärksamma. Först märker du ett nytt rörande variabel som inte existerar. Denna variabel lagrar om spelaren berörde styrelsen på föregående Uppdatering ring och förhindrar att ett långvarigt finger spelas flera gånger utan att släppa från skärmen. Lägg till rörande som en instansvariabel överst i din klassdeklaration.

 bool rörande; 

Du märker också att du gör två metalsamtal inom Uppdatering metod för metoder som inte existerar än. Lägg till dessa metoder i din klassdeklaration direkt under Uppdatering metod:

 privat tomt HandleBoardTouch (TouchLocation touch)  privat void HandleResetTouch (TouchLocation touch)  

Nu går vi igenom Uppdatering metod du ser att du söker efter aktuella berör på skärmen. Om det rör sig om och spelaren inte rörde på skärmen tidigare, tar du den första kontakten och skickar den till de metoder som hanterar styrelsen berör och återställer handen. Om spelaren inte rör skärmen och de var tidigare uppdaterar du rörande variabel till false.

Låt oss nu fylla i HandleBoardTouch och HandleResetTouch metoder. Dessa motsvarar arbetsflödet steg 5 respektive 6.

Hanteringskort

När en användare röra på skärmen måste spelet göra några saker:

  • Kontrollera om kontakten ligger inom spelområdet
  • Om det är i spelområdet, kontrollera om platsen som berörs redan är upptagen
  • Om platsen är inte ockuperad, lägg sedan en bit där och kontrollera om den aktuella spelaren har vunnit
  • Om den spelade delen var vinnaren, uppdatera spelets status som sådan. Om den spelade delen upptog den sista tillgängliga platsen markerar du spelet som oåterkallelig
  • Slutligen byt aktuell spelare

Uppdatera HandleBoardTouch att läsa enligt följande, hantera alla ovanstående steg:

 privat void HandleBoardTouch (TouchLocation touch) om (vinnare == TicTacToePlayer.None) for (int i = 0; i < 3; i++)  for (int j = 0; j < 3; j++)  Rectangle box = GetGridSpace(i, j, 150, 150); if (grid[i, j] == TicTacToePlayer.None && box.Contains((int)touch.Position.X, (int)touch.Position.Y))  grid[i, j] = current; CheckForWin(current); CheckForWinnable(); current = current == TicTacToePlayer.PlayerO ? TicTacToePlayer.PlayerX : TicTacToePlayer.PlayerO;      

Som du kan se följer den här metoden den grundläggande konturen ovan. Om det inte finns en vinnare, det itereras över varje rutnät, kontrollerar om kontakten är i det utrymmet, kontrollerar om utrymmet är öppet och lägger sedan en bit där. Att kontrollera om en vinnare och se till att spelet fortfarande är winnable hanteras med andra metoder. Låt oss stubba ut dessa metoder nu. Under HandleBoardTouch metod, lägg till följande kod:

 privat tomt CheckForWin (TicTacToePlayer-spelare)  privat tomt CheckForWinnable ()  

Förlåt nu dessa metoder tomma och kompilera och kör ditt spel. Försök vidröra styrelsen genom att klicka på emulatorn och titta på statusmeddelandet och spela upp spelningsstyckena. Du är väl på väg till ett komplett spel nu!

Vinn Villkor

Vid denna tidpunkt kommer du till det riktiga köttet i spelet, den vinnande logiken. Som diskuterats tidigare uppträder ett vinnande tillstånd när en spelares bitar upptar en av raderna, en av kolumnerna eller en av de två diagonalerna. Spelet blir oanvändbart när det inte finns någon vinnare deklarerat och det finns inga lediga platser kvar. För detta spel använder vi en multidimensionell array för att lagra nätets tillstånd och ut ur rutan C # tillhandahåller inte metoder för att kolla rader, kolumner eller diagonaler. Lyckligtvis stöder språket en fantastisk funktion som kallas förlängningsmetoder som du använder här för att göra din kod lite renare.

Skapa hjälparna

För att skapa förlängningsmetoder måste du först skapa en ny klass. Klicka på projektets namn i Solution Explorer och klicka på "Add> Class ...". Namn på din klass MultiDimensionalArrayExtensions och klicka på "Add". När din nya fil öppnas för redigering, ändra den så att din klassdeklaration ser ut som följande:

 offentliga statiska klass MultiDimensionalArrayExtensions  

Du ser att du har lagt till modifierarna offentlig och statisk till klassdeklarationen. Detta behövs för att skapa förlängningsmetoder. Nu ska vi skapa ett par metoder som vi behöver, var och en returnerar en IEnumerable för enkel fråga:

  • Row - Returnerar alla objekt i en viss rad
  • Kolumn - Returnerar alla objekt i en viss kolumn
  • Diagonal - Returnerar alla objekt i en diagonal
  • Alla - Returnerar alla objekt i den multidimensionella gruppen

Ändra din klass igen och lägg till metoderna enligt följande:

 offentliga statiska klass MultiDimensionalArrayExtensions public static IEnumerable Rad(denna T [,] array, int rad) var columnLower = array.GetLowerBound (1); var columnUpper = array.GetUpperBound (1); för (int i = columnLower; i <= columnUpper; i++)  yield return array[row, i];   public static IEnumerable Kolumn(denna T [,] array, int kolumn) var rowLower = array.GetLowerBound (0); var rowUpper = array.GetUpperBound (0); för (int i = rowLower; i <= rowUpper; i++)  yield return array[i, column];   public static IEnumerable Diagonal(denna T [,] array, DiagonalDirection riktning) var rowLower = array.GetLowerBound (0); var rowUpper = array.GetUpperBound (0); var columnLower = array.GetLowerBound (1); var columnUpper = array.GetUpperBound (1); för (int rad = rowLower, column = columnLower; row <= rowUpper && column <= columnUpper; row++, column++)  int realColumn = column; if (direction == DiagonalDirection.DownLeft) realColumn = columnUpper - columnLower - column; yield return array[row, realColumn];   public enum DiagonalDirection  DownRight, DownLeft  public static IEnumerable Allt(denna T [,] array) var rowLower = array.GetLowerBound (0); var rowUpper = array.GetUpperBound (0); var columnLower = array.GetLowerBound (1); var columnUpper = array.GetUpperBound (1); för (int rad = rowLower; row <= rowUpper; row++)  for (int column = columnLower; column <= columnUpper; column++)  yield return array[row, column];     

Du kan se att det inte finns så mycket med dessa metoder. För var och en av dem är en viss del av den flerdimensionella gruppen itererad över och den delen av matrisen returneras som en del av en IEnumerable. Den enda riktigt knepiga delen kan vara användningen av avkastning nyckelord. En förklaring av beteendet för det sökordet ligger utanför ramen för den här artikeln, men referensen C # på MSDN för avkastningsordet kan vara till hjälp om du vill lära dig mer. Som en sidotal tas mycket av arbetet med dessa förlängningsmetoder från ett användarbidrag på StackOverflow som du kan hitta här

Implementera Win Logic

Nu när de nödvändiga utvidgningsmetoderna genomförs, bör det vara ganska enkelt att genomföra win logiken. Låt oss gå tillbaka till CheckForWin metod och implementera det. Uppdatera metoden för att läsa enligt följande:

 privat tomt CheckForWin (TicTacToePlayer-spelare) Func checkWinner = b => b == spelare; om (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) .Alla (checkWinner)) vinnare = spelare;  

Med tanke på vad du redan vet från att skapa förlängningsmetoder, borde det vara ganska enkelt att dechiffrera. Du skapar först en "Func": http: //msdn.microsoft.com/en-us/library/bb549151.aspx objekt som agerar en delegat och låter dig använda ett lambda uttalande (det b => b == spelare del) för att fråga en IEnumerable (som de som returneras från förlängningsmetoden) och returnera a bool. Du tillämpar sedan detta Func objekt över varje rad, kolumn och diagonal med hjälp av IEnumerable.All metod. Om något av dessa fall är sant, tilldelar du vinnare instansvariabel till spelare parameter. Om inget av dessa fall är sant så händer ingenting.

Ändra nu din CheckForWinnable metod:

 privat tomt CheckForWinnable () om (vinnare == TicTacToePlayer.None) Func checkNone = b => b == TicTacToePlayer.None; om (! grid.All () .Varje (checkNone)) winnable = false;  

Denna metod liknar mycket CheckForWin. Först kontrollerar du för att se om spelet har en vinnare. Om det inte gör det skapar du en Func objekt som kommer att kontrollera om ett objekt är lika med TicTacToePlayer Ingen. Du tillämpar sedan detta Func mot alla utrymmen i rutnätet och kontrollerar om någon av utrymmena är obesluten. Om ingen är, är spelet inte längre winnable, och du växlar instansvariabeln winnable.

Efterbehandling

Vid denna tidpunkt är ditt spel redo att gå. Du kan kompilera och köra ditt projekt genom att slå F5 och börja spela (antingen själv eller med en partner). Vänd om att placera bitar på brädet, titta på statusmeddelandet och se vad som händer när du vinner. När du vinner eller ritar, klicka på återställningsknappen och titta på spelet återgå till sin ursprungliga status.

Vid denna tidpunkt finns det en mängd olika saker du kan göra. Du kan genomföra ett vinnande räkning som visar hur många olika tider varje spelare har vunnit. Du kan ändra hur stycken visar, eller lägg till en cool animation när en vinnare förklaras. Du kan tema spelet för att göra det lite mer intressant, kanske genom att pitta Rebel Alliance mot Galactic Empire?

Vid denna tidpunkt är det allt upp till dig att expandera och utveckla vad du vill. Jag hoppas att du njöt av att följa denna handledning så mycket som jag tyckte om att skriva det och ser fram emot att höra dina kommentarer.