import { db, schema } from '../db/index.js' import { sql, eq } from 'drizzle-orm' import { nanoid } from 'nanoid' import { createTournament, joinTournament, startTournament } from './tournaments.js' import { randomArena } from './arenas.js' import { getCurrentSeason } from './seasons.js' import { logger } from '../lib/logger.js' /** Seed a dev tournament with mock bots for testing betting/tournament UI */ export async function seedDevTournament(): Promise { // Skip if a tournament already exists const existing = db.select({ count: sql`count(*)` }) .from(schema.tournaments) .get() if (existing && existing.count > 0) { logger.info('dev-seed', 'tournaments already exist — skipping seed') return } // Grab 8 mock bots for the tournament const bots = db.select({ id: schema.bots.id, name: schema.bots.name }) .from(schema.bots) .limit(8) .all() if (bots.length < 4) { logger.warn('dev-seed', `only ${bots.length} bots — need at least 4 for tournament seed`) return } // Create tournament const tournamentId = createTournament( 'SATOSHI SHOWDOWN #1', 'single_elim', 8, 100, ) // Join bots for (const bot of bots.slice(0, 8)) { try { joinTournament(tournamentId, bot.id) } catch (err) { logger.warn('dev-seed', `failed to join bot ${bot.name}: ${(err as Error).message}`) } } // Start the tournament (seeds by elo, generates bracket) try { startTournament(tournamentId) logger.info('dev-seed', `seeded tournament "${tournamentId}" with ${Math.min(bots.length, 8)} bots`) } catch (err) { logger.warn('dev-seed', `failed to start tournament: ${(err as Error).message}`) } logger.info('dev-seed', 'dev seeding complete — tournament ready for testing') } /** Seed a diverse fight card with scheduled fights for the Fight Card page */ export async function seedFightCard(): Promise { // Skip if scheduled fights already exist const scheduled = db.select({ count: sql`count(*)` }) .from(schema.fights) .where(eq(schema.fights.status, 'scheduled')) .get() if (scheduled && scheduled.count > 0) { logger.info('dev-seed', 'scheduled fights already exist — skipping fight card seed') return } // Get all bots by type const allBots = db.select({ id: schema.bots.id, name: schema.bots.name, botType: schema.bots.botType, webhookUrl: schema.bots.webhookUrl, archetype: schema.bots.archetype, tier: schema.bots.tier, eloRating: schema.bots.eloRating, }) .from(schema.bots) .where(eq(schema.bots.isActive, true)) .all() const humans = allBots.filter(b => b.webhookUrl === 'http://human.local/') const mockBots = allBots.filter(b => b.botType === 'mock') const classicBots = allBots.filter(b => b.botType === 'classic') const userBots = allBots.filter(b => b.botType === 'regular' && b.webhookUrl !== 'http://human.local/') function pick(arr: T[]): T { return arr[Math.floor(Math.random() * arr.length)] } const season = getCurrentSeason() const fights: Array<{ botAId: string; botBId: string; label: string }> = [] // 1. AI vs AI (mock bots fighting each other) — main event if (mockBots.length >= 2) { const a = pick(mockBots.filter(b => b.tier >= 4)) || pick(mockBots) let b = pick(mockBots.filter(x => x.id !== a.id && x.tier >= 3)) || pick(mockBots.filter(x => x.id !== a.id)) if (b) fights.push({ botAId: a.id, botBId: b.id, label: 'AI vs AI (high tier)' }) } // 2. Human vs AI (mock bot) if (humans.length > 0 && mockBots.length > 0) { fights.push({ botAId: pick(humans).id, botBId: pick(mockBots).id, label: 'Human vs AI' }) } // 3. AI vs AI (different archetypes, mid tier) if (mockBots.length >= 4) { const midBots = mockBots.filter(b => b.tier >= 1 && b.tier <= 3) if (midBots.length >= 2) { const a = pick(midBots) const b = pick(midBots.filter(x => x.id !== a.id && x.archetype !== a.archetype)) || pick(midBots.filter(x => x.id !== a.id)) if (b) fights.push({ botAId: a.id, botBId: b.id, label: 'AI vs AI (mid tier)' }) } } // 4. Human vs Classic Bot if (humans.length > 0 && classicBots.length > 0) { fights.push({ botAId: pick(humans).id, botBId: pick(classicBots).id, label: 'Human vs Classic' }) } // 5. Bot vs Bot (user bots or mock if none) if (userBots.length >= 2) { const a = pick(userBots) const b = pick(userBots.filter(x => x.id !== a.id)) if (b) fights.push({ botAId: a.id, botBId: b.id, label: 'Bot vs Bot (user)' }) } else if (mockBots.length >= 6) { const usedIds = new Set(fights.flatMap(f => [f.botAId, f.botBId])) const available = mockBots.filter(b => !usedIds.has(b.id)) if (available.length >= 2) { fights.push({ botAId: available[0].id, botBId: available[1].id, label: 'Bot vs Bot' }) } } // 6. AI vs Classic Bot if (mockBots.length > 0 && classicBots.length > 0) { fights.push({ botAId: pick(mockBots).id, botBId: pick(classicBots).id, label: 'AI vs Classic' }) } // 7. Human vs Human (if we have 2+) if (humans.length >= 2) { const a = pick(humans) const b = pick(humans.filter(x => x.id !== a.id)) if (b) fights.push({ botAId: a.id, botBId: b.id, label: 'Human vs Human' }) } // 8. Rookie rumble — tier 0 bots { const rookies = mockBots.filter(b => b.tier === 0) if (rookies.length >= 2) { fights.push({ botAId: rookies[0].id, botBId: rookies[1].id, label: 'Rookie Rumble' }) } } // 9. Legend clash — tier 5 bots { const legends = mockBots.filter(b => b.tier >= 5) const usedIds = new Set(fights.flatMap(f => [f.botAId, f.botBId])) const available = legends.filter(b => !usedIds.has(b.id)) if (available.length >= 2) { fights.push({ botAId: available[0].id, botBId: available[1].id, label: 'Legend Clash' }) } } // 10. Wild card — random pairing from anything left { const usedIds = new Set(fights.flatMap(f => [f.botAId, f.botBId])) const remaining = allBots.filter(b => !usedIds.has(b.id)) if (remaining.length >= 2) { const a = pick(remaining) const b = pick(remaining.filter(x => x.id !== a.id)) if (b) fights.push({ botAId: a.id, botBId: b.id, label: 'Wild Card' }) } } // Insert all as scheduled fights const now = new Date() for (let i = 0; i < fights.length; i++) { const f = fights[i] const scheduledTime = new Date(now.getTime() + (i + 1) * 10 * 60 * 1000) // stagger 10 min apart try { db.insert(schema.fights).values({ id: nanoid(12), botAId: f.botAId, botBId: f.botBId, arena: randomArena().id, status: 'scheduled', currentSeason: season.id, scheduledAt: scheduledTime.toISOString(), createdAt: now.toISOString(), }).run() logger.info('dev-seed', `fight card: ${f.label}`) } catch (err) { logger.warn('dev-seed', `fight card failed: ${(err as Error).message}`) } } logger.info('dev-seed', `seeded ${fights.length} scheduled fights for fight card`) }