├── .gitignore ├── Dockerfile ├── Dockerfile.local ├── Kernel ├── Chess.cs ├── Engine.cs ├── Errors.cs ├── Kernel.csproj ├── Moves.cs ├── Properties │ └── AssemblyInfo.cs └── Quantize.cs ├── LICENSE ├── README.md ├── TrulyQuantumChess.sln ├── Vanilla ├── ConsoleIO.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── Vanilla.csproj ├── WebApp ├── ApiModule.cs ├── Bootstrapper.cs ├── Content │ ├── index.js │ ├── logo.png │ ├── logo_high_resolution.png │ ├── pieces │ │ └── simple │ │ │ ├── black_bishop_alive.png │ │ │ ├── black_bishop_dead.png │ │ │ ├── black_king_alive.png │ │ │ ├── black_king_dead.png │ │ │ ├── black_knight_alive.png │ │ │ ├── black_knight_dead.png │ │ │ ├── black_pawn_alive.png │ │ │ ├── black_pawn_dead.png │ │ │ ├── black_queen_alive.png │ │ │ ├── black_queen_dead.png │ │ │ ├── black_rook_alive.png │ │ │ ├── black_rook_dead.png │ │ │ ├── white_bishop_alive.png │ │ │ ├── white_bishop_dead.png │ │ │ ├── white_king_alive.png │ │ │ ├── white_king_dead.png │ │ │ ├── white_knight_alive.png │ │ │ ├── white_knight_dead.png │ │ │ ├── white_pawn_alive.png │ │ │ ├── white_pawn_dead.png │ │ │ ├── white_queen_alive.png │ │ │ ├── white_queen_dead.png │ │ │ ├── white_rook_alive.png │ │ │ └── white_rook_dead.png │ ├── play.js │ └── stylesheet.css ├── Database.cs ├── HtmlModule.cs ├── Model.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── Templates │ ├── ActiveGames.sshtml │ ├── Index.sshtml │ ├── Master.sshtml │ └── Play.sshtml ├── WebApp.csproj ├── WebAppConfig.cs ├── WebAppConfig.json ├── WebAppConfig_dockerized.json ├── WebAppConfig_dockerized_local.json └── packages.config ├── WebAppConfig_dockerized.json └── docker-compose.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # junk 2 | junk 3 | 4 | # Autosave files 5 | *~ 6 | 7 | # build 8 | [Oo]bj/ 9 | [Bb]in/ 10 | packages/ 11 | TestResults/ 12 | 13 | # globs 14 | Makefile.in 15 | *.DS_Store 16 | *.sln.cache 17 | *.suo 18 | *.cache 19 | *.pidb 20 | *.userprefs 21 | *.usertasks 22 | config.log 23 | config.make 24 | config.status 25 | aclocal.m4 26 | install-sh 27 | autom4te.cache/ 28 | *.user 29 | *.tar.gz 30 | tarballs/ 31 | test-results/ 32 | Thumbs.db 33 | 34 | # Mac bundle stuff 35 | *.dmg 36 | *.app 37 | 38 | # resharper 39 | *_Resharper.* 40 | *.Resharper 41 | 42 | # dotCover 43 | *.dotCover 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mono:latest 2 | EXPOSE 9000 3 | COPY ./ /src 4 | WORKDIR /src 5 | RUN nuget restore && \ 6 | find . -name "*RuntimeInformation.dll" -delete && \ 7 | xbuild /p:Configuration=Release && \ 8 | find . -name "*RuntimeInformation.dll" -delete && \ 9 | cp WebApp/WebAppConfig_dockerized.json WebApp/bin/Release/WebAppConfig.json 10 | WORKDIR /src/WebApp/bin/Release 11 | CMD mono WebApp.exe 12 | -------------------------------------------------------------------------------- /Dockerfile.local: -------------------------------------------------------------------------------- 1 | FROM mono:latest 2 | EXPOSE 9000 3 | COPY ./ /src 4 | WORKDIR /src 5 | RUN nuget restore && \ 6 | find . -name "*RuntimeInformation.dll" -delete && \ 7 | xbuild /p:Configuration=Debug && \ 8 | find . -name "*RuntimeInformation.dll" -delete && \ 9 | cp WebApp/WebAppConfig_dockerized_local.json WebApp/bin/Debug/WebAppConfig.json 10 | WORKDIR /src/WebApp/bin/Debug 11 | CMD mono WebApp.exe 12 | -------------------------------------------------------------------------------- /Kernel/Chess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using TrulyQuantumChess.Kernel.Errors; 4 | using TrulyQuantumChess.Kernel.Moves; 5 | 6 | namespace TrulyQuantumChess.Kernel.Chess { 7 | public enum Player { 8 | White, 9 | Black 10 | } 11 | 12 | public static class PlayerUtils { 13 | public static Player InvertPlayer(Player player) { 14 | switch (player) { 15 | case Player.White: 16 | return Player.Black; 17 | case Player.Black: 18 | return Player.White; 19 | default: 20 | throw new AssertionException($"Unsupported player: {player}"); 21 | } 22 | } 23 | 24 | public static string ToString(Player player) { 25 | switch (player) { 26 | case Player.White: return "white"; 27 | case Player.Black: return "black"; 28 | default: throw new AssertionException($"Unsupported player: {player}"); 29 | } 30 | } 31 | 32 | public static Player FromString(string player) { 33 | switch (player) { 34 | case "white": return Player.White; 35 | case "black": return Player.Black; 36 | default: throw new AssertionException($"Unsupported player string: \"{player}\""); 37 | } 38 | } 39 | } 40 | 41 | public enum PieceType { 42 | Pawn, 43 | Knight, 44 | Bishop, 45 | Rook, 46 | Queen, 47 | King 48 | } 49 | 50 | public static class PieceTypeUtils { 51 | public static string ToString(PieceType piece_type) { 52 | switch (piece_type) { 53 | case PieceType.Pawn: return "pawn"; 54 | case PieceType.Knight: return "knight"; 55 | case PieceType.Bishop: return "bishop"; 56 | case PieceType.Rook: return "rook"; 57 | case PieceType.Queen: return "queen"; 58 | case PieceType.King: return "king"; 59 | default: throw new AssertionException($"Unsupported piece type: {piece_type}"); 60 | } 61 | } 62 | 63 | public static PieceType FromString(string piece_type) { 64 | switch (piece_type) { 65 | case "pawn": return PieceType.Pawn; 66 | case "knight": return PieceType.Knight; 67 | case "bishop": return PieceType.Bishop; 68 | case "rook": return PieceType.Rook; 69 | case "queen": return PieceType.Queen; 70 | case "king": return PieceType.King; 71 | default: throw new AssertionException($"Unsupported piece type string: \"{piece_type}\""); 72 | } 73 | } 74 | } 75 | 76 | public struct Piece { 77 | public readonly Player Player; 78 | public readonly PieceType PieceType; 79 | 80 | public Piece(Player player, PieceType pieceType) { 81 | Player = player; 82 | PieceType = pieceType; 83 | } 84 | 85 | public static bool operator == (Piece a, Piece b) { 86 | return a.Player == b.Player && a.PieceType == b.PieceType; 87 | } 88 | 89 | public static bool operator != (Piece a, Piece b) { 90 | return !(a == b); 91 | } 92 | 93 | public override bool Equals(object obj) { 94 | if (obj is Piece) { 95 | Piece piece = (Piece) obj; 96 | return this == piece; 97 | } else { 98 | return false; 99 | } 100 | } 101 | 102 | public override int GetHashCode() { 103 | return 1 + PieceType.GetHashCode() * 2 + Player.GetHashCode(); 104 | } 105 | } 106 | 107 | public struct Position { 108 | private readonly int Ind_; 109 | 110 | public Position(int index) { 111 | Ind_ = index; 112 | } 113 | 114 | public static Position FromIndex(int index) { 115 | var res = new Position(index); 116 | AssertionException.Assert(res.Ind_ >= 0 && res.Ind_ < 64, $"Chessboard index is out of bounds: {res.Ind_}"); 117 | return res; 118 | } 119 | 120 | public static Position FromCoords(int x, int y) { 121 | var res = new Position(y * 8 + x); 122 | AssertionException.Assert(res.Ind_ >= 0 && res.Ind_ < 64, $"Chessboard index is out of bounds: {res.Ind_}"); 123 | return res; 124 | } 125 | 126 | public int Ind { 127 | get { return Ind_; } 128 | } 129 | 130 | public int X { 131 | get { return Ind_ % 8; } 132 | } 133 | 134 | public int Y { 135 | get { return Ind_ / 8; } 136 | } 137 | 138 | public static bool operator == (Position a, Position b) { 139 | return a.Ind_ == b.Ind_; 140 | } 141 | 142 | public static bool operator != (Position a, Position b) { 143 | return !(a == b); 144 | } 145 | 146 | public override bool Equals(object obj) { 147 | if (obj is Position) { 148 | Position position = (Position) obj; 149 | return this == position; 150 | } else { 151 | return false; 152 | } 153 | } 154 | 155 | public override int GetHashCode() { 156 | return Ind_.GetHashCode(); 157 | } 158 | 159 | public override string ToString() { 160 | char c1 = Convert.ToChar(Convert.ToInt32('A') + X); 161 | char c2 = Convert.ToChar(Convert.ToInt32('1') + Y); 162 | return $"{c1}{c2}"; 163 | } 164 | 165 | public static Position Parse(string str) { 166 | str = str.ToLower(); 167 | AssertionException.Assert(str.Length == 2, "Invalid position format"); 168 | int x = Convert.ToInt32(str[0]) - Convert.ToInt32('a'); 169 | int y = Convert.ToInt32(str[1]) - Convert.ToInt32('1'); 170 | return FromCoords(x, y); 171 | } 172 | } 173 | 174 | public enum GameState { 175 | GameStillGoing, 176 | WhiteVictory, 177 | BlackVictory, 178 | Tie 179 | } 180 | 181 | public static class GameStateUtils { 182 | public static string ToString(GameState game_state) { 183 | switch (game_state) { 184 | case GameState.GameStillGoing: return "game_still_going"; 185 | case GameState.WhiteVictory: return "white_victory"; 186 | case GameState.BlackVictory: return "black_victory"; 187 | case GameState.Tie: return "tie"; 188 | default: throw new AssertionException($"Unsupported game state: {game_state}"); 189 | } 190 | } 191 | 192 | public static GameState FromString(string game_state) { 193 | switch (game_state) { 194 | case "game_still_going": return GameState.GameStillGoing; 195 | case "white_victory": return GameState.WhiteVictory; 196 | case "black_victory": return GameState.BlackVictory; 197 | case "tie": return GameState.Tie; 198 | default: throw new AssertionException($"Unsupported game state string: \"{game_state}\""); 199 | } 200 | } 201 | } 202 | 203 | public class Chessboard { 204 | private readonly Piece?[] Pieces_ = new Piece?[64]; 205 | private GameState GameState_ = GameState.Tie; 206 | 207 | public static Chessboard EmptyChessboard() { 208 | return new Chessboard(); 209 | } 210 | 211 | public static Chessboard EmptyChessboard(GameState chessboard_state) { 212 | var res = new Chessboard(); 213 | res.GameState_ = chessboard_state; 214 | return res; 215 | } 216 | 217 | public static Chessboard StartingChessboard() { 218 | var chessboard = EmptyChessboard(); 219 | chessboard.GameState_ = GameState.GameStillGoing; 220 | 221 | // Setting up white power pieces 222 | chessboard.Pieces_[0] = new Piece(Player.White, PieceType.Rook); 223 | chessboard.Pieces_[1] = new Piece(Player.White, PieceType.Knight); 224 | chessboard.Pieces_[2] = new Piece(Player.White, PieceType.Bishop); 225 | chessboard.Pieces_[3] = new Piece(Player.White, PieceType.Queen); 226 | chessboard.Pieces_[4] = new Piece(Player.White, PieceType.King); 227 | chessboard.Pieces_[5] = new Piece(Player.White, PieceType.Bishop); 228 | chessboard.Pieces_[6] = new Piece(Player.White, PieceType.Knight); 229 | chessboard.Pieces_[7] = new Piece(Player.White, PieceType.Rook); 230 | 231 | // Setting up black power pieces 232 | chessboard.Pieces_[56] = new Piece(Player.Black, PieceType.Rook); 233 | chessboard.Pieces_[57] = new Piece(Player.Black, PieceType.Knight); 234 | chessboard.Pieces_[58] = new Piece(Player.Black, PieceType.Bishop); 235 | chessboard.Pieces_[59] = new Piece(Player.Black, PieceType.Queen); 236 | chessboard.Pieces_[60] = new Piece(Player.Black, PieceType.King); 237 | chessboard.Pieces_[61] = new Piece(Player.Black, PieceType.Bishop); 238 | chessboard.Pieces_[62] = new Piece(Player.Black, PieceType.Knight); 239 | chessboard.Pieces_[63] = new Piece(Player.Black, PieceType.Rook); 240 | 241 | // Setting up white pawns 242 | for (int i = 8; i < 16; i++) { 243 | chessboard.Pieces_[i] = new Piece(Player.White, PieceType.Pawn); 244 | } 245 | 246 | // Setting up black pawns 247 | for (int i = 48; i < 56; i++) { 248 | chessboard.Pieces_[i] = new Piece(Player.Black, PieceType.Pawn); 249 | } 250 | 251 | return chessboard; 252 | } 253 | 254 | public Chessboard Clone() { 255 | var res = new Chessboard(); 256 | res.GameState_ = GameState_; 257 | for (int i = 0; i < 64; i++) { 258 | res.Pieces_[i] = Pieces_[i]; 259 | } 260 | return res; 261 | } 262 | 263 | public GameState GameState { 264 | get { return GameState_; } 265 | } 266 | 267 | public Piece? this[Position pos] { 268 | get { 269 | return Pieces_[pos.Ind]; 270 | } 271 | set { 272 | Pieces_[pos.Ind] = value; 273 | } 274 | } 275 | 276 | public Piece? this[int index] { 277 | get { 278 | var pos = Position.FromIndex(index); 279 | return Pieces_[pos.Ind]; 280 | } 281 | set { 282 | var pos = Position.FromIndex(index); 283 | Pieces_[pos.Ind] = value; 284 | } 285 | } 286 | 287 | public Piece? this[int x, int y] { 288 | get { 289 | var pos = Position.FromCoords(x, y); 290 | return Pieces_[pos.Ind]; 291 | } 292 | set { 293 | var pos = Position.FromCoords(x, y); 294 | Pieces_[pos.Ind] = value; 295 | } 296 | } 297 | 298 | public static bool operator == (Chessboard a, Chessboard b) { 299 | for (int i = 0; i < 64; i++) { 300 | if (a.Pieces_[i] != b.Pieces_[i]) 301 | return false; 302 | } 303 | return a.GameState_ == b.GameState_; 304 | } 305 | 306 | public static bool operator != (Chessboard a, Chessboard b) { 307 | return !(a == b); 308 | } 309 | 310 | public override bool Equals(object obj) { 311 | if (obj is Chessboard) { 312 | var chessboard = obj as Chessboard; 313 | return this == chessboard; 314 | } else { 315 | return false; 316 | } 317 | } 318 | 319 | public override int GetHashCode() { 320 | int res = 0, hash_base = 17; 321 | unchecked { 322 | for (int i = 0; i < 64; i++) { 323 | res *= hash_base; 324 | res += Pieces_[i].GetHashCode(); 325 | } 326 | } 327 | return res; 328 | } 329 | 330 | public int GetHashCodeWithGameState() { 331 | return unchecked(GetHashCode() * 4 + GameState_.GetHashCode()); 332 | } 333 | 334 | private static int Signum(int x) { 335 | if (x > 0) { 336 | return 1; 337 | } else if (x < 0) { 338 | return -1; 339 | } else { 340 | return 0; 341 | } 342 | } 343 | 344 | private bool CheckIntermediateSquares(Piece piece, Position source, Position target, bool capture) { 345 | int dx = target.X - source.X; 346 | int dx_abs = Math.Abs(dx); 347 | int dx_sig = Signum(dx); 348 | 349 | int dy = target.Y - source.Y; 350 | int dy_abs = Math.Abs(dy); 351 | int dy_sig = Signum(dy); 352 | 353 | switch (piece.PieceType) { 354 | case PieceType.Pawn: 355 | switch (piece.Player) { 356 | case Player.White: 357 | if (!capture) 358 | return source.X == target.X && (source.Y + 1 == target.Y || source.Y == 1 && target.Y == 3 && this[source.X, 2] == null); 359 | else 360 | return (source.X == target.X + 1 || source.X + 1 == target.X) && source.Y + 1 == target.Y; 361 | case Player.Black: 362 | if (!capture) 363 | return source.X == target.X && (source.Y - 1 == target.Y || source.Y == 6 && target.Y == 4 && this[source.X, 5] == null); 364 | else 365 | return (source.X == target.X + 1 || source.X + 1 == target.X) && source.Y - 1 == target.Y; 366 | default: 367 | throw new AssertionException($"Unsupported player: {piece.Player}"); 368 | } 369 | 370 | case PieceType.Knight: 371 | return dx_abs == 1 && dy_abs == 2 || dx_abs == 2 && dy_abs == 1; 372 | 373 | case PieceType.Bishop: 374 | if (dx_abs != dy_abs) 375 | return false; 376 | for (int i = 1; i < dx_abs; i++) { 377 | if (this[source.X + i * dx_sig, source.Y + i * dy_sig] != null) 378 | return false; 379 | } 380 | return true; 381 | 382 | case PieceType.Rook: 383 | if (dx != 0 && dy != 0) 384 | return false; 385 | for (int i = 1; i < dx_abs + dy_abs; i++) { 386 | if (this[source.X + i * dx_sig, source.Y + i * dy_sig] != null) 387 | return false; 388 | } 389 | return true; 390 | 391 | case PieceType.Queen: 392 | if (dx_abs == dy_abs) { 393 | for (int i = 1; i < dx_abs; i++) { 394 | if (this[source.X + i * dx_sig, source.Y + i * dy_sig] != null) 395 | return false; 396 | } 397 | return true; 398 | } else if (dx_abs == 0 || dy_abs == 0) { 399 | for (int i = 1; i < dx_abs + dy_abs; i++) { 400 | if (this[source.X + i * dx_sig, source.Y + i * dy_sig] != null) 401 | return false; 402 | } 403 | return true; 404 | } else { 405 | return false; 406 | } 407 | 408 | case PieceType.King: 409 | return (dx_abs == 0 || dx_abs == 1) && (dy_abs == 0 || dy_abs == 1); 410 | 411 | default: 412 | throw new AssertionException($"Unsupported piece type: {piece.PieceType}"); 413 | } 414 | } 415 | 416 | public bool CheckOrdinaryMoveApplicable(OrdinaryMove move) { 417 | if (move.Source == move.Target) { 418 | // Dummy moves aren't allowed by the rules of the game 419 | return false; 420 | } 421 | 422 | Piece? source = this[move.Source]; 423 | if (source != move.ActorPiece) { 424 | // The chessboard doesn't have an actor piece at the source position 425 | // Therefore the move is inapplicable 426 | return false; 427 | } 428 | 429 | Piece? target = this[move.Target]; 430 | bool capture = target.HasValue; 431 | if (capture && target.Value.Player == move.ActorPlayer) { 432 | // You can't capture your own pieces 433 | return false; 434 | } 435 | 436 | return CheckIntermediateSquares(move.ActorPiece, move.Source, move.Target, capture); 437 | } 438 | 439 | public void ApplyOrdinaryMove(OrdinaryMove move) { 440 | AssertionException.Assert(CheckOrdinaryMoveApplicable(move), $"Attempted applying inapplicable ordinary move"); 441 | this[move.Target] = this[move.Source]; 442 | this[move.Source] = null; 443 | 444 | if (move.ActorPiece.PieceType == PieceType.Pawn && 445 | (move.Target.Y == 0 || move.Target.Y == 7)) 446 | { 447 | // Pawn entered its final destination, promoting it to a queen 448 | AssertionException.Assert(this[move.Target].HasValue && this[move.Target].Value.PieceType == PieceType.Pawn, 449 | "We just applied the pawn move, but somehow no pawn present at the target square"); 450 | this[move.Target] = new Piece(this[move.Target].Value.Player, PieceType.Queen); 451 | } 452 | 453 | if (GameState_ == GameState.GameStillGoing) { 454 | bool white_king_present = false; 455 | bool black_king_present = false; 456 | for (int i = 0; i < 64; i++) { 457 | if (!Pieces_[i].HasValue) 458 | continue; 459 | if (Pieces_[i].Value.PieceType != PieceType.King) 460 | continue; 461 | switch (Pieces_[i].Value.Player) { 462 | case Player.White: 463 | white_king_present = true; 464 | break; 465 | case Player.Black: 466 | black_king_present = true; 467 | break; 468 | } 469 | } 470 | if (!white_king_present && !black_king_present) { 471 | GameState_ = GameState.Tie; 472 | } else if (!white_king_present) { 473 | GameState_ = GameState.BlackVictory; 474 | } else if (!black_king_present) { 475 | GameState_ = GameState.WhiteVictory; 476 | } 477 | } 478 | } 479 | 480 | public bool CheckQuantumMoveApplicable(QuantumMove move) { 481 | if (move.Source == move.Target) { 482 | // Dummy moves aren't allowed by the rules of the game 483 | return false; 484 | } 485 | 486 | Piece? source = this[move.Source]; 487 | if (source != move.ActorPiece) { 488 | // The chessboard doesn't have an actor piece at the source position 489 | // Therefore the move is inapplicable 490 | return false; 491 | } 492 | 493 | Piece? target = this[move.Target]; 494 | if (target != null) { 495 | // You can't quantum-capture! 496 | return false; 497 | } 498 | 499 | if (move.Middle.HasValue) { 500 | Piece? middle = this[move.Middle.Value]; 501 | if (middle != null) { 502 | // Move is inapplicable 503 | return false; 504 | } 505 | 506 | return CheckIntermediateSquares(move.ActorPiece, move.Source, move.Middle.Value, false) && 507 | CheckIntermediateSquares(move.ActorPiece, move.Middle.Value, move.Target, false); 508 | } else { 509 | return CheckIntermediateSquares(move.ActorPiece, move.Source, move.Target, false); 510 | } 511 | } 512 | 513 | public void ApplyQuantumMove(QuantumMove move) { 514 | AssertionException.Assert(CheckQuantumMoveApplicable(move), $"Attempted applying inapplicable quantum move"); 515 | this[move.Target] = this[move.Source]; 516 | this[move.Source] = null; 517 | 518 | if (move.ActorPiece.PieceType == PieceType.Pawn && 519 | (move.Target.Y == 0 || move.Target.Y == 7)) 520 | { 521 | // Pawn entered its final destination, promoting it to a queen 522 | AssertionException.Assert(this[move.Target].HasValue && this[move.Target].Value.PieceType == PieceType.Pawn, 523 | "We just applied the pawn move, but somehow no pawn present at the target square"); 524 | this[move.Target] = new Piece(this[move.Target].Value.Player, PieceType.Queen); 525 | } 526 | } 527 | 528 | public bool CheckCastleMoveApplicable(CastleMove move) { 529 | int c; 530 | 531 | switch (move.ActorPlayer) { 532 | case Player.White: 533 | c = 0; 534 | break; 535 | 536 | case Player.Black: 537 | c = 7; 538 | break; 539 | 540 | default: throw new AssertionException($"Unsupported player: {move.ActorPlayer}"); 541 | } 542 | 543 | switch (move.CastleType) { 544 | case CastleType.Left: return 545 | this[0, c] == new Piece(move.ActorPlayer, PieceType.Rook) && 546 | this[1, c] == null && 547 | this[2, c] == null && 548 | this[3, c] == null && 549 | this[4, c] == new Piece(move.ActorPlayer, PieceType.King); 550 | 551 | case CastleType.Right: return 552 | this[4, c] == new Piece(move.ActorPlayer, PieceType.King) && 553 | this[5, c] == null && 554 | this[6, c] == null && 555 | this[7, c] == new Piece(move.ActorPlayer, PieceType.Rook); 556 | 557 | default: 558 | throw new AssertionException($"Unsupported castle type: {move.CastleType}"); 559 | } 560 | } 561 | 562 | public void ApplyCastleMove(CastleMove move) { 563 | AssertionException.Assert(CheckCastleMoveApplicable(move), $"Attempted applying inapplicable castle move"); 564 | int c; 565 | 566 | switch (move.ActorPlayer) { 567 | case Player.White: 568 | c = 0; 569 | break; 570 | 571 | case Player.Black: 572 | c = 7; 573 | break; 574 | 575 | default: throw new AssertionException($"Unsupported player: {move.ActorPlayer}"); 576 | } 577 | 578 | switch (move.CastleType) { 579 | case CastleType.Left: 580 | this[0, c] = null; 581 | this[1, c] = null; 582 | this[2, c] = new Piece(move.ActorPlayer, PieceType.King); 583 | this[3, c] = new Piece(move.ActorPlayer, PieceType.Rook); 584 | this[4, c] = null; 585 | break; 586 | 587 | case CastleType.Right: 588 | this[4, c] = null; 589 | this[5, c] = new Piece(move.ActorPlayer, PieceType.Rook); 590 | this[6, c] = new Piece(move.ActorPlayer, PieceType.King); 591 | this[7, c] = null; 592 | break; 593 | 594 | default: 595 | throw new AssertionException($"Unsupported castle type: {move.CastleType}"); 596 | } 597 | 598 | } 599 | 600 | public void RegisterVictory(Player player) { 601 | if (GameState_ == GameState.GameStillGoing) { 602 | switch (player) { 603 | case Player.White: 604 | GameState_ = GameState.WhiteVictory; 605 | break; 606 | 607 | case Player.Black: 608 | GameState_ = GameState.BlackVictory; 609 | break; 610 | } 611 | } 612 | } 613 | 614 | public void RegisterTie() { 615 | if (GameState_ == GameState.GameStillGoing) 616 | GameState_ = GameState.Tie; 617 | } 618 | } 619 | } 620 | -------------------------------------------------------------------------------- /Kernel/Engine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using TrulyQuantumChess.Kernel.Errors; 4 | using TrulyQuantumChess.Kernel.Moves; 5 | using TrulyQuantumChess.Kernel.Chess; 6 | using TrulyQuantumChess.Kernel.Quantize; 7 | 8 | namespace TrulyQuantumChess.Kernel.Engine { 9 | public class QuantumChessEngine { 10 | public QuantumChessEngine() { 11 | QuantumChessboard_ = QuantumChessboard.StartingQuantumChessboard(); 12 | ActivePlayer_ = Player.White; 13 | CreationTime_ = DateTime.Now; 14 | LastMovePositions_ = new Position[0]; 15 | } 16 | 17 | public QuantumChessEngine(QuantumChessboard quantum_chessboard, Player active_player, DateTime creation_time, 18 | Position[] last_move_positions) 19 | { 20 | QuantumChessboard_ = quantum_chessboard; 21 | ActivePlayer_ = active_player; 22 | CreationTime_ = creation_time; 23 | LastMovePositions_ = last_move_positions; 24 | } 25 | 26 | private readonly QuantumChessboard QuantumChessboard_; 27 | private Player ActivePlayer_; 28 | private DateTime CreationTime_; 29 | private Position[] LastMovePositions_; 30 | 31 | public QuantumChessboard QuantumChessboard { 32 | get { return QuantumChessboard_; } 33 | } 34 | 35 | public Player ActivePlayer { 36 | get { return ActivePlayer_; } 37 | } 38 | 39 | public DateTime CreationTime { 40 | get { return CreationTime_; } 41 | } 42 | 43 | public GameState GameState { 44 | get { return QuantumChessboard_.GameState; } 45 | } 46 | 47 | public Position[] LastMovePositions { 48 | get { return LastMovePositions_; } 49 | } 50 | 51 | public void Submit(QuantumChessMove move) { 52 | if (move.ActorPlayer != ActivePlayer_) 53 | MoveProcessException.Throw("Waiting for another player's move"); 54 | 55 | if (move is CapitulateMove) { 56 | QuantumChessboard_.RegisterVictory(PlayerUtils.InvertPlayer(ActivePlayer_)); 57 | LastMovePositions_ = new Position[0]; 58 | } else if (move is AgreeToTieMove) { 59 | QuantumChessboard_.RegisterTie(); 60 | LastMovePositions_ = new Position[0]; 61 | } else if (move is OrdinaryMove) { 62 | var omove = move as OrdinaryMove; 63 | if (QuantumChessboard_.CheckOrdinaryMoveApplicable(omove)) { 64 | QuantumChessboard_.ApplyOrdinaryMove(omove); 65 | ActivePlayer_ = PlayerUtils.InvertPlayer(ActivePlayer_); 66 | LastMovePositions_ = new Position[]{omove.Source, omove.Target}; 67 | } else { 68 | MoveProcessException.Throw("Move is inapplicable on all harmonics"); 69 | } 70 | } else if (move is QuantumMove) { 71 | var qmove = move as QuantumMove; 72 | if (QuantumChessboard_.CheckQuantumMoveApplicable(qmove)) { 73 | QuantumChessboard_.ApplyQuantumMove(qmove); 74 | ActivePlayer_ = PlayerUtils.InvertPlayer(ActivePlayer_); 75 | if (qmove.Middle.HasValue) 76 | LastMovePositions_ = new Position[]{qmove.Source, qmove.Middle.Value, qmove.Target}; 77 | else 78 | LastMovePositions_ = new Position[]{qmove.Source, qmove.Target}; 79 | } else { 80 | MoveProcessException.Throw("Quantum move is inapplicable on all harmonics"); 81 | } 82 | } else if (move is CastleMove) { 83 | var cmove = move as CastleMove; 84 | if (QuantumChessboard_.CheckCastleMoveApplicable(cmove)) { 85 | QuantumChessboard_.ApplyCastleMove(cmove); 86 | ActivePlayer_ = PlayerUtils.InvertPlayer(ActivePlayer_); 87 | int c = cmove.ActorPlayer == Player.White ? 0 : 7; 88 | LastMovePositions_ = new Position[2]; 89 | LastMovePositions_[0] = Position.FromCoords(4, c); 90 | if (cmove.CastleType == CastleType.Left) 91 | LastMovePositions_[1] = Position.FromCoords(0, c); 92 | else 93 | LastMovePositions_[1] = Position.FromCoords(7, c); 94 | } else { 95 | MoveProcessException.Throw("Castle is inapplicable on all harmonics"); 96 | } 97 | } else { 98 | AssertionException.Assert(false, $"Unsupported move type: {move.GetType().Name}"); 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Kernel/Errors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TrulyQuantumChess.Kernel.Errors { 4 | public abstract class QuantumChessException : Exception { 5 | public QuantumChessException(string message) 6 | : base(message) 7 | {} 8 | } 9 | 10 | public class AssertionException : QuantumChessException { 11 | public AssertionException(string message) 12 | : base(message) 13 | {} 14 | 15 | public static void Assert(bool expr, string message) { 16 | if (!expr) { 17 | throw new AssertionException(message); 18 | } 19 | } 20 | } 21 | 22 | public class MoveProcessException : QuantumChessException { 23 | public MoveProcessException(string message) 24 | : base(message) 25 | {} 26 | 27 | public static void Throw(string message) { 28 | throw new MoveProcessException(message); 29 | } 30 | } 31 | 32 | public class MoveParseException : QuantumChessException { 33 | public MoveParseException(string message) 34 | : base(message) 35 | {} 36 | 37 | public static void Throw(string message) { 38 | throw new MoveParseException(message); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Kernel/Kernel.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {B4B23590-B467-4F12-929B-313A2025613E} 7 | Library 8 | Kernel 9 | Kernel 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | false 21 | 22 | 23 | true 24 | bin\Release 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Kernel/Moves.cs: -------------------------------------------------------------------------------- 1 | using TrulyQuantumChess.Kernel.Chess; 2 | 3 | namespace TrulyQuantumChess.Kernel.Moves { 4 | public abstract class QuantumChessMove { 5 | public abstract Player ActorPlayer { get; } 6 | } 7 | 8 | public sealed class CapitulateMove : QuantumChessMove { 9 | public CapitulateMove(Player actorPlayer) { 10 | ActorPlayer_ = actorPlayer; 11 | } 12 | 13 | private Player ActorPlayer_; 14 | 15 | public override Player ActorPlayer { 16 | get { return ActorPlayer_; } 17 | } 18 | } 19 | 20 | public sealed class AgreeToTieMove : QuantumChessMove { 21 | public AgreeToTieMove(Player actorPlayer) { 22 | ActorPlayer_ = actorPlayer; 23 | } 24 | 25 | private Player ActorPlayer_; 26 | 27 | public override Player ActorPlayer { 28 | get { return ActorPlayer_; } 29 | } 30 | } 31 | 32 | public sealed class OrdinaryMove : QuantumChessMove { 33 | public OrdinaryMove(Piece actorPiece, Position source, Position target) { 34 | ActorPiece_ = actorPiece; 35 | Source_ = source; 36 | Target_ = target; 37 | } 38 | 39 | private readonly Piece ActorPiece_; 40 | private readonly Position Source_; 41 | private readonly Position Target_; 42 | 43 | public Piece ActorPiece { 44 | get { return ActorPiece_; } 45 | } 46 | 47 | public Position Source { 48 | get { return Source_; } 49 | } 50 | 51 | public Position Target { 52 | get { return Target_; } 53 | } 54 | 55 | public override Player ActorPlayer { 56 | get { return ActorPiece_.Player; } 57 | } 58 | } 59 | 60 | public sealed class QuantumMove : QuantumChessMove { 61 | public QuantumMove(Piece actorPiece, Position source, Position? middle, Position target) { 62 | ActorPiece_ = actorPiece; 63 | Source_ = source; 64 | Middle_ = middle; 65 | Target_ = target; 66 | } 67 | 68 | private readonly Piece ActorPiece_; 69 | private readonly Position Source_; 70 | private readonly Position? Middle_; 71 | private readonly Position Target_; 72 | 73 | public Piece ActorPiece { 74 | get { return ActorPiece_; } 75 | } 76 | 77 | public Position Source { 78 | get { return Source_; } 79 | } 80 | 81 | public Position? Middle { 82 | get { return Middle_; } 83 | } 84 | 85 | public Position Target { 86 | get { return Target_; } 87 | } 88 | 89 | public override Player ActorPlayer { 90 | get { return ActorPiece_.Player; } 91 | } 92 | } 93 | 94 | public enum CastleType { 95 | Left, Right 96 | } 97 | 98 | public sealed class CastleMove : QuantumChessMove { 99 | public CastleMove(Player actorPlayer, CastleType castleType) { 100 | ActorPlayer_ = actorPlayer; 101 | CastleType_ = castleType; 102 | } 103 | 104 | private readonly Player ActorPlayer_; 105 | private readonly CastleType CastleType_; 106 | 107 | public override Player ActorPlayer { 108 | get { return ActorPlayer_; } 109 | } 110 | 111 | public CastleType CastleType { 112 | get { return CastleType_; } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Kernel/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("TrulyQuantumChess.Kernel")] 8 | [assembly: AssemblyDescription("The quantum chess engine")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("Cap. Hindsight ")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /Kernel/Quantize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using TrulyQuantumChess.Kernel.Errors; 6 | using TrulyQuantumChess.Kernel.Chess; 7 | using TrulyQuantumChess.Kernel.Moves; 8 | 9 | namespace TrulyQuantumChess.Kernel.Quantize { 10 | public static class MeasurementUtils { 11 | private static readonly Random Random_ = new Random(); 12 | 13 | public static double Probability(ulong degeneracy, ulong total) => 14 | Convert.ToDouble(degeneracy) / Convert.ToDouble(total); 15 | 16 | public static bool Decide(double probability) { 17 | double rand = Random_.NextDouble(); 18 | bool res = rand < probability; 19 | // Console.WriteLine($"Measurement with probability {probability} rendered {res}"); 20 | return res; 21 | } 22 | 23 | public static bool Decide(ulong degeneracy, ulong total) { 24 | return Decide(Probability(degeneracy, total)); 25 | } 26 | } 27 | 28 | public class QuantumHarmonic { 29 | public readonly Chessboard Board; 30 | public ulong Degeneracy; 31 | 32 | public QuantumHarmonic(Chessboard board, ulong degeneracy) { 33 | Board = board; 34 | Degeneracy = degeneracy; 35 | } 36 | 37 | public QuantumHarmonic Clone() { 38 | return new QuantumHarmonic(Board.Clone(), Degeneracy); 39 | } 40 | } 41 | 42 | public struct QuantumPiece { 43 | public readonly Piece? Piece; 44 | public readonly double Probability; 45 | 46 | public QuantumPiece(Piece? piece, double probability) { 47 | Piece = piece; 48 | Probability = probability; 49 | } 50 | } 51 | 52 | public class QuantumChessboard { 53 | private List Harmonics_ = new List(); 54 | private GameState GameState_ = GameState.Tie; 55 | 56 | public QuantumChessboard() { 57 | } 58 | 59 | public QuantumChessboard(List harmonics, GameState gameState) { 60 | Harmonics_ = harmonics; 61 | GameState_ = gameState; 62 | } 63 | 64 | public static QuantumChessboard StartingQuantumChessboard() { 65 | var res = new QuantumChessboard(); 66 | res.GameState_ = GameState.GameStillGoing; 67 | res.Harmonics_.Add(new QuantumHarmonic(Chessboard.StartingChessboard(), 1)); 68 | return res; 69 | } 70 | 71 | private ulong DegeneracyNormalization() { 72 | ulong res = 0; 73 | foreach (QuantumHarmonic harmonic in Harmonics_) 74 | res += harmonic.Degeneracy; 75 | return res; 76 | } 77 | 78 | private static ulong Gcd(ulong a, ulong b) { 79 | while (a != 0 && b != 0) { 80 | if (a > b) 81 | a %= b; 82 | else if (b > a) 83 | b %= a; 84 | else 85 | return a; 86 | } 87 | return a + b; 88 | } 89 | 90 | private void RenormalizeDegeneracies() { 91 | ulong gcd = 0; 92 | foreach (QuantumHarmonic harmonic in Harmonics_) 93 | gcd = Gcd(gcd, harmonic.Degeneracy); 94 | foreach (QuantumHarmonic harmonic in Harmonics_) 95 | harmonic.Degeneracy /= gcd; 96 | } 97 | 98 | private void RegroupHarmonics() { 99 | RemoveVanishing(); 100 | AssertionException.Assert(Harmonics_.Count > 0, "Empty quantum superposition found"); 101 | 102 | Harmonics_.Sort((a, b) => a.Board.GetHashCodeWithGameState().CompareTo(b.Board.GetHashCodeWithGameState())); 103 | var new_harmonics = new List(); 104 | 105 | QuantumHarmonic prev_harmonic = Harmonics_[0]; 106 | for (int i = 1; i < Harmonics_.Count; i++) { 107 | if (Harmonics_[i].Board == prev_harmonic.Board) { 108 | prev_harmonic.Degeneracy += Harmonics_[i].Degeneracy; 109 | } else { 110 | new_harmonics.Add(prev_harmonic); 111 | prev_harmonic = Harmonics_[i]; 112 | } 113 | } 114 | new_harmonics.Add(prev_harmonic); 115 | 116 | Harmonics_ = new_harmonics; 117 | Harmonics_.Sort((a, b) => b.Degeneracy.CompareTo(a.Degeneracy)); 118 | } 119 | 120 | private void RemoveVanishing() { 121 | FilterBy((h) => h.Degeneracy > 0); 122 | } 123 | 124 | 125 | private void FilterBy(Predicate pred) { 126 | var new_harmonics = new List(); 127 | foreach (QuantumHarmonic harmonic in Harmonics_) { 128 | if (pred(harmonic)) 129 | new_harmonics.Add(harmonic); 130 | } 131 | AssertionException.Assert(new_harmonics.Count > 0, "Filtered into empty quantum superposition"); 132 | Harmonics_ = new_harmonics; 133 | RenormalizeDegeneracies(); 134 | } 135 | 136 | private void PerformMeasurement(Position pos) { 137 | var piece_degeneracies = new Dictionary(); 138 | ulong overall_degeneracy = 0; 139 | 140 | foreach (QuantumHarmonic harmonic in Harmonics_) { 141 | Piece? square = harmonic.Board[pos]; 142 | if (square.HasValue) { 143 | if (!piece_degeneracies.ContainsKey(square.Value)) 144 | piece_degeneracies[square.Value] = 0; 145 | piece_degeneracies[square.Value] += harmonic.Degeneracy; 146 | overall_degeneracy += harmonic.Degeneracy; 147 | } 148 | } 149 | 150 | if (piece_degeneracies.Count <= 1) { 151 | // No measurement is needed 152 | return; 153 | } 154 | 155 | foreach (Piece piece in piece_degeneracies.Keys) { 156 | ulong degeneracy = piece_degeneracies[piece]; 157 | if (MeasurementUtils.Decide(degeneracy, overall_degeneracy)) { 158 | // Removing all the harmonics with another piece 159 | FilterBy((h) => h.Board[pos] == null || h.Board[pos] == piece); 160 | return; 161 | } else { 162 | overall_degeneracy -= degeneracy; 163 | } 164 | } 165 | AssertionException.Assert(false, "One of the pieces has to be chosen"); 166 | } 167 | 168 | private void PerformMeasurements() { 169 | RemoveVanishing(); 170 | for (int i = 0; i < 64; i++) { 171 | PerformMeasurement(Position.FromIndex(i)); 172 | } 173 | } 174 | 175 | private void PerformSpontaneousMeasurement() { 176 | ulong total_degeneracy = 0; 177 | foreach (QuantumHarmonic harmonic in Harmonics_) { 178 | total_degeneracy += harmonic.Degeneracy; 179 | } 180 | 181 | foreach (QuantumHarmonic harmonic in Harmonics_) { 182 | if (MeasurementUtils.Decide(harmonic.Degeneracy, total_degeneracy)) { 183 | var new_harmonics = new List(); 184 | new_harmonics.Add(harmonic); 185 | Harmonics_ = new_harmonics; 186 | return; 187 | } 188 | total_degeneracy -= harmonic.Degeneracy; 189 | } 190 | } 191 | 192 | private void UpdateGameState() { 193 | if (GameState_ != GameState.GameStillGoing) 194 | return; 195 | 196 | if (Harmonics_.All((h) => h.Board.GameState != GameState.GameStillGoing)) { 197 | ulong white_victory_degeneracy = Harmonics_ 198 | .Where((h) => h.Board.GameState == GameState.WhiteVictory) 199 | .Select((h) => h.Degeneracy) 200 | .Aggregate(0ul, (a, b) => a + b); 201 | 202 | ulong black_victory_degeneracy = Harmonics_ 203 | .Where((h) => h.Board.GameState == GameState.BlackVictory) 204 | .Select((h) => h.Degeneracy) 205 | .Aggregate(0ul, (a, b) => a + b); 206 | 207 | ulong tie_degeneracy = Harmonics_ 208 | .Where((h) => h.Board.GameState == GameState.Tie) 209 | .Select((h) => h.Degeneracy) 210 | .Aggregate(0ul, (a, b) => a + b); 211 | 212 | ulong total_degeneracy = white_victory_degeneracy + black_victory_degeneracy + tie_degeneracy; 213 | if (!MeasurementUtils.Decide(tie_degeneracy, total_degeneracy)) { 214 | total_degeneracy -= tie_degeneracy; 215 | if (MeasurementUtils.Decide(white_victory_degeneracy, total_degeneracy)) { 216 | GameState_ = GameState.WhiteVictory; 217 | } else { 218 | GameState_ = GameState.BlackVictory; 219 | } 220 | } else { 221 | GameState_ = GameState.Tie; 222 | } 223 | } 224 | } 225 | 226 | private void UpdateQuantumCheckboard() { 227 | PerformMeasurements(); 228 | if (Harmonics_.Count >= 1024) 229 | PerformSpontaneousMeasurement(); 230 | RegroupHarmonics(); 231 | RenormalizeDegeneracies(); 232 | UpdateGameState(); 233 | } 234 | 235 | public List Harmonics { 236 | get { return Harmonics_; } 237 | } 238 | 239 | public GameState GameState { 240 | get { return GameState_; } 241 | } 242 | 243 | public bool CheckOrdinaryMoveApplicable(OrdinaryMove move) { 244 | return Harmonics_.Any((h) => h.Board.GameState == GameState.GameStillGoing && 245 | h.Board.CheckOrdinaryMoveApplicable(move)); 246 | } 247 | 248 | public void ApplyOrdinaryMove(OrdinaryMove move) { 249 | bool applied = false; 250 | foreach (QuantumHarmonic harmonic in Harmonics_) { 251 | if (harmonic.Board.CheckOrdinaryMoveApplicable(move)) { 252 | harmonic.Board.ApplyOrdinaryMove(move); 253 | applied = true; 254 | } 255 | } 256 | UpdateQuantumCheckboard(); 257 | AssertionException.Assert(applied, "Ordinary move couldn't be applied on any harmonic"); 258 | } 259 | 260 | public bool CheckQuantumMoveApplicable(QuantumMove move) { 261 | return Harmonics_.Any((h) => h.Board.GameState == GameState.GameStillGoing && 262 | h.Board.CheckQuantumMoveApplicable(move)); 263 | } 264 | 265 | public void ApplyQuantumMove(QuantumMove move) { 266 | bool applied = false; 267 | var new_harmonics = new List(); 268 | foreach (QuantumHarmonic harmonic in Harmonics_) { 269 | if (harmonic.Board.CheckQuantumMoveApplicable(move)) { 270 | // Passing to the superposition of the original and new harmonics 271 | QuantumHarmonic new_harmonic = harmonic.Clone(); 272 | new_harmonic.Board.ApplyQuantumMove(move); 273 | new_harmonics.Add(harmonic); 274 | new_harmonics.Add(new_harmonic); 275 | applied = true; 276 | } else { 277 | // Keeping the original harmonic with degeneracy doubled 278 | harmonic.Degeneracy *= 2; 279 | new_harmonics.Add(harmonic); 280 | } 281 | } 282 | Harmonics_ = new_harmonics; 283 | UpdateQuantumCheckboard(); 284 | AssertionException.Assert(applied, "Quantum move couldn't be applied on any harmonic"); 285 | } 286 | 287 | public bool CheckCastleMoveApplicable(CastleMove move) { 288 | return Harmonics_.Any((h) => h.Board.GameState == GameState.GameStillGoing && 289 | h.Board.CheckCastleMoveApplicable(move)); 290 | } 291 | 292 | public void ApplyCastleMove(CastleMove move) { 293 | bool applied = false; 294 | foreach (QuantumHarmonic harmonic in Harmonics_) { 295 | if (harmonic.Board.CheckCastleMoveApplicable(move)) { 296 | harmonic.Board.ApplyCastleMove(move); 297 | applied = true; 298 | } 299 | } 300 | UpdateQuantumCheckboard(); 301 | AssertionException.Assert(applied, "Ordinary move couldn't be applied on any harmonic"); 302 | } 303 | 304 | public void RegisterVictory(Player player) { 305 | foreach (QuantumHarmonic harmonic in Harmonics_) { 306 | if (harmonic.Board.GameState == GameState.GameStillGoing) 307 | harmonic.Board.RegisterVictory(player); 308 | } 309 | UpdateGameState(); 310 | } 311 | 312 | public void RegisterTie() { 313 | foreach (QuantumHarmonic harmonic in Harmonics_) { 314 | if (harmonic.Board.GameState == GameState.GameStillGoing) { 315 | harmonic.Board.RegisterTie(); 316 | } 317 | } 318 | UpdateGameState(); 319 | } 320 | 321 | public QuantumPiece GetQuantumPiece(Position pos) { 322 | Piece? piece = null; 323 | ulong filled = 0, empty = 0; 324 | foreach (QuantumHarmonic harmonic in Harmonics_) { 325 | Piece? classical = harmonic.Board[pos]; 326 | if (classical.HasValue) { 327 | AssertionException.Assert(piece == null || piece == classical, 328 | $"The square {pos} appears in a superposition of two pieces"); 329 | piece = classical; 330 | filled += harmonic.Degeneracy; 331 | } else { 332 | empty += harmonic.Degeneracy; 333 | } 334 | } 335 | if (piece.HasValue) { 336 | return new QuantumPiece(piece, MeasurementUtils.Probability(filled, filled + empty)); 337 | } else { 338 | return new QuantumPiece(null, 1.0); 339 | } 340 | } 341 | } 342 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrulyQuantumChess 2 | A game of truly quantum chess, with interference, entanglement, etc. 3 | 4 | You can play online in your browser on https://truly-quantum-chess.sloppy.zone. 5 | 6 | There is also a mirror of the game on https://quantum-chess.mateowang.dev if the above link is down. 7 | 8 | Alternatively, download, build & run your local mongodb and webapp server. To do so simply run: 9 | ``` 10 | docker-compose build 11 | docker-compose up 12 | ``` 13 | 14 | For those people who have reached me asking if they can implement their own features: the source code is licensed under WTFPL, 15 | which pretty much means that you can do what the f*ck you want with it :) 16 | 17 | However, if you decide to add a feature or two, please consider sending me a pull request so that I can push your changes to prod. 18 | -------------------------------------------------------------------------------- /TrulyQuantumChess.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kernel", "Kernel\Kernel.csproj", "{B4B23590-B467-4F12-929B-313A2025613E}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vanilla", "Vanilla\Vanilla.csproj", "{AE55A203-1CFD-44B8-88DE-BBB9F58A90B2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "WebApp\WebApp.csproj", "{31354668-3E37-46C5-BE82-66F09A09D9BD}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {B4B23590-B467-4F12-929B-313A2025613E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {B4B23590-B467-4F12-929B-313A2025613E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {B4B23590-B467-4F12-929B-313A2025613E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {B4B23590-B467-4F12-929B-313A2025613E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AE55A203-1CFD-44B8-88DE-BBB9F58A90B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AE55A203-1CFD-44B8-88DE-BBB9F58A90B2}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AE55A203-1CFD-44B8-88DE-BBB9F58A90B2}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AE55A203-1CFD-44B8-88DE-BBB9F58A90B2}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {31354668-3E37-46C5-BE82-66F09A09D9BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {31354668-3E37-46C5-BE82-66F09A09D9BD}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {31354668-3E37-46C5-BE82-66F09A09D9BD}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {31354668-3E37-46C5-BE82-66F09A09D9BD}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(MonoDevelopProperties) = preSolution 30 | Policies = $0 31 | $0.DotNetNamingPolicy = $1 32 | $1.DirectoryNamespaceAssociation = None 33 | $1.ResourceNamePolicy = FileFormatDefault 34 | $0.TextStylePolicy = $2 35 | $2.inheritsSet = null 36 | $2.scope = text/x-csharp 37 | $0.CSharpFormattingPolicy = $3 38 | $3.IndentSwitchSection = True 39 | $3.NewLinesForBracesInTypes = False 40 | $3.NewLinesForBracesInMethods = False 41 | $3.NewLineForMembersInObjectInit = True 42 | $3.NewLineForMembersInAnonymousTypes = True 43 | $3.NewLineForClausesInQuery = True 44 | $3.SpacingAfterMethodDeclarationName = False 45 | $3.SpaceAfterMethodCallName = False 46 | $3.SpaceAfterCast = True 47 | $3.SpaceBeforeOpenSquareBracket = False 48 | $3.inheritsSet = Mono 49 | $3.inheritsScope = text/x-csharp 50 | $3.scope = text/x-csharp 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /Vanilla/ConsoleIO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | using TrulyQuantumChess.Kernel.Errors; 6 | using TrulyQuantumChess.Kernel.Moves; 7 | using TrulyQuantumChess.Kernel.Chess; 8 | using TrulyQuantumChess.Kernel.Quantize; 9 | using TrulyQuantumChess.Kernel.Engine; 10 | 11 | namespace TrulyQuantumChess.Vanilla.ConsoleIO { 12 | public static class Input { 13 | public static QuantumChessMove ReadMove(QuantumChessEngine engine) { 14 | switch (engine.ActivePlayer) { 15 | case Player.White: 16 | Console.Write("white> "); 17 | break; 18 | case Player.Black: 19 | Console.Write("black> "); 20 | break; 21 | } 22 | 23 | string move_str = Console.ReadLine(); 24 | 25 | string capitulate_move_regex = @"^(quit|exit|capitulate)$"; 26 | string agree_to_tie_move_regex = @"^tie$"; 27 | string ordinary_move_regex = @"^([A-Za-z][1-8])\s*([A-Za-z][1-8])$"; 28 | string quantum_move_regex = @"^(?:q|Q|quantum)\s+([A-Za-z][1-8])\s*((?:[A-Za-z][1-8])?)\s*([A-Za-z][1-8])$"; 29 | string castle_move_regex = @"^castle (left|right)$"; 30 | 31 | Match ordinary_match = Regex.Match(move_str, ordinary_move_regex); 32 | Match quantum_match = Regex.Match(move_str, quantum_move_regex); 33 | Match castle_match = Regex.Match(move_str, castle_move_regex); 34 | 35 | if (Regex.IsMatch(move_str, capitulate_move_regex)) { 36 | return new CapitulateMove(engine.ActivePlayer); 37 | } else if (Regex.IsMatch(move_str, agree_to_tie_move_regex)) { 38 | return new AgreeToTieMove(engine.ActivePlayer); 39 | } else if (ordinary_match.Success) { 40 | Position source = Position.Parse(ordinary_match.Groups[1].Captures[0].Value); 41 | Position target = Position.Parse(ordinary_match.Groups[2].Captures[0].Value); 42 | QuantumPiece qpiece = engine.QuantumChessboard.GetQuantumPiece(source); 43 | if (qpiece.Piece.HasValue) 44 | return new OrdinaryMove(qpiece.Piece.Value, source, target); 45 | else 46 | throw new MoveParseException($"No piece found at {source}"); 47 | } else if (quantum_match.Success) { 48 | Position source = Position.Parse(quantum_match.Groups[1].Captures[0].Value); 49 | Position? middle = null; 50 | if (quantum_match.Groups[2].Captures[0].Length > 0) 51 | middle = Position.Parse(quantum_match.Groups[2].Captures[0].Value); 52 | Position target = Position.Parse(quantum_match.Groups[3].Captures[0].Value); 53 | QuantumPiece qpiece = engine.QuantumChessboard.GetQuantumPiece(source); 54 | if (qpiece.Piece.HasValue) 55 | return new QuantumMove(qpiece.Piece.Value, source, middle, target); 56 | else 57 | throw new MoveParseException($"No piece found at {source}"); 58 | } else if (castle_match.Success) { 59 | string castle_type_str = castle_match.Groups[1].Captures[0].Value; 60 | CastleType castle_type; 61 | if (castle_type_str == "left") { 62 | castle_type = CastleType.Left; 63 | } else if (castle_type_str == "right") { 64 | castle_type = CastleType.Right; 65 | } else { 66 | throw new MoveParseException($"Unsupported castle type: {castle_type_str}"); 67 | } 68 | return new CastleMove(engine.ActivePlayer, castle_type); 69 | } else { 70 | throw new MoveParseException("Unable to parse move"); 71 | } 72 | } 73 | 74 | public static QuantumChessMove ReadMoveRepeated(QuantumChessEngine engine) { 75 | for (;;) { 76 | try { 77 | return ReadMove(engine); 78 | } catch (MoveParseException e) { 79 | Console.WriteLine(e.Message); 80 | } 81 | } 82 | } 83 | } 84 | 85 | public static class Output { 86 | private static char GetUnicodeChar(Piece? piece) { 87 | if (piece.HasValue) { 88 | switch (piece.Value.Player) { 89 | case Player.White: 90 | switch (piece.Value.PieceType) { 91 | case PieceType.Pawn: return '\u2659'; 92 | case PieceType.Knight: return '\u2658'; 93 | case PieceType.Bishop: return '\u2657'; 94 | case PieceType.Rook: return '\u2656'; 95 | case PieceType.Queen: return '\u2655'; 96 | case PieceType.King: return '\u2654'; 97 | default: throw new AssertionException($"Unsupported piece type: {piece.Value.PieceType}"); 98 | } 99 | 100 | case Player.Black: 101 | switch (piece.Value.PieceType) { 102 | case PieceType.Pawn: return '\u265F'; 103 | case PieceType.Knight: return '\u265E'; 104 | case PieceType.Bishop: return '\u265D'; 105 | case PieceType.Rook: return '\u265C'; 106 | case PieceType.Queen: return '\u265B'; 107 | case PieceType.King: return '\u265A'; 108 | default: throw new AssertionException($"Unsupported piece type: {piece.Value.PieceType}"); 109 | } 110 | 111 | default: 112 | throw new AssertionException($"Unsupported player: {piece.Value.Player}"); 113 | } 114 | } else { 115 | return '.'; 116 | } 117 | } 118 | 119 | private static void DisplayHarmonics(List harmonics) { 120 | for (int y = 7; y >= 0; y--) { 121 | foreach (QuantumHarmonic harmonic in harmonics) { 122 | Console.Write($"{y+1} "); 123 | for (int x = 0; x < 8; x++) { 124 | Console.Write(GetUnicodeChar(harmonic.Board[x, y])); 125 | Console.Write(" "); 126 | } 127 | Console.Write(" "); 128 | } 129 | Console.WriteLine(); 130 | } 131 | foreach (QuantumHarmonic harmonic in harmonics) { 132 | Console.Write(" a b c d e f g h "); 133 | } 134 | Console.WriteLine(); 135 | foreach (QuantumHarmonic harmonic in harmonics) { 136 | string win_str; 137 | switch (harmonic.Board.GameState) { 138 | case GameState.GameStillGoing: 139 | win_str = " "; 140 | break; 141 | case GameState.WhiteVictory: 142 | win_str = "[w]"; 143 | break; 144 | case GameState.BlackVictory: 145 | win_str = "[b]"; 146 | break; 147 | case GameState.Tie: 148 | win_str = "[=]"; 149 | break; 150 | default: 151 | throw new AssertionException($"Unsupported game state: {harmonic.Board.GameState}"); 152 | } 153 | string deg = harmonic.Degeneracy == 1 ? " " : $"x{harmonic.Degeneracy}"; 154 | Console.Write($" {win_str} {deg}"); 155 | int expected_ln = 8; 156 | int actual_ln = harmonic.Degeneracy.ToString().Length; 157 | for (int i = actual_ln; i != expected_ln; i++) 158 | Console.Write(" "); 159 | Console.Write(" "); 160 | } 161 | Console.WriteLine(); 162 | } 163 | 164 | public static void DisplayQuantumChessboard(QuantumChessboard qboard, int cols) { 165 | int n = qboard.Harmonics.Count; 166 | int rows = (n + cols - 1) / cols; 167 | for (int row = 0; row < rows; row++) { 168 | var harmonics = new List(); 169 | for (int col = 0; col < cols; col++) { 170 | int ind = row * cols + col; 171 | if (ind < n) 172 | harmonics.Add(qboard.Harmonics[ind]); 173 | } 174 | DisplayHarmonics(harmonics); 175 | if (row != rows - 1) 176 | Console.WriteLine(); 177 | } 178 | } 179 | } 180 | } -------------------------------------------------------------------------------- /Vanilla/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using TrulyQuantumChess.Kernel.Errors; 4 | using TrulyQuantumChess.Kernel.Moves; 5 | using TrulyQuantumChess.Kernel.Chess; 6 | using TrulyQuantumChess.Kernel.Quantize; 7 | using TrulyQuantumChess.Kernel.Engine; 8 | using TrulyQuantumChess.Vanilla.ConsoleIO; 9 | 10 | namespace TrulyQuantumChess.Vanilla { 11 | public static class Program { 12 | public static void Main(string[] args) { 13 | int cols = 8; 14 | if (args.Length >= 1) { 15 | cols = Convert.ToInt32(args[0]); 16 | } 17 | 18 | var engine = new QuantumChessEngine(); 19 | for (;;) { 20 | Output.DisplayQuantumChessboard(engine.QuantumChessboard, cols); 21 | Console.WriteLine(); 22 | 23 | for (;;) { 24 | try { 25 | QuantumChessMove move = Input.ReadMoveRepeated(engine); 26 | engine.Submit(move); 27 | break; 28 | } catch (MoveParseException e) { 29 | Console.WriteLine(e.Message); 30 | } catch (MoveProcessException e) { 31 | Console.WriteLine(e.Message); 32 | } 33 | } 34 | 35 | switch (engine.GameState) { 36 | case GameState.WhiteVictory: 37 | Console.WriteLine(); 38 | Console.WriteLine("White victory!"); 39 | return; 40 | 41 | case GameState.BlackVictory: 42 | Console.WriteLine(); 43 | Console.WriteLine("Black victory!"); 44 | return; 45 | 46 | case GameState.Tie: 47 | Console.WriteLine(); 48 | Console.WriteLine("Players are tied!"); 49 | return; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Vanilla/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("Vanilla")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /Vanilla/Vanilla.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {AE55A203-1CFD-44B8-88DE-BBB9F58A90B2} 7 | Exe 8 | Vanilla 9 | Vanilla 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | true 21 | 22 | 23 | true 24 | bin\Release 25 | prompt 26 | 4 27 | true 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {B4B23590-B467-4F12-929B-313A2025613E} 40 | Kernel 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /WebApp/ApiModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Web.Script.Serialization; 9 | 10 | using TrulyQuantumChess.Kernel.Errors; 11 | using TrulyQuantumChess.Kernel.Moves; 12 | using TrulyQuantumChess.Kernel.Chess; 13 | using TrulyQuantumChess.Kernel.Quantize; 14 | using TrulyQuantumChess.Kernel.Engine; 15 | 16 | using Nancy; 17 | using Nancy.ModelBinding; 18 | 19 | namespace TrulyQuantumChess.WebApp { 20 | public class ApiModule : NancyModule { 21 | public ApiModule() 22 | : base(WebAppConfig.Instance.Prefix + "/api") 23 | { 24 | Get["/new_game"] = (args) => NewGame(args, new CancellationToken()).Result; 25 | Get["/game_info"] = (args) => GameInfo(args, new CancellationToken()).Result; 26 | Post["/submit_move"] = (args) => SubmitMove(args, new CancellationToken()).Result; 27 | 28 | // This doesn't work yet, because we would have to obtain exclusive locks for each chessboard 29 | // Although, we probably have to obtain them anyway... Future will tell. 30 | // Get["/new_game", true] = NewGame; 31 | // Get["/game_info", true] = GameInfo; 32 | // Post["/submit_move", true] = SubmitMove; 33 | } 34 | 35 | private struct RecaptchaResponseModel { 36 | public bool success; 37 | public string challenge_ts; 38 | public string hostname; 39 | public string[] error_codes; 40 | } 41 | 42 | private static readonly JavaScriptSerializer Serializer_ = 43 | new JavaScriptSerializer(); 44 | 45 | private async Task ValidateCaptchaResponse(string captcha_response) { 46 | using (var client = new HttpClient()) { 47 | var values = new Dictionary { 48 | { "secret", WebAppConfig.Instance.Captcha.Secret }, 49 | { "response", captcha_response } 50 | }; 51 | var content = new FormUrlEncodedContent(values); 52 | var response = await client.PostAsync("https://www.google.com/recaptcha/api/siteverify", content); 53 | var response_string = await response.Content.ReadAsStringAsync(); 54 | response_string = response_string.Replace("error-codes", "error_codes"); // I know this is a hack. I don't care. 55 | RecaptchaResponseModel model = Serializer_.Deserialize(response_string); 56 | return model.success; 57 | } 58 | } 59 | 60 | private async Task NewGame(dynamic args, CancellationToken cancellation_token) { 61 | if (WebAppConfig.Instance.Captcha.Enabled) { 62 | bool captcha_validated = await ValidateCaptchaResponse(Request.Query["captcha_response"]); 63 | if (!captcha_validated) { 64 | return 500; 65 | } 66 | } 67 | var engine = new QuantumChessEngine(); 68 | string game_id = await WebAppManagers.DatabaseManager.InsertEngine(engine); 69 | var new_game_response = new Model.NewGameResponse() { 70 | GameId = game_id 71 | }; 72 | return Response.AsJson(new_game_response); 73 | } 74 | 75 | private async Task GameInfo(dynamic args, CancellationToken cancellation_token) { 76 | string game_id = Request.Query["gameId"]; 77 | QuantumChessEngine engine = await WebAppManagers.DatabaseManager.RequestEngine(game_id); 78 | 79 | var response = new Model.InfoResponse(); 80 | response.ActivePlayer = PlayerUtils.ToString(engine.ActivePlayer); 81 | response.GameState = GameStateUtils.ToString(engine.GameState); 82 | response.Squares = new Dictionary(); 83 | for (int i = 0; i < 64; i++) { 84 | Position pos = Position.FromIndex(i); 85 | QuantumPiece qpiece = engine.QuantumChessboard.GetQuantumPiece(pos); 86 | if (qpiece.Piece.HasValue) { 87 | response.Squares[pos.ToString()] = new Model.InfoResponse.SquareEncoded() { 88 | Player = PlayerUtils.ToString(qpiece.Piece.Value.Player), 89 | Piece = PieceTypeUtils.ToString(qpiece.Piece.Value.PieceType), 90 | Probability = qpiece.Probability 91 | }; 92 | } else { 93 | response.Squares[pos.ToString()] = null; 94 | } 95 | } 96 | 97 | response.LastMovePositions = engine.LastMovePositions.Select((pos) => pos.ToString().ToLower()).ToArray(); 98 | 99 | return Response.AsJson(response); 100 | } 101 | 102 | private async Task SubmitMove(dynamic args, CancellationToken cancellation_token) { 103 | Model.MoveRequest request = this.Bind(); 104 | try { 105 | QuantumChessEngine engine = await WebAppManagers.DatabaseManager.RequestEngine(request.GameId); 106 | QuantumChessMove move = request.Parse(engine); 107 | engine.Submit(move); 108 | await WebAppManagers.DatabaseManager.UpdateEngine(request.GameId, engine); 109 | return new { 110 | Success = true 111 | }; 112 | } catch (QuantumChessException e) { 113 | return new { 114 | Success = false, 115 | Message = e.Message 116 | }; 117 | } 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /WebApp/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Nancy; 4 | using Nancy.Conventions; 5 | 6 | namespace TrulyQuantumChess.WebApp { 7 | public class Bootstrapper : DefaultNancyBootstrapper { 8 | protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines) { 9 | base.ApplicationStartup(container, pipelines); 10 | 11 | Conventions.ViewLocationConventions.Clear(); 12 | Conventions.ViewLocationConventions.Add((viewName, model, context) => { 13 | return String.Concat("Templates/", viewName); 14 | }); 15 | Conventions.StaticContentsConventions.Add( 16 | StaticContentConventionBuilder.AddDirectory(WebAppConfig.Instance.Prefix + "/content", "/Content") 17 | ); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /WebApp/Content/index.js: -------------------------------------------------------------------------------- 1 | var g_captcha_response = ""; 2 | 3 | function captcha_callback(captcha_response) { 4 | g_captcha_response = captcha_response; 5 | $("#launch_new_game_btn").prop("disabled", false); 6 | } 7 | 8 | $(function() { 9 | $("#launch_new_game_btn").click(function() { 10 | $.get(prefix + "/api/new_game", 11 | { 12 | "captcha_response": g_captcha_response 13 | }, 14 | function(data) { 15 | var gameId = data.gameId; 16 | window.location = prefix + "/play?gameId=" + gameId; 17 | }); 18 | }); 19 | }); -------------------------------------------------------------------------------- /WebApp/Content/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/logo.png -------------------------------------------------------------------------------- /WebApp/Content/logo_high_resolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/logo_high_resolution.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_bishop_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_bishop_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_bishop_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_bishop_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_king_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_king_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_king_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_king_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_knight_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_knight_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_knight_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_knight_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_pawn_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_pawn_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_pawn_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_pawn_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_queen_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_queen_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_queen_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_queen_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_rook_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_rook_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/black_rook_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/black_rook_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_bishop_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_bishop_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_bishop_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_bishop_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_king_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_king_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_king_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_king_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_knight_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_knight_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_knight_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_knight_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_pawn_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_pawn_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_pawn_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_pawn_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_queen_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_queen_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_queen_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_queen_dead.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_rook_alive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_rook_alive.png -------------------------------------------------------------------------------- /WebApp/Content/pieces/simple/white_rook_dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caphindsight/TrulyQuantumChess/041b6dff3ceec1415cb086c38c11ec39d4e4eb60/WebApp/Content/pieces/simple/white_rook_dead.png -------------------------------------------------------------------------------- /WebApp/Content/play.js: -------------------------------------------------------------------------------- 1 | var move = { 2 | move_type: "", 3 | source: "", 4 | middle: "", 5 | target: "" 6 | }; 7 | 8 | function reset_move() { 9 | move.move_type = ""; 10 | move.source = ""; 11 | move.middle = ""; 12 | move.target = ""; 13 | $(".square-selected").removeClass("square-selected"); 14 | $(".square-selected-quantum").removeClass("square-selected-quantum"); 15 | } 16 | 17 | var prev_chessboard = null; 18 | var active_player = null; 19 | 20 | function squares_equal(a, b) { 21 | if (a == null && b == null) { 22 | return true; 23 | } else if (a != null && b == null) { 24 | return false; 25 | } else if (a == null && b != null) { 26 | return false; 27 | } else { 28 | if (a.player != b.player) 29 | return false; 30 | if (a.piece != b.piece) 31 | return false; 32 | if (a.probability != b.probability) 33 | return false; 34 | return true; 35 | } 36 | } 37 | 38 | function submit_move() { 39 | if (move.middle == move.source || move.middle == move.target) 40 | move.middle = ""; 41 | $.post(prefix + "/api/submit_move", { 42 | "gameId": gameId, 43 | "moveType": move.move_type, 44 | "source": move.source, 45 | "middle": move.middle, 46 | "target": move.target 47 | }, function(data) { 48 | if (!data.success) { 49 | $("#error_message").text(data.message); 50 | } else { 51 | $("#error_message").text(""); 52 | } 53 | update_chessboard(); 54 | }).fail(function(err) { 55 | alert("Move failed: " + err); 56 | }).always(function() { 57 | reset_move(); 58 | }); 59 | } 60 | 61 | function real_width(width, probability, piece_ratio) { 62 | var x = width * (1.0 - piece_ratio) / 2; 63 | return x + width * piece_ratio * probability; 64 | } 65 | 66 | function draw(canvas, data) { 67 | var ctx = canvas.getContext("2d"); 68 | ctx.clearRect(0, 0, canvas.width, canvas.height); 69 | if (data != null) { 70 | var r = pieces_width_ratios[data.piece]; 71 | var img_alive = new Image(); 72 | img_alive.src = prefix + "/content/pieces/" + pieces_collection + "/" + data.player + "_" + data.piece + "_alive.png"; 73 | img_alive.onload = function() { 74 | ctx.drawImage(img_alive, 0, 0, real_width(img_alive.width, data.probability, r), img_alive.height, 75 | 0, 0, real_width(canvas.width, data.probability, r), canvas.height); 76 | }; 77 | 78 | var img_dead = new Image(); 79 | img_dead.src = prefix + "/content/pieces/" + pieces_collection + "/" + data.player + "_" + data.piece + "_dead.png"; 80 | img_dead.onload = function() { 81 | ctx.drawImage(img_dead, real_width(img_dead.width, data.probability, r), 0, real_width(img_dead.width, 1.0 - data.probability, r), img_dead.height, 82 | real_width(canvas.width, data.probability, r), 0, real_width(canvas.width, 1.0 - data.probability, r), canvas.height); 83 | }; 84 | } 85 | } 86 | 87 | function update_chessboard() { 88 | var board = $.get(prefix + "/api/game_info", {"gameId": gameId}, function(data) { 89 | if (data.gameState != "game_still_going") { 90 | $(".chessboard").addClass("game-over"); 91 | var message = "???"; 92 | if (data.gameState == "white_victory") { 93 | message = "White victory!"; 94 | } else if (data.gameState == "black_victory") { 95 | message = "Black victory!"; 96 | } else if (data.gameState == "tie") { 97 | message = "Players are tied!"; 98 | } else { 99 | message = "Unknown game state: " + data.gameState; 100 | } 101 | $("#game_state").html("

" + message + "

"); 102 | $("#new_game").show(); 103 | } 104 | active_player = data.activePlayer; 105 | $("#active_player").text(data.activePlayer); 106 | for (var pos in data.squares) { 107 | var pos_in_last_move = false; 108 | for (var i in data.lastMovePositions) { 109 | if (data.lastMovePositions[i] == pos) { 110 | pos_in_last_move = true; 111 | break; 112 | } 113 | } 114 | if (pos_in_last_move) { 115 | $("#sq-" + pos).addClass("square-last-move"); 116 | } else { 117 | $("#sq-" + pos).removeClass("square-last-move"); 118 | } 119 | if (prev_chessboard == null || !squares_equal(prev_chessboard.squares[pos], data.squares[pos])) 120 | draw($("#sq-" + pos)[0], data.squares[pos]); 121 | } 122 | prev_chessboard = data; 123 | }); 124 | } 125 | 126 | $(function() { 127 | update_chessboard(); 128 | setInterval(update_chessboard, 5000); 129 | var rows = "abcdefgh"; 130 | 131 | // Setting up triggers 132 | for (var x_i in rows) { 133 | var x = rows[x_i]; 134 | for (var y = 1; y <= 8; y++) { 135 | $("#sq-" + x + y).click(function(e) { 136 | var $elem = $(e.target); 137 | var pos = e.target.id[3] + e.target.id[4]; 138 | 139 | if (move.source == "") { 140 | // Selecting source square 141 | if (prev_chessboard == null || prev_chessboard.squares[pos].player != active_player) { 142 | $("#error_message").text("Active player is " + active_player + "!"); 143 | return; 144 | } 145 | move.move_type = "ordinary"; 146 | move.source = pos; 147 | $elem.addClass("square-selected"); 148 | } else if (move.source == pos && move.move_type == "ordinary") { 149 | // Making move quantum 150 | move.move_type = "quantum"; 151 | $elem.removeClass("square-selected"); 152 | $elem.addClass("square-selected-quantum"); 153 | } else if (move.source == pos && move.move_type == "quantum") { 154 | // Resetting move 155 | reset_move(); 156 | } else if (move.move_type == "quantum" && move.middle == "") { 157 | move.middle = pos; 158 | $elem.addClass("square-selected"); 159 | } else { 160 | move.target = pos; 161 | submit_move(); 162 | } 163 | }); 164 | } 165 | } 166 | 167 | $("#capitulate_btn").click(function() { 168 | move.move_type = "capitulate"; 169 | submit_move(); 170 | }); 171 | 172 | $("#castle_left_btn").click(function() { 173 | move.move_type = "castle_left"; 174 | submit_move(); 175 | }); 176 | 177 | $("#castle_right_btn").click(function() { 178 | move.move_type = "castle_right"; 179 | submit_move(); 180 | }); 181 | }); -------------------------------------------------------------------------------- /WebApp/Content/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 2rem; 3 | padding-bottom: 2rem; 4 | } 5 | 6 | .red { 7 | color: red; 8 | } 9 | 10 | .centered { 11 | text-align: center; 12 | } 13 | 14 | .tip { 15 | padding-top: 7px; 16 | padding-bottom: 7px; 17 | } 18 | 19 | .center-of-screen { 20 | height: 34px; 21 | width: 150px; 22 | position: fixed; 23 | top: 50%; 24 | left: 50%; 25 | margin-top: -17px; 26 | margin-left: -75px; 27 | } 28 | 29 | .chessboard-container { 30 | padding-top: 10px; 31 | } 32 | 33 | table.chessboard { 34 | border: 2px solid black; 35 | border-collapse: collapse; 36 | table-layout: fixed; 37 | width: 320px; 38 | } 39 | 40 | table.chessboard.game-over { 41 | opacity: 0.3; 42 | } 43 | 44 | table.chessboard td { 45 | overflow: hidden; 46 | width: 40px; 47 | height: 40px; 48 | padding: 0px; 49 | margin: 0px; 50 | } 51 | 52 | table.chessboard td.coord { 53 | overflow: hidden; 54 | width: 40px; 55 | height: 40px; 56 | padding: 0px; 57 | margin: 0px; 58 | text-align: center; 59 | vertical-align: middle; 60 | } 61 | 62 | table.chessboard td canvas { 63 | width: 40px; 64 | height: 40px; 65 | vertical-align: bottom; 66 | } 67 | 68 | .square-last-move:not(.square-selected):not(.square-selected-quantum) { 69 | background-color: #99ccff; 70 | } 71 | 72 | .square-black:not(.square-selected):not(.square-selected-quantum):not(.square-last-move) { 73 | background-color: #d3d3d3; 74 | } 75 | 76 | .square-white:not(.square-selected):not(.square-selected-quantum):not(.square-last-move) { 77 | background-color: #ffffe0; 78 | } 79 | 80 | .square-selected { 81 | background-color: #99ff99; 82 | } 83 | 84 | .square-selected-quantum { 85 | background-color: #e600e6; 86 | } 87 | -------------------------------------------------------------------------------- /WebApp/Database.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | using TrulyQuantumChess.Kernel.Chess; 6 | using TrulyQuantumChess.Kernel.Quantize; 7 | using TrulyQuantumChess.Kernel.Engine; 8 | 9 | using MongoDB.Bson; 10 | using MongoDB.Driver; 11 | 12 | 13 | namespace TrulyQuantumChess.WebApp { 14 | public class MongoException : Exception { 15 | public MongoException(string message) 16 | : base(message) 17 | {} 18 | } 19 | 20 | public class Game { 21 | public ObjectId Id; 22 | public QuantumChessEngine Engine; 23 | 24 | public Game(ObjectId id, QuantumChessEngine engine) { 25 | Id = id; 26 | Engine = engine; 27 | } 28 | } 29 | 30 | public struct GameInfo { 31 | public string GameId; 32 | 33 | public TimeSpan LastModification; 34 | public string LastModificationString { 35 | get { return TimeSpanString(LastModification); } 36 | } 37 | 38 | public TimeSpan CreationTime; 39 | public string CreationTimeString { 40 | get { return TimeSpanString(CreationTime); } 41 | } 42 | 43 | public GameState GameState; 44 | public string GameStateString { 45 | get { return GameStateUtils.ToString(GameState); } 46 | } 47 | 48 | private static string TimeSpanString(TimeSpan time_span) { 49 | if (time_span <= TimeSpan.FromSeconds(90)) 50 | return $"{Convert.ToInt32(time_span.TotalSeconds)} secs"; 51 | else if (time_span <= TimeSpan.FromMinutes(90)) 52 | return $"{Convert.ToInt32(time_span.TotalMinutes)} mins"; 53 | else if (time_span <= TimeSpan.FromHours(90)) 54 | return $"{Convert.ToInt32(time_span.TotalHours)} hours"; 55 | else 56 | return $"{Convert.ToInt32(time_span.TotalDays)} days"; 57 | } 58 | 59 | } 60 | 61 | public interface IDatabaseManager { 62 | Task> RequestActiveGames(); 63 | Task RequestEngine(string gameId); 64 | Task InsertEngine(QuantumChessEngine engine); 65 | Task UpdateEngine(string gameId, QuantumChessEngine engine); 66 | Task CleanOldEntities(DateTime modification_instant); 67 | } 68 | 69 | public class MongoManager : IDatabaseManager { 70 | private readonly IMongoClient Client_; 71 | private readonly IMongoDatabase Database_; 72 | private IMongoCollection ActiveGames_; 73 | 74 | public MongoManager() { 75 | Client_ = new MongoClient(WebAppConfig.Instance.Mongo.ConnectionString); 76 | Database_ = Client_.GetDatabase(WebAppConfig.Instance.Mongo.Database); 77 | Database_.RunCommandAsync((Command)"{ping:1}").Wait(); 78 | Console.WriteLine("Established connection to mongo db"); 79 | ActiveGames_ = Database_.GetCollection("active_games"); 80 | } 81 | 82 | private static readonly BsonDocument EmptyFilter_ = 83 | new BsonDocument(); 84 | 85 | private static BsonDocument FilterById(ObjectId id) { 86 | var filter = new BsonDocument(); 87 | filter.Set("_id", id); 88 | return filter; 89 | } 90 | 91 | private static BsonDocument FilterById(string id) { 92 | return FilterById(new ObjectId(id)); 93 | } 94 | 95 | public async Task> RequestActiveGames() { 96 | var res = new List(); 97 | using (var cursor = await ActiveGames_.FindAsync(EmptyFilter_)) { 98 | while (await cursor.MoveNextAsync()) { 99 | var batch = cursor.Current; 100 | foreach (var document in batch) { 101 | res.Add(new GameInfo() { 102 | GameId = document["_id"].AsObjectId.ToString(), 103 | LastModification = DateTime.Now - document["last_modification_time"].ToLocalTime(), 104 | CreationTime = DateTime.Now - document["creation_time"].ToLocalTime(), 105 | GameState = GameStateUtils.FromString(document["game_state"].AsString), 106 | }); 107 | } 108 | } 109 | } 110 | res.Sort((x, y) => x.LastModification.CompareTo(y.LastModification)); 111 | return res; 112 | } 113 | 114 | public async Task RequestEngine(string gameId) { 115 | using (var cursor = await ActiveGames_.FindAsync(FilterById(gameId))) { 116 | while (await cursor.MoveNextAsync()) { 117 | var batch = cursor.Current; 118 | foreach (var document in batch) { 119 | return ChessBsonSerializationUtils.Deserialize(document).Engine; 120 | } 121 | } 122 | } 123 | throw new MongoException($"No game with gameId \"{gameId}\" found"); 124 | } 125 | 126 | public async Task InsertEngine(QuantumChessEngine engine) { 127 | var game = new Game(ObjectId.GenerateNewId(), engine); 128 | await ActiveGames_.InsertOneAsync(ChessBsonSerializationUtils.Serialize(game)); 129 | return game.Id.ToString(); 130 | } 131 | 132 | public async Task UpdateEngine(string gameId, QuantumChessEngine engine) { 133 | var replacement = new Game(new ObjectId(gameId), engine); 134 | await ActiveGames_.FindOneAndReplaceAsync(FilterById(gameId), ChessBsonSerializationUtils.Serialize(replacement)); 135 | } 136 | 137 | public async Task CleanOldEntities(DateTime modification_instant) { 138 | using (var cursor = await ActiveGames_.FindAsync(EmptyFilter_)) { 139 | while (await cursor.MoveNextAsync()) { 140 | var batch = cursor.Current; 141 | foreach (var document in batch) { 142 | DateTime last_access_time = document["last_modification_time"].ToLocalTime(); 143 | if (last_access_time < modification_instant) { 144 | await ActiveGames_.DeleteOneAsync(FilterById(document["_id"].AsObjectId)); 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | public static class ChessBsonSerializationUtils { 153 | public static BsonDocument Serialize(Game game) { 154 | var document = new BsonDocument(); 155 | document.Set("_id", new BsonObjectId(game.Id)); 156 | document.Set("active_player", PlayerUtils.ToString(game.Engine.ActivePlayer)); 157 | document.Set("game_state", GameStateUtils.ToString(game.Engine.QuantumChessboard.GameState)); 158 | document.Set("creation_time", game.Engine.CreationTime); 159 | document.Set("last_modification_time", DateTime.Now); 160 | 161 | var bson_harmonics = new BsonArray(); 162 | foreach (QuantumHarmonic harmonic in game.Engine.QuantumChessboard.Harmonics) { 163 | var bson_harmonic = new BsonDocument(); 164 | bson_harmonic.Set("harmonic_state", GameStateUtils.ToString(harmonic.Board.GameState)); 165 | bson_harmonic.Set("degeneracy", Convert.ToInt64(harmonic.Degeneracy)); 166 | 167 | var bson_chessboard = new BsonArray(); 168 | for (int i = 0; i < 64; i++) { 169 | Piece? piece = harmonic.Board[i]; 170 | if (piece.HasValue) { 171 | var bson_piece = new BsonDocument(); 172 | bson_piece.Set("player", PlayerUtils.ToString(piece.Value.Player)); 173 | bson_piece.Set("piece", PieceTypeUtils.ToString(piece.Value.PieceType)); 174 | bson_chessboard.Add(bson_piece); 175 | } else { 176 | bson_chessboard.Add(BsonNull.Value); 177 | } 178 | } 179 | 180 | bson_harmonic.Set("chessboard", bson_chessboard); 181 | bson_harmonics.Add(bson_harmonic); 182 | } 183 | document.Set("harmonics", bson_harmonics); 184 | 185 | var last_move_positions = new BsonArray(); 186 | foreach (Position pos in game.Engine.LastMovePositions) 187 | last_move_positions.Add(pos.ToString()); 188 | document.Set("last_move_positions", last_move_positions); 189 | 190 | return document; 191 | } 192 | 193 | public static Game Deserialize(BsonDocument document) { 194 | ObjectId id = document["_id"].AsObjectId; 195 | Player active_player = PlayerUtils.FromString(document["active_player"].AsString); 196 | GameState game_state = GameStateUtils.FromString(document["game_state"].AsString); 197 | DateTime creation_time = document["creation_time"].ToLocalTime(); 198 | 199 | var harmonics = new List(); 200 | foreach (BsonValue bson_harmonic_val in document["harmonics"].AsBsonArray) { 201 | BsonDocument bson_harmonic = bson_harmonic_val.AsBsonDocument; 202 | GameState harmonic_state = GameStateUtils.FromString(bson_harmonic["harmonic_state"].AsString); 203 | ulong degeneracy = Convert.ToUInt64(bson_harmonic["degeneracy"].AsInt64); 204 | BsonArray bson_chessboard = bson_harmonic["chessboard"].AsBsonArray; 205 | var chessboard = Chessboard.EmptyChessboard(harmonic_state); 206 | for (int i = 0; i < 64; i++) { 207 | BsonValue bson_square_val = bson_chessboard[i]; 208 | if (bson_square_val.IsBsonNull) { 209 | chessboard[i] = null; 210 | } else { 211 | BsonDocument bson_square = bson_square_val.AsBsonDocument; 212 | Player square_player = PlayerUtils.FromString(bson_square["player"].AsString); 213 | PieceType square_piece = PieceTypeUtils.FromString(bson_square["piece"].AsString); 214 | chessboard[i] = new Piece(square_player, square_piece); 215 | } 216 | } 217 | harmonics.Add(new QuantumHarmonic(chessboard, degeneracy)); 218 | } 219 | var quantum_chessboard = new QuantumChessboard(harmonics, game_state); 220 | 221 | var last_move_positions = new List(); 222 | foreach (BsonValue pos_val in document["last_move_positions"].AsBsonArray) 223 | last_move_positions.Add(Position.Parse(pos_val.AsString)); 224 | 225 | var engine = new QuantumChessEngine(quantum_chessboard, active_player, creation_time, last_move_positions.ToArray()); 226 | return new Game(id, engine); 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /WebApp/HtmlModule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using Nancy; 6 | 7 | namespace TrulyQuantumChess.WebApp { 8 | public class HtmlModule : NancyModule { 9 | public HtmlModule() 10 | : base(WebAppConfig.Instance.Prefix) 11 | { 12 | Get["/"] = Index; 13 | Get["/play"] = Play; 14 | Get["/active-games", true] = ActiveGames; 15 | } 16 | 17 | private dynamic Index(dynamic args) { 18 | return View["Index.sshtml", new { 19 | WebAppConfig.Instance.Prefix, 20 | WebAppConfig.Instance.DocUrl, 21 | CaptchaEnabled = WebAppConfig.Instance.Captcha.Enabled, 22 | CaptchaCode = WebAppConfig.Instance.Captcha.Public, 23 | PageTitle = "Truly Quantum Chess", 24 | }]; 25 | } 26 | 27 | private dynamic Play(dynamic args) { 28 | var model = new { 29 | WebAppConfig.Instance.Prefix, 30 | GameId = Request.Query["gameId"], 31 | PageTitle = "Game #" + Request.Query["gameId"], 32 | PiecesCollection = WebAppConfig.Instance.Pieces.Collection, 33 | PawnWidthRatio = WebAppConfig.Instance.Pieces.WidthRatios.Pawn, 34 | KnightWidthRatio = WebAppConfig.Instance.Pieces.WidthRatios.Knight, 35 | BishopWidthRatio = WebAppConfig.Instance.Pieces.WidthRatios.Bishop, 36 | RookWidthRatio = WebAppConfig.Instance.Pieces.WidthRatios.Rook, 37 | QueenWidthRatio = WebAppConfig.Instance.Pieces.WidthRatios.Queen, 38 | KingWidthRatio = WebAppConfig.Instance.Pieces.WidthRatios.King, 39 | }; 40 | return View["Play.sshtml", model]; 41 | } 42 | 43 | private async Task ActiveGames(dynamic args, CancellationToken cancellation_token) { 44 | var model = new { 45 | WebAppConfig.Instance.Prefix, 46 | PageTitle = "Active games", 47 | GameIds = await WebAppManagers.DatabaseManager.RequestActiveGames() 48 | }; 49 | return View["ActiveGames.sshtml", model]; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /WebApp/Model.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using TrulyQuantumChess.Kernel.Errors; 5 | using TrulyQuantumChess.Kernel.Moves; 6 | using TrulyQuantumChess.Kernel.Chess; 7 | using TrulyQuantumChess.Kernel.Engine; 8 | 9 | namespace TrulyQuantumChess.WebApp.Model { 10 | public class MoveRequest { 11 | public string GameId; 12 | public string MoveType; 13 | public string Source; 14 | public string Middle; 15 | public string Target; 16 | 17 | public QuantumChessMove Parse(QuantumChessEngine engine) { 18 | if (MoveType == "ordinary") { 19 | Position source = Position.Parse(Source); 20 | Position target = Position.Parse(Target); 21 | Piece? piece = engine.QuantumChessboard.GetQuantumPiece(source).Piece; 22 | if (piece.HasValue) 23 | return new OrdinaryMove(piece.Value, source, target); 24 | else 25 | throw new MoveParseException($"No piece found at {source}"); 26 | } else if (MoveType == "quantum") { 27 | Position source = Position.Parse(Source); 28 | Position? middle = null; 29 | if (!String.IsNullOrEmpty(Middle)) 30 | middle = Position.Parse(Middle); 31 | Position target = Position.Parse(Target); 32 | Piece? piece = engine.QuantumChessboard.GetQuantumPiece(source).Piece; 33 | if (piece.HasValue) 34 | return new QuantumMove(piece.Value, source, middle, target); 35 | else 36 | throw new MoveParseException($"No piece found at {source}"); 37 | } else if (MoveType == "capitulate") { 38 | return new CapitulateMove(engine.ActivePlayer); 39 | } else if (MoveType == "castle_left") { 40 | return new CastleMove(engine.ActivePlayer, CastleType.Left); 41 | } else if (MoveType == "castle_right") { 42 | return new CastleMove(engine.ActivePlayer, CastleType.Right); 43 | } else { 44 | throw new MoveParseException($"Unsupported move type: {MoveType}"); 45 | } 46 | } 47 | } 48 | 49 | public class NewGameResponse { 50 | public string GameId; 51 | } 52 | 53 | public class InfoResponse { 54 | public class SquareEncoded { 55 | public string Player; 56 | public string Piece; 57 | public double Probability; 58 | } 59 | 60 | public string GameState; 61 | public string ActivePlayer; 62 | public Dictionary Squares; 63 | public string[] LastMovePositions; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /WebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | using Nancy; 5 | using Nancy.Hosting.Self; 6 | 7 | namespace TrulyQuantumChess.WebApp { 8 | public static class Program { 9 | public static void Main(string[] args) { 10 | var cleaner = new Thread(() => { 11 | try { 12 | for (;;) { 13 | try { 14 | WebAppManagers.DatabaseManager.CleanOldEntities(DateTime.Now - TimeSpan.FromHours(WebAppConfig.Instance.CleanAfterHours)).Wait(); 15 | } catch (ThreadAbortException) { 16 | throw; 17 | } catch (Exception e) { 18 | Console.WriteLine($"Error occured while cleaning: {e}"); 19 | } 20 | Thread.Sleep(TimeSpan.FromMinutes(10)); 21 | } 22 | } catch (ThreadAbortException) {} 23 | }); 24 | 25 | StaticConfiguration.DisableErrorTraces = !WebAppConfig.Instance.Debug; 26 | 27 | var uri = new Uri(WebAppConfig.Instance.ListenUrl); 28 | using (var host = new NancyHost(uri)) { 29 | host.Start(); 30 | Console.WriteLine($"Listening on {uri}.."); 31 | 32 | cleaner.Start(); 33 | 34 | long host_alive = 1; 35 | 36 | Console.CancelKeyPress += (sender, e) => { 37 | Interlocked.Exchange(ref host_alive, 0L); 38 | }; 39 | 40 | for (;;) { 41 | if (Interlocked.Read(ref host_alive) == 0) 42 | goto abort; 43 | 44 | while (Console.KeyAvailable) { 45 | if (Console.ReadKey(true).Key == ConsoleKey.Escape) 46 | goto abort; 47 | } 48 | 49 | Thread.Sleep(100); 50 | } 51 | 52 | abort: 53 | host.Stop(); 54 | cleaner.Abort(); 55 | cleaner.Join(); 56 | Console.WriteLine("Bye."); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /WebApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("WebApp")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | -------------------------------------------------------------------------------- /WebApp/Templates/ActiveGames.sshtml: -------------------------------------------------------------------------------- 1 | @Master['Master.sshtml'] 2 | 3 | @Section['Body'] 4 |

Active games:

5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | @Each.GameIds 16 | 17 | 20 | 23 | 26 | 29 | 30 | @EndEach 31 | 32 |
LinkLast modificationGame state 11 | Created
18 | @Current.GameId 19 | 21 | @Current.LastModificationString 22 | 24 | @Current.GameStateString 25 | 27 | Created @Current.CreationTimeString ago 28 |
33 | @EndSection -------------------------------------------------------------------------------- /WebApp/Templates/Index.sshtml: -------------------------------------------------------------------------------- 1 | @Master['Master.sshtml'] 2 | 3 | @Section['Head'] 4 | 5 | @If.CaptchaEnabled 6 | 7 | @EndIf 8 | @EndSection 9 | 10 | @Section['Body'] 11 |
12 | 13 |

14 | 15 | Rules of quantum chess 16 |
17 | 41 | @EndSection -------------------------------------------------------------------------------- /WebApp/Templates/Master.sshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | @Model.PageTitle 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | @Section['Head']; 18 | 19 | 20 |
21 |
22 |

23 | Truly quantum chess alpha 24 |

25 |

26 | The thousand-year-old game of chess meets quantum physics. 27 |

28 |
29 |
30 | @Section['Body']; 31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /WebApp/Templates/Play.sshtml: -------------------------------------------------------------------------------- 1 | @Master['Master.sshtml'] 2 | 3 | @Section['Head'] 4 | 16 | 17 | @EndSection 18 | 19 | @Section['Body'] 20 |
21 | Copy the URL of this page and send it to your friend 22 |
23 | 27 | active player: - 28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 |
abcdefgh
8 8
7 7
6 6
5 5
4 4
3 3
2 2
1 1
abcdefgh
153 | Tap two times on the same piece to make a quantum move 154 |
155 | 156 | 157 | 158 |
159 | @EndSection 160 | -------------------------------------------------------------------------------- /WebApp/WebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {31354668-3E37-46C5-BE82-66F09A09D9BD} 7 | Exe 8 | WebApp 9 | WebApp 10 | v4.5 11 | 12 | 13 | true 14 | full 15 | false 16 | bin\Debug 17 | DEBUG; 18 | prompt 19 | 4 20 | true 21 | 22 | 23 | true 24 | bin\Release 25 | prompt 26 | 4 27 | true 28 | 29 | 30 | 31 | 32 | ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll 33 | 34 | 35 | ..\packages\Nancy.Hosting.Self.1.4.1\lib\net40\Nancy.Hosting.Self.dll 36 | 37 | 38 | 39 | 40 | ..\packages\MongoDB.Bson.2.4.2\lib\net45\MongoDB.Bson.dll 41 | 42 | 43 | ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.0.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll 44 | 45 | 46 | ..\packages\MongoDB.Driver.Core.2.4.2\lib\net45\MongoDB.Driver.Core.dll 47 | 48 | 49 | ..\packages\MongoDB.Driver.2.4.2\lib\net45\MongoDB.Driver.dll 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | PreserveNewest 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | PreserveNewest 75 | 76 | 77 | PreserveNewest 78 | 79 | 80 | PreserveNewest 81 | 82 | 83 | PreserveNewest 84 | 85 | 86 | PreserveNewest 87 | 88 | 89 | PreserveNewest 90 | 91 | 92 | PreserveNewest 93 | 94 | 95 | PreserveNewest 96 | 97 | 98 | PreserveNewest 99 | 100 | 101 | PreserveNewest 102 | 103 | 104 | PreserveNewest 105 | 106 | 107 | PreserveNewest 108 | 109 | 110 | PreserveNewest 111 | 112 | 113 | PreserveNewest 114 | 115 | 116 | PreserveNewest 117 | 118 | 119 | PreserveNewest 120 | 121 | 122 | PreserveNewest 123 | 124 | 125 | PreserveNewest 126 | 127 | 128 | PreserveNewest 129 | 130 | 131 | PreserveNewest 132 | 133 | 134 | PreserveNewest 135 | 136 | 137 | PreserveNewest 138 | 139 | 140 | PreserveNewest 141 | 142 | 143 | PreserveNewest 144 | 145 | 146 | PreserveNewest 147 | 148 | 149 | PreserveNewest 150 | 151 | 152 | PreserveNewest 153 | 154 | 155 | PreserveNewest 156 | 157 | 158 | PreserveNewest 159 | 160 | 161 | PreserveNewest 162 | 163 | 164 | PreserveNewest 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | {B4B23590-B467-4F12-929B-313A2025613E} 176 | Kernel 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /WebApp/WebAppConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Web.Script.Serialization; 4 | 5 | namespace TrulyQuantumChess.WebApp { 6 | public class WebAppConfig { 7 | private static readonly WebAppConfig Instance_; 8 | 9 | public static WebAppConfig Instance { 10 | get { return Instance_; } 11 | } 12 | 13 | static WebAppConfig() { 14 | string input = File.ReadAllText("WebAppConfig.json"); 15 | var jss = new JavaScriptSerializer(); 16 | Instance_ = jss.Deserialize(input); 17 | } 18 | 19 | public string ListenUrl { get; private set; } 20 | public string Prefix { get; private set; } 21 | public MongoConnection Mongo { get; private set; } 22 | public double CleanAfterHours { get; private set; } 23 | public bool Debug { get; private set; } 24 | public PiecesInfo Pieces { get; private set; } 25 | public string DocUrl { get; private set; } 26 | public CaptchaSettings Captcha { get; private set; } 27 | } 28 | 29 | // Helper for dependency injections 30 | public static class WebAppManagers { 31 | private static readonly IDatabaseManager DatabaseManager_ = 32 | new MongoManager(); 33 | 34 | public static IDatabaseManager DatabaseManager { 35 | get { return DatabaseManager_; } 36 | } 37 | } 38 | 39 | public class MongoConnection { 40 | public string ConnectionString { get; private set; } 41 | public string Database { get; private set; } 42 | } 43 | 44 | public class PiecesInfo { 45 | public string Collection { get; private set; } 46 | public PiecesWidthRatiosInfo WidthRatios { get; private set; } 47 | } 48 | 49 | public class PiecesWidthRatiosInfo { 50 | public double Pawn { get; private set; } 51 | public double Knight { get; private set; } 52 | public double Bishop { get; private set; } 53 | public double Rook { get; private set; } 54 | public double Queen { get; private set; } 55 | public double King { get; private set; } 56 | } 57 | 58 | public class CaptchaSettings { 59 | public bool Enabled { get; private set; } 60 | public string Public { get; private set; } 61 | public string Secret { get; private set; } 62 | } 63 | } -------------------------------------------------------------------------------- /WebApp/WebAppConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ListenUrl" : "http://localhost:9000", 3 | "Prefix": "", 4 | "Mongo": { 5 | "ConnectionString": "mongodb://localhost:32768", 6 | "Database": "truly_quantum_chess" 7 | }, 8 | "CleanAfterHours": 2, 9 | "Debug": true, 10 | "Pieces": { 11 | "Collection": "simple", 12 | "WidthRatios": { 13 | "Pawn": 0.59, 14 | "Knight": 0.78, 15 | "Bishop": 0.82, 16 | "Rook": 0.69, 17 | "Queen": 0.93, 18 | "King": 0.83 19 | } 20 | }, 21 | "DocUrl": "https://github.com/caphindsight/TrulyQuantumChess/wiki", 22 | "Captcha": { 23 | "Enabled": false, 24 | "Public": "6Lf-LhcUAAAAAG11TNlD8rqnjhOW9WuUpfVy1qfL", 25 | "Secret": "6Lf-LhcUAAAAAEU_QLMfIJ94eqLpnFXc-qfyz-Jl" 26 | } 27 | } -------------------------------------------------------------------------------- /WebApp/WebAppConfig_dockerized.json: -------------------------------------------------------------------------------- 1 | { 2 | "ListenUrl" : "http://localhost:9000", 3 | "Prefix": "", 4 | "Mongo": { 5 | "ConnectionString": "mongodb://mongo.storage.hindsight.hindsight.node.intern", 6 | "Database": "truly_quantum_chess" 7 | }, 8 | "CleanAfterHours": 2, 9 | "Debug": true, 10 | "Pieces": { 11 | "Collection": "simple", 12 | "WidthRatios": { 13 | "Pawn": 0.59, 14 | "Knight": 0.78, 15 | "Bishop": 0.82, 16 | "Rook": 0.69, 17 | "Queen": 0.93, 18 | "King": 0.83 19 | } 20 | }, 21 | "DocUrl": "https://github.com/caphindsight/TrulyQuantumChess/wiki", 22 | "Captcha": { 23 | "Enabled": true, 24 | "Public": "6LdUlikTAAAAANmAPmncAzWMV20RlVlZOlbhi7R7", 25 | "Secret": "6LdUlikTAAAAAFnV06iD6MPauLtede0IbWTgyRQN" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApp/WebAppConfig_dockerized_local.json: -------------------------------------------------------------------------------- 1 | { 2 | "ListenUrl" : "http://localhost:9000", 3 | "Prefix": "", 4 | "Mongo": { 5 | "ConnectionString": "mongodb://mongo:27017", 6 | "Database": "truly_quantum_chess" 7 | }, 8 | "CleanAfterHours": 2, 9 | "Debug": true, 10 | "Pieces": { 11 | "Collection": "simple", 12 | "WidthRatios": { 13 | "Pawn": 0.59, 14 | "Knight": 0.78, 15 | "Bishop": 0.82, 16 | "Rook": 0.69, 17 | "Queen": 0.93, 18 | "King": 0.83 19 | } 20 | }, 21 | "DocUrl": "https://github.com/caphindsight/TrulyQuantumChess/wiki", 22 | "Captcha": { 23 | "Enabled": false, 24 | "Public": "6LdUlikTAAAAANmAPmncAzWMV20RlVlZOlbhi7R7", 25 | "Secret": "6LdUlikTAAAAAFnV06iD6MPauLtede0IbWTgyRQN" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /WebApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WebAppConfig_dockerized.json: -------------------------------------------------------------------------------- 1 | { 2 | "ListenUrl" : "http://localhost:9000", 3 | "Prefix": "", 4 | "Mongo": { 5 | "ConnectionString": "mongodb://mongo:27017", 6 | "Database": "truly_quantum_chess" 7 | }, 8 | "CleanAfterHours": 2, 9 | "Debug": true, 10 | "Pieces": { 11 | "Collection": "simple", 12 | "WidthRatios": { 13 | "Pawn": 0.59, 14 | "Knight": 0.78, 15 | "Bishop": 0.82, 16 | "Rook": 0.69, 17 | "Queen": 0.93, 18 | "King": 0.83 19 | } 20 | }, 21 | "DocUrl": "https://github.com/caphindsight/TrulyQuantumChess/wiki", 22 | "Captcha": { 23 | "Enabled": false, 24 | "Public": "6LdUlikTAAAAANmAPmncAzWMV20RlVlZOlbhi7R7", 25 | "Secret": "6LdUlikTAAAAAFnV06iD6MPauLtede0IbWTgyRQN" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | web: 4 | image: chess 5 | build: 6 | context: . 7 | dockerfile: "Dockerfile.local" 8 | command: "mono WebApp.exe" 9 | ports: 10 | - "9000:9000" 11 | depends_on: 12 | - "mongo" 13 | mongo: 14 | image: "mongo" 15 | ports: 16 | - "27017:27017" --------------------------------------------------------------------------------