WebAssembly Demo
Open PQL can run directly in web browsers using WebAssembly (WASM), enabling poker analysis in client-side applications without server dependencies.
Try Online
Experience Open PQL in your browser: PQL Playground
Running the Demo Locally
Prerequisites
Installation
# Install Trunk
cargo install trunk
# Add the WebAssembly target
rustup target add wasm32-unknown-unknown
Build and Run
# Clone the repository
git clone https://github.com/solve-poker/open-pql.git
cd open-pql
# Serve the WASM demo
trunk serve --config ./open-pql-wasm/Trunk.toml
# Open http://localhost:8080 in your browser
Demo Features
Interactive Query Interface
The demo provides a web-based interface for running PQL queries:
- Query Editor: Syntax-highlighted PQL input
- Instant Results: Real-time calculation and display
- Error Handling: Clear error messages for invalid queries
- Examples: Pre-loaded example queries to get started
Supported Functionality
The WASM demo supports the full PQL feature set:
- Equity calculations
- Hand analysis
- Board texture analysis
- Range vs range comparisons
- Multi-player scenarios
Example Queries
Try these queries in the demo:
-- Basic equity
select equity from hero='AhKh', villain='QQ', game='holdem'
-- Range analysis
select equity from hero='JJ+,AK', villain='22+,A7+', game='holdem'
-- Board analysis
select avg(equity) from hero='AhKh', villain='QQ'
where boardsuitcount(flop) >= 2, game='holdem'
-- Hand type probability
select avg(handtype(river) = 'flush') from hero='As9s', game='holdem'
Integration Guide
Basic Setup
Create a new web project with Open PQL WASM support:
<!DOCTYPE html>
<html>
<head>
<title>Poker Analysis App</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.query-box { width: 100%; height: 100px; margin: 10px 0; }
.result { margin: 10px 0; padding: 10px; background: #f0f0f0; }
</style>
</head>
<body>
<h1>Poker Analysis</h1>
<textarea class="query-box" id="query" placeholder="Enter PQL query...">
select equity from hero='AhKh', villain='QQ', game='holdem'</textarea>
<button onclick="executeQuery()">Execute</button>
<div id="result" class="result"></div>
<script type="module">
import init, { execute_pql } from './pkg/open_pql_wasm.js';
async function run() {
await init();
window.execute_pql = execute_pql;
}
run();
window.executeQuery = function() {
const query = document.getElementById('query').value;
const resultDiv = document.getElementById('result');
try {
const result = execute_pql(query);
resultDiv.innerHTML = ``;
resultDiv.style.color = 'black';
} catch (error) {
resultDiv.innerHTML = ``;
resultDiv.style.color = 'red';
}
};
</script>
</body>
</html>
Rust WASM Bindings
// src/lib.rs
use wasm_bindgen::prelude::*;
use open_pql::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[macro_export]
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
#[wasm_bindgen]
pub fn execute_pql(query: &str) -> Result<String, JsValue> {
// Set up panic hook for better error messages
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
// Parse and execute the PQL query
match parse_and_execute_pql(query) {
Ok(result) => Ok(format!("{:.3}", result)),
Err(e) => Err(JsValue::from_str(&format!("PQL Error: {:?}", e))),
}
}
fn parse_and_execute_pql(query: &str) -> Result<f64, PQLError> {
// Implementation depends on your PQL parsing setup
// This is a simplified example
// Parse the query
let parsed_query = parse_pql(query)?;
// Execute the query
execute_query(parsed_query)
}
#[wasm_bindgen]
pub fn validate_hand(hand: &str) -> bool {
hand.parse::<Hand>().is_ok()
}
#[wasm_bindgen]
pub fn validate_range(range: &str) -> bool {
range.parse::<PQLRange>().is_ok()
}
#[wasm_bindgen]
pub struct EquityCalculator {
game: PQLGame,
}
#[wasm_bindgen]
impl EquityCalculator {
#[wasm_bindgen(constructor)]
pub fn new(game: &str) -> Result<EquityCalculator, JsValue> {
let game = match game {
"holdem" => PQLGame::Holdem,
"shortdeck" => PQLGame::ShortDeck,
_ => return Err(JsValue::from_str("Invalid game type")),
};
Ok(EquityCalculator { game })
}
#[wasm_bindgen]
pub fn calculate_equity(&self, hero: &str, villain: &str, board: &str) -> Result<f64, JsValue> {
let hero_hand: Hand = hero.parse()
.map_err(|e| JsValue::from_str(&format!("Invalid hero hand: {}", e)))?;
let villain_hand: Hand = villain.parse()
.map_err(|e| JsValue::from_str(&format!("Invalid villain hand: {}", e)))?;
let board: Board = board.parse()
.map_err(|e| JsValue::from_str(&format!("Invalid board: {}", e)))?;
let result = calculate_equity_internal(&[hero_hand, villain_hand], &board, self.game)
.map_err(|e| JsValue::from_str(&format!("Calculation error: {:?}", e)))?;
Ok(result.equity[0])
}
}
Cargo.toml Configuration
[package]
name = "poker-wasm-app"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
open-pql = "0.0.3"
js-sys = "0.3"
[dependencies.web-sys]
version = "0.3"
features = [
"console",
"Document",
"Element",
"HtmlElement",
"Window",
]
[dependencies.console_error_panic_hook]
version = "0.1"
optional = true
[features]
default = ["console_error_panic_hook"]
Advanced React Integration
// PokerAnalyzer.jsx
import React, { useState, useEffect } from 'react';
import init, { EquityCalculator } from './pkg/poker_wasm_app.js';
function PokerAnalyzer() {
const [calculator, setCalculator] = useState(null);
const [hero, setHero] = useState('AhKh');
const [villain, setVillain] = useState('QcQd');
const [board, setBoard] = useState('');
const [equity, setEquity] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
init().then(() => {
const calc = new EquityCalculator('holdem');
setCalculator(calc);
});
}, []);
const calculateEquity = () => {
if (!calculator) return;
try {
const result = calculator.calculate_equity(hero, villain, board);
setEquity((result * 100).toFixed(1));
setError(null);
} catch (err) {
setError(err.toString());
setEquity(null);
}
};
return (
<div className="poker-analyzer">
<h2>Poker Equity Calculator</h2>
<div className="input-group">
<label>Hero Hand:</label>
<input
value={hero}
onChange={(e) => setHero(e.target.value)}
placeholder="AhKh"
/>
</div>
<div className="input-group">
<label>Villain Hand:</label>
<input
value={villain}
onChange={(e) => setVillain(e.target.value)}
placeholder="QcQd"
/>
</div>
<div className="input-group">
<label>Board:</label>
<input
value={board}
onChange={(e) => setBoard(e.target.value)}
placeholder="AcKs2d (optional)"
/>
</div>
<button onClick={calculateEquity}>Calculate</button>
{equity && (
<div className="result success">
Hero Equity: {equity}%
</div>
)}
{error && (
<div className="result error">
Error: {error}
</div>
)}
</div>
);
}
export default PokerAnalyzer;
Performance Considerations
WASM Performance
- Cold Start: Initial WASM module loading takes ~100ms
- Calculation Speed: Equity calculations run at near-native speed
- Memory Usage: Efficient memory management for large range calculations
- Bundle Size: Optimized build produces ~500KB gzipped WASM module
Optimization Tips
// Optimize for size
#[wasm_bindgen]
pub fn calculate_equity_fast(hero: &str, villain: &str) -> f64 {
// Cache parsed hands when possible
// Use stack allocation for small calculations
// Avoid unnecessary string allocations
fast_equity_calculation(hero, villain)
}
// Batch operations for better performance
#[wasm_bindgen]
pub fn batch_calculate(scenarios: &JsValue) -> Result<Vec<f64>, JsValue> {
let scenarios: Vec<EquityScenario> = scenarios.into_serde()
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let results = scenarios.iter()
.map(|scenario| calculate_scenario(scenario))
.collect::<Result<Vec<_>, _>>()?;
Ok(results)
}
Deployment
Static Site Deployment
# Build for production
trunk build --release --config ./open-pql-wasm/Trunk.toml
# Deploy to any static hosting service
# Files will be in dist/ directory
CDN Integration
<!-- Load from CDN -->
<script type="module">
import init, { execute_pql } from 'https://unpkg.com/open-pql-wasm@latest/open_pql_wasm.js';
init().then(() => {
// PQL functions are now available
const result = execute_pql("select equity from hero='AA', villain='KK', game='holdem'");
console.log('Equity:', result);
});
</script>
The WebAssembly integration allows Open PQL to run in any modern web browser, enabling powerful poker analysis directly in client-side applications without server dependencies.