@@ -43,6 +43,18 @@ const OPENING_BOOK = {
4343
4444 // Early game center control patterns
4545 '3132113122' : 2 , '3132413242' : 4 , '3132513252' : 4 , '3132213222' : 2 ,
46+
47+ // Edge defense - Human opens on column 0
48+ '0131' : 0 , // Contest the edge!
49+ '013101' : 0 , // Continue contesting
50+
51+ // Edge defense - Human opens on column 6
52+ '6131' : 6 , // Contest the edge!
53+ '613161' : 6 , // Continue contesting
54+
55+ // Block 3-stacks on edges
56+ '010111' : 0 , // Block left edge 3-stack
57+ '616161' : 6 , // Block right edge 3-stack
4658} ;
4759const MAX_OPENING_MOVES = 15 ; // Use opening book for first 15 moves (7-8 ply per side)
4860
@@ -559,6 +571,38 @@ GameState.prototype.countPotentialLines = function(player) {
559571 return lines ;
560572}
561573
574+ // Detect threats building on edges (columns 0, 1, 5, 6)
575+ GameState . prototype . detectEdgeThreats = function ( player ) {
576+ let threats = 0 ;
577+ const edgeCols = [ 0 , 1 , 5 , 6 ] ;
578+
579+ for ( const col of edgeCols ) {
580+ const height = this . bitboard . heights [ col ] ;
581+
582+ // Check vertical stacking - count consecutive pieces from the top
583+ if ( height >= 2 ) {
584+ let consecutive = 0 ;
585+ let maxConsecutive = 0 ;
586+
587+ for ( let row = 0 ; row < height ; row ++ ) {
588+ if ( this . board [ col ] [ row ] === player ) {
589+ consecutive ++ ;
590+ maxConsecutive = Math . max ( maxConsecutive , consecutive ) ;
591+ } else {
592+ consecutive = 0 ;
593+ }
594+ }
595+
596+ // Threat if there are 2+ consecutive pieces and room to grow
597+ if ( maxConsecutive >= 2 && height < TOTAL_ROWS ) {
598+ threats ++ ;
599+ }
600+ }
601+ }
602+
603+ return threats ;
604+ } ;
605+
562606// Comprehensive evaluation for near-perfect play
563607GameState . prototype . advancedEvaluate = function ( player ) {
564608 const opponent = player === 1 ? 2 : 1 ;
@@ -605,6 +649,12 @@ GameState.prototype.advancedEvaluate = function(player) {
605649 score += this . countPotentialLines ( 2 ) * AI_CONFIG . MOBILITY_WEIGHT ;
606650 score -= this . countPotentialLines ( 1 ) * AI_CONFIG . MOBILITY_WEIGHT ;
607651
652+ // 7. Edge threat detection
653+ const aiEdgeThreats = this . detectEdgeThreats ( 2 ) ;
654+ const humanEdgeThreats = this . detectEdgeThreats ( 1 ) ;
655+ score += aiEdgeThreats * 400 ;
656+ score -= humanEdgeThreats * 600 ; // Weight human edge threats higher (defensive)
657+
608658 return score ;
609659}
610660
@@ -644,37 +694,40 @@ function orderMoves(node, depth, ttBestMove) {
644694 const moveScores = moves . map ( col => {
645695 let score = 0 ;
646696
647- // 1. TT move has highest priority (10000)
697+ // 1. Immediate win detection (HIGHEST PRIORITY - always try winning moves first)
698+ const testState = new GameState ( node ) ;
699+ testState . makeMove ( 2 , col ) ;
700+ if ( testState . isWin ( ) && testState . score === COMPUTER_WIN_SCORE ) {
701+ score += 100000 ;
702+ }
703+
704+ // 2. Block opponent's immediate win (CRITICAL - must block threats)
705+ const blockState = new GameState ( node ) ;
706+ blockState . makeMove ( 1 , col ) ;
707+ if ( blockState . isWin ( ) && blockState . score === HUMAN_WIN_SCORE ) {
708+ score += 90000 ;
709+ }
710+
711+ // 3. TT move (good move from previous search)
648712 if ( col === ttBestMove ) {
649- score += 10000 ;
713+ score += 5000 ;
650714 }
651715
652- // 2 . Killer moves (900 and 800)
716+ // 4 . Killer moves (900 and 800)
653717 if ( depth < killerMoves . length ) {
654718 if ( col === killerMoves [ depth ] [ 0 ] ) score += 900 ;
655719 if ( col === killerMoves [ depth ] [ 1 ] ) score += 800 ;
656720 }
657721
658- // 3 . History heuristic
722+ // 5 . History heuristic
659723 score += historyTable [ col ] || 0 ;
660724
661- // 4 . Center preference (positional bonus)
662- const centerBonus = [ 10 , 20 , 30 , 40 , 30 , 20 , 10 ] ;
725+ // 6 . Center preference (reduced bonus to avoid over-prioritization )
726+ const centerBonus = [ 5 , 10 , 15 , 20 , 15 , 10 , 5 ] ;
663727 score += centerBonus [ col ] ;
664728
665- // 5. Immediate win detection (should be tried first after TT)
666- const testState = new GameState ( node ) ;
667- testState . makeMove ( 2 , col ) ;
668- if ( testState . isWin ( ) && testState . score === COMPUTER_WIN_SCORE ) {
669- score += 50000 ; // Even higher than TT move - always try winning moves first
670- }
671-
672- // 6. Block opponent's immediate win
673- const blockState = new GameState ( node ) ;
674- blockState . makeMove ( 1 , col ) ;
675- if ( blockState . isWin ( ) && blockState . score === HUMAN_WIN_SCORE ) {
676- score += 8000 ; // High priority but below winning move
677- }
729+ // 7. Threat prevention evaluation (reuse states to optimize performance)
730+ score += evaluateThreatPrevention ( node , col , blockState , testState ) ;
678731
679732 return { col, score } ;
680733 } ) ;
@@ -685,6 +738,49 @@ function orderMoves(node, depth, ttBestMove) {
685738 return moveScores . map ( m => m . col ) ;
686739}
687740
741+ // ============================================================================
742+ // THREAT PREVENTION EVALUATION
743+ // ============================================================================
744+ // Evaluate how important it is to play in this column to prevent opponent threats
745+ // This is a lighter-weight version that reuses states already created in orderMoves
746+ function evaluateThreatPrevention ( node , col , blockState , testState ) {
747+ let score = 0 ;
748+ const row = node . bitboard . heights [ col ] ;
749+ if ( row >= TOTAL_ROWS ) return 0 ;
750+
751+ // Reuse blockState if provided, otherwise create it
752+ if ( ! blockState ) {
753+ blockState = new GameState ( node ) ;
754+ blockState . makeMove ( 1 , col ) ; // Human moves here
755+ }
756+
757+ // Check if this creates a 3-in-a-row threat for opponent
758+ const threatsAfter = blockState . countThreats ( 1 , 3 ) ;
759+ if ( threatsAfter > 0 ) {
760+ score += 3000 * threatsAfter ;
761+ }
762+
763+ // Check for potential double-threat setup
764+ const doubleThreatsAfter = blockState . countDoubleThreats ( 1 ) ;
765+ if ( doubleThreatsAfter > 0 ) {
766+ score += 7000 ;
767+ }
768+
769+ // Reuse testState if provided, otherwise create it
770+ if ( ! testState ) {
771+ testState = new GameState ( node ) ;
772+ testState . makeMove ( 2 , col ) ;
773+ }
774+
775+ // Also reward moves that create threats for AI
776+ const aiThreats = testState . countThreats ( 2 , 3 ) ;
777+ if ( aiThreats > 0 ) {
778+ score += 2000 * aiThreats ;
779+ }
780+
781+ return score ;
782+ }
783+
688784// listen for messages from the main thread
689785self . addEventListener ( 'message' , function ( e ) {
690786 switch ( e . data . messageType ) {
0 commit comments