A high-performance Rust implementation of the Durak card game with Python bindings, designed for AI/ML research and development.
- High Performance: Core game logic implemented in Rust for maximum speed
- Python-Friendly API: Clean, intuitive Python interface for easy AI player development
- Gym-like Interface: Step-by-step API similar to OpenAI Gym for reinforcement learning
- Flexible Player System: Easy to implement custom AI players in Python
- Type Hints: Full type annotations for better IDE support and developer experience
# Install from source
pip install -e .
# Or build with maturin
maturin developfrom durak_rt import GameEnv, GamePlayer
import numpy as np
class MyPlayer(GamePlayer):
def choose_action(self, state, actions, history=None):
# Your AI logic here
return np.random.randint(len(actions))
# Create environment with your player
env = GameEnv(MyPlayer())
# Play a full game
rewards = env.play()
print(f"Rewards: {rewards}")from durak_rt import GameEnv, GamePlayer
class SimplePlayer(GamePlayer):
def choose_action(self, state, actions, history=None):
return 0 # Choose first action
env = GameEnv(SimplePlayer())
# Reset to initial state
state = env.reset()
# Step through the game
while not env.is_done():
legal_actions = env.get_legal_actions()
action_idx = 0 # Your action selection logic
observation, reward, done, info = env.step(action_idx)
if done:
break
# Get final results
rewards = env.get_rewards()
winner = env.get_winner()from durak_rt import GameEnv, GamePlayer
class Player1(GamePlayer):
def choose_action(self, state, actions, history=None):
# Player 1 logic
return 0
class Player2(GamePlayer):
def choose_action(self, state, actions, history=None):
# Player 2 logic
return 0
# Create environment with both players
env = GameEnv(Player1(), player2=Player2())
rewards = env.play()The main game environment class.
-
__init__(player1, player2=None, seed=None): Create a new game environmentplayer1: Required. AGamePlayerinstanceplayer2: Optional. AGamePlayerinstance. If None, uses a random playerseed: Optional. Random seed for reproducibility
-
reset(seed=None): Reset the game to initial state- Returns: Initial observable game state
-
step(action_index): Execute one game stepaction_index: Index of action fromget_legal_actions()- Returns:
(observation, reward, done, info)tuple
-
get_state(player=None): Get current observable game stateplayer: Optional player index (0 or 1). Defaults to current acting player- Returns: Observable game state
-
get_legal_actions(): Get list of legal actions for current state- Returns:
ActionListobject
- Returns:
-
is_done(): Check if game is over- Returns:
Trueif game is finished
- Returns:
-
get_rewards(): Get rewards for both players- Returns:
(player1_reward, player2_reward)tuple
- Returns:
-
get_winner(): Get the winner (if game is over)- Returns:
0for Player 1,1for Player 2, orNoneif no winner
- Returns:
-
play(): Play a full game to completion- Returns:
(player1_reward, player2_reward)tuple
- Returns:
num_actions(): Get total number of possible actionsstate_shape(): Get shape of game state numpy array
Base class for implementing game players. Subclass this and implement choose_action.
choose_action(state, actions, history=None): Choose an actionstate: CurrentObservableGameStateactions:ActionListof available actionshistory: Optional list of previous game states- Returns: Index of chosen action (integer)
Represents the game state from a player's perspective.
acting_player: Current acting player (0 or 1)player_hand: List of cards in player's handattack_table: List of cards on attack tabledefense_table: List of cards on defense tabledeck_size: Number of cards remaining in deckvisible_card: The visible trump carddefender_has_taken: Whether defender has taken cardsdefender: Current defender (0 or 1)cards_in_opp_hand: Number of cards in opponent's hand
to_numpy(): Convert state to numpy array for ML models
Container for available actions.
actions: List of action strings (e.g.,["Attack(6♠)", "StopAttack"])
to_indices(): Get action indicesto_bitmap(): Get action bitmap as numpy array__len__(): Number of available actions__getitem__(index): Get action at index
See python/durak_rt/examples.py for comprehensive examples including:
RandomPlayer: Random action selectionGreedyPlayer: Simple heuristic-based playerHumanPlayer: Interactive player for testing- Step-by-step game loop examples
- Full game examples
durak-rs/
├── src/
│ ├── game/ # Core game logic (Rust)
│ │ ├── actions.rs
│ │ ├── cards.rs
│ │ ├── game.rs
│ │ ├── gamestate.rs
│ │ └── player.rs
│ └── python/ # Python bindings (PyO3)
│ ├── env_py.rs
│ ├── player_py.rs
│ ├── actions_py.rs
│ └── gamestate_py.rs
├── python/
│ └── durak_rt/ # Python package
│ ├── __init__.py
│ ├── examples.py
│ └── ...
├── Cargo.toml
└── pyproject.toml
The project includes a web server and web application for playing Durak in a browser.
# Build and run the server
cargo run --bin server
# The server will start on http://localhost:3000- Start the server (see above)
- Open your browser and navigate to
http://localhost:3000 - Click "New Game" to create a game session
- Use the available action buttons to make moves
- The game state updates automatically
The web app provides:
- Visual card display with suit colors
- Real-time game state updates
- Legal action buttons
- Game status indicators
# Development build
maturin develop
# Release build
maturin build --release# Rust tests
cargo test
# Python tests (if available)
pytest- The core game logic runs in Rust for maximum performance
- Python callbacks (player actions) have minimal overhead
- State observations are efficiently converted to numpy arrays
- Consider batching multiple games for training AI models
Contributions are welcome! Please ensure:
- Code follows Rust and Python style guidelines
- All tests pass
- Documentation is updated
- Type hints are included for Python code
[Add your license here]
Built with PyO3 for Python-Rust interop.