|
using System; |
|
using System.Collections.Generic; |
|
using System.IO; |
|
using System.Linq; |
|
|
|
namespace Sokoban |
|
{ |
|
class Program |
|
{ |
|
static void Main(string[] args) |
|
{ |
|
var levels = BoardDefinitionProvider.GetDefinitions().ToArray(); |
|
Console.WriteLine("Provide level code:"); |
|
var code = Console.ReadLine(); |
|
var level = levels.FirstOrDefault(l => l.LevelSecret == code) ?? levels.FirstOrDefault(); |
|
if (level == null) |
|
Console.WriteLine("No levels available"); |
|
|
|
while (level != null) |
|
{ |
|
Console.Clear(); |
|
Console.WriteLine("Use arrows to move, R for level reset and Esc to exit"); |
|
var game = new Game(level.Board); |
|
BoardPrinter.Print(level.Board); |
|
while (true) |
|
{ |
|
if (Console.KeyAvailable) |
|
{ |
|
var key = Console.ReadKey(true); |
|
Move? direction = null; |
|
switch (key.Key) |
|
{ |
|
case ConsoleKey.UpArrow: |
|
direction = Move.Up; |
|
break; |
|
case ConsoleKey.DownArrow: |
|
direction = Move.Down; |
|
break; |
|
case ConsoleKey.LeftArrow: |
|
direction = Move.Left; |
|
break; |
|
case ConsoleKey.RightArrow: |
|
direction = Move.Right; |
|
break; |
|
case ConsoleKey.R: |
|
game = new Game(level.Board); |
|
BoardPrinter.Print(level.Board); |
|
break; |
|
case ConsoleKey.Escape: |
|
return; |
|
} |
|
if (direction != null) |
|
{ |
|
var fieldsToRefresh = game.MovePlayer(direction.Value).ToArray(); |
|
BoardPrinter.RefreshFields(game.Board, fieldsToRefresh); |
|
if (game.Won()) |
|
break; |
|
} |
|
} |
|
} |
|
level = levels.FirstOrDefault(l => l.LevelId == level.LevelId + 1); |
|
if (level == null) |
|
Console.WriteLine("Victory!"); |
|
else |
|
Console.WriteLine($"New level {level.LevelId}! Code: {level.LevelSecret}"); |
|
Console.ReadLine(); |
|
} |
|
} |
|
} |
|
|
|
public class Game |
|
{ |
|
private FieldLocation playerPosition; |
|
public FieldValue[][] Board { get; } |
|
|
|
private FieldValue this[FieldLocation fieldLocation] |
|
{ |
|
get { return Board[fieldLocation.Row][fieldLocation.Column]; } |
|
set |
|
{ |
|
Board[fieldLocation.Row][fieldLocation.Column] = value; |
|
if ((value & FieldValue.Player) == FieldValue.Player) |
|
playerPosition = fieldLocation; |
|
} |
|
} |
|
|
|
public Game(FieldValue[][] board) |
|
{ |
|
FieldValue[][] newBoard = new FieldValue[board.Length][]; |
|
for (int i = 0; i < newBoard.Length; i++) |
|
{ |
|
newBoard[i] = (FieldValue[])board[i].Clone(); |
|
} |
|
this.Board = newBoard; |
|
for (var row = 0; row < board.Length; row++) |
|
for (var column = 0; column < board[row].Length; column++) |
|
if ((board[row][column] & FieldValue.Player) == FieldValue.Player) |
|
playerPosition = new FieldLocation(column, row); |
|
} |
|
|
|
public IEnumerable<FieldLocation> MovePlayer(Move move) |
|
{ |
|
switch (move) |
|
{ |
|
case Move.Up: |
|
return TryMove(new FieldLocation(playerPosition.Column, playerPosition.Row - 1), new FieldLocation(playerPosition.Column, playerPosition.Row - 2)); |
|
case Move.Down: |
|
return TryMove(new FieldLocation(playerPosition.Column, playerPosition.Row + 1), new FieldLocation(playerPosition.Column, playerPosition.Row + 2)); |
|
case Move.Left: |
|
return TryMove(new FieldLocation(playerPosition.Column - 1, playerPosition.Row), new FieldLocation(playerPosition.Column - 2, playerPosition.Row)); |
|
case Move.Right: |
|
return TryMove(new FieldLocation(playerPosition.Column + 1, playerPosition.Row), new FieldLocation(playerPosition.Column + 2, playerPosition.Row)); |
|
} |
|
return Enumerable.Empty<FieldLocation>(); |
|
} |
|
|
|
private IEnumerable<FieldLocation> TryMove(FieldLocation destination, FieldLocation behindDestination) |
|
{ |
|
var destinationFieldValue = this[destination]; |
|
if (destinationFieldValue == FieldValue.Wall) |
|
yield break; |
|
if (destinationFieldValue == FieldValue.Empty || destinationFieldValue == FieldValue.Goal) |
|
{ |
|
this[playerPosition] &= ~FieldValue.Player; |
|
yield return playerPosition; |
|
this[destination] |= FieldValue.Player; |
|
yield return destination; |
|
} |
|
if ((destinationFieldValue & FieldValue.Box) == FieldValue.Box) |
|
{ |
|
var valueBehind = this[behindDestination]; |
|
if (valueBehind == FieldValue.Wall || (valueBehind & FieldValue.Box) == FieldValue.Box) |
|
yield break; |
|
this[behindDestination] |= FieldValue.Box; |
|
yield return behindDestination; |
|
this[playerPosition] &= ~FieldValue.Player; |
|
yield return playerPosition; |
|
this[destination] = (this[destination] | FieldValue.Player) & ~FieldValue.Box; |
|
yield return destination; |
|
} |
|
} |
|
|
|
public bool Won() |
|
{ |
|
return !Board |
|
.SelectMany(row => row.Select(field => field)) |
|
.Any(field => field == FieldValue.Box || field == FieldValue.Goal); |
|
} |
|
} |
|
|
|
public struct FieldLocation |
|
{ |
|
public int Row { get; set; } |
|
public int Column { get; set; } |
|
public FieldLocation(int column, int row) |
|
{ |
|
Row = row; |
|
Column = column; |
|
} |
|
} |
|
|
|
public static class BoardPrinter |
|
{ |
|
private static Dictionary<FieldValue, FieldDisplay> displayMapper = new Dictionary<FieldValue, FieldDisplay> |
|
{ |
|
{ FieldValue.Wall, new FieldDisplay(ConsoleColor.DarkRed) }, |
|
{ FieldValue.Player, new FieldDisplay(ConsoleColor.DarkGreen) }, |
|
{ FieldValue.Player | FieldValue.Goal, new FieldDisplay(ConsoleColor.DarkGreen) }, |
|
{ FieldValue.Box, new FieldDisplay(ConsoleColor.Gray, ConsoleColor.DarkGray, 'X') }, |
|
{ FieldValue.Box | FieldValue.Goal, new FieldDisplay(ConsoleColor.Gray, ConsoleColor.Blue, 'X') }, |
|
{ FieldValue.Goal, new FieldDisplay(ConsoleColor.Blue) }, |
|
{ FieldValue.Empty, new FieldDisplay(ConsoleColor.Green) } |
|
}; |
|
|
|
public static void Print(FieldValue[][] board) |
|
{ |
|
Console.Clear(); |
|
foreach (var row in board) |
|
{ |
|
foreach (var fieldValue in row) |
|
PrintField(fieldValue); |
|
Console.WriteLine(); |
|
} |
|
} |
|
|
|
public static void RefreshFields(FieldValue[][] board, FieldLocation[] fieldsToRefresh) |
|
{ |
|
foreach (var field in fieldsToRefresh) |
|
{ |
|
Console.SetCursorPosition(field.Column, field.Row); |
|
PrintField(board[field.Row][field.Column]); |
|
} |
|
Console.SetCursorPosition(0, board.Length); |
|
} |
|
|
|
private static void PrintField(FieldValue fieldValue) |
|
{ |
|
var fieldDisplay = displayMapper[fieldValue]; |
|
Console.BackgroundColor = fieldDisplay.BackgroundColor; |
|
Console.ForegroundColor = fieldDisplay.ForegroundColor; |
|
Console.Write(fieldDisplay.Sign); |
|
Console.ResetColor(); |
|
} |
|
|
|
private struct FieldDisplay |
|
{ |
|
public ConsoleColor BackgroundColor { get; set; } |
|
public ConsoleColor ForegroundColor { get; set; } |
|
public char Sign { get; set; } |
|
public FieldDisplay(ConsoleColor backgroundColor, ConsoleColor foregroundColor = ConsoleColor.White, char sign = ' ') |
|
{ |
|
BackgroundColor = backgroundColor; |
|
ForegroundColor = foregroundColor; |
|
Sign = sign; |
|
} |
|
} |
|
} |
|
|
|
[Flags] |
|
public enum FieldValue : byte |
|
{ |
|
Empty = 0, |
|
Wall = 1, |
|
Player = 2, |
|
Box = 4, |
|
Goal = 8 |
|
} |
|
|
|
public enum Move : byte |
|
{ |
|
Up, |
|
Down, |
|
Left, |
|
Right |
|
} |
|
|
|
public static class BoardDefinitionProvider |
|
{ |
|
private static Dictionary<char, FieldValue> fieldMap = new Dictionary<char, FieldValue> |
|
{ |
|
{ '#', FieldValue.Wall }, |
|
{ '@', FieldValue.Player }, |
|
{ '+', FieldValue.Player | FieldValue.Goal }, |
|
{ '$', FieldValue.Box }, |
|
{ '*', FieldValue.Box | FieldValue.Goal }, |
|
{ '.', FieldValue.Goal }, |
|
{ ' ', FieldValue.Empty } |
|
}; |
|
|
|
public static IEnumerable<BoardDefinition> GetDefinitions() |
|
{ |
|
var levelId = 1; |
|
var allLines = File.ReadAllLines("_BoardDefinition.txt"); |
|
var enumerator = allLines.GetEnumerator(); |
|
while (enumerator.MoveNext()) |
|
{ |
|
var key = enumerator.Current as string; |
|
enumerator.MoveNext(); |
|
var size = (enumerator.Current as string).Split(' '); |
|
var width = int.Parse(size[0]); |
|
var height = int.Parse(size[1]); |
|
var board = new FieldValue[height][]; |
|
for (var i = 0; i < height; i++) |
|
{ |
|
board[i] = new FieldValue[width]; |
|
enumerator.MoveNext(); |
|
var line = enumerator.Current as string; |
|
for (var c = 0; c < width; c++) |
|
{ |
|
board[i][c] = fieldMap[line[c]]; |
|
} |
|
} |
|
yield return new BoardDefinition(levelId, key, board); |
|
levelId++; |
|
} |
|
} |
|
} |
|
|
|
public class BoardDefinition |
|
{ |
|
public int LevelId { get; } |
|
public string LevelSecret { get; } |
|
public FieldValue[][] Board { get; } |
|
public BoardDefinition(int levelId, string levelSecret, FieldValue[][] board) |
|
{ |
|
LevelId = levelId; |
|
LevelSecret = levelSecret; |
|
Board = board; |
|
} |
|
} |
|
} |