333 lines
9.0 KiB
C#
333 lines
9.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace TestPiktiv
|
|
{
|
|
public class TicTacToe
|
|
{
|
|
private const int WinLength = 3;
|
|
private int height = 15;
|
|
private int width = 15;
|
|
private char[,] buf;
|
|
private bool isRunning;
|
|
private char[,] board;
|
|
private char[,] matchState;
|
|
private (List<int> p1, List<int> p2) score = (new List<int>(), new List<int>());
|
|
|
|
private ConsoleKey[] xKeys =
|
|
[
|
|
ConsoleKey.D1,
|
|
ConsoleKey.D2,
|
|
ConsoleKey.D3,
|
|
ConsoleKey.D4,
|
|
ConsoleKey.D5,
|
|
ConsoleKey.D6,
|
|
ConsoleKey.D7,
|
|
ConsoleKey.D8,
|
|
ConsoleKey.D9
|
|
];
|
|
|
|
private int[,] magicSquare = new int[3, 3]
|
|
{
|
|
{4, 9, 2},
|
|
{3, 5, 7},
|
|
{8, 1, 6},
|
|
};
|
|
private bool player;
|
|
private string winText;
|
|
private bool shouldEndGame;
|
|
|
|
public static void Main(string[] args)
|
|
{
|
|
TicTacToe game = new();
|
|
game.Start();
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
Console.Clear();
|
|
buf = new char[height, width];
|
|
Console.CursorVisible = false;
|
|
InitializeBoard();
|
|
BuildBoard(matchState);
|
|
isRunning = true;
|
|
shouldEndGame = false;
|
|
player = true;
|
|
Tick();
|
|
Console.CursorVisible = true;
|
|
}
|
|
|
|
private void InitializeBoard()
|
|
{
|
|
matchState = new char[3, 3];
|
|
var index = 1;
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
char inc = index++.ToString()[0];
|
|
matchState[i, j] = inc;
|
|
}
|
|
}
|
|
BuildBoard(matchState);
|
|
}
|
|
|
|
private void ClearBuffer()
|
|
{
|
|
for (int i = 0; i < height; i++)
|
|
{
|
|
for (int j = 0; j < height; j++)
|
|
{
|
|
buf[i, j] = ' ';
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BuildBoard(char[,] state)
|
|
{
|
|
board = new char[7, 7]
|
|
{
|
|
{'╔','═','╦','═','╦','═','╗'},
|
|
{'║',state[0,0],'║',state[0,1],'║',state[0,2],'║'},
|
|
{'╠','═','╬','═','╬','═','╣'},
|
|
{'║',state[1,0],'║',state[1,1],'║',state[1,2],'║'},
|
|
{'╠','═','╬','═','╬','═','╣'},
|
|
{'║',state[2,0],'║',state[2,1],'║',state[2,2],'║'},
|
|
{'╚','═','╩','═','╩','═','╝'},
|
|
};
|
|
}
|
|
|
|
private void SolveBoard(int index)
|
|
{
|
|
int h = index / 3;
|
|
int w = index % 3;
|
|
if (matchState[h, w] != 'X' && matchState[h, w] != 'O')
|
|
{
|
|
matchState[h, w] = player ? 'X' : 'O';
|
|
CheckWiningCondition_CheckNeighbors((h, w));
|
|
player = !player;
|
|
}
|
|
}
|
|
|
|
// a fun/hacky way of solving this problem using a magic square where the sum of a
|
|
// speific set of numbers on all lines and columns is always 15
|
|
private void CheckWiningCondition_MagicSquare((int h, int w) index)
|
|
{
|
|
if (player)
|
|
{
|
|
score.p1.Add(magicSquare[index.h, index.w]);
|
|
if (CheckMagicSquare(score.p1))
|
|
{
|
|
HandleWin(player);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
score.p2.Add(magicSquare[index.h, index.w]);
|
|
if (CheckMagicSquare(score.p2))
|
|
{
|
|
HandleWin(!player);
|
|
}
|
|
}
|
|
|
|
private void CheckWiningCondition_CheckNeighbors((int h, int w) index)
|
|
{
|
|
if (CheckNeighbors(index))
|
|
{
|
|
HandleWin(player);
|
|
}
|
|
}
|
|
|
|
// solved by scanning the direct neighbors, optimized solution that works with any board size
|
|
private bool CheckNeighbors((int h, int w) index)
|
|
{
|
|
char symbol = matchState[index.h, index.w];
|
|
var rows = matchState.GetLength(0);
|
|
var columns = matchState.GetLength(1);
|
|
|
|
(int dh, int dw)[] axes = [(0, 1), (1, 0), (-1, 1), (1, 1)];
|
|
|
|
foreach (var (dh, dw) in axes)
|
|
{
|
|
int found = 1;
|
|
|
|
for (int k = 1; ; k++)
|
|
{
|
|
int r = index.h + dh * k;
|
|
int l = index.w + dw * k;
|
|
if (r < 0 || r >= rows || l < 0 || l >= columns)
|
|
{
|
|
break;
|
|
}
|
|
if (matchState[r, l] != symbol)
|
|
{
|
|
break;
|
|
}
|
|
found++;
|
|
}
|
|
|
|
for (int k = 1; ; k++)
|
|
{
|
|
int r = index.h - dh * k;
|
|
int l = index.w - dw * k;
|
|
if (r < 0 || r >= rows || l < 0 || l >= columns)
|
|
{
|
|
break;
|
|
}
|
|
if (matchState[r, l] != symbol)
|
|
{
|
|
break;
|
|
}
|
|
found++;
|
|
}
|
|
|
|
if (found >= WinLength)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
private bool CheckMagicSquare(List<int> claimed)
|
|
{
|
|
if (claimed.Count < 3)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < claimed.Count; i++)
|
|
{
|
|
for (int j = i + 1; j < claimed.Count; j++)
|
|
{
|
|
for (int k = j + 1; k < claimed.Count; k++)
|
|
{
|
|
if (claimed[i] + claimed[j] + claimed[k] == 15)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
private void HandleWin(bool player)
|
|
{
|
|
winText = player ? "Player 1 won!" : "Player 2 won!";
|
|
shouldEndGame = true;
|
|
}
|
|
|
|
private void Tick()
|
|
{
|
|
while (isRunning)
|
|
{
|
|
CheckStatus(HandleInput());
|
|
Render();
|
|
Thread.Sleep(40);
|
|
if (shouldEndGame)
|
|
{
|
|
EndGame();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private void CheckStatus(ConsoleKey consoleKey)
|
|
{
|
|
if (xKeys.Contains(consoleKey))
|
|
{
|
|
SolveBoard(Array.IndexOf(xKeys, consoleKey));
|
|
}
|
|
|
|
for (int i = 0; i < matchState.GetLength(0); i++)
|
|
{
|
|
for (int j = 0; j < matchState.GetLength(1); j++)
|
|
{
|
|
if (matchState[i, j] != 'X' && matchState[i, j] != 'O')
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
shouldEndGame = true;
|
|
}
|
|
|
|
private ConsoleKey HandleInput()
|
|
{
|
|
if (Console.KeyAvailable)
|
|
{
|
|
var key = Console.ReadKey(true).Key;
|
|
if (key == ConsoleKey.Escape)
|
|
{
|
|
shouldEndGame = true;
|
|
}
|
|
return key;
|
|
}
|
|
return ConsoleKey.None;
|
|
}
|
|
|
|
private void Render()
|
|
{
|
|
ClearBuffer();
|
|
BuildBoard(matchState);
|
|
AddBoardToBuffer();
|
|
Present();
|
|
}
|
|
|
|
private void AddBoardToBuffer()
|
|
{
|
|
for (int i = 4; i < height - 4; i++)
|
|
{
|
|
for (int j = 4; j < width - 4; j++)
|
|
{
|
|
buf[i, j] = board[i - 4, j - 4];
|
|
}
|
|
}
|
|
}
|
|
|
|
private void Present()
|
|
{
|
|
var sb = new StringBuilder(height * (width + 1));
|
|
for (int i = 0; i < height; i++)
|
|
{
|
|
for (int j = 0; j < width; j++)
|
|
{
|
|
sb.Append(buf[i, j]);
|
|
}
|
|
if (i < height + 1)
|
|
{
|
|
sb.Append('\n');
|
|
}
|
|
}
|
|
Console.SetCursorPosition(0, 0);
|
|
Console.Write(sb.ToString());
|
|
}
|
|
|
|
private void EndGame()
|
|
{
|
|
Console.SetCursorPosition(0, height + 1);
|
|
Console.WriteLine(winText);
|
|
Console.WriteLine("Game Over. Press any key to restart!");
|
|
Console.WriteLine("..or press Esc to exit!");
|
|
var key = Console.ReadKey(true).Key;
|
|
if (key == ConsoleKey.Escape)
|
|
{
|
|
isRunning = false;
|
|
return;
|
|
}
|
|
Start();
|
|
}
|
|
|
|
}
|
|
}
|