Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 12, 2025

The AI only detected vertical stacking threats on edge columns [0,1,5,6], allowing humans to win easily by building 4-in-a-row on center columns (2,3,4). Win scores of ±4 also overlapped with evaluation scores reaching 50k, causing the AI to confuse strong positions with actual wins.

Core Fix: Vertical Threat Detection

// Before: Only edges
GameState.prototype.detectEdgeThreats = function(player) {
    const edgeCols = [0, 1, 5, 6];
    // ...
}

// After: All columns
GameState.prototype.detectVerticalThreats = function(player) {
    for (let col = 0; col < TOTAL_COLUMNS; col++) {
        // Count consecutive from stack top
        // 2+ = threat, 3+ = urgent
    }
}

Changes

  • Win scores: ±4 → ±1000000 to avoid overlap with evaluation scores (max 50k)
  • Vertical threats: Check all columns 0-6, not just edges
  • Forced moves: Added getForcedMove() to detect immediate wins/blocks before search
  • Search depth: Enforce minimum 12 plies
  • Early termination: Threshold 8k → 50k to prevent premature exit
  • Move selection: Deterministic center-preference [3,2,4,1,5,0,6] instead of random
  • Depth bonus: Win scores adjusted by depth (999980-1000000) to prefer faster wins
  • Threat weights: Increased 10x (DOUBLE_THREAT: 5k → 50k, THREAT: 500 → 5k)
  • Opening book: Added defensive patterns for center column stacks

Helper Functions

function isComputerWinScore(score) {
    return score >= COMPUTER_WIN_SCORE - AI_CONFIG.MAX_DEPTH;  // 999980-1000000
}

Test Coverage

Added 3 test cases for vertical threats on center columns (2,3,4).

Original prompt

Complete AI Optimization for Connect-4

Executive Summary

The AI has multiple critical bugs that allow humans to easily win. This PR must fix ALL issues to create a near-unbeatable AI.

Board Configuration

  • 7x7 board (TOTAL_COLUMNS = 7, TOTAL_ROWS = 7)
  • This is intentional and correct - do NOT change board size

Bug Evidence

In image1, the human won with a vertical 4-in-a-row on column 4 (center-adjacent column). The AI failed to block this obvious threat.


Critical Bugs to Fix

Bug #1: Win Scores Too Small (MOST CRITICAL)

File: Connect-4.js, Lines 4-5

// CURRENT (BROKEN):
const HUMAN_WIN_SCORE = -4;
const COMPUTER_WIN_SCORE = 4;

// FIX:
const HUMAN_WIN_SCORE = -1000000;
const COMPUTER_WIN_SCORE = 1000000;

Why: Current values overlap with evaluation scores (5000+), causing AI to confuse wins with good positions.


Bug #2: Random Move Selection

File: Connect-4.js, Lines 1143-1154

// CURRENT (BROKEN):
return candidates[Math.floor(Math.random() * candidates.length)];

// FIX: Deterministic tie-breaking (prefer center)
const preference = [3, 2, 4, 1, 5, 0, 6];
for (const col of preference) {
    if (candidates.includes(col)) return col;
}
return bestMove;

Why: Random selection causes inconsistent play when moves appear "equal".


Bug #3: Early Search Termination

File: Connect-4.js, Lines 924-927

// CURRENT (BROKEN):
if (Math.abs(bestScore) > 8000) {
    break;
}

// FIX: Remove this block entirely or change threshold
if (Math.abs(bestScore) > 50000) {  // Only exit for near-win positions
    break;
}

Why: Evaluation scores can exceed 8000 (DOUBLE_THREAT_WEIGHT = 5000), causing premature search termination.


Bug #4: Missing Forced Move Detection

File: Connect-4.js, Add new function before makeComputerMove()

// Check for forced moves (instant wins or required blocks)
function getForcedMove(gameState) {
    const validMoves = [];
    const winningMoves = [];
    const blockingMoves = [];
    
    for (let col = 0; col < TOTAL_COLUMNS; col++) {
        if (gameState.bitboard.heights[col] >= TOTAL_ROWS) continue;
        validMoves.push(col);
        
        // Check if AI can win immediately
        const winTest = new GameState(gameState);
        winTest.makeMove(2, col);
        if (winTest.isWin()) {
            winningMoves.push(col);
        }
        
        // Check if human would win if they play here
        const blockTest = new GameState(gameState);
        blockTest.makeMove(1, col);
        if (blockTest.isWin()) {
            blockingMoves.push(col);
        }
    }
    
    // Priority 1: Take immediate win
    if (winningMoves.length > 0) {
        return { col: winningMoves[0], type: 'win' };
    }
    
    // Priority 2: Block immediate threat (if only one blocking move)
    if (blockingMoves.length === 1) {
        return { col: blockingMoves[0], type: 'block' };
    }
    
    // Priority 3: Multiple threats = losing position, but still must block one
    if (blockingMoves.length > 1) {
        return { col: blockingMoves[0], type: 'desperate-block' };
    }
    
    // Priority 4: Only one valid move
    if (validMoves.length === 1) {
        return { col: validMoves[0], type: 'only-move' };
    }
    
    return null; // No forced move, use search
}

Update makeComputerMove() to use this:

function makeComputerMove(maxDepth) {
    let col;
    let isWinImminent = false;
    let isLossImminent = false;
    
    // Check for forced moves FIRST (before opening book)
    const forcedMove = getForcedMove(currentGameState);
    if (forcedMove) {
        col = forcedMove.col;
        if (forcedMove.type === 'win') {
            isWinImminent = true;
        } else if (forcedMove.type === 'desperate-block') {
            isLossImminent = true;
        }
    }
    
    // Then check opening book...
    if (col === undefined) {
        const boardKey = getBoardStateKey(currentGameState);
        // ... rest of opening book logic
    }
    
    // ... rest of function
}

Bug #5: Minimum Search Depth Not Enforced

File: Connect-4.js, Line 865

// CURRENT:
const actualMaxDepth = Math.min(maxDepth, AI_CONFIG.MAX_DEPTH);

// FIX: Enforce minimum depth
const MIN_SEARCH_DEPTH = 12;
const actualMaxDepth = Math.max(MIN_SEARCH_DEPTH, Math.min(maxDepth, AI_CONFIG.MAX_DEPTH));

Bug #6: Vertical Threat Detection Limited to Edge Columns

File: Connect-4.js, Lines 575-604

Replace detectEdgeThreats() with comprehensive vertical threat detection for ALL columns:

// Detect vertical stacking threats on ANY column
GameState.prototype.detectVerticalThreats = function(player) {
    let threats = 0;
    
    for (let col = 0; col < TOTAL_COLUMNS; col++) {
        const height = this.bitboard.heights[col];
        if (height < 2 || height >= TOTAL_ROWS) continue;
...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created as a result of the following prompt from Copilot chat.*
> # Complete AI Optimization for Connect-4
> 
> ## Executive Summary
> The AI has multiple critical bugs that allow humans to easily win. This PR must fix ALL issues to create a near-unbeatable AI.
> 
> ## Board Configuration
> - **7x7 board** (TOTAL_COLUMNS = 7, TOTAL_ROWS = 7)
> - This is intentional and correct - do NOT change board size
> 
> ## Bug Evidence
> In ![image1](image1), the human won with a **vertical 4-in-a-row on column 4** (center-adjacent column). The AI failed to block this obvious threat.
> 
> ---
> 
> ## Critical Bugs to Fix
> 
> ### Bug #1: Win Scores Too Small (MOST CRITICAL)
> **File**: `Connect-4.js`, Lines 4-5
> ```javascript
> // CURRENT (BROKEN):
> const HUMAN_WIN_SCORE = -4;
> const COMPUTER_WIN_SCORE = 4;
> 
> // FIX:
> const HUMAN_WIN_SCORE = -1000000;
> const COMPUTER_WIN_SCORE = 1000000;
> ```
> **Why**: Current values overlap with evaluation scores (5000+), causing AI to confuse wins with good positions.
> 
> ---
> 
> ### Bug #2: Random Move Selection
> **File**: `Connect-4.js`, Lines 1143-1154
> ```javascript
> // CURRENT (BROKEN):
> return candidates[Math.floor(Math.random() * candidates.length)];
> 
> // FIX: Deterministic tie-breaking (prefer center)
> const preference = [3, 2, 4, 1, 5, 0, 6];
> for (const col of preference) {
>     if (candidates.includes(col)) return col;
> }
> return bestMove;
> ```
> **Why**: Random selection causes inconsistent play when moves appear "equal".
> 
> ---
> 
> ### Bug #3: Early Search Termination
> **File**: `Connect-4.js`, Lines 924-927
> ```javascript
> // CURRENT (BROKEN):
> if (Math.abs(bestScore) > 8000) {
>     break;
> }
> 
> // FIX: Remove this block entirely or change threshold
> if (Math.abs(bestScore) > 50000) {  // Only exit for near-win positions
>     break;
> }
> ```
> **Why**: Evaluation scores can exceed 8000 (DOUBLE_THREAT_WEIGHT = 5000), causing premature search termination.
> 
> ---
> 
> ### Bug #4: Missing Forced Move Detection
> **File**: `Connect-4.js`, Add new function before `makeComputerMove()`
> ```javascript
> // Check for forced moves (instant wins or required blocks)
> function getForcedMove(gameState) {
>     const validMoves = [];
>     const winningMoves = [];
>     const blockingMoves = [];
>     
>     for (let col = 0; col < TOTAL_COLUMNS; col++) {
>         if (gameState.bitboard.heights[col] >= TOTAL_ROWS) continue;
>         validMoves.push(col);
>         
>         // Check if AI can win immediately
>         const winTest = new GameState(gameState);
>         winTest.makeMove(2, col);
>         if (winTest.isWin()) {
>             winningMoves.push(col);
>         }
>         
>         // Check if human would win if they play here
>         const blockTest = new GameState(gameState);
>         blockTest.makeMove(1, col);
>         if (blockTest.isWin()) {
>             blockingMoves.push(col);
>         }
>     }
>     
>     // Priority 1: Take immediate win
>     if (winningMoves.length > 0) {
>         return { col: winningMoves[0], type: 'win' };
>     }
>     
>     // Priority 2: Block immediate threat (if only one blocking move)
>     if (blockingMoves.length === 1) {
>         return { col: blockingMoves[0], type: 'block' };
>     }
>     
>     // Priority 3: Multiple threats = losing position, but still must block one
>     if (blockingMoves.length > 1) {
>         return { col: blockingMoves[0], type: 'desperate-block' };
>     }
>     
>     // Priority 4: Only one valid move
>     if (validMoves.length === 1) {
>         return { col: validMoves[0], type: 'only-move' };
>     }
>     
>     return null; // No forced move, use search
> }
> ```
> 
> Update `makeComputerMove()` to use this:
> ```javascript
> function makeComputerMove(maxDepth) {
>     let col;
>     let isWinImminent = false;
>     let isLossImminent = false;
>     
>     // Check for forced moves FIRST (before opening book)
>     const forcedMove = getForcedMove(currentGameState);
>     if (forcedMove) {
>         col = forcedMove.col;
>         if (forcedMove.type === 'win') {
>             isWinImminent = true;
>         } else if (forcedMove.type === 'desperate-block') {
>             isLossImminent = true;
>         }
>     }
>     
>     // Then check opening book...
>     if (col === undefined) {
>         const boardKey = getBoardStateKey(currentGameState);
>         // ... rest of opening book logic
>     }
>     
>     // ... rest of function
> }
> ```
> 
> ---
> 
> ### Bug #5: Minimum Search Depth Not Enforced
> **File**: `Connect-4.js`, Line 865
> ```javascript
> // CURRENT:
> const actualMaxDepth = Math.min(maxDepth, AI_CONFIG.MAX_DEPTH);
> 
> // FIX: Enforce minimum depth
> const MIN_SEARCH_DEPTH = 12;
> const actualMaxDepth = Math.max(MIN_SEARCH_DEPTH, Math.min(maxDepth, AI_CONFIG.MAX_DEPTH));
> ```
> 
> ---
> 
> ### Bug #6: Vertical Threat Detection Limited to Edge Columns
> **File**: `Connect-4.js`, Lines 575-604
> 
> Replace `detectEdgeThreats()` with comprehensive vertical threat detection for ALL columns:
> 
> ```javascript
> // Detect vertical stacking threats on ANY column
> GameState.prototype.detectVerticalThreats = function(player) {
>     let threats = 0;
>     
>     for (let col = 0; col < TOTAL_COLUMNS; col++) {
>         const height = this.bitboard.heights[col];
>         if (height < 2 || height >= TOTAL_ROWS) continue;
>         
>         // Count consecutive pieces from top of stack
>         let consecutive = 0;
>         for (let row = height - 1; row >= 0; row--) {
>             if (this.board[col][row] === player) {
>                 consecutive++;
>             } else {
>                 break; // Stop at first non-player piece
>             }
>         }
>         
>         // Threat if 2+ consecutive at top with room to grow
>         if (consecutive >= 2) {
>             threats++;
>             // Extra threat for 3 in a row (one move from winning)
>             if (consecutive >= 3) {
>                 threats += 2;
>             }
>         }
>     }
>     
>     return threats;
> };
> ```
> 
> Update `advancedEvaluate()` to use this new function:
> ```javascript
> // 7. Vertical threat detection (ALL columns, not just edges)
> const aiVerticalThreats = this.detectVerticalThreats(2);
> const humanVerticalThreats = this.detectVerticalThreats(1);
> score += aiVerticalThreats * 800;
> score -= humanVerticalThreats * 1200; // Weight human threats higher (defensive priority)
> ```
> 
> ---
> 
> ### Bug #7: Win Score Comparison in Search Loop
> **File**: `Connect-4.js`, Lines 903-912
> ```javascript
> // Update to use new win score values
> if (origin.score === HUMAN_WIN_SCORE) {  // Now -1000000
>     isLossImminent = true;
>     break;
> } else if (origin.score === COMPUTER_WIN_SCORE) {  // Now 1000000
>     col = tentativeCol;
>     isWinImminent = true;
>     break;
> }
> ```
> 
> ---
> 
> ### Bug #8: Evaluation Function Win Check
> **File**: `Connect-4.js`, Lines 611-612
> ```javascript
> // CURRENT:
> if (this.score === COMPUTER_WIN_SCORE) return 100000;
> if (this.score === HUMAN_WIN_SCORE) return -100000;
> 
> // FIX: Return the actual win scores (now they're large enough)
> if (this.score === COMPUTER_WIN_SCORE) return COMPUTER_WIN_SCORE;
> if (this.score === HUMAN_WIN_SCORE) return HUMAN_WIN_SCORE;
> ```
> 
> ---
> 
> ### Bug #9: Negamax Win Score Handling
> **File**: `Connect-4.js`, Lines 1028-1030
> ```javascript
> // CURRENT:
> if (childNode.isWin()) {
>     score = childNode.score;
> }
> 
> // FIX: Properly handle win scores relative to current player
> if (childNode.isWin()) {
>     // If the player who just moved won, it's good for them
>     // In negamax, we need to return the score from current player's perspective
>     score = childNode.score;
>     // Add depth bonus - prefer faster wins
>     if (score > 0) {
>         score = score - recursionsRemaining; // Prefer faster AI wins
>     } else {
>         score = score + recursionsRemaining; // Prefer slower human wins (delay loss)
>     }
> }
> ```
> 
> ---
> 
> ## Additional Optimizations
> 
> ### Optimization #1: Increase AI Config Weights
> ```javascript
> const AI_CONFIG = {
>     MAX_DEPTH: 20,
>     MAX_TIME: 5000,
>     // ...
>     DOUBLE_THREAT_WEIGHT: 50000,  // Increased from 5000
>     THREAT_WEIGHT: 5000,          // Increased from 500
>     POTENTIAL_THREAT_WEIGHT: 500, // Increased from 50
>     CENTER_WEIGHT: 200,           // Increased from 100
>     ODD_EVEN_WEIGHT: 600,         // Increased from 300
>     MOBILITY_WEIGHT: 20,          // Increased from 10
>     VERTICAL_THREAT_WEIGHT: 1500, // New
> };
> ```
> 
> ### Optimization #2: Better Opening Book
> Add more defensive patterns:
> ```javascript
> const OPENING_BOOK = {
>     // ... existing entries ...
>     
>     // Vertical stacking defense (any column)
>     '4142': 4,       // Block column 4 stack
>     '414241': 4,     // Continue blocking column 4
>     '2122': 2,       // Block column 2 stack
>     '5152': 5,       // Block column 5 stack
>     
>     // Human stacks center - MUST respond
>     '3132': 3,       // Stack center with them
>     '313231': 3,     // Continue center control
>     '31323132': 3,   // Keep stacking center
> };
> ```
> 
> ---
> 
> ## Testing Requirements
> 
> After implementing fixes, verify:
> 1. [ ] AI takes immediate wins (all columns)
> 2. [ ] AI blocks immediate threats (all columns, not just edges)
> 3. [ ] AI blocks vertical stacking on column 4 (the screenshot scenario)
> 4. [ ] AI never makes random moves when one is clearly better
> 5. [ ] Search completes to minimum depth 12
> 6. [ ] Win detection works correctly (scores are ±1000000)
> 7. [ ] All existing tests in `ai-tests.js` still pass
> 
> ## Files to Modify
> 1. `Connect-4.js` - All the fixes above
> 2. `ai-tests.js` - Add new test cases for vertical threats on center columns
> 
> ---
> 
> ## Summary of Changes
> 
> | Issue | Current Value | Fixed Value |
> |-------|--------------|-------------|
> | HUMAN_WIN_SCORE | -4 | -1000000 |
> | COMPUTER_WIN_SCORE | 4 | 1000000 |
> | Early exit threshold | 8000 | 50000 |
> | Minimum search depth | None | 12 |
> | Move selection | Random | Deterministic |
> | Vertical detection | Edge only | All columns |
> | Forced move detection | None | Implemented |
> 

<!-- START COPILOT CODING AGENT TIPS -->
---

 Let Copilot coding agent [set things up for you](https://github.com/CodeKunalTomar/OptiConnect/issues/new?title=+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)  coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link

vercel bot commented Dec 12, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
opticonnect Ready Ready Preview Comment Dec 12, 2025 1:29pm

Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Co-authored-by: CodeKunalTomar <111980003+CodeKunalTomar@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix critical bugs in Connect-4 AI optimization Fix AI vertical threat detection blind spot on center columns Dec 12, 2025
Copilot AI requested a review from CodeKunalTomar December 12, 2025 13:31
@CodeKunalTomar CodeKunalTomar marked this pull request as ready for review December 12, 2025 13:32
Copilot AI review requested due to automatic review settings December 12, 2025 13:32
@CodeKunalTomar CodeKunalTomar merged commit 90e85c0 into main Dec 12, 2025
2 checks passed
@CodeKunalTomar CodeKunalTomar deleted the copilot/fix-connect4-ai-bugs branch December 12, 2025 13:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes a critical bug where the AI failed to detect vertical stacking threats on center columns (2, 3, 4), only monitoring edge columns (0, 1, 5, 6). The fix comprehensively improves the AI by increasing win score values to avoid overlap with evaluation scores, implementing forced move detection, replacing random move selection with deterministic tie-breaking, and enhancing threat detection across all columns.

Key changes:

  • Win scores increased from ±4 to ±1,000,000 to prevent confusion with evaluation scores (which can reach 50,000+)
  • Vertical threat detection expanded from edge columns only to all columns (0-6)
  • Added forced move detection for immediate wins/blocks before consulting opening book or search

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
Connect-4.js Core AI fixes including win score constants, vertical threat detection for all columns, forced move detection, deterministic tie-breaking, depth bonuses, and enhanced opening book entries
ai-tests.js Added three test cases specifically for vertical threat detection on center columns 2, 3, and 4 to verify the fix
Comments suppressed due to low confidence (16)

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

Connect-4.js:36

    '313241': 2, '313251': 4, '313211': 2, '313221': 2, '313261': 4,

Connect-4.js:36

    '313241': 2, '313251': 4, '313211': 2, '313221': 2, '313261': 4,

Connect-4.js:36

    '313241': 2, '313251': 4, '313211': 2, '313221': 2, '313261': 4,

Connect-4.js:36

    '313241': 2, '313251': 4, '313211': 2, '313221': 2, '313261': 4,

Connect-4.js:42

    '313231': 3, // Continue center dominance

Connect-4.js:1076

  • This expression assigns property score to itself.
            node.score = node.score;

ai-tests.js:176

  • Variable currentGameState is used like a local variable, but is missing a declaration.
        currentGameState = new GameState();

Connect-4.js:470

  • Unused variable opponent.
    const opponent = player === 1 ? 2 : 1;

Connect-4.js:500

  • Unused variable evenRows.
    const evenRows = [1, 3, 5];

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

Connect-4.js:30

    '3132': 3, '0132': 3, '1132': 2, '2132': 2, '4132': 4, '5132': 4, '6132': 3,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +61 to +63
'414241': 4, // Continue blocking column 4
'2122': 2, // Block column 2 stack
'5152': 5, // Block column 5 stack
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opening book entry '414241': 4 represents a game sequence where AI plays column 4 in response to human's first move on column 4. However, the opening book already defines that when human plays column 4 first ('41'), AI should respond with column 3 (line 27). This means the sequence '414241' would never occur if the AI follows its own opening book, making this entry unreachable dead code. Similar logic applies to entries '2122' and '5152' on lines 62-63.

Suggested change
'414241': 4, // Continue blocking column 4
'2122': 2, // Block column 2 stack
'5152': 5, // Block column 5 stack

Copilot uses AI. Check for mistakes.
// Human stacks center - AI contests by stacking same column
'3132': 3, // Contest center column
'313231': 3, // Continue center control
'31323132': 3, // Keep stacking center
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opening book entry '31323132': 3 on line 68 represents a sequence where human and AI alternate playing column 3 four times. However, this is an 8-character key representing 4 moves, which means it would only be consulted after move 4. But looking at the pattern: after '3132' (line 30/66), AI plays 3. Then if human plays 3 again, the board key is '313231', and the book says AI plays 3 (line 42/67). Then if human plays 3 again, the key would be '31323132', which is this entry. However, at this point, the column would likely be getting full (4 pieces in a 7-row board), and this represents a very specific scenario. Consider whether this entry adds value or if the search would handle this adequately.

Suggested change
'31323132': 3, // Keep stacking center

Copilot uses AI. Check for mistakes.
if (score === COMPUTER_WIN_SCORE) {
score = score - recursionsRemaining; // Prefer faster AI wins
} else if (score === HUMAN_WIN_SCORE) {
score = score + recursionsRemaining; // Prefer slower human wins (delay loss)
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "prefer slower human wins (delay loss)" but the actual effect is achieved by adding recursionsRemaining to HUMAN_WIN_SCORE, which makes shallower human wins more negative (worse from AI's perspective). The comment is correct in intent but could be clearer. Consider rephrasing to: "Make immediate human wins worse (more negative) so AI prefers positions where losses are delayed".

Suggested change
score = score + recursionsRemaining; // Prefer slower human wins (delay loss)
score = score + recursionsRemaining; // Make immediate human wins worse (more negative) so AI prefers positions where losses are delayed

Copilot uses AI. Check for mistakes.
'5152': 5, // Block column 5 stack

// Human stacks center - AI contests by stacking same column
'3132': 3, // Contest center column
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opening book has duplicate key '3132' with different intended behaviors. Line 30 assigns it to respond to "human center (31), AI center (32)" scenario, while line 66 has a comment "Human stacks center - AI contests by stacking same column" with the same key. Both map to column 3, but the duplicate entry suggests confusion about what this key represents. The key '3132' means moves 31 (human column 3) and 32 (AI column 3), so lines 30 and 66 represent the same board state and should not be duplicated.

Suggested change
'3132': 3, // Contest center column

Copilot uses AI. Check for mistakes.

// Human stacks center - AI contests by stacking same column
'3132': 3, // Contest center column
'313231': 3, // Continue center control
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The opening book has duplicate key '313231' on lines 42 and 67. Both entries map to column 3 but are listed under different comment sections. This duplication is unnecessary and makes the opening book harder to maintain. Consider removing one of these duplicate entries.

Suggested change
'313231': 3, // Continue center control

Copilot uses AI. Check for mistakes.
Comment on lines 1242 to +1244
if (childNodes[col] !== undefined &&
((player === 2 && childNodes[col].score === node.score) ||
(player === 1 && childNodes[col].score === node.score))) {
((player === 2 && childNodes[col].score === bestScore) ||
(player === 1 && childNodes[col].score === bestScore))) {
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tie-breaking logic compares childNodes[col].score with bestScore, but these scores are from different perspectives. The childNodes[col].score contains the score from the child node's perspective (set by recursive think() calls), while bestScore contains the negated score from the parent's perspective (computed as -childNode.score at line 1163). This comparison will not correctly identify tied moves. Consider storing the computed scores in a separate array during the search loop, or tracking which moves achieved the best score using a different mechanism.

Copilot uses AI. Check for mistakes.

// Comprehensive evaluation for near-perfect play
GameState.prototype.advancedEvaluate = function(player) {
const opponent = player === 1 ? 2 : 1;
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable opponent.

Suggested change
const opponent = player === 1 ? 2 : 1;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants