/// <summary> /// fügt Informationen über das Spielfeld als Kommentare im Quellcode hinzu /// </summary> /// <param name="csFile">Cs-Datei, wo der Code hinzugefügt werden soll</param> /// <param name="field">Spielfeld, welches dargestellt werden soll</param> /// <param name="headLine">Überschrift, welche über dem Level verwendet werden soll</param> public static void AddLevelComments(CsFile csFile, SokowahnField field, string headLine) { int commentWidth = Math.Max(field.width + 2, 35); commentWidth = (commentWidth + 1) / 2 * 2 + field.width % 2; string levelId = field.GetLevelId(); string emptyLine = " *" + new string(' ', commentWidth - 2) + "*"; csFile.Write("/" + new string('*', commentWidth)); csFile.Write(emptyLine); csFile.Write((" * " + new string(' ', (commentWidth - 14 - headLine.Length) / 2) + "--- " + headLine + " ---").PadRight(commentWidth, ' ') + "*"); csFile.Write(emptyLine); csFile.Write(" " + new string('*', commentWidth)); csFile.Write(emptyLine); csFile.Write((" * Level-Hash: " + levelId.Remove(0, levelId.LastIndexOf('_') + 1)).PadRight(commentWidth, ' ') + "*"); csFile.Write(emptyLine); csFile.Write((" * Size : " + field.width + " x " + field.height + " (" + (field.width * field.height).ToString("N0") + ")").PadRight(commentWidth, ' ') + "*"); csFile.Write((" * Boxes : " + field.boxesCount).PadRight(commentWidth, ' ') + "*"); csFile.Write(emptyLine); string centerChars = new string(' ', (commentWidth - field.width - 2) / 2); csFile.Write(field.ToString().Replace("\r", "").Split('\n').Select(x => " *" + centerChars + x + centerChars + "*")); csFile.Write(emptyLine); csFile.Write(" " + new string('*', commentWidth) + "/"); }
/// <summary> /// erstellt ein vollständiges Hashbuilder-Projekt und kompiliert dieses /// </summary> /// <param name="field">Spielfeld, welches als Grundlage dient</param> /// <param name="solutionPath">Pfad zum Ordner, wo die Projektmappe erstellt werden soll</param> public static void CreateProject(SokowahnField field, string solutionPath) { var scanner = new SokowahnField(field); Console.WriteLine(scanner.ToString()); Console.WriteLine(); string levelId = scanner.GetLevelId(); var solutionGuid = CsProject.NewGuid("S" + levelId); var projectGuid = CsProject.NewGuid("P" + levelId); string projectName = "Sokowahn_HashBuilder_" + levelId; var csFile = new CsFile(); csFile.Write(); csFile.Write(); GenLevelTools.AddLevelComments(csFile, scanner, "Hash Builder Alpha"); csFile.Write(); csFile.Write(); #region # // --- using *.* --- csFile.Write("#region # using *.*"); csFile.Write(); csFile.Write("using System;"); csFile.Write("using System.Linq;"); csFile.Write("using System.Collections.Generic;"); csFile.Write("using System.Diagnostics;"); csFile.Write(); csFile.Write("// ReSharper disable UnusedMember.Local"); csFile.Write(); csFile.Write("#endregion"); csFile.Write(); csFile.Write(); #endregion #region # // --- Program.cs --- csFile.Write("namespace " + projectName, ns => { ns.Write("static unsafe class Program", cl => { GenLevelTools.AddLevelBasics(cl, scanner); GenLevelTools.AddBoxFunctions(cl); #region # // --- static int ScanTopLeftPos(int startPos) --- cl.Write("static readonly int[] PlayerDirections = { -1, +1, -FieldWidth, +FieldWidth };"); cl.Write(); cl.Write("static readonly int[] ScanTmp = new int[FieldCount];"); cl.Write(); cl.Write("static int ScanTopLeftPosIntern(int* next, bool* scanned, char* fd)", sc => { sc.Write("int bestPos = int.MaxValue;"); sc.Write("int nextPos = 1;"); sc.Write("while (nextPos > 0)", wh => { wh.Write("int checkPos = next[--nextPos];"); wh.Write("if (checkPos < bestPos) bestPos = checkPos;"); wh.Write("scanned[checkPos] = true;"); wh.Write("if (!scanned[checkPos - 1] && fd[checkPos - 1] == ' ') next[nextPos++] = checkPos - 1;"); wh.Write("if (!scanned[checkPos + 1] && fd[checkPos + 1] == ' ') next[nextPos++] = checkPos + 1;"); wh.Write("if (!scanned[checkPos - FieldWidth] && fd[checkPos - FieldWidth] == ' ') next[nextPos++] = checkPos - FieldWidth;"); wh.Write("if (!scanned[checkPos + FieldWidth] && fd[checkPos + FieldWidth] == ' ') next[nextPos++] = checkPos + FieldWidth;"); }); sc.Write("return bestPos;"); }); cl.Write(); cl.Write("static int ScanTopLeftPos(int startPos)", sc => { sc.Write("fixed (int* next = ScanTmp) fixed (char* fd = FieldData)", f => { f.Write("bool* scanned = stackalloc bool[FieldCount];"); f.Write("*next = startPos;"); f.Write("return ScanTopLeftPosIntern(next, scanned, fd);"); }); }); cl.Write(); #endregion #region # // --- static int ScanReverseMoves(int startPlayerPos, ushort[] output) --- cl.Write("static int ScanReverseMoves(int startPlayerPos, ushort[] output, int boxesCount)", sc => { sc.Write("int outputLen = 0;"); sc.Write(); sc.Write("bool* scannedFields = stackalloc bool[FieldCount];"); sc.Write("ushort* scanTodo = stackalloc ushort[FieldCount];"); sc.Write(); sc.Write("int scanTodoPos = 0;"); sc.Write("int scanTodoLen = 0;"); sc.Write(); sc.Write("scanTodo[scanTodoLen++] = (ushort)startPlayerPos;"); sc.Write("scannedFields[startPlayerPos] = true;"); sc.Write(); sc.Write("while (scanTodoPos < scanTodoLen)", wh => { wh.Write("ushort scan = scanTodo[scanTodoPos++];"); wh.Write(); wh.Write("#region # // --- links (zurück nach rechts) ---"); wh.Write("switch (FieldData[scan - 1])", sw => { sw.Write("case '#': break;"); sw.Write("case ' ': if (!scannedFields[scan - 1]) { scannedFields[scan - 1] = true; scanTodo[scanTodoLen++] = (ushort)(scan - 1); } break;"); sw.Write("default:", bx => { bx.Write("if (FieldData[scan + 1] != ' ') break;"); bx.Write(); bx.Write("MoveBox((ushort)(scan - 1), scan);"); bx.Write(); bx.Write("for (int i = 0; i < boxesCount; i++) output[outputLen + i] = BoxPosis[i];"); bx.Write("output[outputLen + boxesCount] = (ushort)(scan + 1);"); bx.Write("outputLen += boxesCount + 1;"); bx.Write(); bx.Write("MoveBox(scan, (ushort)(scan - 1));"); }); sw.Write("break;"); }); wh.Write("#endregion"); wh.Write(); wh.Write("#region # // --- rechts (zurück nach links) ---"); wh.Write("switch (FieldData[scan + 1])", sw => { sw.Write("case '#': break;"); sw.Write("case ' ': if (!scannedFields[scan + 1]) { scannedFields[scan + 1] = true; scanTodo[scanTodoLen++] = (ushort)(scan + 1); } break;"); sw.Write("default:", bx => { bx.Write("if (FieldData[scan - 1] != ' ') break;"); bx.Write(); bx.Write("MoveBox((ushort)(scan + 1), scan);"); bx.Write(); bx.Write("for (int i = 0; i < boxesCount; i++) output[outputLen + i] = BoxPosis[i];"); bx.Write("output[outputLen + boxesCount] = (ushort)(scan - 1);"); bx.Write("outputLen += boxesCount + 1;"); bx.Write(); bx.Write("MoveBox(scan, (ushort)(scan + 1));"); }); sw.Write("break;"); }); wh.Write("#endregion"); wh.Write(); wh.Write("#region # // --- oben (zurück nach unten) ---"); wh.Write("switch (FieldData[scan - FieldWidth])", sw => { sw.Write("case '#': break;"); sw.Write("case ' ': if (!scannedFields[scan - FieldWidth]) { scannedFields[scan - FieldWidth] = true; scanTodo[scanTodoLen++] = (ushort)(scan - FieldWidth); } break;"); sw.Write("default:", bx => { bx.Write("if (FieldData[scan + FieldWidth] != ' ') break;"); bx.Write(); bx.Write("MoveBox((ushort)(scan - FieldWidth), scan);"); bx.Write(); bx.Write("for (int i = 0; i < boxesCount; i++) output[outputLen + i] = BoxPosis[i];"); bx.Write("output[outputLen + boxesCount] = (ushort)(scan + FieldWidth);"); bx.Write("outputLen += boxesCount + 1;"); bx.Write(); bx.Write("MoveBox(scan, (ushort)(scan - FieldWidth));"); }); sw.Write("break;"); }); wh.Write("#endregion"); wh.Write(); wh.Write("#region # // --- unten (zurück nach oben) ---"); wh.Write("switch (FieldData[scan + FieldWidth])", sw => { sw.Write("case '#': break;"); sw.Write("case ' ': if (!scannedFields[scan + FieldWidth]) { scannedFields[scan + FieldWidth] = true; scanTodo[scanTodoLen++] = (ushort)(scan + FieldWidth); } break;"); sw.Write("default:", bx => { bx.Write("if (FieldData[scan - FieldWidth] != ' ') break;"); bx.Write(); bx.Write("MoveBox((ushort)(scan + FieldWidth), scan);"); bx.Write(); bx.Write("for (int i = 0; i < boxesCount; i++) output[outputLen + i] = BoxPosis[i];"); bx.Write("output[outputLen + boxesCount] = (ushort)(scan - FieldWidth);"); bx.Write("outputLen += boxesCount + 1;"); bx.Write(); bx.Write("MoveBox(scan, (ushort)(scan + FieldWidth));"); }); sw.Write("break;"); }); wh.Write("#endregion"); wh.Write(); }); sc.Write(); sc.Write("return outputLen;"); }); cl.Write(); #endregion #region # // --- static void Main() --- cl.Write("static void Main()", main => { main.Write("for (int boxesCount = 1; boxesCount <= TargetPosis.Length; boxesCount++)", bx => { bx.Write("int stateLen = boxesCount + 1;"); bx.Write("var todoBuf = new ushort[16777216 / (stateLen + 1) * (stateLen + 1)];"); bx.Write("int todoLen = 0;"); bx.Write("var stopWatch = Stopwatch.StartNew();"); bx.Write(); #region # // --- Suche End-Varianten --- bx.Write("#region # // --- search all finish-positions -> put into \"todoBuf\" ---", sc => { sc.Write("var checkDuplicates = new HashSet<ulong>();"); sc.Write(); sc.Write("foreach (var boxesVariant in SokoTools.FieldBoxesVariants(TargetPosis.Length, boxesCount).Select(v => v.Select(f => TargetPosis[f]).ToArray()))", fe => { fe.Write("foreach (var box in boxesVariant) FieldData[box] = '$';"); fe.Write(); fe.Write("ulong boxCrc = SokoTools.CrcCompute(SokoTools.CrcStart, boxesVariant, 0, boxesVariant.Length);"); fe.Write(); fe.Write("foreach (var box in boxesVariant)", feb => { feb.Write("foreach (int playerDir in PlayerDirections)", febs => { febs.Write("int playerPos = box - playerDir;"); febs.Write("if (FieldData[playerPos] != ' ') continue;"); febs.Write("if (FieldData[playerPos - playerDir] != ' ') continue;"); febs.Write(); febs.Write("ulong crc = SokoTools.CrcCompute(boxCrc, playerPos);"); febs.Write("if (checkDuplicates.Contains(crc)) continue;"); febs.Write("checkDuplicates.Add(crc);"); febs.Write(); febs.Write("int topPlayerPos = ScanTopLeftPos(playerPos);"); febs.Write(); febs.Write("if (topPlayerPos != playerPos)", febst => { febst.Write("crc = SokoTools.CrcCompute(boxCrc, topPlayerPos);"); febst.Write("if (checkDuplicates.Contains(crc)) continue;"); febst.Write("checkDuplicates.Add(crc);"); }); febs.Write(); febs.Write("todoBuf[todoLen++] = 0;"); febs.Write("for(int i = 0; i < boxesVariant.Length; i++) todoBuf[todoLen + i] = boxesVariant[i];"); febs.Write("todoBuf[todoLen + boxesVariant.Length] = (ushort)topPlayerPos;"); febs.Write("todoLen += stateLen;"); }); }); fe.Write(); fe.Write("foreach (var box in boxesVariant) FieldData[box] = ' ';"); }); }); bx.Write("#endregion"); bx.Write(); #endregion #region # // --- Durchsuche Rückwärts alle Möglichkeiten --- bx.Write("#region # // --- search all possible positions (bruteforce-reverse) ---", sc => { sc.Write("var hash = new DictionaryFastCrc<ushort>();"); sc.Write("var nextBuf = new ushort[stateLen * boxesCount * 4];"); sc.Write(); sc.Write("int todoPos = 0;"); sc.Write("while (todoPos < todoLen)", wh => { wh.Write("ushort depth = todoBuf[todoPos++];"); wh.Write("ulong crc = SetBoxes(todoBuf, todoPos, boxesCount);"); wh.Write("int playerPos = ScanTopLeftPos(todoBuf[todoPos + boxesCount]);"); wh.Write("crc = SokoTools.CrcCompute(crc, playerPos);"); wh.Write(); wh.Write("if (hash.ContainsKey(crc))", skip => { skip.Write("todoPos += stateLen;"); skip.Write("continue;"); }); wh.Write(); wh.Write("hash.Add(crc, depth);"); wh.Write("if ((hash.Count & 0xffff) == 0) Console.WriteLine(\"[\" + boxesCount + \"] (\" + depth + \") \" + ((todoLen - todoPos) / (stateLen + 1)).ToString(\"N0\") + \" / \" + hash.Count.ToString(\"N0\"));"); wh.Write(); wh.Write("depth++;"); wh.Write("int nextLength = ScanReverseMoves(playerPos, nextBuf, boxesCount);"); wh.Write("for (int next = 0; next < nextLength; next += stateLen)", f => { f.Write("todoBuf[todoLen++] = depth;"); f.Write("for (int i = 0; i < stateLen; i++) todoBuf[todoLen++] = nextBuf[next + i];"); }); wh.Write(); wh.Write("todoPos += stateLen;"); wh.Write("if (todoBuf.Length - todoLen < nextLength * 2)", arr => { arr.Write("Array.Copy(todoBuf, todoPos, todoBuf, 0, todoLen - todoPos);"); arr.Write("todoLen -= todoPos;"); arr.Write("todoPos = 0;"); }); }); sc.Write("stopWatch.Stop();"); sc.Write("Console.WriteLine();"); sc.Write("Console.ForegroundColor = ConsoleColor.Yellow;"); sc.Write("Console.WriteLine(\"[\" + boxesCount + \"] ok. Hash: \" + hash.Count.ToString(\"N0\") + \" (\" + stopWatch.ElapsedMilliseconds.ToString(\"N0\") + \" ms)\");"); sc.Write("Console.ForegroundColor = ConsoleColor.Gray;"); sc.Write("Console.WriteLine();"); }); bx.Write("#endregion"); #endregion }); }); #endregion }); }); csFile.SaveToFile(solutionPath + "Program.cs"); #endregion var csSokoTools = GenStaticTools.GenSokoTools(projectName); csSokoTools.SaveToFile(solutionPath + "SokoTools.cs"); var csDictFast = GenStaticTools.GenDictionaryFastCrc(projectName); csDictFast.SaveToFile(solutionPath + "DictionaryFastCrc.cs"); var projectFile = CsProject.CreateCsProjectFile(projectGuid, projectName, new[] { "System" }, new[] { "Program.cs", "SokoTools.cs", "DictionaryFastCrc.cs" }); projectFile.SaveToFile(solutionPath + projectName + ".csproj"); var solutionFile = CsProject.CreateSolutionFile(solutionGuid, projectGuid, "Sokowahn", projectName + ".csproj"); solutionFile.SaveToFile(solutionPath + projectName + ".sln"); CsCompiler.Compile(solutionPath + projectName + ".sln"); }
/// <summary> /// analysiert alle möglichen Kistenstellungen mit einfachen (langsamen) Methoden /// </summary> /// <param name="field">Feld, welches durchsucht werden soll</param> /// <param name="minBoxes">minimale Anzahl der Kisten, welche berechnet werden sollen</param> /// <param name="maxBoxes">maximale Anzahl der Kisten, welche berechnet werden sollen</param> static List<Dictionary<ulong, ushort>> MiniSolverHashBuilder(SokowahnField field, int minBoxes = 1, int maxBoxes = int.MaxValue) { var scanner = new SokowahnField(field); var targetFields = scanner.fieldData.Select((c, i) => new { c, i }).Where(f => f.c == '.' || f.c == '*').Select(f => (ushort)f.i).ToArray(); if (targetFields.Length < maxBoxes) maxBoxes = targetFields.Length; if (minBoxes < 1 || minBoxes > maxBoxes) throw new ArgumentException("minBoxes"); var hashResults = new List<Dictionary<ulong, ushort>>(); for (int boxesCount = minBoxes; boxesCount <= maxBoxes; boxesCount++) { // --- Variablen initialisieren --- var boxes = new ushort[boxesCount]; int stateLen = 1 + boxes.Length; var todoBuf = new ushort[16777216 * 2 / (stateLen + 1) * (stateLen + 1)]; int todoPos = 0, todoLen = 0; var stopWatch = Stopwatch.StartNew(); // --- Startaufgaben scannen und setzen --- { int emptyPlayerPos = scanner.fieldData.ToList().IndexOf(' '); int[] playerDirections = { -1, +1, -scanner.width, +scanner.width }; var checkDuplicates = new HashSet<ulong>(); foreach (var boxesVariant in SokoTools.FieldBoxesVariantsStatic(targetFields.Length, boxesCount).Select(v => v.SelectArray(f => targetFields[f]))) { scanner.SetPlayerPos(emptyPlayerPos); scanner.SetBoxes(boxesVariant); var fieldData = scanner.fieldData; foreach (ushort box in boxesVariant) { foreach (int playerDir in playerDirections) { int playerPos = box - playerDir; if (fieldData[playerPos] == '#' || fieldData[playerPos] == '$' || fieldData[playerPos] == '*') continue; int revPos = playerPos - playerDir; if (fieldData[revPos] == '#' || fieldData[revPos] == '$' || fieldData[revPos] == '*') continue; scanner.SetPlayerPos(playerPos); scanner.SetPlayerTopLeft(); ulong crc = scanner.GetGameStateCrc(); if (checkDuplicates.Contains(crc)) continue; checkDuplicates.Add(crc); todoBuf[todoLen++] = 0; todoLen += scanner.GetGameState(todoBuf, todoLen); } } } } Console.WriteLine(field.ToString()); Console.WriteLine(); // --- Aufgaben weiter rückwärts gerichtet abarbeiten --- { var hash = new Dictionary<ulong, ushort>(); var nextBuf = new ushort[stateLen * boxesCount * 4]; int limitTickCount = Environment.TickCount + 1000; while (todoPos < todoLen) { ushort depth = todoBuf[todoPos++]; scanner.SetGameState(todoBuf, todoPos); todoPos += stateLen; scanner.SetPlayerTopLeft(); ulong crc = scanner.GetGameStateCrc(); if (hash.ContainsKey(crc)) continue; hash.Add(crc, depth); if ((hash.Count & 0xff) == 0 && Environment.TickCount > limitTickCount) { limitTickCount = Environment.TickCount + 1000; Console.WriteLine("[" + boxesCount + "] (" + depth + ") " + ((todoLen - todoPos) / (stateLen + 1)).ToString("N0") + " / " + hash.Count.ToString("N0")); } depth++; int nextLength = scanner.ScanReverseMoves(nextBuf) * stateLen; for (int next = 0; next < nextLength; next += stateLen) { todoBuf[todoLen++] = depth; for (int i = 0; i < stateLen; i++) todoBuf[todoLen++] = nextBuf[next + i]; } if (todoBuf.Length - todoLen < nextLength * 2) { Array.Copy(todoBuf, todoPos, todoBuf, 0, todoLen - todoPos); todoLen -= todoPos; todoPos = 0; } } stopWatch.Stop(); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("[" + boxesCount + "] ok. Hash: " + hash.Count.ToString("N0") + " (" + stopWatch.ElapsedMilliseconds.ToString("N0") + " ms)"); Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(); hashResults.Add(hash); } } return hashResults; }
static void ScanBlocker(SokowahnField field) { Console.WriteLine(field.ToString()); Console.WriteLine(); var test = new DeadlockBlocker(field); int width = field.width; var allPossibleBoxPositions = test.blockerSingle.Select((b, i) => new { b, i }).Where(x => !x.b).SelectArray(x => x.i); int boxDistanceLimit = SokoTools.MaxBoxDistance(allPossibleBoxPositions, 0, allPossibleBoxPositions.Length, width); var areas = new int[field.fieldData.Length]; int boxCount = 2; var nextBuf = new ushort[boxCount * 4 * (boxCount + 1)]; for (int boxDistance = 1; boxDistance <= boxDistanceLimit; boxDistance++) { Func<int[], int, bool> validateMethod = (boxes, index) => !test.blockerSingle[boxes[index]] && SokoTools.MaxBoxDistance(boxes, 0, index + 1, width) <= boxDistance; foreach (var set in SokoTools.FieldBoxesVariantsExtended(field.fieldData.Length, boxCount, validateMethod)) { if (test.blockerSingle[set[set.Length - 1]]) continue; if (SokoTools.MaxBoxDistance(set, 0, set.Length, width) != boxDistance) continue; test.ScanAreasWithBoxes(set, areas); field.SetGameState(areas[2], set.SelectArray(x => (ushort)x)); if (field.boxesRemain == 0) continue; int nextCount = field.ScanMoves(nextBuf); if (nextCount == 0) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine("--- DEAD ---\r\n"); Console.ForegroundColor = ConsoleColor.Gray; } Console.WriteLine(field.ToString()); if (nextCount == 0) { int stop = 0; } } } }
static ushort[] TestScan(int width, TopLeftTodo topLeftTodo, HashSet<ushort> ways, SokowahnField view, bool debug = true) { var state = topLeftTodo.state; view.SetGameState(state); var result = ScanBestTopLeftWay(state[0], width, ways, new HashSet<ushort>(state.Skip(1))); if (state.Length > 1 && CheckDeadlock(topLeftTodo, view, result[result.Count - 1])) return null; if (debug) { Console.SetCursorPosition(0, Math.Max(0, Console.CursorTop - 2)); Console.WriteLine(new string(' ', Console.WindowWidth - 1)); Console.SetCursorPosition(0, 1); Console.WriteLine(view.ToString()); Console.WriteLine(); string line = state[0] + " - " + string.Join(", ", result.Skip(1)); Console.WriteLine(line); Console.WriteLine(); } var known = new HashSet<ushort>(topLeftTodo.known); if (known.Contains(result[result.Count - 1])) return new[] { result[result.Count - 1] }; var resultFiltered = result.Where(f => !known.Contains(f)).ToArray(); return resultFiltered; }
/// <summary> /// startet ein Mini-Konsolen Spiel /// </summary> /// <param name="field">Spielfeld, was gespielt werden soll</param> public static void Run(SokowahnField field) { var game = new SokowahnField(field); var steps = new Stack<ushort[]>(); for (; ; ) { string output = game.ToString(); int playerChar = output.IndexOfAny(new[] { '@', '+' }); Console.Clear(); Console.ForegroundColor = ConsoleColor.Gray; Console.Write(output.Substring(0, playerChar)); Console.ForegroundColor = ConsoleColor.Yellow; Console.Write(output[playerChar]); Console.ForegroundColor = ConsoleColor.Gray; Console.Write(output.Remove(0, playerChar + 1)); Console.WriteLine(); Console.WriteLine(); Console.WriteLine("Steps: " + steps.Count.ToString("N0")); Console.WriteLine(); Console.WriteLine("Remain: " + game.boxesRemain); if (game.boxesRemain == 0) return; bool step = false; var oldState = game.GetGameState(); switch (Console.ReadKey(true).Key) { case ConsoleKey.Escape: return; case ConsoleKey.A: case ConsoleKey.NumPad4: case ConsoleKey.LeftArrow: step = game.MoveLeft(); break; case ConsoleKey.D: case ConsoleKey.NumPad6: case ConsoleKey.RightArrow: step = game.MoveRight(); break; case ConsoleKey.W: case ConsoleKey.NumPad8: case ConsoleKey.UpArrow: step = game.MoveUp(); break; case ConsoleKey.S: case ConsoleKey.NumPad2: case ConsoleKey.DownArrow: step = game.MoveDown(); break; case ConsoleKey.Z: case ConsoleKey.Delete: case ConsoleKey.Backspace: { if (steps.Count == 0) break; game.SetGameState(steps.Pop(), 0); } break; default: continue; } if (step) { steps.Push(oldState); } } }