diff --git a/CheckersSpielBot/AiService.cs b/CheckersSpielBot/AiService.cs
new file mode 100644
index 0000000..d1d599e
--- /dev/null
+++ b/CheckersSpielBot/AiService.cs
@@ -0,0 +1,175 @@
+namespace CheckersSpielBot
+{
+ public class AiService
+ {
+ private const int MaxDepth = 7;
+ private const int WinScore = 100000;
+ private const int LoseScore = -100000;
+
+ private readonly int _aiPlayer;
+ private readonly int _humanPlayer;
+
+ public AiService(int aiPlayer)
+ {
+ _aiPlayer = aiPlayer;
+ _humanPlayer = aiPlayer == 1 ? 2 : 1;
+ }
+ public Move? GetBestMove(int[,] board)
+ {
+ Move? bestMove = null;
+ int bestScore = int.MinValue;
+
+ var allMoves = GetAllMovesForPlayer(board, _aiPlayer);
+ if (allMoves.Count == 0) return null;
+
+ foreach (var (originRow, originCol, move) in allMoves)
+ {
+ int[,] newBoard = ApplyMove(board, originRow, originCol, move);
+
+ int score = Minimax(newBoard, MaxDepth - 1, int.MinValue, int.MaxValue, false, move.IsCapture, move.DestinationRow, move.DestinationCol);
+
+ if (score > bestScore)
+ {
+ bestScore = score;
+ bestMove = move;
+ }
+ }
+
+ return bestMove;
+ }
+
+ // Minimax with Alpha-Beta Pruning
+ private int Minimax(int[,] board, int depth, int alpha, int beta, bool isMaximizing, bool lastMoveWasCapture, int lastMoveRow, int lastMoveCol)
+ {
+ int currentPlayer = isMaximizing ? _aiPlayer : _humanPlayer;
+
+ // Chain jump
+ if (lastMoveWasCapture)
+ {
+ var chainJumps = new MoveGeneratorService(board).GetLegalMoves(lastMoveRow, lastMoveCol, capturesOnly: true);
+
+ if (chainJumps.Count > 0)
+ {
+ // Same player continue jumping / don't switch turn
+ if (isMaximizing)
+ {
+ int best = int.MinValue;
+ foreach (var jump in chainJumps)
+ {
+ int[,] newBoard = ApplyMove(board, lastMoveRow, lastMoveCol, jump);
+ int score = Minimax(newBoard, depth, alpha, beta, true, true, jump.DestinationRow, jump.DestinationCol);
+
+ best = Math.Max(best, score);
+ alpha = Math.Max(alpha, best);
+
+ if (beta <= alpha) break;
+ }
+ return best;
+ }
+ else
+ {
+ int best = int.MaxValue;
+ foreach (var jump in chainJumps)
+ {
+ int[,] newBoard = ApplyMove(board, lastMoveRow, lastMoveCol, jump);
+ int score = Minimax(newBoard, depth, alpha, beta, false, true, jump.DestinationRow, jump.DestinationCol);
+
+ best = Math.Min(best, score);
+ beta = Math.Min(beta, best);
+
+ if (beta <= alpha) break;
+ }
+ return best;
+ }
+ }
+ }
+
+ // Terminal / depth check
+ var allMoves = GetAllMovesForPlayer(board, currentPlayer);
+
+ if (allMoves.Count == 0) { return isMaximizing ? LoseScore : WinScore; }
+
+ if (depth == 0) { return BoardEvaluator.Evaluate(board, _aiPlayer); }
+
+ if (isMaximizing)
+ {
+ int best = int.MinValue;
+ foreach (var (oRow, oCol, move) in allMoves)
+ {
+ int[,] newBoard = ApplyMove(board, oRow, oCol, move);
+ int score = Minimax(newBoard, depth - 1, alpha, beta, false, move.IsCapture, move.DestinationRow, move.DestinationCol);
+
+ best = Math.Max(best, score);
+ alpha = Math.Max(alpha, best);
+
+ if (beta <= alpha) { break; }
+ }
+ return best;
+ }
+ else
+ {
+ int best = int.MaxValue;
+ foreach (var (oRow, oCol, move) in allMoves)
+ {
+ int[,] newBoard = ApplyMove(board, oRow, oCol, move);
+ int score = Minimax(newBoard, depth - 1, alpha, beta, true, move.IsCapture, move.DestinationRow, move.DestinationCol);
+
+ best = Math.Min(best, score);
+ beta = Math.Min(beta, best);
+
+ if (beta <= alpha) { break; }
+ }
+ return best;
+ }
+ }
+
+ #region helpers
+ private static List<(int, int, Move)> GetAllMovesForPlayer(int[,] board, int player)
+ {
+ var moveGen = new MoveGeneratorService(board);
+ var captures = new List<(int, int, Move)>();
+ var normals = new List<(int, int, Move)>();
+
+ for (int r = 0; r < 8; r++)
+ {
+ for (int c = 0; c < 8; c++)
+ {
+ if (!BoardHelper.BelongsToPlayer(board[r, c], player)) continue;
+
+ var jumps = moveGen.GetLegalMoves(r, c, capturesOnly: true);
+ if (jumps.Count > 0)
+ {
+ foreach (var j in jumps) captures.Add((r, c, j));
+ continue;
+ }
+
+ var moves = moveGen.GetLegalMoves(r, c, capturesOnly: false);
+ foreach (var m in moves)
+ {
+ normals.Add((r, c, m));
+ }
+ }
+ }
+
+ return captures.Count > 0 ? captures : normals;
+ }
+ private static int[,] ApplyMove(int[,] board, int originRow, int originCol, Move move)
+ {
+ int[,] newBoard = (int[,])board.Clone();
+ int piece = newBoard[originRow, originCol];
+
+ newBoard[move.DestinationRow, move.DestinationCol] = piece;
+ newBoard[originRow, originCol] = 0;
+
+ if (move.IsCapture) { newBoard[move.CapturedPieceRow, move.CapturedPieceCol] = 0; }
+
+ // Promotion
+ if (newBoard[move.DestinationRow, move.DestinationCol] == 1 && move.DestinationRow == 0) { newBoard[move.DestinationRow, move.DestinationCol] = 3; }
+
+ if (newBoard[move.DestinationRow, move.DestinationCol] == 2 && move.DestinationRow == 7) { newBoard[move.DestinationRow, move.DestinationCol] = 4; }
+
+ return newBoard;
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/App.xaml b/CheckersSpielBot/App.xaml
index e0afa19..aba1c62 100644
--- a/CheckersSpielBot/App.xaml
+++ b/CheckersSpielBot/App.xaml
@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CheckersSpielBot"
- StartupUri="MainWindow.xaml">
+ StartupUri="LoginWindow.xaml">
diff --git a/CheckersSpielBot/App.xaml.cs b/CheckersSpielBot/App.xaml.cs
index 3386db9..250649e 100644
--- a/CheckersSpielBot/App.xaml.cs
+++ b/CheckersSpielBot/App.xaml.cs
@@ -1,14 +1,8 @@
-using System.Configuration;
-using System.Data;
-using System.Windows;
+using System.Windows;
namespace CheckersSpielBot
{
- ///
- /// Interaction logic for App.xaml
- ///
public partial class App : Application
{
}
-
-}
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/BoardEvaluator.cs b/CheckersSpielBot/BoardEvaluator.cs
new file mode 100644
index 0000000..26eb7b3
--- /dev/null
+++ b/CheckersSpielBot/BoardEvaluator.cs
@@ -0,0 +1,66 @@
+namespace CheckersSpielBot
+{
+ public static class BoardEvaluator
+ {
+ private const int PieceValue = 100;
+ private const int KingValue = 160;
+
+ //Weights (Musst be Balanced To have a good Ai)
+ private const int AdvancementWeight = 5;
+ private const int BackRowWeight = 15;
+ private const int CenterWeight = 10;
+ private const int MobilityWeight = 8;
+
+ public static int Evaluate(int[,] board, int aiPlayer)
+ {
+ int score = 0;
+ int humanPlayer = aiPlayer == 1 ? 2 : 1;
+
+ MoveGeneratorService moveGen = new MoveGeneratorService(board);
+
+ int aiMobility = 0;
+ int humanMobility = 0;
+
+ for (int row = 0; row < 8; row++)
+ {
+ for (int col = 0; col < 8; col++)
+ {
+ int piece = board[row, col];
+ if (piece == 0) continue;
+
+ //Reward For who will play first
+ bool isAi = BoardHelper.BelongsToPlayer(piece, aiPlayer);
+ bool isKing = BoardHelper.IsKing(piece);
+
+ int sign = isAi ? 1 : -1;
+
+ // Material
+ score += sign * (isKing ? KingValue : PieceValue);
+
+ // Advancement — how far the piece has advanced toward promotion
+ int advancement = isAi ? (aiPlayer == 1 ? 7 - row : row) : (aiPlayer == 1 ? row : 7 - row); // Red moves up, Blue moves down
+
+ score += sign * advancement * AdvancementWeight;
+
+ // Back row protection — pieces on starting back row
+ bool onBackRow = isAi ? (aiPlayer == 1 && row == 7) || (aiPlayer == 2 && row == 0) : (aiPlayer == 1 && row == 0) || (aiPlayer == 2 && row == 7);
+ if (onBackRow && !isKing) { score += sign * BackRowWeight; }
+
+ // Center control — [columns:2,3,4,5 && rows:2,3,4,5]
+ if (col >= 2 && col <= 5 && row >= 2 && row <= 5) { score += sign * CenterWeight; }
+
+ // Mobility
+ int moves = moveGen.GetLegalMoves(row, col, capturesOnly: false).Count;
+
+ if (isAi) { aiMobility += moves; }
+ else { humanMobility += moves; }
+ }
+ }
+
+ // Mobility difference
+ score += (aiMobility - humanMobility) * MobilityWeight;
+
+ return score;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/BoardHelper.cs b/CheckersSpielBot/BoardHelper.cs
new file mode 100644
index 0000000..e87f030
--- /dev/null
+++ b/CheckersSpielBot/BoardHelper.cs
@@ -0,0 +1,24 @@
+namespace CheckersSpielBot
+{
+ public static class BoardHelper
+ {
+ public static bool IsPlayableSquare(int row, int col) => (row + col) % 2 == 1;
+ public static bool IsWithinBounds(int row, int col) => (uint)row < 8 && (uint)col < 8;
+
+ public static bool BelongsToPlayer(int piece, int player) =>
+ player == 1 ? piece == 1 || piece == 3
+ : piece == 2 || piece == 4;
+
+ public static bool IsOpponentPiece(int own, int target)
+ {
+ if (target == 0) return false;
+ bool ownRed = own == 1 || own == 3;
+ bool targetRed = target == 1 || target == 3;
+ return ownRed != targetRed;
+ }
+
+ public static bool IsKing(int piece) => piece == 3 || piece == 4;
+ public static bool IsRedPiece(int piece) => piece == 1 || piece == 3;
+ public static string PlayerLabel(int player) => player == 1 ? "Red" : "Blue";
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/BoardRenderer.cs b/CheckersSpielBot/BoardRenderer.cs
new file mode 100644
index 0000000..0b096fe
--- /dev/null
+++ b/CheckersSpielBot/BoardRenderer.cs
@@ -0,0 +1,126 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Shapes;
+
+namespace CheckersSpielBot
+{
+ public class BoardRenderer
+ {
+ // Frozen brushes — allocated once
+ private static readonly SolidColorBrush BrushLightSquare = MakeBrush(0xEE, 0xEE, 0xEE);
+ private static readonly SolidColorBrush BrushDarkSquare = MakeBrush(0x66, 0x66, 0x66);
+ private static readonly SolidColorBrush BrushSelected = MakeBrush(0xFF, 0xD7, 0x00);
+ private static readonly SolidColorBrush BrushValidMove = MakeBrush(0x4C, 0xAF, 0x50);
+ private static readonly SolidColorBrush BrushMandatory = MakeBrush(0xFF, 0x60, 0x00);
+ private static readonly SolidColorBrush BrushRedPiece = MakeBrush(0xB4, 0x1E, 0x1E);
+ private static readonly SolidColorBrush BrushBluePiece = MakeBrush(0x1E, 0x1E, 0xB4);
+ private static readonly SolidColorBrush BrushKingStar = new SolidColorBrush(Colors.Gold);
+
+ private static SolidColorBrush MakeBrush(byte r, byte g, byte b)
+ {
+ var br = new SolidColorBrush(Color.FromRgb(r, g, b));
+ br.Freeze();
+ return br;
+ }
+
+ private readonly MainWindow _window;
+ private readonly GameService _game;
+
+ public BoardRenderer(MainWindow window, GameService game)
+ {
+ _window = window;
+ _game = game;
+ }
+
+ public void RenderBoard(int selectedRow, int selectedCol,
+ bool hasPieceSelected, bool isChainJumpActive)
+ {
+ for (int row = 0; row < 8; row++)
+ for (int col = 0; col < 8; col++)
+ RenderCell(row, col, selectedRow, selectedCol,
+ hasPieceSelected, isChainJumpActive);
+
+ UpdatePieceCounts();
+ }
+
+ private void RenderCell(int row, int col,
+ int selectedRow, int selectedCol,
+ bool hasPieceSelected, bool isChainJumpActive)
+ {
+ Button btn = GetCellButton(row, col);
+ if (btn == null) return;
+
+ btn.Background = ResolveCellBackground(row, col, selectedRow, selectedCol,
+ hasPieceSelected, isChainJumpActive);
+ btn.Content = BuildPieceVisual(_game.Board[row, col]);
+ }
+
+ private SolidColorBrush ResolveCellBackground(int row, int col,
+ int selectedRow, int selectedCol,
+ bool hasPieceSelected,
+ bool isChainJumpActive)
+ {
+ if (hasPieceSelected && selectedRow == row && selectedCol == col)
+ return BrushSelected;
+
+ if (hasPieceSelected && !isChainJumpActive && IsValidMoveTarget(row, col, selectedRow, selectedCol))
+ return BrushValidMove;
+
+ if (!hasPieceSelected && _game.MandatoryCapturePieces.Contains((row, col)))
+ return BrushMandatory;
+
+ return BoardHelper.IsPlayableSquare(row, col) ? BrushDarkSquare : BrushLightSquare;
+ }
+
+ private bool IsValidMoveTarget(int row, int col, int selectedRow, int selectedCol)
+ {
+ return _game.GetLegalMoves(selectedRow, selectedCol, capturesOnly: false)
+ .Exists(m => m.DestinationRow == row && m.DestinationCol == col);
+ }
+
+ private static UIElement? BuildPieceVisual(int piece)
+ {
+ if (piece == 0) return null;
+
+ bool isRed = BoardHelper.IsRedPiece(piece);
+ bool isKing = BoardHelper.IsKing(piece);
+
+ var circle = new Ellipse
+ {
+ Width = 42,
+ Height = 42,
+ Fill = isRed ? BrushRedPiece : BrushBluePiece,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+
+ var grid = new Grid();
+ grid.Children.Add(circle);
+
+ if (isKing)
+ {
+ grid.Children.Add(new TextBlock
+ {
+ Text = "★",
+ FontSize = 16,
+ Foreground = BrushKingStar,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ });
+ }
+
+ return grid;
+ }
+
+ private void UpdatePieceCounts()
+ {
+ var (red, blue) = _game.GetPieceCounts();
+ _window.RedScoreText.Text = $"Red: {red}";
+ _window.BlueScoreText.Text = $"Blue: {blue}";
+ }
+
+ private Button GetCellButton(int row, int col) =>
+ (Button)_window.FindName($"Cell_{row}_{col}");
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/CheckersSpielBot.csproj b/CheckersSpielBot/CheckersSpielBot.csproj
index defb4d1..c9baa96 100644
--- a/CheckersSpielBot/CheckersSpielBot.csproj
+++ b/CheckersSpielBot/CheckersSpielBot.csproj
@@ -8,4 +8,14 @@
true
+
+
+
+
+
+
+
+
+
+
diff --git a/CheckersSpielBot/DatabaseService.cs b/CheckersSpielBot/DatabaseService.cs
new file mode 100644
index 0000000..c6d5aab
--- /dev/null
+++ b/CheckersSpielBot/DatabaseService.cs
@@ -0,0 +1,209 @@
+using System.IO;
+using Microsoft.Data.Sqlite;
+
+namespace CheckersSpielBot
+{
+ public class DatabaseService
+ {
+ private readonly string _connectionString;
+
+ public DatabaseService()
+ {
+ string dbPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory, "checkers.db");
+
+ _connectionString = $"Data Source={dbPath}";
+
+ InitializeSchema();
+ }
+
+ private void InitializeSchema()
+ {
+ using var conn = new SqliteConnection(_connectionString);
+ conn.Open();
+
+ var cmd = conn.CreateCommand();
+ cmd.CommandText = @"
+ CREATE TABLE IF NOT EXISTS players (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL UNIQUE,
+ password TEXT NOT NULL,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+
+ CREATE TABLE IF NOT EXISTS gameHistory (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ playerId INTEGER NOT NULL,
+ startedAt TEXT NOT NULL DEFAULT (datetime('now')),
+ endedAt TEXT NULL,
+ winner TEXT NULL,
+ wentFirst TEXT NOT NULL DEFAULT 'player',
+ FOREIGN KEY (playerId) REFERENCES players(id)
+ );";
+ cmd.ExecuteNonQuery();
+ }
+
+ public bool RegisterPlayer(string username, string password, out string error)
+ {
+ error = string.Empty;
+ try
+ {
+ using var conn = new SqliteConnection(_connectionString);
+ conn.Open();
+
+ var check = conn.CreateCommand();
+ check.CommandText = "SELECT COUNT(*) FROM players WHERE username = @u";
+ check.Parameters.AddWithValue("@u", username);
+ long exists = (long)check.ExecuteScalar()!;
+
+ if (exists > 0)
+ {
+ error = "Username already exists.";
+ return false;
+ }
+
+ string hashed = BCrypt.Net.BCrypt.HashPassword(password);
+
+ var cmd = conn.CreateCommand();
+ cmd.CommandText = "INSERT INTO players (username, password) VALUES (@u, @p)";
+ cmd.Parameters.AddWithValue("@u", username);
+ cmd.Parameters.AddWithValue("@p", hashed);
+ cmd.ExecuteNonQuery();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ error = ex.Message;
+ return false;
+ }
+ }
+
+ public bool LoginPlayer(string username, string password,
+ out int playerId, out string error)
+ {
+ playerId = -1;
+ error = string.Empty;
+ try
+ {
+ using var conn = new SqliteConnection(_connectionString);
+ conn.Open();
+
+ var cmd = conn.CreateCommand();
+ cmd.CommandText = "SELECT id, password FROM players WHERE username = @u";
+ cmd.Parameters.AddWithValue("@u", username);
+
+ using var reader = cmd.ExecuteReader();
+ if (!reader.Read())
+ {
+ error = "Username not found.";
+ return false;
+ }
+
+ int id = reader.GetInt32(0);
+ string stored = reader.GetString(1);
+
+ if (!BCrypt.Net.BCrypt.Verify(password, stored))
+ {
+ error = "Incorrect password.";
+ return false;
+ }
+
+ playerId = id;
+ return true;
+ }
+ catch (Exception ex)
+ {
+ error = ex.Message;
+ return false;
+ }
+ }
+
+ public int StartGame(int playerId, string wentFirst)
+ {
+ try
+ {
+ using var conn = new SqliteConnection(_connectionString);
+ conn.Open();
+
+ var cmd = conn.CreateCommand();
+ cmd.CommandText = @"
+ INSERT INTO gameHistory (playerId, startedAt, wentFirst)
+ VALUES (@pid, datetime('now'), @wf);
+ SELECT last_insert_rowid();";
+ cmd.Parameters.AddWithValue("@pid", playerId);
+ cmd.Parameters.AddWithValue("@wf", wentFirst);
+
+ return Convert.ToInt32(cmd.ExecuteScalar());
+ }
+ catch { return -1; }
+ }
+
+ public void EndGame(int gameId, string winner)
+ {
+ try
+ {
+ using var conn = new SqliteConnection(_connectionString);
+ conn.Open();
+
+ var cmd = conn.CreateCommand();
+ cmd.CommandText = @"
+ UPDATE gameHistory
+ SET endedAt = datetime('now'), winner = @w
+ WHERE id = @id";
+ cmd.Parameters.AddWithValue("@w", winner);
+ cmd.Parameters.AddWithValue("@id", gameId);
+ cmd.ExecuteNonQuery();
+ }
+ catch { }
+ }
+
+ public List GetHistory(int playerId)
+ {
+ var list = new List();
+ try
+ {
+ using var conn = new SqliteConnection(_connectionString);
+ conn.Open();
+
+ var cmd = conn.CreateCommand();
+ cmd.CommandText = @"
+ SELECT id, startedAt, endedAt, winner, wentFirst
+ FROM gameHistory
+ WHERE playerId = @pid
+ ORDER BY startedAt DESC";
+ cmd.Parameters.AddWithValue("@pid", playerId);
+
+ using var reader = cmd.ExecuteReader();
+ while (reader.Read())
+ {
+ var entry = new GameHistoryEntry
+ {
+ Id = reader.GetInt32(0),
+ StartedAt = DateTime.Parse(reader.GetString(1)),
+ EndedAt = reader.IsDBNull(2)
+ ? null
+ : DateTime.Parse(reader.GetString(2)),
+ Winner = reader.IsDBNull(3) ? "—" : reader.GetString(3),
+ WentFirst = reader.GetString(4)
+ };
+ list.Add(entry);
+ }
+ }
+ catch { }
+ return list;
+ }
+ }
+
+ public class GameHistoryEntry
+ {
+ public int Id { get; set; }
+ public DateTime StartedAt { get; set; }
+ public DateTime? EndedAt { get; set; }
+ public string Winner { get; set; } = "—";
+ public string WentFirst { get; set; } = "—";
+
+ public string Duration => EndedAt.HasValue
+ ? $"{(EndedAt.Value - StartedAt).TotalMinutes:F0} min"
+ : "In progress";
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/Dialogues/HistoryDialog.xaml b/CheckersSpielBot/Dialogues/HistoryDialog.xaml
new file mode 100644
index 0000000..0dfb061
--- /dev/null
+++ b/CheckersSpielBot/Dialogues/HistoryDialog.xaml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CheckersSpielBot/Dialogues/HistoryDialog.xaml.cs b/CheckersSpielBot/Dialogues/HistoryDialog.xaml.cs
new file mode 100644
index 0000000..fcc471c
--- /dev/null
+++ b/CheckersSpielBot/Dialogues/HistoryDialog.xaml.cs
@@ -0,0 +1,16 @@
+using System.Windows;
+
+namespace CheckersSpielBot
+{
+ public partial class HistoryDialog : Window
+ {
+ public HistoryDialog()
+ {
+ InitializeComponent();
+
+ var db = new DatabaseService();
+ var history = db.GetHistory(PlayerSession.PlayerId);
+ HistoryGrid.ItemsSource = history;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/GameModeDialog.xaml b/CheckersSpielBot/GameModeDialog.xaml
new file mode 100644
index 0000000..ac2601b
--- /dev/null
+++ b/CheckersSpielBot/GameModeDialog.xaml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CheckersSpielBot/GameModeDialog.xaml.cs b/CheckersSpielBot/GameModeDialog.xaml.cs
new file mode 100644
index 0000000..9f043eb
--- /dev/null
+++ b/CheckersSpielBot/GameModeDialog.xaml.cs
@@ -0,0 +1,26 @@
+using System.Windows;
+
+namespace CheckersSpielBot
+{
+ public partial class GameModeDialog : Window
+ {
+ public bool HumanGoesFirst { get; private set; } = true;
+
+ public GameModeDialog()
+ {
+ InitializeComponent();
+ }
+
+ private void HumanFirst_Click(object sender, RoutedEventArgs e)
+ {
+ HumanGoesFirst = true;
+ Close();
+ }
+
+ private void BotFirst_Click(object sender, RoutedEventArgs e)
+ {
+ HumanGoesFirst = false;
+ Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/GameService.cs b/CheckersSpielBot/GameService.cs
new file mode 100644
index 0000000..6e25609
--- /dev/null
+++ b/CheckersSpielBot/GameService.cs
@@ -0,0 +1,192 @@
+namespace CheckersSpielBot
+{
+ public class GameService
+ {
+ // 0=Empty | 1=Red | 2=Blue | 3=RedKing | 4=BlueKing
+ public int[,] Board { get; private set; } = new int[8, 8];
+
+ public int ActivePlayer { get; private set; } = 1;
+ public bool IsChainJumpActive { get; private set; } = false;
+ public int ChainJumpRow { get; private set; } = -1;
+ public int ChainJumpCol { get; private set; } = -1;
+
+ public HashSet<(int, int)> MandatoryCapturePieces { get; private set; } = new();
+
+ private MoveGeneratorService _moveGenerator;
+
+ public GameService()
+ {
+ _moveGenerator = new MoveGeneratorService(Board);
+ }
+
+ // Setup
+ public void InitializeNewGame()
+ {
+ Board = new int[8, 8];
+ ActivePlayer = 1;
+ IsChainJumpActive = false;
+ ChainJumpRow = -1;
+ ChainJumpCol = -1;
+
+ _moveGenerator = new MoveGeneratorService(Board);
+
+ PlaceInitialPieces();
+ RefreshMandatoryCaptures();
+ }
+
+ private void PlaceInitialPieces()
+ {
+ for (int row = 0; row < 3; row++)
+ for (int col = 0; col < 8; col++)
+ if (BoardHelper.IsPlayableSquare(row, col))
+ Board[row, col] = 2; // Blue top
+
+ for (int row = 5; row < 8; row++)
+ for (int col = 0; col < 8; col++)
+ if (BoardHelper.IsPlayableSquare(row, col))
+ Board[row, col] = 1; // Red bottom
+ }
+
+ // Move generation
+ public List GetLegalMoves(int row, int col, bool capturesOnly) =>
+ _moveGenerator.GetLegalMoves(row, col, capturesOnly);
+
+ // Move execution
+ public MoveResult ExecuteMove(int originRow, int originCol, Move move)
+ {
+ int piece = Board[originRow, originCol];
+
+ Board[move.DestinationRow, move.DestinationCol] = piece;
+ Board[originRow, originCol] = 0;
+
+ if (move.IsCapture)
+ Board[move.CapturedPieceRow, move.CapturedPieceCol] = 0;
+
+ PromoteToKingIfEligible(move.DestinationRow, move.DestinationCol);
+
+ ResetChainJump();
+
+ // Chain jump
+ if (move.IsCapture)
+ {
+ var furtherJumps = GetLegalMoves(move.DestinationRow, move.DestinationCol,
+ capturesOnly: true);
+ if (furtherJumps.Count > 0)
+ {
+ IsChainJumpActive = true;
+ ChainJumpRow = move.DestinationRow;
+ ChainJumpCol = move.DestinationCol;
+ return MoveResult.ChainJumpRequired;
+ }
+ }
+
+ // Victory check
+ if (EvaluateVictoryCondition(out int victor))
+ return MoveResult.Victory(victor);
+
+ // Advance turn
+ ActivePlayer = ActivePlayer == 1 ? 2 : 1;
+ RefreshMandatoryCaptures();
+ return MoveResult.TurnAdvanced;
+ }
+
+ private void PromoteToKingIfEligible(int row, int col)
+ {
+ if (Board[row, col] == 1 && row == 0) Board[row, col] = 3;
+ if (Board[row, col] == 2 && row == 7) Board[row, col] = 4;
+ }
+
+ private void ResetChainJump()
+ {
+ IsChainJumpActive = false;
+ ChainJumpRow = -1;
+ ChainJumpCol = -1;
+ }
+
+ // Mandatory captures
+ public void RefreshMandatoryCaptures()
+ {
+ MandatoryCapturePieces = new HashSet<(int, int)>();
+
+ for (int r = 0; r < 8; r++)
+ for (int c = 0; c < 8; c++)
+ {
+ int p = Board[r, c];
+ if (BoardHelper.BelongsToPlayer(p, ActivePlayer) &&
+ GetLegalMoves(r, c, capturesOnly: true).Count > 0)
+ MandatoryCapturePieces.Add((r, c));
+ }
+ }
+
+ // Victory
+ private bool EvaluateVictoryCondition(out int victor)
+ {
+ victor = 0;
+ int opponent = ActivePlayer == 1 ? 2 : 1;
+
+ bool opponentHasPieces = false;
+ bool opponentCanMove = false;
+
+ for (int r = 0; r < 8; r++)
+ {
+ for (int c = 0; c < 8; c++)
+ {
+ if (!BoardHelper.BelongsToPlayer(Board[r, c], opponent)) continue;
+ opponentHasPieces = true;
+ if (GetLegalMoves(r, c, capturesOnly: false).Count > 0)
+ {
+ opponentCanMove = true;
+ break;
+ }
+ }
+ if (opponentCanMove) break;
+ }
+
+ if (!opponentHasPieces || !opponentCanMove)
+ {
+ victor = ActivePlayer;
+ return true;
+ }
+ return false;
+ }
+
+ public List GetAllLegalMovesForPlayer(int player)
+ {
+ var moveGen = new MoveGeneratorService(Board);
+ var captures = new List();
+ var normals = new List();
+
+ for (int r = 0; r < 8; r++)
+ {
+ for (int c = 0; c < 8; c++)
+ {
+ if (!BoardHelper.BelongsToPlayer(Board[r, c], player)) continue;
+
+ var jumps = moveGen.GetLegalMoves(r, c, capturesOnly: true);
+ if (jumps.Count > 0) { captures.AddRange(jumps); continue; }
+
+ normals.AddRange(moveGen.GetLegalMoves(r, c, capturesOnly: false));
+ }
+ }
+
+ return captures.Count > 0 ? captures : normals;
+ }
+
+ #region helpers
+ public bool BelongsToActivePlayer(int piece) =>
+ BoardHelper.BelongsToPlayer(piece, ActivePlayer);
+ public (int red, int blue) GetPieceCounts()
+ {
+ int red = 0, blue = 0;
+ for (int r = 0; r < 8; r++)
+ for (int c = 0; c < 8; c++)
+ {
+ int p = Board[r, c];
+ if (p == 1 || p == 3) red++;
+ else if (p == 2 || p == 4) blue++;
+ }
+ return (red, blue);
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/LoginWindow.xaml b/CheckersSpielBot/LoginWindow.xaml
new file mode 100644
index 0000000..3c729ce
--- /dev/null
+++ b/CheckersSpielBot/LoginWindow.xaml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CheckersSpielBot/LoginWindow.xaml.cs b/CheckersSpielBot/LoginWindow.xaml.cs
new file mode 100644
index 0000000..5559eb2
--- /dev/null
+++ b/CheckersSpielBot/LoginWindow.xaml.cs
@@ -0,0 +1,128 @@
+using System.Windows;
+using System.Windows.Media;
+
+namespace CheckersSpielBot
+{
+ public partial class LoginWindow : Window
+ {
+ private readonly DatabaseService _db = new();
+ private bool _isLoginMode = true;
+
+ public LoginWindow()
+ {
+ InitializeComponent();
+ ApplyTabStyle();
+ }
+
+ #region Tab switching
+ private void SwitchToLogin(object sender, RoutedEventArgs e)
+ {
+ _isLoginMode = true;
+ ActionBtn.Content = "Login";
+ ErrorText.Text = string.Empty;
+ PwdBox.Clear();
+ ApplyTabStyle();
+ }
+
+ private void SwitchToRegister(object sender, RoutedEventArgs e)
+ {
+ _isLoginMode = false;
+ ActionBtn.Content = "Register";
+ ErrorText.Text = string.Empty;
+ PwdBox.Clear();
+ ApplyTabStyle();
+ }
+
+ private void ApplyTabStyle()
+ {
+ // Active tab — white text, red underline
+ // Inactive tab — grey text, grey underline
+ if (_isLoginMode)
+ {
+ TabLoginBtn.Foreground = Brushes.White;
+ TabRegisterBtn.Foreground = new SolidColorBrush(Color.FromRgb(0x88, 0x88, 0x88));
+ }
+ else
+ {
+ TabLoginBtn.Foreground = new SolidColorBrush(Color.FromRgb(0x88, 0x88, 0x88));
+ TabRegisterBtn.Foreground = Brushes.White;
+ }
+ }
+ #endregion
+
+ #region Actions
+ private void ActionBtn_Click(object sender, RoutedEventArgs e)
+ {
+ if (_isLoginMode) HandleLogin();
+ else HandleRegister();
+ }
+
+ private void HandleLogin()
+ {
+ string username = UsernameBox.Text.Trim();
+ string password = PwdBox.Password;
+
+ if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
+ {
+ ShowError("Please enter username and password.");
+ return;
+ }
+
+ if (!_db.LoginPlayer(username, password, out int playerId, out string error))
+ {
+ ShowError(error);
+ return;
+ }
+
+ PlayerSession.PlayerId = playerId;
+ PlayerSession.Username = username;
+
+ var mainWindow = new MainWindow();
+ mainWindow.Show();
+ Close();
+ }
+
+ private void HandleRegister()
+ {
+ string username = UsernameBox.Text.Trim();
+ string password = PwdBox.Password;
+
+ if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
+ {
+ ShowError("Please enter username and password.");
+ return;
+ }
+
+ if (username.Length < 3)
+ {
+ ShowError("Username must be at least 3 characters.");
+ return;
+ }
+
+ if (password.Length < 6)
+ {
+ ShowError("Password must be at least 6 characters.");
+ return;
+ }
+
+ if (!_db.RegisterPlayer(username, password, out string error))
+ {
+ ShowError(error);
+ return;
+ }
+
+ ErrorText.Foreground = Brushes.LightGreen;
+ ErrorText.Text = "Registered! You can now login.";
+ SwitchToLogin(null!, null!);
+ }
+ #endregion
+
+ #region Helpers
+ private void ShowError(string message)
+ {
+ ErrorText.Foreground = Brushes.OrangeRed;
+ ErrorText.Text = message;
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/MainWindow.xaml b/CheckersSpielBot/MainWindow.xaml
index f288122..4f0f220 100644
--- a/CheckersSpielBot/MainWindow.xaml
+++ b/CheckersSpielBot/MainWindow.xaml
@@ -1,20 +1,13 @@
-
+ Background="#222"
+ Closing="Window_Closing">
@@ -23,162 +16,148 @@
-
+
+
+
+
+
+
- DockPanel.Dock="Right">
- Click="NewGame_Click"/>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/CheckersSpielBot/MainWindow.xaml.cs b/CheckersSpielBot/MainWindow.xaml.cs
index 25634a1..0342622 100644
--- a/CheckersSpielBot/MainWindow.xaml.cs
+++ b/CheckersSpielBot/MainWindow.xaml.cs
@@ -1,12 +1,266 @@
-using System.Windows;
+using System.ComponentModel;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
namespace CheckersSpielBot
{
public partial class MainWindow : Window
{
+ private readonly GameService _game;
+ private readonly BoardRenderer _renderer;
+ private readonly DatabaseService _db = new();
+ private AiService _ai;
+
+ private int _humanPlayer = 1;
+ private int _aiPlayer = 2;
+ private bool _gameEnded = false;
+
+ private int _selectedRow = -1;
+ private int _selectedCol = -1;
+ private bool _hasPieceSelected = false;
+ private bool _isAiThinking = false;
+
public MainWindow()
{
InitializeComponent();
+ _game = new GameService();
+ _renderer = new BoardRenderer(this, _game);
+ _ai = new AiService(_aiPlayer);
+ InitializeNewGame();
}
+
+ #region Setup
+ private void InitializeNewGame()
+ {
+ // End previous game as draw if it wasn't finished
+ if (PlayerSession.CurrentGameId != -1 && !_gameEnded)
+ _db.EndGame(PlayerSession.CurrentGameId, "draw");
+
+ _gameEnded = false;
+
+ GameModeDialog dialog = new GameModeDialog();
+ dialog.ShowDialog();
+
+ if (dialog.HumanGoesFirst)
+ {
+ _humanPlayer = 1;
+ _aiPlayer = 2;
+ }
+ else
+ {
+ _humanPlayer = 2;
+ _aiPlayer = 1;
+ }
+
+ _ai = new AiService(_aiPlayer);
+
+ string wentFirst = dialog.HumanGoesFirst ? "player" : "bot";
+ PlayerSession.CurrentGameId = _db.StartGame(PlayerSession.PlayerId, wentFirst);
+
+ _game.InitializeNewGame();
+ ClearSelection();
+ RenderBoard();
+ RefreshStatusBar();
+
+ if (_game.ActivePlayer == _aiPlayer) { TriggerAiTurnAsync(); }
+ }
+ #endregion
+
+ #region Input
+ private void CellClick(object sender, RoutedEventArgs e)
+ {
+ if (_isAiThinking) return;
+ if (_game.ActivePlayer != _humanPlayer) return;
+ if (sender is not Button btn || btn.Tag == null) return;
+
+ ParseCellTag(btn.Tag.ToString()!, out int row, out int col);
+
+ if (_game.IsChainJumpActive) { HandleChainJumpClick(row, col); return; }
+ if (_hasPieceSelected) HandleSecondClick(row, col);
+ else HandleFirstClick(row, col);
+ }
+
+ private void HandleFirstClick(int row, int col)
+ {
+ int piece = _game.Board[row, col];
+ if (!_game.BelongsToActivePlayer(piece)) return;
+
+ if (_game.MandatoryCapturePieces.Count > 0 && !_game.MandatoryCapturePieces.Contains((row, col))) return;
+ if (_game.GetLegalMoves(row, col, capturesOnly: false).Count == 0) return;
+
+ _selectedRow = row;
+ _selectedCol = col;
+ _hasPieceSelected = true;
+ RenderBoard();
+ }
+
+ private void HandleSecondClick(int row, int col)
+ {
+ int piece = _game.Board[row, col];
+
+ if (_game.BelongsToActivePlayer(piece))
+ {
+ bool blockedByCapture = _game.MandatoryCapturePieces.Count > 0
+ && !_game.MandatoryCapturePieces.Contains((row, col));
+ if (!blockedByCapture && _game.GetLegalMoves(row, col, capturesOnly: false).Count > 0)
+ {
+ _selectedRow = row;
+ _selectedCol = col;
+ RenderBoard();
+ }
+ return;
+ }
+
+ var legalMoves = _game.GetLegalMoves(_selectedRow, _selectedCol, capturesOnly: false);
+ var chosen = legalMoves.Find(m => m.DestinationRow == row && m.DestinationCol == col);
+
+ if (chosen != null) { ProcessMoveResult(_game.ExecuteMove(_selectedRow, _selectedCol, chosen)); }
+ }
+
+ private void HandleChainJumpClick(int row, int col)
+ {
+ if (row == _game.ChainJumpRow && col == _game.ChainJumpCol) return;
+
+ var jumps = _game.GetLegalMoves(_game.ChainJumpRow, _game.ChainJumpCol, capturesOnly: true);
+ var chosen = jumps.Find(m => m.DestinationRow == row && m.DestinationCol == col);
+
+ if (chosen != null) { ProcessMoveResult(_game.ExecuteMove(_game.ChainJumpRow, _game.ChainJumpCol, chosen)); }
+ }
+ #endregion
+
+ #region MoveResult
+ private void ProcessMoveResult(MoveResult result)
+ {
+ ClearSelection();
+
+ switch (result.Type)
+ {
+ case MoveResult.ResultType.ChainJumpRequired:
+ if (_game.ActivePlayer == _aiPlayer)
+ {
+ TriggerAiTurnAsync();
+ return;
+ }
+ _hasPieceSelected = true;
+ _selectedRow = _game.ChainJumpRow;
+ _selectedCol = _game.ChainJumpCol;
+ RenderBoard();
+ StatusText.Text = "Chain jump — must continue!";
+ break;
+
+ case MoveResult.ResultType.Victory:
+ RenderBoard();
+ AnnounceWinner(result.Victor);
+ break;
+
+ case MoveResult.ResultType.TurnAdvanced:
+ RenderBoard();
+ RefreshStatusBar();
+ if (_game.ActivePlayer == _aiPlayer) { TriggerAiTurnAsync(); }
+ break;
+ }
+ }
+ #endregion
+
+ #region AI
+ private async void TriggerAiTurnAsync()
+ {
+ _isAiThinking = true;
+ StatusText.Text = "Bot is thinking...";
+
+ int[,] boardSnapshot = (int[,])_game.Board.Clone();
+ Move? bestMove = await Task.Run(() => _ai.GetBestMove(boardSnapshot));
+
+ _isAiThinking = false;
+
+ if (bestMove == null)
+ {
+ AnnounceWinner(_humanPlayer);
+ return;
+ }
+
+ int originRow = -1, originCol = -1;
+ for (int r = 0; r < 8 && originRow == -1; r++)
+ {
+ for (int c = 0; c < 8 && originRow == -1; c++)
+ {
+ if (!BoardHelper.BelongsToPlayer(_game.Board[r, c], _aiPlayer)) continue;
+
+ var moves = _game.GetLegalMoves(r, c, capturesOnly: true);
+ if (moves.Count == 0) { moves = _game.GetLegalMoves(r, c, capturesOnly: false); }
+
+ if (moves.Exists(m => m.DestinationRow == bestMove.DestinationRow &&
+ m.DestinationCol == bestMove.DestinationCol &&
+ m.CapturedPieceRow == bestMove.CapturedPieceRow &&
+ m.CapturedPieceCol == bestMove.CapturedPieceCol))
+ {
+ originRow = r;
+ originCol = c;
+ }
+ }
+ }
+
+ if (originRow == -1) return;
+ ProcessMoveResult(_game.ExecuteMove(originRow, originCol, bestMove));
+ }
+ #endregion
+
+ #region UI
+ private void RenderBoard()
+ {
+ _renderer.RenderBoard(_selectedRow, _selectedCol, _hasPieceSelected, _game.IsChainJumpActive);
+ }
+
+ private void ClearSelection()
+ {
+ _hasPieceSelected = false;
+ _selectedRow = -1;
+ _selectedCol = -1;
+ }
+
+ private void RefreshStatusBar()
+ {
+ if (_game.ActivePlayer == _aiPlayer)
+ {
+ StatusText.Text = "Bot's turn";
+ return;
+ }
+ string hint = _game.MandatoryCapturePieces.Count > 0 ? " — Capture is mandatory!" : "";
+ StatusText.Text = $"Your turn{hint}";
+ }
+
+ private void AnnounceWinner(int victor)
+ {
+ _gameEnded = true;
+ string winnerStr = victor == _humanPlayer ? "player" : "bot";
+ _db.EndGame(PlayerSession.CurrentGameId, winnerStr);
+
+ string message = victor == _humanPlayer ? "You win!" : "Bot wins!";
+ StatusText.Text = message;
+ MessageBox.Show(message, "Game Over", MessageBoxButton.OK, MessageBoxImage.None);
+ }
+
+ private void History_Click(object sender, RoutedEventArgs e)
+ {
+ var dialog = new HistoryDialog();
+ dialog.ShowDialog();
+ }
+
+ private void Window_Closing(object sender, CancelEventArgs e)
+ {
+ if (PlayerSession.CurrentGameId != -1 && !_gameEnded)
+ _db.EndGame(PlayerSession.CurrentGameId, "draw");
+ }
+
+ private static void ParseCellTag(string tag, out int row, out int col)
+ {
+ var parts = tag.Split(',');
+ row = int.Parse(parts[0]);
+ col = int.Parse(parts[1]);
+ }
+
+ private void NewGame(object sender, RoutedEventArgs e) { InitializeNewGame(); }
+ #endregion
}
}
\ No newline at end of file
diff --git a/CheckersSpielBot/Move.cs b/CheckersSpielBot/Move.cs
new file mode 100644
index 0000000..30cb701
--- /dev/null
+++ b/CheckersSpielBot/Move.cs
@@ -0,0 +1,20 @@
+namespace CheckersSpielBot
+{
+ public class Move
+ {
+ public int DestinationRow { get; }
+ public int DestinationCol { get; }
+ public int CapturedPieceRow { get; }
+ public int CapturedPieceCol { get; }
+ public bool IsCapture => CapturedPieceRow >= 0;
+
+ public Move(int destinationRow, int destinationCol,
+ int capturedPieceRow, int capturedPieceCol)
+ {
+ DestinationRow = destinationRow;
+ DestinationCol = destinationCol;
+ CapturedPieceRow = capturedPieceRow;
+ CapturedPieceCol = capturedPieceCol;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/MoveGeneratorService.cs b/CheckersSpielBot/MoveGeneratorService.cs
new file mode 100644
index 0000000..a1de456
--- /dev/null
+++ b/CheckersSpielBot/MoveGeneratorService.cs
@@ -0,0 +1,58 @@
+namespace CheckersSpielBot
+{
+ public class MoveGeneratorService
+ {
+ private readonly int[,] _board;
+
+ public MoveGeneratorService(int[,] board)
+ {
+ _board = board;
+ }
+
+ public List GetLegalMoves(int row, int col, bool capturesOnly)
+ {
+ var moves = new List();
+ int piece = _board[row, col];
+ if (piece == 0) return moves;
+
+ var dirs = ResolveDirections(piece);
+
+ // Captures first
+ foreach (var (dr, dc) in dirs)
+ {
+ int mr = row + dr, mc = col + dc;
+ int lr = row + 2 * dr, lc = col + 2 * dc;
+
+ if (!BoardHelper.IsWithinBounds(mr, mc) || !BoardHelper.IsWithinBounds(lr, lc))
+ continue;
+
+ int mid = _board[mr, mc];
+ if (mid != 0 && BoardHelper.IsOpponentPiece(piece, mid) && _board[lr, lc] == 0)
+ moves.Add(new Move(lr, lc, mr, mc));
+ }
+
+ if (capturesOnly || moves.Count > 0) return moves;
+
+ // Normal moves
+ foreach (var (dr, dc) in dirs)
+ {
+ int lr = row + dr, lc = col + dc;
+ if (BoardHelper.IsWithinBounds(lr, lc) && _board[lr, lc] == 0)
+ moves.Add(new Move(lr, lc, -1, -1));
+ }
+
+ return moves;
+ }
+
+ private static List<(int dr, int dc)> ResolveDirections(int piece)
+ {
+ bool isKing = BoardHelper.IsKing(piece);
+ bool isRedPiece = BoardHelper.IsRedPiece(piece);
+
+ var dirs = new List<(int, int)>();
+ if (isRedPiece || isKing) { dirs.Add((-1, -1)); dirs.Add((-1, +1)); }
+ if (!isRedPiece || isKing) { dirs.Add((+1, -1)); dirs.Add((+1, +1)); }
+ return dirs;
+ }
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/MoveResult.cs b/CheckersSpielBot/MoveResult.cs
new file mode 100644
index 0000000..d84c912
--- /dev/null
+++ b/CheckersSpielBot/MoveResult.cs
@@ -0,0 +1,21 @@
+namespace CheckersSpielBot
+{
+ public class MoveResult
+ {
+ public enum ResultType { TurnAdvanced, ChainJumpRequired, Victory }
+
+ public ResultType Type { get; private set; }
+ public int Victor { get; private set; }
+
+ private MoveResult() { }
+
+ public static readonly MoveResult TurnAdvanced = new() { Type = ResultType.TurnAdvanced };
+ public static readonly MoveResult ChainJumpRequired = new() { Type = ResultType.ChainJumpRequired };
+
+ public static MoveResult Victory(int victor) => new()
+ {
+ Type = ResultType.Victory,
+ Victor = victor
+ };
+ }
+}
\ No newline at end of file
diff --git a/CheckersSpielBot/PlayerSession.cs b/CheckersSpielBot/PlayerSession.cs
new file mode 100644
index 0000000..fb3b505
--- /dev/null
+++ b/CheckersSpielBot/PlayerSession.cs
@@ -0,0 +1,9 @@
+namespace CheckersSpielBot
+{
+ public static class PlayerSession
+ {
+ public static int PlayerId { get; set; } = -1;
+ public static string Username { get; set; } = string.Empty;
+ public static int CurrentGameId { get; set; } = -1;
+ }
+}
\ No newline at end of file