Skip to main content

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

  • Rust (latest stable)
  • Trunk for building and serving WASM applications

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 = `<strong>Result:</strong> ${result}`;
resultDiv.style.color = 'black';
} catch (error) {
resultDiv.innerHTML = `<strong>Error:</strong> ${error}`;
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.