How to create easily a flask chess web game

 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:

# 1. Create the virtual environment (named '.venv')
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 .venv in your project directory. This folder contains a "clean" copy of Python.

  • activate: This tells your computer to use the Python inside the .venv folder 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:

  1. Press Ctrl + Shift + P.

  2. Type "Python: Select Interpreter".

  3. Choose the one that points to your new .venv folder.

Next you are ready to go your first flask web game.You can deploy your webapp on your hosted site


Post a Comment

0 Comments