Skip to main content
Glama

Texas Holdem MCP Server

by freshlife001
poker-client.ts19.3 kB
// First install socket.io-client: npm install socket.io-client @types/socket.io-client // Remove the import and use the global io object // import { io } from 'socket.io-client'; // Connect to the server const socket = (window as any).io(); // Game state interface Player { playerId: string; name: string; } interface TablePlayer { id: string; name: string; chips: number; bet: number; folded: boolean; isAllIn: boolean; isDealer: boolean; isSmallBlind: boolean; isBigBlind: boolean; isActive: boolean; hand?: string[]; } interface TableState { id: string; name: string; players: TablePlayer[]; communityCards: string[]; pot: number; currentBet: number; smallBlind: number; bigBlind: number; stage: string; maxPlayers: number; remainingActionTime?: number; // Add this field to receive the timer from server } interface TableInfo { id: string; name: string; players: number; maxPlayers: number; smallBlind: number; bigBlind: number; stage: string; } // Add interface for player action data interface PlayerActionData { playerId: string; playerName: string; action: string; amount: number; message: string; timestamp: number; } let player: Player | null = null; let currentTable: string | null = null; let currentTableState: TableState | null = null; // Add countdown timer variables let countdownTimer: number | null = null; let countdownValue: number = 0; // Add countdown element reference const countdownElement = document.createElement('div'); countdownElement.className = 'countdown-timer'; countdownElement.style.display = 'none'; document.body.appendChild(countdownElement); // DOM elements const container = document.getElementById('container') as HTMLDivElement; const loginContainer = document.getElementById('loginContainer') as HTMLDivElement; const tablesContainer = document.getElementById('tablesContainer') as HTMLDivElement; const createTableContainer = document.getElementById('createTableContainer') as HTMLDivElement; const gameContainer = document.getElementById('gameContainer') as HTMLDivElement; const playerNameInput = document.getElementById('playerName') as HTMLInputElement; const loginBtn = document.getElementById('loginBtn') as HTMLButtonElement; const refreshTablesBtn = document.getElementById('refreshTablesBtn') as HTMLButtonElement; const createTableBtn = document.getElementById('createTableBtn') as HTMLButtonElement; const tableList = document.getElementById('tableList') as HTMLDivElement; const tableNameInput = document.getElementById('tableName') as HTMLInputElement; const smallBlindInput = document.getElementById('smallBlind') as HTMLInputElement; const bigBlindInput = document.getElementById('bigBlind') as HTMLInputElement; const maxPlayersInput = document.getElementById('maxPlayers') as HTMLInputElement; const submitTableBtn = document.getElementById('submitTableBtn') as HTMLButtonElement; const cancelTableBtn = document.getElementById('cancelTableBtn') as HTMLButtonElement; const tableNameElement = document.getElementById('tableNameText') as HTMLHeadingElement; const potElement = document.getElementById('pot') as HTMLDivElement; const communityCardsElement = document.getElementById('communityCards') as HTMLDivElement; const playerHandElement = document.getElementById('playerHand') as HTMLDivElement; const foldBtn = document.getElementById('foldBtn') as HTMLButtonElement; const checkBtn = document.getElementById('checkBtn') as HTMLButtonElement; const callBtn = document.getElementById('callBtn') as HTMLButtonElement; const betBtn = document.getElementById('betBtn') as HTMLButtonElement; const betAmountInput = document.getElementById('betAmount') as HTMLInputElement; const raiseBtn = document.getElementById('raiseBtn') as HTMLButtonElement; const leaveTableBtn = document.getElementById('leaveTableBtn') as HTMLButtonElement; // Event listeners loginBtn.addEventListener('click', handleLogin); refreshTablesBtn.addEventListener('click', fetchTables); createTableBtn.addEventListener('click', showCreateTableForm); submitTableBtn.addEventListener('click', createTable); cancelTableBtn.addEventListener('click', hideCreateTableForm); leaveTableBtn.addEventListener('click', leaveTable); foldBtn.addEventListener('click', () => performAction('fold')); checkBtn.addEventListener('click', () => performAction('check')); callBtn.addEventListener('click', () => performAction('call')); betBtn.addEventListener('click', () => performAction('bet', parseInt(betAmountInput.value))); raiseBtn.addEventListener('click', () => performAction('raise', parseInt(betAmountInput.value))); // Socket event listeners socket.on('playerAction', (actionData: PlayerActionData) => { // Display a notification with the action message showNotification(actionData.message); }); // Example notification function function showNotification(message:string) { const notification = document.createElement('div'); notification.className = 'action-notification'; notification.textContent = message; // Add to notification area document.querySelector('.notification-area')?.appendChild(notification); // Auto-remove after a few seconds setTimeout(() => { notification.classList.add('fade-out'); setTimeout(() => notification.remove(), 500); }, 3000); } socket.on('tableUpdate', handleTableUpdate); // Helper functions for MCP API function sendPokerRequest(method: string, params: any): Promise<any> { return new Promise((resolve, reject) => { const request = { method, params, id: Date.now() }; console.log(`[Client] Sending request: ${method}`, params); socket.emit('action', request, (response: any) => { console.log(`[Client] Received response for ${method}:`, response); if (response.error) { console.error(`[Client] Error in ${method}:`, response.error); reject(response.error); } else { resolve(response.result); } }); }); } // Login handler async function handleLogin(): Promise<void> { const name = playerNameInput.value.trim(); if (!name) { alert('Please enter your name'); return; } try { const result = await sendPokerRequest('register', { name }); player = result; console.log('Logged in as:', player); // Show tables view loginContainer.classList.add('hidden'); tablesContainer.classList.remove('hidden'); // Fetch available tables fetchTables(); } catch (error: any) { alert(`Login failed: ${error.message}`); } } // Fetch tables async function fetchTables(): Promise<void> { try { const tables = await sendPokerRequest('listTables', {}); renderTables(tables); } catch (error: any) { alert(`Failed to fetch tables: ${error.message}`); } } // Render tables function renderTables(tables: TableInfo[]): void { tableList.innerHTML = ''; if (tables.length === 0) { tableList.innerHTML = '<p>No tables available. Create one!</p>'; return; } tables.forEach(table => { const tableCard = document.createElement('div'); tableCard.className = 'table-card'; tableCard.innerHTML = ` <h3>${table.name}</h3> <p>Players: ${table.players}/${table.maxPlayers}</p> <p>Blinds: $${table.smallBlind}/$${table.bigBlind}</p> <p>Status: ${table.stage}</p> `; tableCard.addEventListener('click', () => joinTable(table.id)); tableList.appendChild(tableCard); }); } // Show create table form function showCreateTableForm(): void { tablesContainer.classList.add('hidden'); createTableContainer.classList.remove('hidden'); } // Hide create table form function hideCreateTableForm(): void { createTableContainer.classList.add('hidden'); tablesContainer.classList.remove('hidden'); } // Create table async function createTable(): Promise<void> { const name = tableNameInput.value.trim(); const smallBlind = parseInt(smallBlindInput.value); const bigBlind = parseInt(bigBlindInput.value); const maxPlayers = parseInt(maxPlayersInput.value); if (!name) { alert('Please enter a table name'); return; } try { const table = await sendPokerRequest('createTable', { name, smallBlind, bigBlind, maxPlayers }); console.log('Created table:', table); hideCreateTableForm(); fetchTables(); } catch (error: any) { alert(`Failed to create table: ${error.message}`); } } // Join table async function joinTable(tableId: string): Promise<void> { try { // Clear any previous table state if we're joining a new table if (currentTable) { // Unsubscribe from previous table updates socket.emit('unsubscribe', currentTable); } const result = await sendPokerRequest('joinTable', { playerId: player?.playerId, tableId }); if (result.success) { currentTable = tableId; // Subscribe to table updates socket.emit('subscribe', tableId); // Fetch initial table state const tableState = await sendPokerRequest('getTableState', { tableId, playerId: player?.playerId }); // Show game view tablesContainer.classList.add('hidden'); gameContainer.classList.remove('hidden'); container.classList.add('hide-title'); // Render table state renderTableState(tableState); } } catch (error: any) { alert(`Failed to join table: ${error.message}`); } } // Perform action async function performAction(action: string, amount: number = 0): Promise<void> { if (!currentTable) return; try { const result = await sendPokerRequest('performAction', { playerId: player?.playerId, action, amount }); if (!result.success) { alert('Action failed'); } } catch (error: any) { alert(`Failed to perform action: ${error.message}`); } } // Convert card notation to symbols function formatCardWithSymbols(card: string): string { if (!card) return ''; const value = card.slice(0, -1); const suit = card.slice(-1).toLowerCase(); let suitSymbol = ''; switch (suit) { case 'h': suitSymbol = '♥'; break; case 'd': suitSymbol = '♦'; break; case 'c': suitSymbol = '♣'; break; case 's': suitSymbol = '♠'; break; } return `${value}${suitSymbol}`; } // Handle table update function handleTableUpdate(tableState: TableState): void { console.log('[Client] Received table update:', tableState); if (tableState.id !== currentTable) return; // Preserve player hand if it's not included in the update if (currentTableState && currentTableState.players) { const currentPlayer = currentTableState.players.find(p => p.id === player?.playerId); const updatedPlayer = tableState.players.find(p => p.id === player?.playerId); if (currentPlayer && currentPlayer.hand && updatedPlayer && !updatedPlayer.hand) { updatedPlayer.hand = currentPlayer.hand; } } currentTableState = tableState; renderTableState(tableState); } // Render table state function renderTableState(tableState: TableState): void { // Update table name tableNameElement.textContent = tableState.name; // Update pot potElement.textContent = `Pot: $${tableState.pot}`; // Update game stage const stageElement = document.getElementById('game-stage'); if (stageElement) { // Format the stage name for display (capitalize and replace hyphens with spaces) const stageName = tableState.stage .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); stageElement.textContent = `${tableState.name}: ${stageName}`; } // Update community cards communityCardsElement.innerHTML = ''; tableState.communityCards.forEach(card => { const cardElement = document.createElement('div'); cardElement.className = 'card'; // Convert card notation to symbols const cardText = formatCardWithSymbols(card); // For hearts and diamonds, wrap the symbol in a span with red class if (cardText.endsWith('♥') || cardText.endsWith('♦')) { const value = cardText.slice(0, -1); const symbol = cardText.slice(-1); cardElement.innerHTML = `${value}<span class="red">${symbol}</span>`; } else { cardElement.innerHTML = cardText; } communityCardsElement.appendChild(cardElement); }); // Update player hand playerHandElement.innerHTML = ''; const currentPlayerObj = tableState.players.find(p => p.id === player?.playerId); if (currentPlayerObj && currentPlayerObj.hand) { currentPlayerObj.hand.forEach(card => { const cardElement = document.createElement('div'); cardElement.className = 'card'; // Convert card notation to symbols const cardText = formatCardWithSymbols(card); // For hearts and diamonds, wrap the symbol in a span with red class if (cardText.endsWith('♥') || cardText.endsWith('♦')) { const value = cardText.slice(0, -1); const symbol = cardText.slice(-1); cardElement.innerHTML = `${value}<span class="red">${symbol}</span>`; } else { cardElement.innerHTML = cardText; } playerHandElement.appendChild(cardElement); }); } // Update player seats const pokerSeatContainer = document.querySelector('.player-seats-container') as HTMLDivElement; // Remove existing player seats document.querySelectorAll('.player-seat').forEach(seat => seat.remove()); // Add player seats tableState.players.forEach((p, index) => { // Calculate position based on container dimensions const containerRect = pokerSeatContainer.getBoundingClientRect(); const containerWidth = containerRect.width; const containerHeight = containerRect.height; // Calculate center point const centerX = containerWidth / 2; const centerY = containerHeight / 2; // Calculate radius (slightly smaller than container) const radiusX = centerX * 0.8; const radiusY = centerY * 0.8; // Calculate angle for this player (distribute evenly around the table) const angle = (index / tableState.players.length) * 2 * Math.PI; // Calculate position (subtract half the seat size for centering) const seatSize = 120; // Size of player seat const x = centerX + radiusX * Math.cos(angle) - (seatSize / 2); const y = centerY + radiusY * Math.sin(angle) - (seatSize / 2); const seatElement = document.createElement('div'); seatElement.className = 'player-seat'; if (p.isActive) { seatElement.classList.add('active'); } // Use percentage-based positioning for better responsiveness seatElement.style.position = 'absolute'; seatElement.style.left = `${(x / containerWidth) * 100}%`; seatElement.style.top = `${(y / containerHeight) * 100}%`; seatElement.innerHTML = ` <div>${p.name}</div> <div>$${p.chips}</div> ${p.bet > 0 ? `<div>Bet: $${p.bet}</div>` : ''} ${p.folded ? '<div>Folded</div>' : ''} ${p.isAllIn ? '<div>All In</div>' : ''} ${p.isDealer ? '<div>D</div>' : ''} ${p.isSmallBlind ? '<div>SB</div>' : ''} ${p.isBigBlind ? '<div>BB</div>' : ''} `; pokerSeatContainer.appendChild(seatElement); }); // Update action buttons const isPlayerTurn = currentPlayerObj && currentPlayerObj.isActive; const canCheck = isPlayerTurn && (currentPlayerObj.bet >= tableState.currentBet); const canCall = isPlayerTurn && (currentPlayerObj.bet < tableState.currentBet); const canBet = isPlayerTurn && tableState.currentBet === 0; const canRaise = isPlayerTurn && tableState.currentBet > 0; foldBtn.disabled = !isPlayerTurn; checkBtn.disabled = !canCheck; callBtn.disabled = !canCall; betBtn.disabled = !canBet; raiseBtn.disabled = !canRaise; if (canCall) { const callAmount = tableState.currentBet - (currentPlayerObj.bet || 0); callBtn.textContent = `Call $${callAmount}`; } else { callBtn.textContent = 'Call'; } if (canBet) { betAmountInput.min = tableState.bigBlind.toString(); betAmountInput.value = tableState.bigBlind.toString(); } if (canRaise) { const minRaise = tableState.currentBet * 2; betAmountInput.min = minRaise.toString(); betAmountInput.value = minRaise.toString(); } // Handle countdown timer // Clear any existing countdown if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; } // If it's player's turn and we have remaining time info, show countdown if (isPlayerTurn && tableState.remainingActionTime && tableState.remainingActionTime > 0) { countdownValue = Math.ceil(tableState.remainingActionTime); updateCountdownDisplay(); countdownTimer = window.setInterval(() => { countdownValue--; if (countdownValue <= 0) { clearInterval(countdownTimer!); countdownTimer = null; } updateCountdownDisplay(); }, 1000); } else { // Hide countdown if it's not player's turn countdownElement.style.display = 'none'; } } // Add function to update countdown display function updateCountdownDisplay(): void { if (countdownValue <= 0) { countdownElement.style.display = 'none'; return; } countdownElement.style.display = 'block'; // Position the countdown near the action buttons const actionButtons = document.querySelector('.action-buttons'); if (actionButtons) { const rect = actionButtons.getBoundingClientRect(); countdownElement.style.position = 'absolute'; countdownElement.style.top = `${rect.top - 60}px`; countdownElement.style.left = `${rect.left}px`; } // Update the content with countdown value let content = `<div class="time-remaining ${countdownValue <= 10 ? 'urgent' : ''}"> Time remaining: ${countdownValue}s </div>`; // Add warning message when time is running low if (countdownValue <= 10) { content += `<div class="warning-message"> Warning: Make your move soon or it will be auto-played! </div>`; } countdownElement.innerHTML = content; } // Add cleanup when leaving table async function leaveTable(): Promise<void> { if (!currentTable) return; // Clear countdown timer when leaving table if (countdownTimer) { clearInterval(countdownTimer); countdownTimer = null; } try { const result = await sendPokerRequest('leaveTable', { playerId: player?.playerId }); if (result.success) { // Unsubscribe from table updates socket.emit('unsubscribe', currentTable); // Reset table state variables currentTable = null; currentTableState = null; // Show tables view gameContainer.classList.add('hidden'); tablesContainer.classList.remove('hidden'); container.classList.remove('hide-title'); // Refresh tables with a slight delay to ensure server has processed the leave setTimeout(fetchTables, 500); } } catch (error: any) { alert(`Failed to leave table: ${error.message}`); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshlife001/mcp_poker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server