Skip to content

Latest commit

 

History

23 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

College Basketball Prediction Game 🏀

A web-based prediction game where university students pick winners of NCAA men's college basketball games, earn/lose points based on odds-based scoring (favorites worth less, underdogs worth more), and compete on seasonal leaderboards.

Features

  • ✅ .edu authentication - Only university students can sign up
  • 🏀 Live game data - Real-time NCAA basketball games from ESPN
  • 📊 Odds-based scoring - Log-scaled scoring based on betting odds
  • 🏆 Season leaderboards - Compete against other players
  • 👤 User profiles - Season points + lifetime points tracking
  • ⏰ Pick locking - Picks lock 5 minutes before game time
  • 🔄 Auto-scoring - Games scored automatically when final

Tech Stack

  • Frontend: Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS
  • Backend: Next.js API Routes
  • Database: Supabase (PostgreSQL)
  • Authentication: Supabase Auth
  • Data Sources:
    • ESPN API (unofficial) for games/scores
    • The Odds API for betting lines
  • Deployment: Vercel

Prerequisites

  • Node.js 18+ and npm/yarn/pnpm
  • Supabase account (free tier works)
  • The Odds API key (free tier: 500 requests/month)

Local Setup

1. Clone and Install Dependencies

npm install

2. Set Up Supabase

  1. Create a new project at supabase.com
  2. Go to SQL Editor and run the migrations in order:
    • supabase/migrations/001_initial_schema.sql
    • supabase/migrations/002_rls_policies.sql
  3. Get your project URL and keys from Settings > API

3. Get The Odds API Key

  1. Sign up at the-odds-api.com
  2. Get your API key from the dashboard
  3. Free tier includes 500 requests/month

4. Configure Environment Variables

Create a .env.local file:

# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# The Odds API
ODDS_API_KEY=your-odds-api-key

# ESPN API (no key needed)
ESPN_API_BASE_URL=https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball

# Optional: Cron secret for securing cron endpoints
CRON_SECRET=your-random-secret-string

# App URL
NEXT_PUBLIC_APP_URL=http://localhost:3000

5. Run Development Server

npm run dev

Open http://localhost:3000

6. Seed Data (Development)

To populate games for testing, manually trigger the cron jobs:

# Sync games from ESPN
curl -X POST http://localhost:3000/api/cron/sync-games

# Fetch odds
curl -X POST http://localhost:3000/api/cron/fetch-odds

# Score finished games
curl -X POST http://localhost:3000/api/cron/score-finals

Deployment to Vercel

1. Push to GitHub

git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/yourusername/basketball-picks.git
git push -u origin main

2. Deploy to Vercel

  1. Go to vercel.com and sign in
  2. Click Add New Project
  3. Import your GitHub repository
  4. Add all environment variables from .env.local
  5. Deploy!

3. Configure Cron Jobs

Vercel will automatically set up the cron jobs defined in vercel.json:

  • /api/cron/sync-games - Every 15 minutes
  • /api/cron/fetch-odds - Every 20 minutes
  • /api/cron/score-finals - Every 10 minutes

Optional: Restrict cron endpoints by setting CRON_SECRET in Vercel and sending it as Authorization: Bearer {CRON_SECRET} header.

Scoring System

This app uses log-scaled odds-based scoring to make picking underdogs more rewarding while keeping scores balanced.

How It Works

  1. Convert odds to implied probability:

    • Negative odds (favorite): p = |odds| / (|odds| + 100)
    • Positive odds (underdog): p = 100 / (odds + 100)
  2. Calculate points for correct pick:

    points = round(A * ln(1/p) + B)
    

    Where:

    • A = 80 (multiplier)
    • B = 20 (base points)
    • ln = natural logarithm
  3. Calculate points for incorrect pick:

    points = -round(correct_points * LOSS_MULT)
    

    Where LOSS_MULT = 0.7

Example Scenarios

Scenario Odds Probability Correct Incorrect
Heavy favorite -300 75% +43 pts -30 pts
Moderate favorite -150 60% +61 pts -43 pts
Even odds 0 50% +75 pts -53 pts
Moderate underdog +150 40% +93 pts -65 pts
Heavy underdog +300 25% +131 pts -92 pts

Key points:

  • Picking favorites correctly = lower reward
  • Picking underdogs correctly = higher reward
  • Losing hurts, but less than winning helps (70% penalty)
  • Logarithmic scaling prevents extreme outliers

Database Schema

Key Tables

  • users - User profiles (.edu validated)
  • seasons - Basketball seasons (e.g., 2026)
  • games - NCAA games with scores and odds
  • picks - User predictions
  • user_season_stats - Fast leaderboard queries
  • user_lifetime_stats - All-time points

See supabase/migrations/001_initial_schema.sql for full schema.

API Routes

Public Routes

  • GET /api/games - Fetch games with user picks
  • GET /api/leaderboard - Season leaderboard
  • GET /api/users/[username] - User profile

Authenticated Routes

  • POST /api/picks - Create/update pick
  • GET /api/picks - Get user's picks

Cron Routes (Internal)

  • POST /api/cron/sync-games - Sync from ESPN
  • POST /api/cron/fetch-odds - Fetch from Odds API
  • POST /api/cron/score-finals - Score finished games

Project Structure

hackathon2026/
├── src/
│   ├── app/                    # Next.js pages (App Router)
│   │   ├── api/                # API routes
│   │   │   ├── auth/           # Auth callback
│   │   │   ├── games/          # Games endpoint
│   │   │   ├── picks/          # Picks endpoint
│   │   │   ├── leaderboard/    # Leaderboard endpoint
│   │   │   ├── users/          # User profiles
│   │   │   └── cron/           # Background jobs
│   │   ├── dashboard/          # Main game picking UI
│   │   ├── leaderboard/        # Leaderboard page
│   │   ├── profile/            # User profiles
│   │   └── login/              # Auth page
│   ├── lib/                    # Core logic
│   │   ├── supabase/           # Supabase clients
│   │   ├── espn.ts             # ESPN API integration
│   │   ├── odds.ts             # Odds API integration
│   │   └── scoring.ts          # Scoring engine
│   ├── components/             # React components
│   └── types/                  # TypeScript types
├── supabase/
│   └── migrations/             # SQL migrations
├── package.json
├── tsconfig.json
├── tailwind.config.ts
└── vercel.json                 # Deployment config

Demo Script

Quick Demo Flow (5 minutes)

  1. Sign Up (30 sec)

    • Go to /login
    • Sign up with a .edu email
    • Show validation working
  2. Browse Games (1 min)

    • Dashboard shows upcoming NCAA games
    • Filter by upcoming/live/final
    • Show odds displayed for each game
  3. Make Picks (1 min)

    • Click to pick home or away team
    • Show pick confirmation
    • Try to edit pick before lock time
    • Show that picks lock 5 min before game
  4. View Leaderboard (1 min)

    • Navigate to Leaderboard
    • Show rankings by season points
    • Show win/loss records and accuracy
  5. Profile (1 min)

    • Click on your username
    • Show season stats vs lifetime stats
    • Show recent picks history
  6. Scoring Demo (30 sec)

    • If a game is final, show how points were awarded
    • Explain underdog = more points
    • Show both correct and incorrect picks

Admin/Development Commands

# Manually trigger game sync
curl -X POST http://localhost:3000/api/cron/sync-games

# Manually trigger odds fetch
curl -X POST http://localhost:3000/api/cron/fetch-odds

# Manually score games
curl -X POST http://localhost:3000/api/cron/score-finals

Development Notes

.edu Validation

  • Client-side: Input validation on signup form
  • Server-side: Enforced in signup API (mandatory)
  • Email must end with .edu to create account

Picks Locking

  • Picks lock 5 minutes before game start time
  • Lock time enforced server-side in POST /api/picks
  • UI shows "Picks locked" message

Idempotent Scoring

  • Scoring only processes picks where points_awarded IS NULL
  • Safe to run scoring cron multiple times
  • Prevents double-awarding points

ESPN API Notes

  • Unofficial API - endpoints may change
  • No API key required
  • Returns today's games by default
  • Can specify date range: ?dates=20260301,20260302

Odds API Notes

  • Official API with key
  • Free tier: 500 requests/month
  • Caches for 5 minutes to reduce API calls
  • Fuzzy team name matching (handles variations)

Troubleshooting

No games showing up

# Check if games are in database
# Run in Supabase SQL Editor:
SELECT * FROM games ORDER BY start_time DESC LIMIT 10;

# If empty, manually trigger sync:
curl -X POST http://localhost:3000/api/cron/sync-games

Odds not displaying

# Verify ODDS_API_KEY is set
echo $ODDS_API_KEY

# Manually trigger odds fetch:
curl -X POST http://localhost:3000/api/cron/fetch-odds

# Check API quota at the-odds-api.com dashboard

Picks not scoring

# Verify game is marked as final:
SELECT * FROM games WHERE status = 'final' AND home_score IS NOT NULL;

# Trigger scoring:
curl -X POST http://localhost:3000/api/cron/score-finals

# Check for unscored picks:
SELECT * FROM picks WHERE points_awarded IS NULL;

RLS Policy Errors

Make sure you ran BOTH migrations:

  1. 001_initial_schema.sql (creates tables)
  2. 002_rls_policies.sql (enables RLS and creates policies)

Future Enhancements

  • Friends system (send/accept requests)
  • Friends-only leaderboard view
  • Group/college leaderboards
  • Push notifications for game starts
  • Historical season archives
  • Bracket challenges (March Madness)
  • Multi-sport support

License

MIT

Credits

Built for Hackathon 2026. Uses data from ESPN (unofficial API) and The Odds API.

About

College basketball prediction game for university students. Pick NCAA winners, earn log-scaled points based on betting odds, and climb season leaderboards. Next.js, TypeScript, Supabase, Vercel cron jobs.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages