Skip to content

Comments

basic tournament creation impl#64

Open
DIodide wants to merge 28 commits intostagingfrom
iamin-vinay/tournament-creation
Open

basic tournament creation impl#64
DIodide wants to merge 28 commits intostagingfrom
iamin-vinay/tournament-creation

Conversation

@DIodide
Copy link
Member

@DIodide DIodide commented Jan 11, 2026

Architecture Overview

flowchart TD
    subgraph Frontend["Frontend (blockwarriors-next)"]
        TL[Tournament List Page]
        TD[Tournament Detail Page]
        TC[Tournament Creation Form]
        TM[Tournament Match View]
    end

    subgraph Backend["Backend (Convex)"]
        TS[tournaments.ts]
        TMS[tournamentMatches.ts]
        HTTP[http.ts extensions]
    end

    subgraph Database["Schema Extensions"]
        TTable[tournaments table]
        TPTable[tournament_participants table]
        TMTable[tournament_matches table]
    end

    Frontend --> Backend
    Backend --> Database

Loading

Phase 1: Database Schema

Add three new tables to packages/backend/convex/schema.ts:

tournaments table

  • name, description, format (round_robin | double_elim)
  • status: "registration" | "in_progress" | "completed" | "cancelled"
  • is_official: boolean (true = admin-created qualifiers/finals)
  • created_by: user_id string
  • max_teams, min_teams, games_per_match (for best-of-N)
  • registration_deadline, start_time, end_time

tournament_participants table

  • tournament_id, team_id
  • seed (optional, for seeding)
  • joined_at
  • Indexes: by_tournament_id, by_team_id, by_tournament_id_and_team_id

tournament_matches table

  • tournament_id, round, match_number
  • team1_id, team2_id (references teams)
  • winner_team_id, status
  • scheduled_time
  • games: array of match_ids (references existing matches table)
  • games_required (e.g., 2 for best-of-3)

Phase 2: Convex Backend Functions

New file: packages/backend/convex/tournaments.ts

Mutations:

  • createTournament - Create tournament (check admin for is_official)
  • joinTournament - Register team (any team member can invoke)
  • leaveTournament - Withdraw team before tournament starts
  • startTournament - Generate Round Robin matches (admin/creator only)
  • cancelTournament - Cancel tournament (admin/creator only)

Queries:

  • getTournament - Get tournament with participant count
  • listTournaments - List tournaments (filterable by status, official)
  • getTournamentBracket - Get all matches for bracket view
  • getMyTournaments - Tournaments where user's team is participating

New file: packages/backend/convex/tournamentMatches.ts

Mutations:

  • createTournamentGame - Create a game match within a tournament match
  • completeTournamentMatch - Mark match complete, update standings
  • generateRoundRobinSchedule - Internal: generate n*(n-1)/2 matches

Queries:

  • getTournamentMatch - Get match details with game results
  • getTournamentStandings - Calculate wins/losses/points for Round Robin

Extensions to packages/backend/convex/http.ts

New endpoints for Minecraft server:

  • POST /tournaments/{tid}/matches/{mid}/games/new - Create game in tournament match
  • POST /tournaments/{tid}/matches/{mid}/games/update - Update game result
  • GET /tournaments/{tid}/matches/queued - Get queued tournament matches

Phase 3: Frontend Implementation

Tournament List Page: /dashboard/tournaments/page.tsx

  • Grid of tournament cards (official badge, status, team count)
  • Filters: All, Official, My Tournaments
  • "Create Tournament" button (visible to all, but official checkbox admin-only)

Tournament Detail Page: /dashboard/tournaments/[id]/page.tsx

  • Header: name, description, format, status, dates
  • Registration panel: Join/Leave button (if registration open)
  • Participant list with team names
  • Round Robin bracket/schedule view (when started)
  • Standings table with W/L/Points

Tournament Match Page: /dashboard/tournaments/[id]/matches/[matchId]/page.tsx

  • Similar to existing /dashboard/matches/[id]/page.tsx
  • Shows tournament context (round, standings impact)
  • Lists individual games within the match
  • Token generation for team members

Tournament Creation Form: /dashboard/tournaments/create/page.tsx

  • Name, description, format dropdown
  • Min/max teams, games per match (best-of-N)
  • Registration deadline, start time
  • Official toggle (admin-only, hidden for regular users)

Key Implementation Details

Team Authorization Pattern

- Added profile page with image upload, basic information, and team details.
- Created edit profile page for updating user information.
- Introduced team page displaying team members and tournament history.
- Implemented team edit functionality, including leadership transfer and team image management.
- Enhanced backend schema to support new fields for user profiles and teams.
- Added image upload component for profile and team images with validation.
- Updated API to handle profile and team image uploads and deletions.
@DIodide DIodide requested a review from haoteng10 January 12, 2026 21:24
@DIodide
Copy link
Member Author

DIodide commented Jan 12, 2026

Would like to merge this in for now, but will keep the branch undeleted so that work can continue in it

@haoteng10 haoteng10 requested a review from Copilot January 15, 2026 16:00
@haoteng10
Copy link
Member

@copilot Check for bugs

Copy link

Copilot AI commented Jan 15, 2026

@haoteng10 I've opened a new pull request, #67, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive tournament system for BlockWarriors, introducing round robin tournament functionality with database schema changes, backend mutations/queries, HTTP endpoints, and a complete frontend UI with multiple views (matrix, schedule, teams, rounds).

Changes:

  • Added tournament database schema (tournaments, tournament_participants, tournament_matches tables)
  • Implemented backend tournament logic with mutations for creating, joining, leaving, starting, and canceling tournaments
  • Created frontend pages for tournament listing, creation, detail views, and match management
  • Extended team and user profile functionality with image uploads and additional fields

Reviewed changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
packages/backend/convex/schema.ts Added three new tables for tournaments system with appropriate indexes
packages/backend/convex/tournaments.ts New file implementing tournament CRUD operations and round robin scheduling
packages/backend/convex/tournamentMatches.ts New file for tournament match management, standings calculation, and game result recording
packages/backend/convex/teams.ts Extended with tournament history, image uploads, leadership transfer, and forfeit handling
packages/backend/convex/userProfiles.ts Added profile image support and getUserProfileWithTeamMembers query
packages/backend/convex/http.ts Added 8 new HTTP endpoints for tournament operations
packages/backend/convex/seed.ts New comprehensive seeding file for development data
apps/blockwarriors-next/src/lib/tournament-constants.ts Constants and types for tournament system
apps/blockwarriors-next/src/components/ui/image-upload.tsx Reusable image upload component for profiles and teams
apps/blockwarriors-next/src/app/dashboard/tournaments/*.tsx Four new pages for tournament list, creation, detail, and match views
apps/blockwarriors-next/src/app/dashboard/teams/page.tsx Complete rewrite with client-side rendering and enhanced UI
apps/blockwarriors-next/src/app/dashboard/team/*.tsx New team management pages (view, edit)
apps/blockwarriors-next/src/app/dashboard/profile/page.tsx New user profile page
Comments suppressed due to low confidence (1)

packages/backend/convex/tournaments.ts:1

  • Magic number 1000 for starting ELO should be extracted to a constant in a shared configuration file for consistency with the frontend DEFAULT_TOURNAMENT_CONFIG and easier maintenance.
import { query, mutation, internalMutation } from "./_generated/server";

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

);

// Filter out nulls and return
return tournaments.filter((t) => t !== null) as typeof tournaments extends (infer T | null)[] ? NonNullable<T>[] : never;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This complex type casting is hard to read. Consider using a simpler filter with type guard: tournaments.filter((t): t is NonNullable<typeof t> => t !== null)

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +34
* Mock user data - represents different types of participants
* These use fake user IDs since we can't create real auth users
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says 'fake user IDs' but these are actual seed user IDs that could conflict with real users. Consider adding a prefix like 'seed_' to make them more distinguishable and document the cleanup procedure for development databases.

Copilot uses AI. Check for mistakes.
Comment on lines 392 to 398
// Check if we've already reached the required games
const totalGames = tournamentMatch.team1_games_won + tournamentMatch.team2_games_won;
const maxGames = tournamentMatch.games_required * 2 - 1; // Best of N
if (totalGames >= maxGames) {
return { success: false, error: "Maximum games already played" };
}

Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maxGames calculation for best-of-N is incorrect. For games_required=1 (single game), this calculates 12-1=1 (correct). But for games_required=2 (best of 3), it calculates 22-1=3 (correct). However, the logic should prevent creating more games once a team reaches games_required wins, not when total games equals maxGames. The current check on line 393 totalGames >= maxGames could allow one too many games to be created in edge cases.

Suggested change
// Check if we've already reached the required games
const totalGames = tournamentMatch.team1_games_won + tournamentMatch.team2_games_won;
const maxGames = tournamentMatch.games_required * 2 - 1; // Best of N
if (totalGames >= maxGames) {
return { success: false, error: "Maximum games already played" };
}

Copilot uses AI. Check for mistakes.
import { Trophy } from 'lucide-react';
'use client';

import { useState } from 'react';
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import useState.

Suggested change
import { useState } from 'react';

Copilot uses AI. Check for mistakes.
@@ -1,6 +1,7 @@
import { query, mutation } from "./_generated/server";
import { query, mutation, internalMutation } from "./_generated/server";
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import internalMutation.

Suggested change
import { query, mutation, internalMutation } from "./_generated/server";
import { query, mutation } from "./_generated/server";

Copilot uses AI. Check for mistakes.
import { query, mutation, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { Id } from "./_generated/dataModel";
import { internal } from "./_generated/api";
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import internal.

Suggested change
import { internal } from "./_generated/api";

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,755 @@
import { query, mutation, internalMutation } from "./_generated/server";
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import internalMutation.

Suggested change
import { query, mutation, internalMutation } from "./_generated/server";
import { query, mutation } from "./_generated/server";

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +33
* Check if a user can act on behalf of a team (is a member of the team)
*/
async function canActForTeam(
ctx: any,
userId: string,
teamId: Id<"teams">
): Promise<boolean> {
const profile = await ctx.db
.query("user_profiles")
.withIndex("by_user_id", (q: any) => q.eq("user_id", userId))
.first();
return profile?.team_id === teamId;
}

/**
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused function canActForTeam.

Suggested change
* Check if a user can act on behalf of a team (is a member of the team)
*/
async function canActForTeam(
ctx: any,
userId: string,
teamId: Id<"teams">
): Promise<boolean> {
const profile = await ctx.db
.query("user_profiles")
.withIndex("by_user_id", (q: any) => q.eq("user_id", userId))
.first();
return profile?.team_id === teamId;
}
/**

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +29
/**
* Get user's team ID from their profile
*/
async function getUserTeamId(
ctx: any,
userId: string
): Promise<Id<"teams"> | null> {
const profile = await ctx.db
.query("user_profiles")
.withIndex("by_user_id", (q: any) => q.eq("user_id", userId))
.first();
return profile?.team_id ?? null;
}

Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused function getUserTeamId.

Suggested change
/**
* Get user's team ID from their profile
*/
async function getUserTeamId(
ctx: any,
userId: string
): Promise<Id<"teams"> | null> {
const profile = await ctx.db
.query("user_profiles")
.withIndex("by_user_id", (q: any) => q.eq("user_id", userId))
.first();
return profile?.team_id ?? null;
}

Copilot uses AI. Check for mistakes.
Copilot AI and others added 15 commits January 15, 2026 16:10
- Fix missing validation in updateTournament mutation
- Fix null pointer risk in recordGameResult mutation
- Fix incorrect max games calculation in createTournamentGame
- Verify frontend matrix score display (no bug found)

Co-authored-by: haoteng10 <22121227+haoteng10@users.noreply.github.com>
- Remove unused newGamesPerMatch variable
- Fix error message wording for consistency
- Add clarifying comment about byes in tournaments

Co-authored-by: haoteng10 <22121227+haoteng10@users.noreply.github.com>
Fix critical validation and null safety bugs in tournament implementation
Command	Description
/testgame <type> [player1] [player2]	Start a local test match without Convex
/setarena <name\|list\|reload>	Load/view arena configurations
/endgame [winner\|blue\|red\|none]	Force end the current game
/triggerobjective <type> [player]	Simulate game objective events
/simulatedisconnect <player> [reason]	Test disconnect handling
/simulatereconnect <player>	Test reconnect during grace period
DIodide and others added 7 commits January 17, 2026 02:34
{
  "timestamp": 1705500000000,
  "matchId": "abc123",
  "gameType": "pvp",
  "gameState": "IN_PROGRESS",
  "players": [...],
  "events": [
    {"type": "disconnect", "playerId": "uuid", "reason": "QUIT", "result": "GRACE_PERIOD", ...}
  ],
  "disconnectedPlayers": ["uuid1"],
  "gameSpecific": { /* game.getGameState() output */ }
}
…e.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants