Creating Tournaments
This guide shows you how to create and manage tournaments in your game using Funtico’s tournament system. We’ll cover tournament creation, player management, score submission, and prize distribution.
Prerequisites
Section titled “Prerequisites”Before implementing tournaments, ensure you have:
- Funtico Account - Developer account with tournament access enabled
- API Credentials - Your API Key ID and Secret for Basic authentication
- Auth Integration - Users must be authenticated via Login with Funtico
- SDK Installation - Latest version supporting tournament operations
npm install @pillarex/funtico-sdk@latestSDK Setup
Section titled “SDK Setup”Initialize the SDK with your credentials. For tournaments, you’ll need API credentials for Basic authentication:
import { FunticoSDK } from '@pillarex/funtico-sdk';
// Initialize SDK with API credentials for tournament operationsconst sdk = new FunticoSDK({ apiKeyId: process.env.FUNTICO_API_KEY_ID, apiKeySecret: process.env.FUNTICO_API_KEY_SECRET, env: process.env.NODE_ENV === 'production' ? 'production' : 'staging'});Tournament Creation
Section titled “Tournament Creation”Basic Tournament Setup
Section titled “Basic Tournament Setup”Create a simple tournament with essential configuration:
// Backend: Create a basic tournamentasync function createBasicTournament() { try { const tournament = await sdk.createTournament({ name: "Weekly Puzzle Challenge", description: "Compete in our weekly puzzle tournament for TICO prizes!", starts_at: "2024-12-01T10:00:00Z", ends_at: "2024-12-07T22:00:00Z", max_players: 100, score_mode: "descending", // Higher scores win score_type: "points", // Best single score entry_fee_type: "free", entry_limit: 1, // One entry per player is_kyc_required: false, is_published: false, // Create as draft first metadata: { difficulty: "medium", game_mode: "puzzle" } });
console.log('Tournament created:', tournament.id); return tournament; } catch (error) { console.error('Failed to create tournament:', error); throw error; }}Advanced Tournament with Prizes
Section titled “Advanced Tournament with Prizes”Create a tournament with entry fees and prize distribution:
// Backend: Create tournament with prizesasync function createPrizeTournament() { try { const tournament = await sdk.createTournament({ name: "Championship Battle Royale", description: "Ultimate battle royale tournament with massive TICO prizes!", cover_image_url: "https://your-game.com/images/tournament-cover.jpg", starts_at: "2024-12-15T18:00:00Z", ends_at: "2024-12-15T22:00:00Z", max_players: 64, score_mode: "descending", score_type: "points", entry_fee_type: "tico", entry_fee_amount: 10, // 10 TICO entry fee entry_limit: 3, // Up to 3 entries per player is_kyc_required: true, // Required for high-value tournaments is_published: false, metadata: { game_mode: "battle_royale", difficulty: "expert", duration_minutes: 240 }, prize_distributions: [ { type: "specific_place", place: 1, rewards: [{ type: "tico", amount: 300 }] // 1st place: 300 TICO }, { type: "place_range", place_range_start: 2, place_range_end: 3, rewards: [{ type: "tico", amount: 100 }] // 2nd-3rd: 100 TICO each }, { type: "percentage_range", percentage_range: 10, // Top 10% rewards: [{ type: "tico", amount: 25 }] // Top 10%: 25 TICO each } ] });
return tournament; } catch (error) { console.error('Failed to create prize tournament:', error); throw error; }}Tournament Management Endpoints
Section titled “Tournament Management Endpoints”Create backend endpoints for tournament operations:
import express from 'express';
const app = express();
// Middleware for Basic authenticationfunction requireApiAuth(req, res, next) { // Validate Basic auth credentials from your game server const authHeader = req.headers['authorization']; if (!authHeader || !authHeader.startsWith('Basic ')) { return res.status(401).json({ error: 'Missing or invalid authorization header' }); }
const base64Credentials = authHeader.split(' ')[1]; const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii'); const [keyId, keySecret] = credentials.split(':');
if (!isValidApiCredentials(keyId, keySecret)) { return res.status(401).json({ error: 'Invalid API credentials' }); } next();}
// Create tournament endpointapp.post('/api/tournaments', requireApiAuth, async (req, res) => { try { const tournamentData = req.body;
const tournament = await sdk.createTournament(tournamentData);
res.status(201).json({ tournament }); } catch (error) { console.error('Tournament creation failed:', error); res.status(500).json({ error: 'Failed to create tournament' }); }});
// Get tournaments endpointapp.get('/api/tournaments', requireApiAuth, async (req, res) => { try { const tournaments = await sdk.getTournaments();
res.json({ tournaments }); } catch (error) { console.error('Failed to fetch tournaments:', error); res.status(500).json({ error: 'Failed to fetch tournaments' }); }});
// Publish tournament endpointapp.post('/api/tournaments/:id/publish', requireApiAuth, async (req, res) => { try { const { id } = req.params;
await sdk.publishTournament(id);
res.json({ success: true }); } catch (error) { console.error('Failed to publish tournament:', error); res.status(500).json({ error: 'Failed to publish tournament' }); }});Player Tournament Interaction
Section titled “Player Tournament Interaction”Joining Tournaments
Section titled “Joining Tournaments”Handle tournament registration with dual authentication:
// Backend: Tournament join endpoint (requires both Basic auth and access token)app.post('/api/tournaments/:id/join', requireApiAuth, async (req, res) => { try { const { id } = req.params; const { password, user_ip } = req.body;
// Get access token from secure cookie const accessToken = req.cookies.access_token; if (!accessToken) { return res.status(401).json({ error: 'User not authenticated' }); }
// Join tournament with dual authentication const result = await sdk.joinTournament(id, { accessToken, // User authentication password, // Optional tournament password user_ip // Optional for analytics });
res.json({ success: true, data: result }); } catch (error) { console.error('Failed to join tournament:', error);
if (error.error_code === 'tournament_full') { res.status(400).json({ error: 'Tournament is full' }); } else if (error.error_code === 'insufficient_balance') { res.status(400).json({ error: 'Insufficient TICO balance for entry fee' }); } else { res.status(500).json({ error: 'Failed to join tournament' }); } }});Score Submission
Section titled “Score Submission”Submit player scores with validation:
// Backend: Score submission endpointapp.post('/api/tournaments/:id/score', requireApiAuth, async (req, res) => { try { const { id } = req.params; const { score, user_ip } = req.body;
// Get access token from secure cookie const accessToken = req.cookies.access_token; if (!accessToken) { return res.status(401).json({ error: 'User not authenticated' }); }
// Validate score on your server before submission if (!isValidScore(score, req.user)) { return res.status(400).json({ error: 'Invalid score' }); }
// Submit score with dual authentication const result = await sdk.submitTournamentScore(id, { score, accessToken, // User authentication user_ip // Optional for analytics });
res.json({ success: true, data: result }); } catch (error) { console.error('Failed to submit score:', error);
if (error.error_code === 'tournament_ended') { res.status(400).json({ error: 'Tournament has ended' }); } else if (error.error_code === 'not_joined') { res.status(400).json({ error: 'Player has not joined this tournament' }); } else { res.status(500).json({ error: 'Failed to submit score' }); } }});
// Server-side score validation examplefunction isValidScore(score, user) { // Implement your game-specific score validation logic // Check for reasonable score ranges, rate limiting, etc. if (score < 0 || score > 1000000) { return false; }
// Add additional validation based on your game mechanics return true;}Leaderboard Access
Section titled “Leaderboard Access”Provide real-time leaderboard data:
// Backend: Get tournament leaderboardapp.get('/api/tournaments/:id/leaderboard', requireApiAuth, async (req, res) => { try { const { id } = req.params;
const leaderboard = await sdk.getTournamentLeaderboard(id);
res.json({ leaderboard }); } catch (error) { console.error('Failed to get leaderboard:', error); res.status(500).json({ error: 'Failed to get leaderboard' }); }});Frontend Integration
Section titled “Frontend Integration”Tournament List Display
Section titled “Tournament List Display”Show available tournaments to players:
// Frontend: Tournament list componentinterface Tournament { id: string; name: string; description: string; cover_image_url?: string; starts_at: string; ends_at: string; max_players: number; entry_fee_type: 'free' | 'tico'; entry_fee_amount: number; prize_distributions: PrizeDistribution[];}
async function loadTournaments(): Promise<Tournament[]> { try { const response = await fetch('/api/tournaments', { credentials: 'include' });
if (response.ok) { const { tournaments } = await response.json(); return tournaments; } else { throw new Error('Failed to load tournaments'); } } catch (error) { console.error('Error loading tournaments:', error); return []; }}
// React component examplefunction TournamentList() { const [tournaments, setTournaments] = useState<Tournament[]>([]); const [loading, setLoading] = useState(true);
useEffect(() => { loadTournaments() .then(setTournaments) .finally(() => setLoading(false)); }, []);
if (loading) return <div>Loading tournaments...</div>;
return ( <div className="tournament-list"> {tournaments.map(tournament => ( <TournamentCard key={tournament.id} tournament={tournament} onJoin={handleJoinTournament} /> ))} </div> );}Tournament Participation
Section titled “Tournament Participation”Handle player actions:
// Frontend: Tournament interaction functionsasync function joinTournament(tournamentId: string, password?: string) { try { const response = await fetch(`/api/tournaments/${tournamentId}/join`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ password, user_ip: await getUserIP() // Optional }) });
if (response.ok) { showSuccess('Successfully joined tournament!'); // Refresh tournament data await loadTournamentDetails(tournamentId); } else { const error = await response.json(); showError(error.error || 'Failed to join tournament'); } } catch (error) { showError('Network error. Please try again.'); }}
async function submitScore(tournamentId: string, score: number) { try { const response = await fetch(`/api/tournaments/${tournamentId}/score`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ score, user_ip: await getUserIP() // Optional }) });
if (response.ok) { showSuccess(`Score ${score} submitted successfully!`); // Refresh leaderboard await loadLeaderboard(tournamentId); } else { const error = await response.json(); showError(error.error || 'Failed to submit score'); } } catch (error) { showError('Network error. Please try again.'); }}
async function loadLeaderboard(tournamentId: string) { try { const response = await fetch(`/api/tournaments/${tournamentId}/leaderboard`, { credentials: 'include' });
if (response.ok) { const { leaderboard } = await response.json(); updateLeaderboardDisplay(leaderboard); } } catch (error) { console.error('Failed to load leaderboard:', error); }}Error Handling
Section titled “Error Handling”Common Tournament Errors
Section titled “Common Tournament Errors”// Handle tournament-specific errorsfunction handleTournamentError(error: any, operation: string) { if (error.error_code) { switch (error.error_code) { case 'tournament_not_found': return 'Tournament not found. It may have been cancelled.'; case 'tournament_full': return 'Tournament is full. Try joining another tournament.'; case 'tournament_ended': return 'Tournament has ended. Scores can no longer be submitted.'; case 'insufficient_balance': return 'Insufficient TICO balance for entry fee. Please add funds.'; case 'already_joined': return 'You have already joined this tournament.'; case 'entry_limit_reached': return 'You have reached the maximum entries for this tournament.'; case 'invalid_password': return 'Invalid tournament password. Please check and try again.'; case 'kyc_required': return 'KYC verification required to join this tournament.'; default: return `Tournament ${operation} failed. Please try again.`; } }
return 'An unexpected error occurred. Please try again.';}
// Usage in error handlingtry { await joinTournament(tournamentId);} catch (error) { const message = handleTournamentError(error, 'join'); showError(message);}Best Practices
Section titled “Best Practices”Security
Section titled “Security”- Dual Authentication - Always use both Basic auth and Access Token for player operations
- Score Validation - Implement server-side score validation before submission
- Rate Limiting - Prevent abuse of tournament operations
- Input Sanitization - Validate all tournament parameters
Performance
Section titled “Performance”- Caching - Cache tournament lists and leaderboards appropriately
- Real-time Updates - Use WebSockets for live leaderboard updates
- Pagination - Handle large tournament lists efficiently
- Lazy Loading - Load tournament details on demand
User Experience
Section titled “User Experience”- Clear Status - Show tournament phases and user participation status
- Entry Requirements - Display entry fees, requirements, and restrictions clearly
- Prize Information - Transparently show prize structures and distributions
- Real-time Feedback - Provide immediate feedback for all actions
Next Steps
Section titled “Next Steps”- Tournament Core Concepts - Understand tournament system architecture
- Tournament SDK Reference - Complete SDK method documentation