This repository has been archived by the owner on Feb 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
EGLQZH #14
Open
FlucTuAteDev
wants to merge
6
commits into
CsharptutorialHungary:main
Choose a base branch
from
FlucTuAteDev:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
EGLQZH #14
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3aa8732
Console region
FlucTuAteDev 7f712f4
Basic board logic
FlucTuAteDev b604a3d
Players and win condition
FlucTuAteDev 854fbc0
Basic networking
FlucTuAteDev ca8dabd
Networking refactor
FlucTuAteDev ae95436
Scoreboard serialization
FlucTuAteDev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
using Utilities; | ||
|
||
namespace TicTacToe { | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using Utilities; | ||
|
||
namespace TicTacToe.ConsoleHelpers { | ||
enum Justify { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Szép munka.