Game logic bug fixes, finishing up ConsoleInteraction

This commit is contained in:
Markus Himmel
2016-08-27 14:22:15 +02:00
parent aaefe84e57
commit 640202dca4
4 changed files with 95 additions and 26 deletions

View File

@@ -1,54 +1,105 @@
using System;
using System.Collections.Generic;
/*
* ConsoleInteraction.cs
* Copyright (c) 2016 Markus Himmel
* This file is distributed under the terms of the MIT license.
*/
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Morris
{
/// <summary>
/// Ermöglicht Eingabe und Ausgabe der Spielsituation auf der Konsole.
/// </summary>
class ConsoleInteraction : IGameStateObserver, IMoveProvider
{
public ConsoleInteraction()
{
// Konsolenparameter setzen
Console.OutputEncoding = Encoding.Unicode;
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Black;
Console.Clear();
}
public void Notify(IReadOnlyGameState state)
{
// Ein mit Leerzeichen initialisiertes 8*8 Jagged Array
char[][] field = Enumerable.Repeat(0, 8).Select(_ => Enumerable.Repeat(' ', 8).ToArray()).ToArray();
// Ein mit Leerzeichen initialisiertes 13*13 Jagged Array
char[][] field = Enumerable.Repeat(0, 13).Select(_ => Enumerable.Repeat(' ', 13).ToArray()).ToArray();
// Spielpositionen mit Belegung
for (int i = 0; i < GameState.FIELD_SIZE; i++)
{
var point = CoordinateTranslator.CoordinatesFromID(i);
var point = CoordinateTranslator.CoordinatesFromID(i).Select(x => 2 * x).ToArray();
switch (state.Board[i])
{
case Occupation.Free:
field[point.Item1][point.Item2] = 'F';
field[point[0]][point[1]] = '';
break;
case Occupation.Black:
field[point.Item1][point.Item2] = 'B';
field[point[0]][point[1]] = '';
break;
case Occupation.White:
field[point.Item1][point.Item2] = 'W';
field[point[0]][point[1]] = '';
break;
}
}
//for (int i = 0; i < GameState.FIELD_SIZE; i++)
//{
// foreach (int j in GameState.GetConnected(i))
// {
// Linien auf dem Spielfeld zeichnen. Wo diese hingehören, wird aus den Verbindungsdaten, die GameState
// bereithält, "on the fly" bestimmt. Diesen Schritt des Zeichnens des Spielfelds könnte man cachen,
// das wäre hier aber mit Blick auf die Größe des Spielfelds eine premature optimization.
for (int i = 0; i < GameState.FIELD_SIZE; i++)
{
var pointI = CoordinateTranslator.CoordinatesFromID(i).Select(x => 2 * x).ToArray();
foreach (int j in GameState.GetConnected(i).Where(j => j < i))
{
var pointJ = CoordinateTranslator.CoordinatesFromID(j).Select(x => 2 * x).ToArray();
// }
//}
if (pointI[0] == pointJ[0])
{
// Horizontale Linien
for (int k = Math.Min(pointI[1], pointJ[1]) + 1; k <= Math.Max(pointI[1], pointJ[1]) - 1; k++)
{
field[pointI[0]][k] = '-';
}
}
else
{
// Vertikale Linien
for (int k = Math.Min(pointI[0], pointJ[0]) + 1; k <= Math.Max(pointI[0], pointJ[0]) - 1; k++)
{
field[k][pointI[1]] = '|';
}
}
}
}
// Spielfeld tatsächlich ausgeben
foreach (var row in field)
{
Console.WriteLine(new string(row));
}
// Spielstatus mitteilen, falls das Spiel nicht mehr läuft.
switch (state.Result)
{
case GameResult.BlackVictory:
Console.WriteLine("Schwarz hat gewonnen.");
break;
case GameResult.WhiteVictory:
Console.WriteLine("Weiß hat gewonnnen.");
break;
case GameResult.Draw:
Console.WriteLine("Unentschieden.");
break;
}
}
public GameMove GetNextMove(IReadOnlyGameState state)
{
// So lange wieder fragen, bis ein Input eingegeben wird, der geparst werden kann
@@ -57,7 +108,20 @@ namespace Morris
{
try
{
Console.Write("Bitte gib einen Zug ein: ");
string phase;
switch (state.GetPhase(state.NextToMove))
{
case Phase.Placing:
phase = "Platziert";
break;
case Phase.Moving:
phase = "Bewegt";
break;
default:
phase = "Fliegt";
break;
}
Console.Write($"{(state.NextToMove == Player.Black ? "Schwarz" : "Weiß")} am Zug ({phase}): ");
// Eingabe parsen
var input = Console.ReadLine().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
@@ -84,7 +148,7 @@ namespace Morris
}
catch
{
// Einfach nocheinmal versuchen...
// Einfach nocheinmal fragen, wenn der Input nicht geparst werden konnte
}
}
}

View File

@@ -47,12 +47,12 @@ namespace Morris
// Die XML-Kommentare sind hier ausgelassen, weil die Methodennamen ausreichend sprechend sein sollten
public static Tuple<int, int> CoordinatesFromHumanReadable(string human)
public static int[] CoordinatesFromHumanReadable(string human)
{
if (!humans.Keys.Contains(human))
throw new ArgumentException("Dies ist keine gültige Positionsangabe");
return Tuple.Create(human[0] - 'a', human[1] - '1');
return new[] { 6 - (human[1] - '1'), human[0] - 'a' };
}
public static string HumanReadableFromCoordinates(Tuple<int, int> coord)
@@ -65,7 +65,7 @@ namespace Morris
throw new ArgumentException("Dies sind keine gültigen Koordinaten");
}
public static Tuple<int, int> CoordinatesFromID(int id)
public static int[] CoordinatesFromID(int id)
{
return CoordinatesFromHumanReadable(HumanReadableFromID(id));
}

View File

@@ -50,7 +50,7 @@ namespace Morris
new[] { 1, 4, 7 },
new[] { 16, 19, 22 },
new[] { 8, 12, 17 },
new[] { 5, 12, 20 },
new[] { 5, 13, 20 },
new[] { 2, 14, 23 }
}.Select(mill => Array.AsReadOnly(mill)).ToArray());
@@ -296,8 +296,12 @@ namespace Morris
// ggf. wegbewegter Stein
if (move.From.HasValue)
Board[move.From.Value] = Occupation.Free;
else if (++stonesPlaced[NextToMove] == STONES_MAX)
playerPhase[NextToMove] = Phase.Moving;
else
{
currentStones[NextToMove]++;
if (++stonesPlaced[NextToMove] == STONES_MAX)
playerPhase[NextToMove] = Phase.Moving;
}
// Hinbewegter Stein
Board[move.To] = (Occupation)NextToMove;
@@ -311,7 +315,7 @@ namespace Morris
}
// Gegner hat nur noch zwei Steine
if (currentStones[NextToMove.Opponent()] == 2)
if (playerPhase[NextToMove.Opponent()] != Phase.Placing && currentStones[NextToMove.Opponent()] == 2)
Result = (GameResult)NextToMove;
// Gegner ist jetzt dran

View File

@@ -14,6 +14,7 @@ namespace Morris
var g = new Game(a, a);
g.AddObserver(a);
g.Run();
Console.ReadKey();
}
}
}