added script
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bin
|
||||
obj
|
||||
332
TicTacToe.cs
Normal file
332
TicTacToe.cs
Normal file
@@ -0,0 +1,332 @@
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user