Python client for noprune chess bot platform - easily create custom chess systems
Project description
noprune
Build your own chess system in Python with just a few lines of code.
noprune is the official Python client for the noprune.org chess bot platform — where AI chess systems compete against each other in a fair, transparent rating system.
Vision
noprune is a platform where anyone can create, deploy, and compete with their own chess AI.
- No pruning, pure skill — Every system plays every position. No shortcuts.
- Fair matchmaking — Systems are matched by skill level (Turing rating)
- Open competition — Your bot competes 24/7 against others worldwide
- Learn by doing — Start with random moves, evolve to neural networks
Whether you're a beginner learning minimax or a researcher testing cutting-edge models, noprune provides the arena.
Installation
pip install noprune
Quick Start
Create a chess bot in 10 lines:
from noprune import Bot
import random
@Bot(system_id="your-id", secret="your-secret")
def my_bot(board):
# board is a python-chess Board object
moves = list(board.legal_moves)
return random.choice(moves).uci()
my_bot.run() # Connect and start playing!
That's it. Your bot will connect to noprune.org, join the matchmaking queue, and start playing games automatically.
Getting Your Credentials
- Sign up at noprune.org
- Create a new "System" (your bot)
- Copy your
system_idandsecret
Examples
Random Bot (Simplest)
from noprune import Bot
import random
@Bot(system_id="...", secret="...")
def random_bot(board):
return random.choice(list(board.legal_moves)).uci()
random_bot.run()
Time-Aware Bot (Using GameState)
from noprune import Bot, GameState
@Bot(system_id="...", secret="...")
def smart_bot(game: GameState): # Note: takes GameState, not board
# Access time information
if game.my_time_ms < 10000: # Less than 10 seconds
return quick_move(game.board)
# Access other game info
print(f"Move {game.move_number}, playing as {game.color}")
print(f"My time: {game.my_time_ms}ms, Opponent: {game.opponent_time_ms}ms")
return best_move(game.board)
smart_bot.run()
Stockfish Bot (Strong)
from noprune import Bot
import chess.engine
@Bot(system_id="...", secret="...")
def stockfish_bot(board):
with chess.engine.SimpleEngine.popen_uci("/usr/games/stockfish") as engine:
result = engine.play(board, chess.engine.Limit(time=0.1))
return result.move.uci()
stockfish_bot.run()
Full-Featured Bot (Class-based)
from noprune import Bot, GameState, GameResult, Action
class MyBot(Bot):
def on_game_start(self, game: GameState):
print(f"Game started vs {game.opponent}!")
print(f"Playing as {game.color}, time: {game.my_time_ms}ms")
def on_game_end(self, game: GameState, result: GameResult):
print(f"Game over: {result.result} ({result.reason})")
print(f"Winner: {result.winner}")
if result.pgn:
print(f"PGN: {result.pgn}")
def on_move(self, game: GameState, move: str, is_my_move: bool):
who = "I played" if is_my_move else "Opponent played"
print(f"{who}: {move}")
def on_draw_offer(self, game: GameState) -> bool:
# Accept draw if low on time
return game.my_time_ms < 5000
def on_draw_declined(self, game: GameState):
print("Draw offer declined!")
def on_opponent_disconnect(self, game: GameState):
print("Opponent disconnected, waiting...")
def on_opponent_reconnect(self, game: GameState):
print("Opponent reconnected!")
def on_error(self, code: str, message: str):
print(f"Error: {code} - {message}")
def think(self, game: GameState) -> str | Action:
# Your engine logic here
return "e2e4"
bot = MyBot(system_id="...", secret="...")
bot.run()
Resign & Draw Offers
from noprune import Bot, Action
@Bot(system_id="...", secret="...")
def smart_bot(board):
# Resign if losing badly
if is_losing(board):
return Action.RESIGN
# Offer draw in equal endgame
if is_drawn_endgame(board):
return Action.OFFER_DRAW
# Make a move AND offer draw
if should_offer_draw(board):
return Action.move_with_draw("e2e4")
return calculate_best_move(board)
API Reference
Bot
The main class for creating chess bots.
# Decorator style (board only)
@Bot(system_id="...", secret="...", server_url="wss://noprune.org/ws")
def my_bot(board):
return "e2e4"
# Decorator style (with game state)
@Bot(system_id="...", secret="...")
def my_bot(game: GameState):
return "e2e4"
# Class style
class MyBot(Bot):
def think(self, game: GameState):
return "e2e4"
Constructor parameters:
system_id— Your system ID from noprune.orgsecret— Your system secretserver_url— WebSocket URL (default:wss://noprune.org/ws)auto_queue— Auto-join queue after each game (default:True)
Methods to override:
think(game: GameState) -> str | Action— Calculate your move (required)on_game_start(game)— Called when game beginson_game_end(game, result: GameResult)— Called when game endson_move(game, move, is_my_move)— Called after each moveon_draw_offer(game) -> bool— Return True to accept drawon_draw_declined(game)— Your draw offer was declinedon_opponent_disconnect(game)— Opponent lost connectionon_opponent_reconnect(game)— Opponent reconnectedon_error(code, message)— Server error occurred
GameState
@dataclass
class GameState:
game_id: str # Unique game identifier
board: chess.Board # Current position (python-chess)
color: str # "white" or "black"
opponent: str # Opponent's name
opponent_id: str # Opponent's system ID
moves: list[str] # Move history in UCI format
my_time_ms: int # Your remaining time (ms)
opponent_time_ms: int # Opponent's remaining time (ms)
# Properties
is_my_turn: bool # Is it my turn?
move_number: int # Current move number (1-indexed)
ply: int # Total half-moves played
GameResult
@dataclass
class GameResult:
result: str # "1-0", "0-1", "1/2-1/2"
reason: str # "checkmate", "timeout", "resign", etc.
pgn: str # Full game PGN
# Properties
winner: str # "white", "black", or "draw"
Action
Action.RESIGN # Give up
Action.OFFER_DRAW # Propose a draw (without moving)
Action.move("e2e4") # Make a move (same as returning "e2e4")
Action.move_with_draw("e2e4") # Make a move AND offer draw
Environment Variables
export SYSTEM_ID="your-system-id"
export SYSTEM_SECRET="your-secret"
export SERVER_URL="wss://noprune.org/ws" # or ws://localhost:8086/ws
python my_bot.py
Why "noprune"?
In chess programming, "pruning" means skipping moves that seem bad. Top systems like Stockfish prune aggressively — they don't even consider most legal moves.
noprune is different. It's a place where any approach is welcome:
- Brute-force minimax? Great.
- Neural network evaluation? Awesome.
- Random moves? Sure, let's see how it does.
The name reminds us: there's no "wrong" way to build a chess system. Every approach teaches us something.
Links
- Website: noprune.org
- GitHub: github.com/noprune/noprune
- PyPI: pypi.org/project/noprune
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file noprune-0.2.0.tar.gz.
File metadata
- Download URL: noprune-0.2.0.tar.gz
- Upload date:
- Size: 9.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a88da4bbcc77b1542bb89e49a55defc409b3b28be7bfb4d76dc840075d8fc6c1
|
|
| MD5 |
9bc9d17481f683d68b57cb8c860c813b
|
|
| BLAKE2b-256 |
7e5984d738329fac57afba2c5affc0bb4a97876933791fba3e678b6615ed978f
|
File details
Details for the file noprune-0.2.0-py3-none-any.whl.
File metadata
- Download URL: noprune-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
437ee2566e71aac7fc119eab7ba43385ec6a989b40cc73dd0510d691f17227c3
|
|
| MD5 |
42e7bc243bee9c6027c23fe53246f4b1
|
|
| BLAKE2b-256 |
24a455391771ca14273519e8ad61dc549e4bafae1c80e46e8c349d52e61632bd
|