Mårten Wiman

Author
Mårten Wiman

Blogs
From dorm rooms to boardrooms, Marten has built a career connecting young talent to opportunity. Their writing brings fresh, student-centric views on tech hiring and early careers.
author’s Articles

Insights & Stories by Mårten Wiman

Marten Wiman explores what today’s grads want from work—and how recruiters can meet them halfway. Expect a mix of optimism, strategy, and sharp tips.
Clear all
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Filter
Filter

How to approach HackerEarth bot challenges

The bot challenges on HackerEarth have so far all been for two players, with perfect information and no randomness. As such, there are primarily two effective strategies for building bots, which are covered in this tutorial.

Alpha-beta Pruning

This intuitive approach builds on the idea that the best move is one that puts your opponent in the worst position. By extending this logic several moves into the future, we arrive at the Minimax or Negamax algorithm.

function negamax(state, depth):
    if depth == 0:
        return [state.evaluate(), null]
    bestScore = -infinity
    for move in state.getMoves():
        moveScore = -negamax(state.makeMove(move), depth-1)[0]
        if moveScore > bestScore:
            bestScore = moveScore
            bestMove = move
    return [bestScore, bestMove]

Evaluation Functions

An evaluation function estimates how favorable a position is. It returns a large positive value if good for player A, and a negative value if unfavorable. For example, in Reversi - Bot Challenge #5, a simple evaluation function could compute the number of valid moves for A minus those for B. More advanced functions could consider positional advantages like corners.

While complex evaluation functions offer more accuracy, they are harder to write, debug, tune, and evaluate quickly, possibly reducing search depth. It's best to start simple and improve incrementally, testing changes thoroughly.

Pruning

Alpha-beta pruning improves Minimax by skipping branches that won't affect the final decision. For instance, if move m1 leads to a best-case outcome of x1, and move m2 has at least one possible counter resulting in worse than x1, we can discard m2 early without further evaluation.

function alphabeta(state, depth, alpha, beta):
    if depth == 0:
        return [state.evaluate(), null]
    bestScore = -infinity
    for move in state.getMoves():
        moveScore = -alphabeta(state.makeMove(move), depth-1, -beta, -alpha)[0]
        if moveScore > bestScore:
            bestScore = moveScore
            bestMove = move
        alpha = max(alpha, moveScore)
        if alpha >= beta:
            break
    return [bestScore, bestMove]

Alpha-beta pruning is most effective when the best moves are searched first. Optimal ordering can double the search depth compared to Minimax. Even with random move ordering, it's significantly more efficient.

Monte Carlo Tree Search (MCTS)

MCTS is another popular strategy and doesn't require an evaluation function. Instead, it uses random simulations to estimate move quality. Each simulation has two phases: an informed selection phase followed by random moves.

To guide move selection, MCTS uses a formula balancing exploration and exploitation. It favors moves with high win ratios but ensures all moves are eventually explored:

function MCTS(state):
    if state.numberOfSimulations == 0:
        play rest of game randomly
        state.numberOfSimulations += 1
        if current player won:
            return "won"
        else:
            state.wonGames += 1
            return "lost"
    bestScore = -infinity
    for move in state.getMoves():
        childState = state.makeMove(move)
        if childState.numberOfSimulations == 0:
            moveScore = infinity
        else:
            moveScore = (childState.wonGames / childState.numberOfSimulations) +
                        sqrt(2 * log(state.numberOfSimulations) / childState.numberOfSimulations)
        if moveScore > bestScore:
            bestScore = moveScore
            bestMove = move
    simulationResult = MCTS(state.makeMove(bestMove))
    state.numberOfSimulations += 1
    if simulationResult == "won":
        state.wonGames += 1
        return "lost"
    else:
        return "won"

After running MCTS from the root many times, pick the move with the best win ratio or the most simulations. MCTS is better for games with complex dynamics (e.g., Go), where good evaluation functions are hard to design. Alpha-beta is preferred when strong evaluation heuristics are available.

Conclusion

This tutorial introduced alpha-beta pruning and Monte Carlo Tree Search, the two main bot strategies in two-player games. Many improvements exist for both, particularly alpha-beta pruning. For more, explore the Chess Programming Wiki, which contains techniques applicable across many games.

Interested in building bots? Participate in the #UNITEDBYHCL Hackathon and win a trip to Theater of Dreams, Old Trafford and prizes worth $10,000.

Register Now!

To learn more about Battle of Bots, read this article by Gaurav Sen, third place finisher in Battle of Bots 7.