Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

EGLQZH #14

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions EGLQZH/Board.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using Utilities;

namespace TicTacToe {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Szép munka.

sealed class Board {
public const int size = 3;
public Cell[,] Cells { get; private set; }
public Cell? SelectedCell {
get => _selectedCell;
private set {
Cell? prevCell = _selectedCell;
_selectedCell = value;
if (prevCell != null)
GameController.renderer.DrawCell(prevCell);

if (value != null)
GameController.renderer.DrawCell(value);
}
}

public bool IsInWinState { get; private set; }

public Board() {
Cells = new Cell[size, size];
InitializeCells();

SelectedCell = null;
}

public Cell this[int row, int col] {
get { return Cells[row, col]; }
}

public void PlaceItem(CellState team) {
if (SelectedCell == null)
SelectedCell = Cells[0, 0];

var (currRow, currCol) = SelectedCell.Position;

while (true) {
Input.Action action = Input.ReadKey();

if (directions.ContainsKey(action)) {
currRow = MathOperation.Mod(currRow + directions[action].Row, size);
currCol = MathOperation.Mod(currCol + directions[action].Col, size);

SelectedCell = Cells[currRow, currCol];
} else if (action == Input.Action.PlaceMark) {
if (SelectedCell.State != CellState.Empty) {
GameController.renderer.DrawError("The selected cell is occupied!");
continue;
}
SelectedCell.State = team;
CheckWinState();
return;
}
}
}

public void Reset() {
SelectedCell = null;
IsInWinState = false;
foreach (var cell in Cells) {
cell.State = CellState.Empty;
}
}

private void CheckWinState() {
if (SelectedCell == null) return;

int row = 0, col = 0, diagonal = 0, reverseDiagonal = 0;
CellState team = SelectedCell.State;
for (int i = 0; i < size; i++) {
if (Cells[i, SelectedCell.Position.Col].State == team) row++;
if (Cells[SelectedCell.Position.Row, i].State == team) col++;
if (Cells[i, i].State == team) diagonal++;
if (Cells[i, (size - i - 1)].State == team) reverseDiagonal++;
}

IsInWinState = row == size || col == size || diagonal == size || reverseDiagonal == size;
}

private void InitializeCells() {
CreateCells();
AddNeighbours();
}

private void CreateCells() {
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
Cells[i, j] = new Cell(i, j);
}
}
}

private void AddNeighbours() {
for (int row = 0; row < size; row++) {
for (int col = 0; col < size; col++) {
for (int rowOffset = -1; rowOffset <= 1; rowOffset++) {
for (int colOffset = -1; colOffset <= 1; colOffset++) {
int neighbourRow = row + rowOffset;
int neighbourCol = col + colOffset;
if (!IsValidPosition(neighbourRow, neighbourCol))
continue;

Cells[row, col].AddNeighbour(Cells[neighbourRow, neighbourCol]);
}
}
}
}
}

private static bool IsValidPosition(int row, int col) {
return row >= 0 && col >= 0 && row < size && col < size;
}

readonly Dictionary<Input.Action, (int Row, int Col)> directions = new()
{
{ Input.Action.MoveLeft, (0, -1) },
{ Input.Action.MoveRight, (0, 1) },
{ Input.Action.MoveUp, (-1, 0) },
{ Input.Action.MoveDown, (1, 0) }
};
private Cell? _selectedCell = null;
}
}
39 changes: 39 additions & 0 deletions EGLQZH/Cell.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Utilities;

namespace TicTacToe {
enum CellState {
Empty, X, O
}

sealed record class Cell {
public CellState State {
get => _state;
set {
if (_state != value) {
_state = value;
GameController.renderer.DrawCell(this);
}
}
}
public Vector Position { get; init; }

public Cell(int row, int col) {
Position = new Vector(row, col);
}

public void AddNeighbour(Cell neighbour) {
if (!this.IsNextTo(neighbour))
return;

if (!neighbours.Add(neighbour))
throw new Exception("The neighbour is already present in the set!");
}

private bool IsNextTo(Cell other) {
return Position.DistanceFrom(other.Position) == 1;
}

private CellState _state = CellState.Empty;
private readonly HashSet<Cell> neighbours = new();
}
}
123 changes: 123 additions & 0 deletions EGLQZH/ConsoleHelpers/ConsoleRegion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using Utilities;

namespace TicTacToe.ConsoleHelpers {
enum Justify {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Egy típus = 1 fájl elvet lehet érdemes követni.

TopLeft, TopMiddle, TopRight,
CenterLeft, CenterMiddle, CenterRight,
BottomLeft, BottomMiddle, BottomRight
}

sealed class ConsoleRegion {
public Vector Position { get; init; }
public Vector Size { get; init; }
public Justify Alignment { get; init; }
public bool HasBorder { get; set; }

public ConsoleRegion(int row, int col, int width, int height, Justify alignment = Justify.TopLeft, bool border = false) {
if (row < 0 || height < 0 || col < 0 || width < 0 || row + height - 1 > Console.WindowHeight || col + width - 1 > Console.WindowWidth)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elég komplex if. Lehetett volna egy metódus mögé tenni valami jó névvel.

throw new ArgumentOutOfRangeException("The region parameters are out of the valid range of the console window");

Position = new Vector(row, col);
Size = new Vector(height, width);
Alignment = alignment;

buffer = new();
HasBorder = border;
}

public void Write(string text, ConsoleColor? color = null) {
// If the buffer is empty then create a row
if (buffer.Count == 0)
buffer.Add(new());

// Add the text to the last row
buffer.Last().Add(new(text, color));
}

public void WriteLine(string text, ConsoleColor? color = null) {
Write(text, color);
// Start new row in the buffer
buffer.Add(new());
}

public void Clear() {
string spaces = new string(' ', Size.Col);
for (int i = 0; i < Size.Row; i++) {
Console.SetCursorPosition(Position.Col, Position.Row + i);
Console.Write(spaces);
}
}

public void ClearBuffer() => buffer.Clear();

public void Flush(bool clear = true) {
if (clear) Clear();

// Add row padding
int rowPadding = Math.Max((int)((Size.Row - buffer.Count) * multiplier[Alignment].Row), 0);
string rowPad = new string(' ', Size.Col);
for (int i = 0; i < rowPadding; i++) {
Console.SetCursorPosition(Position.Col, Position.Row + i + HasBorder.ToInt());
Console.Write(rowPad);
}

// Cut the overflowing rows according to the alignment
// Eg.: If the alignment is MiddleLeft cut half of the top rows and half of the bottom rows
int heightOverflow = Math.Max(buffer.Count - Size.Row, 0);
int topOffset = (int)(heightOverflow * multiplier[Alignment].Row);
int bottomOffset = heightOverflow - topOffset;
for (int i = topOffset; i < buffer.Count - bottomOffset; i++) {
var row = buffer[i];
if (row.Count == 0)
continue;

Console.SetCursorPosition(Position.Col, Position.Row + rowPadding - topOffset + i);
// Add column padding
int rowLength = row.Aggregate(0, (aggregate, next) => aggregate += next.text.Length);
int colPadding = Math.Max((int)((Size.Col - rowLength) * multiplier[Alignment].Col), 0);
string colPad = new string(' ', colPadding);
Console.Write(colPad);

int widthOverflow = Math.Max(rowLength - Size.Col, 0);
int leftOffset = (int)(widthOverflow * multiplier[Alignment].Col);
// Write buffer
foreach (var (text, color) in row) {
Console.ForegroundColor = color ?? Console.ForegroundColor;
Console.Write(text.Substring(leftOffset, Math.Min(Size.Col, text.Length)));
}
}
}

private void DrawBorder() {

}

private List<List<BufferItem>> buffer;
private static readonly Dictionary<Justify, (double Row, double Col)> multiplier = new() {
{ Justify.TopLeft, (0, 0) },
{ Justify.TopMiddle, (0, .5) },
{ Justify.TopRight, (0, 1) },
{ Justify.CenterLeft, (.5, 0) },
{ Justify.CenterMiddle, (.5, .5) },
{ Justify.CenterRight, (.5, 1) },
{ Justify.BottomLeft, (1, 0) },
{ Justify.BottomMiddle, (1, .5) },
{ Justify.BottomRight, (1, 1) },
};

struct BufferItem {
public string text;
public ConsoleColor? color;

public BufferItem(string text, ConsoleColor? color) {
this.text = text;
this.color = color;
}

public void Deconstruct(out string text, out ConsoleColor? color) {
text = this.text;
color = this.color;
}
}
}
}
10 changes: 10 additions & 0 deletions EGLQZH/EGLQZH.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
73 changes: 73 additions & 0 deletions EGLQZH/GameController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
namespace TicTacToe {
static class GameController {
public static Board board = new();
public static IRenderer renderer = new ConsoleRenderer();

public static async Task Run() {
renderer.DrawGreeting("Welcome to TicTacToe Console!");

// Wait so that the user can read the message
await Task.Delay(1000);
//var clients = await NetworkController.Discover();
//Input.Select(
// prompt: "Available clients (Press ESC to end discovery)",
// clients
//);

//Thread.Sleep(1000);
registeredPlayers = await ScoreboardSerializer.Deserialize();

Player p1 = InitializePlayer("Enter the first player's (X) name", CellState.X);
Player p2 = InitializePlayer("Enter the second player's (O) name", CellState.O);

// Very powerful solution
while (true) {
renderer.DrawBoard();

while (true) {
p1.TakeTurn();
if (board.IsInWinState) {
p1.Score++;
renderer.DrawWin(p1);
break;
}
p2.TakeTurn();
if (board.IsInWinState) {
p2.Score++;
renderer.DrawWin(p2);
break;
}
}

var action = Input.ReadKey(Input.Action.PlayAgain, Input.Action.Exit);
if (action == Input.Action.PlayAgain) {
board.Reset();
continue;
}
if (action == Input.Action.Exit) {
await ScoreboardSerializer.Serialize(registeredPlayers.ToArray());
break;
}
}
}

private static Player InitializePlayer(string namePrompt, CellState team) {
string name = Input.Read(
prompt: namePrompt,
errorMessage: "Please enter a valid name!",
converter: x => x,
validate: name => name != null && name != "");

var registeredPlayer = registeredPlayers.FirstOrDefault(x => x?.Name == name, null);
if (registeredPlayer != null) {
registeredPlayer.Team = team;
return registeredPlayer;
}

var newPlayer = new Player(name, team);
registeredPlayers.Add(newPlayer);
return newPlayer;
}
private static List<Player> registeredPlayers = new();
}
}
Loading