To play click here: Chess Game ♔
At first you have to download or create chessboard template and pieces. I have used chessboard.js for better board and pieces. Next important thing is to plan about your project structure for flask project.project structure bellow:
chess-game/
│
├── app.py # Flask backend
├── templates/
│ └── index.html # Main HTML page
├── static/
│ ├── css/
│ │ ├── chessboard-1.0.0.css # downloaded CSS from chessboard.js
│ │ └── style.css # Custom CSS
│ ├── js/
│ │ ├── chessboard-1.0.0.js # Provided JS
│ │ └── game.js # Game logic
│ └── img/
│ └── chesspieces/
│ └── wikipedia/ # Your piece images
Use vs code or pycharm or any python supported compiler. Here you can see i have used just html,css, java script for frontend python+js for backend. This structure is also usefull for machine learning projects.
If i paste my whole design that will be too large so ,i am just showing basic structure and flask back end like app.py and strong_engine.py. You can also use my engine for your chess game. But remember its not powerfull likely 1100 elo. You can use it as beginner version of chess game . If you want to use more than 2400 elo engine you can use stockfish or lichess engine/ chess.com engines.
Basic html that can be implemented with your downloaded chessboard and poieces:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Chess Game ♔</title>
<!-- Mobile-specific meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#667eea">
<!-- CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/chessboard-1.0.0.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- JavaScript -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--accent-color: #1abc9c;
--success-color: #2ecc71;
--warning-color: #f39c12;
--danger-color: #e74c3c;
--light-color: #f8f9fa;
--dark-color: #343a40;
--text-light: #ffffff;
--text-dark: #2c3e50;
--bg-light: #ffffff;
--bg-dark: #1a1a2e;
--card-light: #ffffff;
--card-dark: #16213e;
--border-light: #e0e0e0;
--border-dark: #2d3748;
--shadow-light: rgba(0, 0, 0, 0.1);
--shadow-dark: rgba(0, 0, 0, 0.3);
}
[data-theme="light"] {
--bg-color: var(--bg-light);
--card-bg: var(--card-light);
--text-color: var(--text-dark);
--border-color: var(--border-light);
--shadow-color: var(--shadow-light);
}
[data-theme="dark"] {
--bg-color: var(--bg-dark);
--card-bg: var(--card-dark);
--text-color: var(--text-light);
--border-color: var(--border-dark);
--shadow-color: var(--shadow-dark);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.container {
background: var(--card-bg);
border-color: var(--border-color);
box-shadow: 0 10px 30px var(--shadow-color);
}
header {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
position: relative;
overflow: hidden;
}
header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="none"><path d="M0,0 L100,0 L100,100 Z" fill="rgba(255,255,255,0.1)"/></svg>');
background-size: cover;
}
header h1 {
position: relative;
z-index: 1;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
z-index: 100;
}
.theme-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: white;
font-size: 1.2rem;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.theme-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: rotate(15deg);
}
.stats-bar {
display: flex;
justify-content: space-between;
margin-top: 15px;
padding: 10px 0;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-value {
font-size: 1.2rem;
font-weight: bold;
color: var(--accent-color);
}
.stat-label {
font-size: 0.8rem;
opacity: 0.8;
}
.controls {
background: var(--card-bg);
border: 1px solid var(--border-color);
}
.controls h3 {
color: var(--primary-color);
border-bottom-color: var(--primary-color);
}
button {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
border: none;
transition: all 0.3s ease;
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 7px 15px rgba(102, 126, 234, 0.4);
}
button.active {
background: linear-gradient(135deg, var(--accent-color) 0%, var(--success-color) 100%);
}
.game-info {
background: var(--card-bg);
border-left-color: var(--primary-color);
border: 1px solid var(--border-color);
}
#status {
background: var(--bg-color);
border: 1px solid var(--border-color);
}
.move-history {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-left-color: var(--accent-color);
}
.move-entry {
background: var(--bg-color);
border: 1px solid var(--border-color);
}
.move-entry:hover {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.promotion-modal {
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
}
.promotion-content {
background: linear-gradient(135deg, var(--card-dark) 0%, #2c3e50 100%);
border: 2px solid var(--accent-color);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
}
.promotion-btn {
background: rgba(255, 255, 255, 0.1);
border: 2px solid var(--primary-color);
backdrop-filter: blur(5px);
}
.promotion-btn:hover {
background: rgba(52, 152, 219, 0.3);
border-color: var(--accent-color);
box-shadow: 0 10px 25px rgba(26, 188, 156, 0.4);
}
/* Glass effect for selected squares */
.selected {
background: rgba(46, 204, 113, 0.7) !important;
backdrop-filter: blur(5px);
box-shadow: inset 0 0 20px rgba(46, 204, 113, 0.3);
}
.valid-move::after {
background: rgba(52, 152, 219, 0.8);
box-shadow: 0 0 15px rgba(52, 152, 219, 0.8);
}
.valid-capture::after {
border-color: rgba(231, 76, 60, 0.8);
box-shadow: 0 0 20px rgba(231, 76, 60, 0.6);
}
/* Pulse animation for AI thinking */
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
.ai-thinking {
animation: pulse 1.5s infinite;
}
/* Responsive improvements */
@media (max-width: 768px) {
.game-container {
padding: 15px;
gap: 20px;
}
.chessboard-container {
padding: 10px;
}
.controls {
padding: 20px;
}
.theme-btn {
width: 35px;
height: 35px;
font-size: 1rem;
}
}
/* Modern scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--bg-color);
}
::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--secondary-color);
}
</style>
</head>
<body data-theme="light">
<div class="container">
<header>
<div class="theme-toggle">
<button id="theme-toggle-btn" class="theme-btn" title="Toggle Dark Mode">
<i class="fas fa-moon"></i>
</button>
</div>
<h1>♔ Chess by ibnsina ♚</h1>
<div class="subtitle">Strategic Chess with AI Assistant</div>
<div class="stats-bar">
<div class="stat-item">
<div class="stat-value" id="move-count">0</div>
<div class="stat-label">Moves</div>
</div>
<div class="stat-item">
<div class="stat-value" id="time-white">00:00</div>
<div class="stat-label">White</div>
</div>
<div class="stat-item">
<div class="stat-value" id="time-black">00:00</div>
<div class="stat-label">Black</div>
</div>
<div class="stat-item">
<div class="stat-value" id="captured-value">0</div>
<div class="stat-label">Material</div>
</div>
</div>
</header>
<div class="game-container">
<div class="chessboard-container">
<div id="board"></div>
<div id="error-message" class="error-message hidden"></div>
<!-- Captured pieces display -->
<div class="captured-pieces">
<div class="captured-white">
<h4><i class="fas fa-chess-king"></i> White Captured</h4>
<div id="captured-white"></div>
</div>
<div class="captured-black">
<h4><i class="fas fa-chess-king"></i> Black Captured</h4>
<div id="captured-black"></div>
</div>
</div>
</div>
<div class="controls">
<div class="game-info">
<h3><i class="fas fa-info-circle"></i> Game Status</h3>
<div id="status" class="status-box">White to move</div>
<div id="game-over" class="hidden game-over-box"></div>
<div id="ai-status" class="hidden ai-thinking">
<i class="fas fa-brain"></i> AI is thinking...
</div>
</div>
<div class="game-modes">
<h3><i class="fas fa-users"></i> Play Mode</h3>
<div class="button-group">
<button id="two-player-btn" class="active">
<i class="fas fa-user-friends"></i> Two Players
</button>
<button id="vs-bot-btn">
<i class="fas fa-robot"></i> Play vs AI
</button>
</div>
</div>
<div class="side-controls hidden">
<h3><i class="fas fa-chess-board"></i> Your Side</h3>
<div class="button-group">
<button id="flip-sides-btn">
<i class="fas fa-exchange-alt"></i> Switch Sides
</button>
</div>
<div id="side-info" class="side-info-box">
<i class="fas fa-chess-king"></i> You are playing as White
</div>
</div>
<div class="bot-controls hidden">
<h3><i class="fas fa-cogs"></i> AI Strength</h3>
<div class="difficulty">
<button class="diff-btn" data-level="1">
<i class="fas fa-baby"></i> Beginner
</button>
<button class="diff-btn" data-level="2">
<i class="fas fa-user-graduate"></i> Intermediate
</button>
<button class="diff-btn active" data-level="3">
<i class="fas fa-user-tie"></i> Advanced
</button>
<button class="diff-btn" data-level="4">
<i class="fas fa-crown"></i> Master
</button>
</div>
</div>
<div class="game-controls">
<h3><i class="fas fa-gamepad"></i> Controls</h3>
<div class="button-group">
<button id="new-game-btn">
<i class="fas fa-redo"></i> New Game
</button>
<button id="flip-board-btn">
<i class="fas fa-sync-alt"></i> Flip Board
</button>
<button id="undo-btn">
<i class="fas fa-undo"></i> Undo Move
</button>
</div>
<div class="help-text">
<small><i class="fas fa-mouse-pointer"></i> Click piece to select, then click highlighted square to move</small>
</div>
</div>
<div class="move-history">
<h3><i class="fas fa-history"></i> Move History</h3>
<div id="move-list"></div>
</div>
</div>
</div>
<!-- Add this section after the move-history div -->
<div class="instructions">
<h3><i class="fas fa-info-circle"></i> Game Instructions</h3>
<div class="instructions-content">
<div class="instruction-item">
<i class="fas fa-robot instruction-icon"></i>
<div class="instruction-text">
<strong>AI Timing:</strong> In Advanced/Master mode, the bot may take a few seconds to think. Be patient!
</div>
</div>
<div class="instruction-item">
<i class="fas fa-undo instruction-icon"></i>
<div class="instruction-text">
<strong>Undo Move:</strong> When playing vs AI, click the Undo button twice to undo both your move and the bot's move.
</div>
</div>
<div class="instruction-item">
<i class="fas fa-chess-pawn instruction-icon"></i>
<div class="instruction-text">
<strong>Pawn Promotion:</strong> When a pawn reaches the last rank, select the piece you want to promote to.
</div>
</div>
<div class="instruction-item">
<i class="fas fa-mouse-pointer instruction-icon"></i>
<div class="instruction-text">
<strong>Controls:</strong> Click a piece to select it, then click a highlighted square to move.
</div>
</div>
<div class="instruction-item">
<i class="fas fa-gamepad instruction-icon"></i>
<div class="instruction-text">
<strong>Game Modes:</strong> Switch between Two Players or Play vs AI using the mode buttons.
</div>
</div>
<div class="instruction-item">
<i class="fas fa-sliders-h instruction-icon"></i>
<div class="instruction-text">
<strong>AI Difficulty:</strong> Beginner for fast moves, Master for challenging play (slower).
</div>
</div>
</div>
</div>
<!-- Promotion Modal -->
<div id="promotion-modal" class="promotion-modal hidden">
<div class="promotion-content">
<h3 id="promotion-title" class="promotion-title">
<i class="fas fa-chess-pawn"></i> Pawn Promotion
</h3>
<p>Choose a piece to promote to:</p>
<div class="promotion-options">
<button id="promotion-queen" class="promotion-btn" data-piece="q">
<img src="/static/img/chesspieces/wikipedia/wQ.png" alt="Queen">
<div><i class="fas fa-crown"></i> Queen</div>
</button>
<button id="promotion-rook" class="promotion-btn" data-piece="r">
<img src="/static/img/chesspieces/wikipedia/wR.png" alt="Rook">
<div><i class="fas fa-chess-rook"></i> Rook</div>
</button>
<button id="promotion-bishop" class="promotion-btn" data-piece="b">
<img src="/static/img/chesspieces/wikipedia/wB.png" alt="Bishop">
<div><i class="fas fa-chess-bishop"></i> Bishop</div>
</button>
<button id="promotion-knight" class="promotion-btn" data-piece="n">
<img src="/static/img/chesspieces/wikipedia/wN.png" alt="Knight">
<div><i class="fas fa-chess-knight"></i> Knight</div>
</button>
</div>
<div class="promotion-hint">
<small>Click outside to auto-select Queen</small>
</div>
</div>
</div>
<!-- JavaScript files -->
<script src="/static/js/chessboard-1.0.0.js"></script>
<script src="/static/js/promotion.js"></script>
<script src="/static/js/game.js"></script>
<!-- Theme and enhanced features script -->
<script>
$(document).ready(function() {
console.log('Enhanced features loaded');
// Theme toggle functionality
const themeToggle = $('#theme-toggle-btn');
const themeIcon = themeToggle.find('i');
const body = $('body');
// Load saved theme or default to light
const savedTheme = localStorage.getItem('chess-theme') || 'light';
body.attr('data-theme', savedTheme);
updateThemeIcon(savedTheme);
themeToggle.click(function() {
const currentTheme = body.attr('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
body.attr('data-theme', newTheme);
localStorage.setItem('chess-theme', newTheme);
updateThemeIcon(newTheme);
// Add transition effect
body.addClass('theme-transition');
setTimeout(() => body.removeClass('theme-transition'), 300);
console.log('Theme changed to:', newTheme);
});
function updateThemeIcon(theme) {
if (theme === 'dark') {
themeIcon.removeClass('fa-moon').addClass('fa-sun');
themeToggle.attr('title', 'Switch to Light Mode');
} else {
themeIcon.removeClass('fa-sun').addClass('fa-moon');
themeToggle.attr('title', 'Switch to Dark Mode');
}
}
// Initialize move counter
let moveCount = 0;
// Function to update stats
function updateStats() {
$('#move-count').text(moveCount);
}
// Listen for moves (you'll need to update this in your game.js)
$(document).on('moveMade', function(event, moveData) {
moveCount++;
updateStats();
});
// Initialize timers
let whiteTime = 0;
let blackTime = 0;
let timerInterval = null;
let currentPlayer = 'white';
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
function startTimer() {
if (timerInterval) clearInterval(timerInterval);
timerInterval = setInterval(function() {
if (currentPlayer === 'white') {
whiteTime++;
$('#time-white').text(formatTime(whiteTime));
} else {
blackTime++;
$('#time-black').text(formatTime(blackTime));
}
}, 1000);
}
function stopTimer() {
if (timerInterval) {
clearInterval(timerInterval);
timerInterval = null;
}
}
function switchTimer() {
currentPlayer = currentPlayer === 'white' ? 'black' : 'white';
}
// Reset timers on new game
$('#new-game-btn').click(function() {
whiteTime = 0;
blackTime = 0;
moveCount = 0;
currentPlayer = 'white';
$('#time-white').text('00:00');
$('#time-black').text('00:00');
updateStats();
startTimer();
});
// Start timer initially
startTimer();
// Expose timer functions to game.js
window.timerFunctions = {
switchTimer: switchTimer,
stopTimer: stopTimer,
startTimer: startTimer
};
// Enhanced promotion modal close on outside click
$(document).on('click', function(e) {
if ($(e.target).closest('#promotion-modal').length === 0 &&
!$(e.target).closest('.promotion-btn').length &&
!$('#promotion-modal').hasClass('hidden')) {
$('#promotion-modal').addClass('hidden');
// Trigger auto-queen selection if callback exists
if (window.promotionCallback) {
window.promotionCallback('q');
}
}
});
});
</script>
</div>
</body>
</html>app.py
from flask import Flask, render_template, jsonify, request
import chess
import os # Required for environment variables
from strong_engine import StrongChessEngine
app = Flask(__name__)
# Initialize strong engine
chess_engine = StrongChessEngine(difficulty=3)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/get_bot_move', methods=['POST'])
def get_bot_move():
data = request.json
fen = data.get('fen')
difficulty = data.get('difficulty', 3)
try:
# Update engine difficulty - use update_depth() method
chess_engine.difficulty = difficulty
chess_engine.update_depth() # This sets all search parameters correctly
# Get best move
from_sq, to_sq, promotion = chess_engine.get_best_move(fen)
if from_sq and to_sq:
return jsonify({
'from_square': from_sq,
'to_square': to_sq,
'promotion': promotion
})
else:
# Simple fallback
board = chess.Board(fen)
import random
if board.is_game_over():
return jsonify({'error': 'Game is over'}), 400
move = random.choice(list(board.legal_moves))
return jsonify({
'from_square': chess.square_name(move.from_square),
'to_square': chess.square_name(move.to_square),
'promotion': chess.piece_symbol(move.promotion).lower() if move.promotion else None
})
except Exception as e:
print(f"Error in get_bot_move: {e}")
import traceback
traceback.print_exc()
return jsonify({'error': str(e)}), 400
@app.route('/get_legal_moves', methods=['POST'])
def get_legal_moves():
data = request.json
fen = data.get('fen')
square = data.get('square')
try:
board = chess.Board(fen)
moves = []
if square:
from_square = chess.parse_square(square)
for move in board.legal_moves:
if move.from_square == from_square:
moves.append(chess.square_name(move.to_square))
else:
for move in board.legal_moves:
moves.append(chess.square_name(move.to_square))
return jsonify({'legal_moves': moves})
except Exception as e:
return jsonify({'error': str(e)}), 400
@app.route('/validate_move', methods=['POST'])
def validate_move():
data = request.json
fen = data.get('fen')
from_sq = data.get('from')
to_sq = data.get('to')
promotion = data.get('promotion')
try:
board = chess.Board(fen)
# Check if this is a pawn promotion move
piece = board.piece_at(chess.parse_square(from_sq))
is_pawn = piece and piece.piece_type == chess.PAWN
target_rank = chess.square_rank(chess.parse_square(to_sq))
# Determine if move requires promotion
if is_pawn and ((piece.color == chess.WHITE and target_rank == 7) or
(piece.color == chess.BLACK and target_rank == 0)):
# If promotion is not specified, we need to ask for it
if not promotion:
return jsonify({
'valid': False,
'requires_promotion': True,
'error': 'Pawn promotion required'
})
# Create the move
move_uci = f"{from_sq}{to_sq}{promotion if promotion else ''}"
move = chess.Move.from_uci(move_uci)
if move in board.legal_moves:
board.push(move)
return jsonify({
'valid': True,
'fen': board.fen(),
'check': board.is_check(),
'checkmate': board.is_checkmate(),
'stalemate': board.is_stalemate(),
'draw': board.is_game_over() and not board.is_checkmate(),
'promotion_made': promotion if promotion else None
})
else:
return jsonify({'valid': False, 'error': 'Illegal move'})
except Exception as e:
return jsonify({'valid': False, 'error': str(e)})
if __name__ == '__main__':
print("🚀 Starting Modern Chess Game Server...")
print("📱 Game optimized for mobile and desktop")
# Test the engine
test_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
result = chess_engine.get_best_move(test_fen)
if result[0]:
print(f"✅ Engine ready: {result[0]} -> {result[1]}")
else:
print("⚠️ Engine test failed")
# Get port from environment variable (Fly.io provides PORT)
port = int(os.environ.get("PORT", 8080))
# Disable debug mode for production
app.run(debug=False, host='0.0.0.0', port=port)strong_engine:
"""
Optimized Chess Engine using python-chess with time management
Compatible with the Flask web interface
"""
import chess
import math
import random
import time
from typing import List, Tuple, Optional
class StrongChessEngine:
"""Optimized chess engine with proper search algorithm and time management"""
def __init__(self, difficulty=3):
"""
difficulty levels:
1: Easy (depth 2-3, 5 seconds max)
2: Medium (depth 3-4, 10 seconds max)
3: Hard (depth 4-5, 15 seconds max)
4: Expert (depth 5-6, 20 seconds max)
"""
self.difficulty = difficulty
# Piece values (centipawns)
self.piece_values = {
chess.PAWN: 100,
chess.KNIGHT: 320,
chess.BISHOP: 330,
chess.ROOK: 500,
chess.QUEEN: 900,
chess.KING: 20000
}
# Piece-square tables - simplified for speed
self.pawn_table = [
0, 0, 0, 0, 0, 0, 0, 0,
50, 50, 50, 50, 50, 50, 50, 50,
10, 10, 20, 25, 25, 20, 10, 10,
5, 5, 10, 20, 20, 10, 5, 5,
0, 0, 0, 15, 15, 0, 0, 0,
5, -5, -10, 0, 0, -10, -5, 5,
5, 10, 10, -20, -20, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0
]
self.knight_table = [
-50, -40, -30, -30, -30, -30, -40, -50,
-40, -20, 0, 5, 5, 0, -20, -40,
-30, 0, 10, 15, 15, 10, 0, -30,
-30, 5, 15, 20, 20, 15, 5, -30,
-30, 0, 15, 20, 20, 15, 0, -30,
-30, 5, 10, 15, 15, 10, 5, -30,
-40, -20, 0, 5, 5, 0, -20, -40,
-50, -40, -30, -30, -30, -30, -40, -50
]
self.bishop_table = [
-20, -10, -10, -10, -10, -10, -10, -20,
-10, 5, 0, 0, 0, 0, 5, -10,
-10, 10, 10, 10, 10, 10, 10, -10,
-10, 0, 10, 10, 10, 10, 0, -10,
-10, 5, 5, 10, 10, 5, 5, -10,
-10, 0, 5, 10, 10, 5, 0, -10,
-10, 0, 0, 0, 0, 0, 0, -10,
-20, -10, -10, -10, -10, -10, -10, -20
]
self.rook_table = [
0, 0, 0, 5, 5, 0, 0, 0,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
-5, 0, 0, 0, 0, 0, 0, -5,
5, 10, 10, 10, 10, 10, 10, 5,
0, 0, 0, 0, 0, 0, 0, 0
]
self.queen_table = [
-20, -10, -10, -5, -5, -10, -10, -20,
-10, 0, 5, 0, 0, 0, 0, -10,
-10, 5, 5, 5, 5, 5, 0, -10,
0, 0, 5, 5, 5, 5, 0, -5,
-5, 0, 5, 5, 5, 5, 0, -5,
-10, 0, 5, 5, 5, 5, 0, -10,
-10, 0, 0, 0, 0, 0, 0, -10,
-20, -10, -10, -5, -5, -10, -10, -20
]
self.king_middle_table = [
-30, -40, -40, -50, -50, -40, -40, -30,
-30, -40, -40, -50, -50, -40, -40, -30,
-30, -40, -40, -50, -50, -40, -40, -30,
-30, -40, -40, -50, -50, -40, -40, -30,
-20, -30, -30, -40, -40, -30, -30, -20,
-10, -20, -20, -20, -20, -20, -20, -10,
20, 20, 0, 0, 0, 0, 20, 20,
20, 30, 10, 0, 0, 10, 30, 20
]
self.king_end_table = [
-50, -40, -30, -20, -20, -30, -40, -50,
-30, -20, -10, 0, 0, -10, -20, -30,
-30, -10, 20, 30, 30, 20, -10, -30,
-30, -10, 30, 40, 40, 30, -10, -30,
-30, -10, 30, 40, 40, 30, -10, -30,
-30, -10, 20, 30, 30, 20, -10, -30,
-30, -30, 0, 0, 0, 0, -30, -30,
-50, -30, -30, -30, -30, -30, -30, -50
]
# Initialize parameters
self.update_depth()
# Search tracking
self.nodes_searched = 0
self.start_time = None
self.timeout = False
self.best_move_found = None
# Move ordering heuristics
self.killer_moves = [[None, None] for _ in range(64)]
self.history_table = [[0 for _ in range(64)] for _ in range(64)]
print(f"🎯 Chess Engine initialized with difficulty {self.difficulty}")
print(f" Target: Depth {self.target_depth}, Time limit: {self.max_time}s")
def update_depth(self):
"""Update search parameters based on current difficulty"""
# Time limits per difficulty
self.time_limits = {1: 5, 2: 10, 3: 15, 4: 20} # seconds
self.depth_limits = {1: 3, 2: 4, 3: 5, 4: 6}
self.max_time = self.time_limits.get(self.difficulty, 10)
self.target_depth = self.depth_limits.get(self.difficulty, 4)
self.max_depth = self.target_depth # For compatibility
self.max_nodes = 1000000 # Default high value
def check_timeout(self) -> bool:
"""Check if search should timeout"""
if self.start_time and (time.time() - self.start_time) > self.max_time:
self.timeout = True
return True
return False
def evaluate_position(self, board: chess.Board) -> float:
"""Fast evaluation of the board position"""
# Quick terminal condition checks
if board.is_checkmate():
return -99999 if board.turn else 99999
if board.is_stalemate() or board.is_insufficient_material():
return 0
# Material and positional evaluation
score = 0
for square in chess.SQUARES:
piece = board.piece_at(square)
if piece:
# Material value
material = self.piece_values[piece.piece_type]
# Position value (simplified)
if piece.color == chess.WHITE:
score += material + self.get_fast_position_value(piece, square, board)
else:
score -= material + self.get_fast_position_value(piece, square, board)
# Simple mobility bonus
legal_moves = len(list(board.legal_moves))
if board.turn == chess.WHITE:
score += legal_moves * 5
else:
score -= legal_moves * 5
return score
def get_fast_position_value(self, piece: chess.Piece, square: chess.Square, board: chess.Board) -> float:
"""Fast position value lookup"""
tables = {
chess.PAWN: self.pawn_table,
chess.KNIGHT: self.knight_table,
chess.BISHOP: self.bishop_table,
chess.ROOK: self.rook_table,
chess.QUEEN: self.queen_table,
chess.KING: self.king_middle_table
}
table = tables.get(piece.piece_type)
if not table:
return 0
# Flip square for black pieces
if piece.color == chess.WHITE:
idx = square
else:
row = 7 - (square // 8)
col = square % 8
idx = row * 8 + col
return table[idx]
def iterative_deepening_search(self, board: chess.Board, max_depth: int) -> Tuple[Optional[chess.Move], float]:
"""Perform iterative deepening search with time management"""
self.start_time = time.time()
self.timeout = False
self.nodes_searched = 0
best_move = None
best_value = -math.inf if board.turn == chess.WHITE else math.inf
# Try depths from 2 to max_depth
for depth in range(2, max_depth + 1):
if self.timeout:
break
print(f" Searching depth {depth}...")
# Search with current depth
try:
current_best_move, current_best_value = self.alpha_beta_search(
board, depth, -math.inf, math.inf, board.turn == chess.WHITE
)
if not self.timeout and current_best_move:
best_move = current_best_move
best_value = current_best_value
print(f" Depth {depth}: eval = {best_value:.1f}, nodes = {self.nodes_searched}")
# If we found a forced mate, no need to search deeper
if abs(best_value) > 90000:
print(f" Found forced mate, stopping search")
break
except Exception as e:
if not self.timeout:
print(f" Error at depth {depth}: {e}")
break
print(f" Search complete: {self.nodes_searched} nodes")
return best_move, best_value
def alpha_beta_search(self, board: chess.Board, depth: int, alpha: float, beta: float,
maximizing: bool, ply: int = 0) -> Tuple[Optional[chess.Move], float]:
"""Alpha-beta search with time management"""
# Check timeout every 1000 nodes
if self.nodes_searched % 1000 == 0 and self.check_timeout():
return None, 0
self.nodes_searched += 1
# Check for terminal conditions
if board.is_checkmate():
return None, -99999 if maximizing else 99999
if board.is_stalemate() or board.is_insufficient_material() or board.can_claim_threefold_repetition():
return None, 0
# Leaf node - use quiescence search or evaluation
if depth == 0:
return None, self.quiescence(board, alpha, beta, maximizing)
# Generate and order moves
moves = self.order_moves(board, ply, depth==1)
best_move = None
best_value = -math.inf if maximizing else math.inf
for move in moves:
# Check timeout
if self.timeout:
break
board.push(move)
# Recursive search
_, move_value = self.alpha_beta_search(board, depth - 1, alpha, beta, not maximizing, ply + 1)
board.pop()
if self.timeout:
break
if maximizing:
if move_value > best_value:
best_value = move_value
best_move = move
alpha = max(alpha, best_value)
else:
if move_value < best_value:
best_value = move_value
best_move = move
beta = min(beta, best_value)
# Alpha-beta cutoff
if beta <= alpha:
# Store killer move
if not board.is_capture(move):
if move != self.killer_moves[ply][0]:
self.killer_moves[ply][1] = self.killer_moves[ply][0]
self.killer_moves[ply][0] = move
break
return best_move, best_value if best_value != -math.inf and best_value != math.inf else 0
def quiescence(self, board: chess.Board, alpha: float, beta: float, maximizing: bool) -> float:
"""Quiescence search to evaluate only capture moves"""
# Static evaluation
stand_pat = self.evaluate_position(board)
if maximizing:
if stand_pat >= beta:
return beta
if stand_pat > alpha:
alpha = stand_pat
else:
if stand_pat <= alpha:
return alpha
if stand_pat < beta:
beta = stand_pat
# Generate capture moves only
capture_moves = []
for move in board.legal_moves:
if board.is_capture(move):
capture_moves.append(move)
# Sort captures by expected gain (MVV-LVA)
capture_moves.sort(key=lambda m: self.estimate_capture_value(board, m), reverse=True)
for move in capture_moves:
# Check timeout
if self.check_timeout():
break
# Delta pruning - skip obviously bad captures
if self.delta_pruning(board, move, stand_pat, alpha, beta, maximizing):
continue
board.push(move)
score = -self.quiescence(board, -beta, -alpha, not maximizing)
board.pop()
if maximizing:
if score >= beta:
return beta
if score > alpha:
alpha = score
else:
if score <= alpha:
return alpha
if score < beta:
beta = score
return alpha if maximizing else beta
def estimate_capture_value(self, board: chess.Board, move: chess.Move) -> int:
"""Estimate capture value for move ordering (MVV-LVA)"""
if not board.is_capture(move):
return 0
victim = board.piece_at(move.to_square)
aggressor = board.piece_at(move.from_square)
if not victim or not aggressor:
return 0
victim_value = self.piece_values.get(victim.piece_type, 0)
aggressor_value = self.piece_values.get(aggressor.piece_type, 0)
return victim_value * 10 - aggressor_value
def delta_pruning(self, board: chess.Board, move: chess.Move, stand_pat: float,
alpha: float, beta: float, maximizing: bool) -> bool:
"""Delta pruning - skip captures that can't improve alpha"""
if not board.is_capture(move):
return False
victim = board.piece_at(move.to_square)
if not victim:
return False
victim_value = self.piece_values.get(victim.piece_type, 0)
# If capturing the victim can't raise alpha, skip it
if maximizing:
if stand_pat + victim_value + 100 < alpha: # Small margin
return True
else:
if stand_pat - victim_value - 100 > beta: # Small margin
return True
return False
def order_moves(self, board: chess.Board, ply: int = 0, is_qsearch: bool = False) -> List[chess.Move]:
"""Order moves for better alpha-beta pruning"""
moves = list(board.legal_moves)
# If very few moves or at leaf of quiescence search, don't spend time sorting
if len(moves) <= 3 or (is_qsearch and len(moves) <= 5):
return moves
move_scores = []
for move in moves:
score = 0
# 1. Captures (ordered by MVV-LVA)
if board.is_capture(move):
score = 10000 + self.estimate_capture_value(board, move)
# 2. Promotions
elif move.promotion:
score = 9000 + self.piece_values.get(move.promotion, 0)
# 3. Killer moves (only in main search)
elif not is_qsearch:
if self.killer_moves[ply][0] == move:
score = 8000
elif self.killer_moves[ply][1] == move:
score = 7000
# 4. Simple positional bonuses
piece = board.piece_at(move.from_square)
if piece:
# Center control
to_file = chess.square_file(move.to_square)
to_rank = chess.square_rank(move.to_square)
# Bonus for moving to center
if 2 <= to_file <= 5 and 2 <= to_rank <= 5:
score += 50
# Bonus for developing pieces early
if board.fullmove_number < 10:
if piece.piece_type in [chess.KNIGHT, chess.BISHOP]:
if move.from_square in [chess.B1, chess.G1, chess.B8, chess.G8,
chess.C1, chess.F1, chess.C8, chess.F8]:
score += 100
# Bonus for castling
if board.is_castling(move):
score += 300
move_scores.append((move, score))
# Sort by score (highest first)
move_scores.sort(key=lambda x: x[1], reverse=True)
return [move for move, _ in move_scores]
def get_best_move(self, fen: str) -> Tuple[Optional[str], Optional[str], Optional[str]]:
"""Get the best move for given position with time management"""
try:
board = chess.Board(fen)
if board.is_game_over():
return None, None, None
print(f"🤖 Engine thinking (difficulty: {self.difficulty}, time limit: {self.max_time}s)...")
# Determine search depth based on position complexity
legal_moves = list(board.legal_moves)
# Adjust depth based on position complexity
if len(legal_moves) > 40: # Very complex position
effective_depth = min(self.target_depth - 1, 4)
elif len(legal_moves) > 25: # Complex position
effective_depth = min(self.target_depth, 5)
else: # Normal position
effective_depth = self.target_depth
print(f" Position has {len(legal_moves)} legal moves, searching to depth {effective_depth}")
# Perform iterative deepening search
best_move, eval_score = self.iterative_deepening_search(board, effective_depth)
# If search timed out or found no move, use fallback
if not best_move or self.timeout:
print(f" {'Timeout' if self.timeout else 'No move found'}, using best available move")
# Try to use the last best move found
if not best_move:
# Order moves and pick the best one based on simple evaluation
ordered_moves = self.order_moves(board)
# Evaluate first few moves quickly
best_score = -math.inf if board.turn == chess.WHITE else math.inf
for move in ordered_moves[:5]: # Only check first 5
board.push(move)
score = self.evaluate_position(board)
board.pop()
if (board.turn == chess.WHITE and score > best_score) or \
(board.turn == chess.BLACK and score < best_score):
best_score = score
best_move = move
# Final fallback: random move
if not best_move:
print(" Using random move as fallback")
best_move = random.choice(list(board.legal_moves))
# Convert move to string format
from_sq = chess.square_name(best_move.from_square)
to_sq = chess.square_name(best_move.to_square)
promotion = chess.piece_symbol(best_move.promotion).lower() if best_move.promotion else None
elapsed = time.time() - self.start_time if self.start_time else 0
print(f"✅ Engine move: {from_sq} -> {to_sq} (eval: {eval_score:.1f}, time: {elapsed:.1f}s)")
return from_sq, to_sq, promotion
except Exception as e:
print(f"❌ Engine error: {e}")
import traceback
traceback.print_exc()
# Last resort: random move
try:
board = chess.Board(fen)
if board.is_game_over():
return None, None, None
move = random.choice(list(board.legal_moves))
from_sq = chess.square_name(move.from_square)
to_sq = chess.square_name(move.to_square)
promotion = chess.piece_symbol(move.promotion).lower() if move.promotion else None
return from_sq, to_sq, promotion
except:
return None, None, None
# Test the engine
if __name__ == "__main__":
print("🧪 Testing Optimized Chess Engine...")
# Test each difficulty level
for difficulty in range(1, 5):
print(f"\n{'='*50}")
print(f"Testing difficulty {difficulty}")
print('='*50)
engine = StrongChessEngine(difficulty=difficulty)
# Test with starting position
test_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
start_time = time.time()
result = engine.get_best_move(test_fen)
elapsed = time.time() - start_time
if result[0]:
print(f"✅ Difficulty {difficulty}: {result[0]} -> {result[1]} (time: {elapsed:.1f}s)")
if elapsed > engine.max_time * 1.1: # 10% tolerance
print(f"⚠️ Warning: Slightly over time limit ({elapsed:.1f}s > {engine.max_time}s)")
else:
print(f"❌ Difficulty {difficulty} test failed")requirements.txt
Flask==2.3.3
python-chess==1.999
numpy>=1.21.0
gunicorn==20.1.0
💻 For Windows (Command Prompt or PowerShell)
Open your terminal in your project folder and run:
# 1. Create the virtual environment (named '.venv')
python -m venv .venv
# 2. Activate the environment
.venv\Scripts\activate
# 3. Install requirements
pip install -r requirements.txt
🍎 For macOS or Linux
Open your terminal in your project folder and run:
python3 -m venv .venv
# 2. Activate the environment
source .venv/bin/activate
# 3. Install requirements
pip install -r requirements.txt
🔍 Key Components Explained
python -m venv .venv: This creates a folder named.venvin your project directory. This folder contains a "clean" copy of Python.activate: This tells your computer to use the Python inside the.venvfolder instead of the global system Python. You will usually see(.venv)appear in parentheses at the start of your command prompt line once it's active.requirements.txt: This is a text file that lists all the packages your project needs (e.g.,livekit,speech_recognition,python-whois).
💡 Tips for VS Code
After you activate the environment in your terminal, you must also tell VS Code to use it:
Press
Ctrl + Shift + P.Type "Python: Select Interpreter".
Choose the one that points to your new
.venvfolder.


0 Comments