Skip to main content

Submit Tournament Score

The groupTournamentScoreSubmit method submits a player's score to an active tournament. Players must have joined the tournament before submitting scores. Scores are validated and ranked according to the tournament's scoring rules.

Overview

Use this method to:

  • Submit scores during active tournament periods
  • Update player rankings in real-time
  • Track multiple score attempts
  • Handle score validation and limits
  • Record submission timestamps for tie-breaking

Method Signature

MoitribeSDK(gameId, 'groupTournamentScoreSubmit', {
tournamentid: string,
score: number,
callback: (result) => {
// Handle response
}
});

Parameters

ParameterTypeRequiredDescription
tournamentidstringYesUnique identifier of the tournament
scorenumberYesThe score value to submit
callbackfunctionYesFunction to handle the response

Response Format

{
success: boolean;
message?: string; // Success message or additional info
currentRank?: number; // Player's current rank after submission
bestScore?: number; // Player's best score in this tournament
submissionCount?: number; // Total submissions by this player
timeLeft?: number; // Time remaining in tournament (milliseconds)
msg?: string; // Error message if success is false
}

JavaScript Example

// Submit a tournament score
MoitribeSDK('my-game-id', 'groupTournamentScoreSubmit', {
tournamentid: 'weekly-challenge-001',
score: 1500,
callback: (result) => {
if (result.success) {
console.log('Score submitted successfully!');
console.log('Current rank:', result.currentRank);
console.log('Best score:', result.bestScore);
console.log('Total submissions:', result.submissionCount);

// Update UI with new ranking
updatePlayerRanking(result.currentRank);
showScoreSubmissionFeedback(result);

} else {
console.error('Failed to submit score:', result.msg);

// Handle specific errors
if (result.msg.includes('not joined')) {
showMessage('You must join the tournament first');
} else if (result.msg.includes('active')) {
showMessage('Tournament is not active');
} else if (result.msg.includes('ended')) {
showMessage('Tournament has ended');
} else if (result.msg.includes('limit')) {
showMessage('Score submission limit reached');
} else {
showMessage('Failed to submit score. Please try again.');
}
}
}
});

TypeScript Example

import MoitribeSDK from '@veniso/moitribe-js';

interface ScoreSubmissionResponse {
success: boolean;
message?: string;
currentRank?: number;
bestScore?: number;
submissionCount?: number;
timeLeft?: number;
msg?: string;
}

// Submit score with proper typing
MoitribeSDK('my-game-id', 'groupTournamentScoreSubmit', {
tournamentid: 'weekly-challenge-001',
score: 1500,
callback: (result: ScoreSubmissionResponse) => {
if (result.success) {
handleSuccessfulSubmission(result);
} else {
handleSubmissionError(result.msg);
}
}
});

function handleSuccessfulSubmission(result: ScoreSubmissionResponse) {
// Update player stats
const playerStats = {
currentRank: result.currentRank,
bestScore: result.bestScore,
submissionCount: result.submissionCount,
timeLeft: result.timeLeft
};

// Update UI
updateScoreDisplay(playerStats);
showSubmissionSuccess(result);

// Track submission analytics
trackScoreSubmission(playerStats);

// Check for achievements
checkScoreAchievements(playerStats);
}

function updateScoreDisplay(stats: any) {
// Update current rank
const rankElement = document.getElementById('current-rank');
if (stats.currentRank) {
rankElement.textContent = `#${stats.currentRank}`;
rankElement.className = 'rank-display';

// Add special styling for top ranks
if (stats.currentRank <= 10) {
rankElement.classList.add('top-rank');
}
if (stats.currentRank <= 3) {
rankElement.classList.add('podium-rank');
}
}

// Update best score
const bestScoreElement = document.getElementById('best-score');
if (stats.bestScore) {
bestScoreElement.textContent = formatScore(stats.bestScore);
animateScoreUpdate(bestScoreElement);
}

// Update submission count
const submissionElement = document.getElementById('submission-count');
if (stats.submissionCount) {
submissionElement.textContent = `${stats.submissionCount} submissions`;
}

// Update time remaining
const timeElement = document.getElementById('time-remaining');
if (stats.timeLeft) {
timeElement.textContent = formatTimeRemaining(stats.timeLeft);
}
}

function showSubmissionSuccess(result: ScoreSubmissionResponse) {
const notification = document.createElement('div');
notification.className = 'score-submission-success';

let message = 'Score submitted successfully!';

if (result.currentRank && result.currentRank <= 100) {
message += ` You're ranked #${result.currentRank}!`;
}

if (result.bestScore) {
const isPersonalBest = result.message?.includes('personal best');
if (isPersonalBest) {
message += ` New personal best: ${formatScore(result.bestScore)}!`;
notification.classList.add('personal-best');
}
}

notification.innerHTML = `
<div class="success-content">
<div class="success-icon">🎯</div>
<div class="success-message">${message}</div>
${result.currentRank ? `<div class="rank-info">Current Rank: #${result.currentRank}</div>` : ''}
</div>
`;

showNotification(notification, 'success', 3000);
}

function handleSubmissionError(errorMsg?: string) {
let userMessage = 'Failed to submit score';
let action = null;

if (errorMsg) {
if (errorMsg.includes('not joined')) {
userMessage = 'You must join the tournament first';
action = () => joinTournament(currentTournamentId);
} else if (errorMsg.includes('active')) {
userMessage = 'Tournament is not currently active';
action = () => checkTournamentStatus(currentTournamentId);
} else if (errorMsg.includes('ended')) {
userMessage = 'This tournament has ended';
action = () => viewTournamentResults(currentTournamentId);
} else if (errorMsg.includes('limit')) {
userMessage = 'You have reached the score submission limit';
action = () => viewSubmissionHistory();
} else if (errorMsg.includes('invalid')) {
userMessage = 'Invalid score value';
} else if (errorMsg.includes('network')) {
userMessage = 'Network error. Please check your connection';
action = () => retryScoreSubmission();
} else {
userMessage = errorMsg;
}
}

showNotification(userMessage, 'error', action);
}

function formatScore(score: number): string {
// Format score based on tournament type
if (score >= 1000000) {
return (score / 1000000).toFixed(1) + 'M';
} else if (score >= 1000) {
return (score / 1000).toFixed(1) + 'K';
} else {
return score.toString();
}
}

function formatTimeRemaining(milliseconds: number): string {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);

if (days > 0) {
return `${days}d ${hours % 24}h`;
} else if (hours > 0) {
return `${hours}h ${minutes % 60}m`;
} else if (minutes > 0) {
return `${minutes}m ${seconds % 60}s`;
} else {
return `${seconds}s`;
}
}

Common Use Cases

Game Score Submission

Integrate score submission with game completion:

class TournamentScoreManager {
constructor(tournamentId) {
this.tournamentId = tournamentId;
this.currentScore = 0;
this.bestScore = 0;
this.submissionCount = 0;
}

// Called when game ends
onGameComplete(finalScore) {
this.currentScore = finalScore;

// Show score submission dialog
this.showScoreSubmissionDialog(finalScore);
}

showScoreSubmissionDialog(score) {
const dialog = document.createElement('div');
dialog.className = 'score-submission-dialog';
dialog.innerHTML = `
<div class="dialog-content">
<h2>Game Complete!</h2>
<div class="score-display">
<div class="score-label">Your Score</div>
<div class="score-value">${this.formatScore(score)}</div>
</div>

${this.bestScore > 0 ? `
<div class="best-score-info">
<div class="best-score-label">Best Score</div>
<div class="best-score-value">${this.formatScore(this.bestScore)}</div>
</div>
` : ''}

<div class="submission-info">
<div class="submission-count">Submissions: ${this.submissionCount}</div>
<div class="time-left">Time left: ${this.getTimeRemaining()}</div>
</div>

<div class="submission-actions">
<button class="btn-cancel" onclick="closeScoreDialog()">Cancel</button>
<button class="btn-submit" onclick="submitScoreToTournament(${score})">
Submit Score
</button>
</div>
</div>
`;

document.body.appendChild(dialog);
}

submitScoreToTournament(score) {
this.showSubmissionProgress();

MoitribeSDK('my-game-id', 'groupTournamentScoreSubmit', {
tournamentid: this.tournamentId,
score: score,
callback: (result) => {
this.hideSubmissionProgress();
closeScoreDialog();

if (result.success) {
this.handleSuccessfulSubmission(result, score);
} else {
this.handleSubmissionError(result);
}
}
});
}

handleSuccessfulSubmission(result, submittedScore) {
// Update local stats
this.submissionCount = result.submissionCount || this.submissionCount + 1;
if (submittedScore > this.bestScore) {
this.bestScore = submittedScore;
}

// Show success animation
this.showSuccessAnimation(result);

// Update tournament UI
this.updateTournamentUI(result);

// Check for rank improvements
if (result.currentRank) {
this.checkRankImprovement(result.currentRank);
}
}

showSuccessAnimation(result) {
const animation = document.createElement('div');
animation.className = 'score-submission-animation';

let animationType = 'success';
if (result.message?.includes('personal best')) {
animationType = 'personal-best';
} else if (result.currentRank && result.currentRank <= 10) {
animationType = 'top-rank';
}

animation.innerHTML = `
<div class="animation-content ${animationType}">
<div class="animation-icon">${this.getAnimationIcon(animationType)}</div>
<div class="animation-text">${this.getAnimationText(animationType, result)}</div>
</div>
`;

document.body.appendChild(animation);

setTimeout(() => {
document.body.removeChild(animation);
}, 3000);
}

getAnimationIcon(type) {
const icons = {
'success': '✓',
'personal-best': '🏆',
'top-rank': '👑'
};
return icons[type] || '✓';
}

getAnimationText(type, result) {
const texts = {
'success': 'Score Submitted!',
'personal-best': `New Personal Best: ${this.formatScore(result.bestScore)}!`,
'top-rank': `Amazing! You're #${result.currentRank}!`
};
return texts[type] || 'Score Submitted!';
}

formatScore(score) {
if (score >= 1000000) {
return (score / 1000000).toFixed(1) + 'M';
} else if (score >= 1000) {
return (score / 1000).toFixed(1) + 'K';
}
return score.toString();
}

getTimeRemaining() {
// This would typically come from tournament data
return '2h 15m';
}
}

// Usage
const scoreManager = new TournamentScoreManager('weekly-challenge-001');

// When game completes
scoreManager.onGameComplete(1500);

window.closeScoreDialog = function() {
const dialog = document.querySelector('.score-submission-dialog');
if (dialog) {
document.body.removeChild(dialog);
}
};

window.submitScoreToTournament = function(score) {
scoreManager.submitScoreToTournament(score);
};

Quick Score Submission

Simple score submission for arcade-style games:

function submitTournamentScore(score) {
// Validate score
if (!isValidScore(score)) {
showNotification('Invalid score value', 'error');
return;
}

// Show loading state
const submitBtn = document.getElementById('submit-score-btn');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';

MoitribeSDK('my-game-id', 'groupTournamentScoreSubmit', {
tournamentid: currentTournamentId,
score: score,
callback: (result) => {
// Restore button
submitBtn.disabled = false;
submitBtn.textContent = originalText;

if (result.success) {
// Update score display
updateScoreDisplay(result);

// Show success message
showScoreSuccess(result);

// Update leaderboard if visible
if (isLeaderboardVisible()) {
refreshLeaderboard();
}

} else {
// Show error
showNotification(result.msg || 'Failed to submit score', 'error');
}
}
});
}

function isValidScore(score) {
// Basic validation
return typeof score === 'number' &&
!isNaN(score) &&
score >= 0 &&
Number.isInteger(score);
}

function updateScoreDisplay(result) {
// Update current rank
if (result.currentRank) {
const rankElement = document.getElementById('player-rank');
rankElement.textContent = `#${result.currentRank}`;

// Add animation for rank change
rankElement.classList.add('rank-updated');
setTimeout(() => {
rankElement.classList.remove('rank-updated');
}, 1000);
}

// Update best score
if (result.bestScore) {
const bestScoreElement = document.getElementById('best-score');
bestScoreElement.textContent = formatScore(result.bestScore);
}

// Update submission count
if (result.submissionCount) {
const countElement = document.getElementById('submission-count');
countElement.textContent = `${result.submissionCount} submissions`;
}
}

function showScoreSuccess(result) {
let message = 'Score submitted successfully!';

if (result.currentRank) {
message += ` Current rank: #${result.currentRank}`;
}

if (result.message?.includes('personal best')) {
message += ' 🏆 Personal best!';
}

showNotification(message, 'success');
}

Score Submission with Confirmation

Add confirmation for high scores:

function submitScoreWithConfirmation(score) {
// Check if this is a significant score
const currentBest = getCurrentBestScore();
const isSignificant = score > currentBest * 1.1; // 10% better than current best

if (isSignificant) {
showScoreConfirmation(score, currentBest);
} else {
submitTournamentScore(score);
}
}

function showScoreConfirmation(score, currentBest) {
const modal = document.createElement('div');
modal.className = 'score-confirmation-modal';
modal.innerHTML = `
<div class="modal-content">
<h2>🎉 Excellent Score!</h2>
<div class="score-comparison">
<div class="new-score">
<div class="score-label">New Score</div>
<div class="score-value highlight">${formatScore(score)}</div>
</div>
<div class="score-arrow">→</div>
<div class="current-best">
<div class="score-label">Previous Best</div>
<div class="score-value">${formatScore(currentBest)}</div>
</div>
</div>
<div class="improvement">
Improvement: ${calculateImprovement(score, currentBest)}
</div>
<div class="confirmation-actions">
<button class="btn-cancel" onclick="cancelScoreSubmission()">Cancel</button>
<button class="btn-submit" onclick="confirmScoreSubmission(${score})">
Submit Score
</button>
</div>
</div>
`;

document.body.appendChild(modal);
}

function calculateImprovement(newScore, oldScore) {
if (oldScore === 0) return 'First score!';

const improvement = ((newScore - oldScore) / oldScore * 100).toFixed(1);
return `+${improvement}%`;
}

window.cancelScoreSubmission = function() {
const modal = document.querySelector('.score-confirmation-modal');
if (modal) {
document.body.removeChild(modal);
}
};

window.confirmScoreSubmission = function(score) {
cancelScoreSubmission();
submitTournamentScore(score);
};

Score Submission History

Track and display submission history:

class ScoreSubmissionTracker {
constructor(tournamentId) {
this.tournamentId = tournamentId;
this.submissions = this.loadSubmissions();
}

loadSubmissions() {
const key = `tournament-submissions-${this.tournamentId}`;
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : [];
}

saveSubmissions() {
const key = `tournament-submissions-${this.tournamentId}`;
localStorage.setItem(key, JSON.stringify(this.submissions));
}

trackSubmission(score, result) {
const submission = {
score: score,
timestamp: Date.now(),
rank: result.currentRank,
wasPersonalBest: result.message?.includes('personal best'),
submissionNumber: result.submissionCount
};

this.submissions.push(submission);
this.saveSubmissions();

// Keep only last 50 submissions
if (this.submissions.length > 50) {
this.submissions = this.submissions.slice(-50);
}
}

getSubmissionHistory() {
return this.submissions.sort((a, b) => b.timestamp - a.timestamp);
}

getBestScore() {
return this.submissions.reduce((best, sub) =>
sub.score > best ? sub.score : best, 0);
}

getBestRank() {
return this.submissions.reduce((best, sub) =>
(!best || sub.rank < best) ? sub.rank : best, null);
}

displaySubmissionHistory() {
const history = this.getSubmissionHistory();
const container = document.getElementById('submission-history');

if (history.length === 0) {
container.innerHTML = '<p>No submissions yet</p>';
return;
}

container.innerHTML = `
<h3>Submission History</h3>
<div class="history-list">
${history.map(sub => `
<div class="history-item ${sub.wasPersonalBest ? 'personal-best' : ''}">
<div class="history-score">${formatScore(sub.score)}</div>
<div class="history-rank">#${sub.rank}</div>
<div class="history-time">${formatRelativeTime(sub.timestamp)}</div>
${sub.wasPersonalBest ? '<div class="best-badge">🏆</div>' : ''}
</div>
`).join('')}
</div>
`;
}
}

function formatRelativeTime(timestamp) {
const now = Date.now();
const diff = now - timestamp;
const minutes = Math.floor(diff / (1000 * 60));
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);

if (days > 0) return `${days}d ago`;
if (hours > 0) return `${hours}h ago`;
if (minutes > 0) return `${minutes}m ago`;
return 'Just now';
}

Error Handling

Handle common score submission errors:

function handleScoreSubmissionError(error, score) {
console.error('Score submission error:', error);

let userMessage = 'Failed to submit score';
let canRetry = true;
let retryDelay = 1000;

if (error.includes('not joined')) {
userMessage = 'You must join the tournament first';
canRetry = false;
showJoinTournamentPrompt();
} else if (error.includes('active')) {
userMessage = 'Tournament is not currently active';
canRetry = false;
showTournamentStatus();
} else if (error.includes('ended')) {
userMessage = 'This tournament has ended';
canRetry = false;
showTournamentResults();
} else if (error.includes('limit')) {
userMessage = 'You have reached the submission limit';
canRetry = false;
showSubmissionLimitInfo();
} else if (error.includes('invalid')) {
userMessage = 'Invalid score value';
canRetry = false;
} else if (error.includes('network')) {
userMessage = 'Network error. Please check your connection';
retryDelay = 5000;
} else if (error.includes('server')) {
userMessage = 'Server error. Please try again';
retryDelay = 3000;
} else {
userMessage = error;
}

showNotification(userMessage, 'error', canRetry ? () => {
setTimeout(() => submitTournamentScore(score), retryDelay);
} : null);
}

Best Practices

Score Validation

Validate scores before submission:

function validateScoreForSubmission(score, tournamentData) {
// Type validation
if (typeof score !== 'number' || isNaN(score)) {
return { valid: false, reason: 'Score must be a valid number' };
}

// Range validation
if (score < 0) {
return { valid: false, reason: 'Score cannot be negative' };
}

// Tournament-specific validation
if (tournamentData.scoreType === 'time' && score > 3600000) {
return { valid: false, reason: 'Time scores cannot exceed 1 hour' };
}

// Reasonableness check (prevent cheating)
const currentBest = getCurrentBestScore();
if (currentBest > 0 && score > currentBest * 100) {
return { valid: false, reason: 'Score seems unrealistic' };
}

return { valid: true };
}

Submission Throttling

Prevent excessive submissions:

class SubmissionThrottler {
constructor() {
this.lastSubmission = 0;
this.submissionCount = 0;
this.resetTime = Date.now();
this.maxSubmissionsPerMinute = 10;
this.maxSubmissionsPerHour = 100;
}

canSubmit() {
const now = Date.now();

// Reset counters if needed
if (now - this.resetTime > 60 * 60 * 1000) { // 1 hour
this.resetCounters();
}

// Check minute limit
const timeSinceLastSubmission = now - this.lastSubmission;
if (timeSinceLastSubmission < 6000) { // Less than 6 seconds
return { canSubmit: false, reason: 'Please wait before submitting again' };
}

// Check minute limit
const submissionsInLastMinute = this.getSubmissionsInLastMinute();
if (submissionsInLastMinute >= this.maxSubmissionsPerMinute) {
return { canSubmit: false, reason: 'Too many submissions. Please wait.' };
}

// Check hour limit
if (this.submissionCount >= this.maxSubmissionsPerHour) {
return { canSubmit: false, reason: 'Hourly submission limit reached' };
}

return { canSubmit: true };
}

recordSubmission() {
const now = Date.now();
this.lastSubmission = now;
this.submissionCount++;
}

resetCounters() {
this.submissionCount = 0;
this.resetTime = Date.now();
}

getSubmissionsInLastMinute() {
// This would need to track recent submissions more precisely
// Simplified for example
return this.lastSubmission > (Date.now() - 60 * 1000) ? 1 : 0;
}
}

const submissionThrottler = new SubmissionThrottler();

function submitScoreWithThrottling(score) {
const canSubmit = submissionThrottler.canSubmit();

if (!canSubmit.canSubmit) {
showNotification(canSubmit.reason, 'warning');
return;
}

submissionThrottler.recordSubmission();
submitTournamentScore(score);
}

Next Steps

After submitting scores:

Related topics: