go command, it returns a score representing the game-theoretic value of the position.
Score Meaning
Scores are shown from the current player’s perspective:Positive
Current player can force a win with perfect play
Zero
Game will end in a draw with perfect play from both sides
Negative
Opponent can force a win with perfect play
From README.md lines 71-74: Positive scores mean the current player wins, zero means draw, and negative means the opponent wins.
Score Magnitude
The absolute value of the score indicates how quickly a player can force the win:- Higher positive score = Faster win for current player
- Higher negative score = Faster loss for current player
- Score near zero = Game will last longer before conclusion
Example from README
- Column 3 (score 18): Best move — forces a win in 18 half-moves
- Column 2 (score 16): Also winning, but takes 16 half-moves
- Column 5 (score 16): Another winning option
- Column 4 (score -20): Losing move — opponent wins in 20 half-moves
- Columns 1, 6, 7 (score -18): All lose in 18 half-moves
Real Example Walkthrough
Let’s analyze the position after4433:
- If Player 1 plays column 3, they can force a win
- The win will occur within 18 half-moves (9 more full turns)
- This assumes both players play perfectly from here
How Scores Are Calculated
Marlin uses Negamax with alpha-beta pruning to compute scores (source/include/solver.hpp:1-39).Negamax Property
From source/include/solver.hpp:7-13:In a two-player zero-sum game:This is why the code negates scores when recursing (source/src/main.cpp:120):
- What’s good for me is bad for my opponent (and vice versa)
- The value of a position for player A = -(value for player B)
Score Return Values
From source/include/solver.hpp:50-57:Output Labels
Marlin translates numeric scores into human-readable labels (source/src/main.cpp:132-135):Immediate wins are detected before running the solver (source/src/main.cpp:93-98), providing instant results.
Score Units: Half-Moves
Scores represent half-moves (also called “ply” in game theory):- 1 half-move = 1 player makes 1 move
- 2 half-moves = 1 full turn (both players move once)
- 18 half-moves = 9 full turns
- Current player will win on their 9th move (after opponent’s 9th response)
Comparing Scores
When multiple moves are winning, choose based on:- Prefer positive over negative (obviously!)
- Among positive scores, prefer higher (faster win)
- Among negative scores, prefer higher (slower loss — more chances for opponent to blunder)
Example Decision
Node Count
The solver also reports how many positions were analyzed:- Search tree size
- Computational effort
- Effectiveness of alpha-beta pruning
Advanced: Alpha-Beta Bounds
From source/include/solver.hpp:26-27:When
- Alpha: The BEST score the current player is guaranteed (lower bound)
- Beta: The WORST score the opponent will allow (upper bound)
alpha >= beta, the solver can prune (skip) entire branches of the game tree, dramatically reducing the number of nodes analyzed.
Related Concepts
Negamax
Algorithm that simplifies minimax by always maximizing from current player’s view (source/include/solver.hpp:4-13)
Alpha-Beta Pruning
Optimization that skips branches that can’t affect the final decision (source/include/solver.hpp:15-29)
Transposition Table
Cache that stores previously computed positions to avoid redundant work (source/include/solver.hpp:89-90)
Move Ordering
Trying center columns first improves pruning effectiveness (source/include/solver.hpp:31-37)
Further Reading
- CLI Commands - How to use the
gocommand - Move Format - How to specify positions