From 586014e23c439c6bc56d1ed7810aa006b44975f5 Mon Sep 17 00:00:00 2001 From: Eduardo Figueroa Date: Thu, 28 Aug 2025 21:32:08 -0700 Subject: [PATCH] init --- app.py | 134 +++++++++++++++++ nginx.conf | 52 +++++++ static/pricedown-b1.ttf | Bin 0 -> 108024 bytes static/scoreboard.js | 69 +++++++++ static/script.js | 65 +++++++++ static/style.css | 299 ++++++++++++++++++++++++++++++++++++++ templates/input.html | 35 +++++ templates/scoreboard.html | 36 +++++ 8 files changed, 690 insertions(+) create mode 100644 app.py create mode 100644 nginx.conf create mode 100644 static/pricedown-b1.ttf create mode 100644 static/scoreboard.js create mode 100644 static/script.js create mode 100644 static/style.css create mode 100644 templates/input.html create mode 100644 templates/scoreboard.html diff --git a/app.py b/app.py new file mode 100644 index 0000000..76272ea --- /dev/null +++ b/app.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +""" +Fig Fam Fantasy Plinko Score Tracker +A local web app for tracking player scores with real-time updates +""" + +import sqlite3 +import json +from flask import Flask, render_template, request, jsonify, Response +from threading import Lock +import time + +app = Flask(__name__) +db_lock = Lock() + +# Database Setup +def init_db(): + """Initialize SQLite database with players table""" + with sqlite3.connect('database.db') as conn: + conn.execute(''' + CREATE TABLE IF NOT EXISTS players ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL DEFAULT '', + score INTEGER DEFAULT 0 + ) + ''') + + # Initialize 14 players if empty + count = conn.execute('SELECT COUNT(*) FROM players').fetchone()[0] + if count == 0: + for i in range(1, 15): + conn.execute( + 'INSERT INTO players (id, name, score) VALUES (?, ?, ?)', + (i, f'Player {i}', 0) + ) + conn.commit() + +# Database Operations +def get_all_players(): + """Retrieve all players sorted by ID for input page""" + with sqlite3.connect('database.db') as conn: + conn.row_factory = sqlite3.Row + return [dict(row) for row in conn.execute( + 'SELECT * FROM players ORDER BY id' + ).fetchall()] + +def get_players_by_score(): + """Retrieve all players sorted by score (highest first) for scoreboard""" + with sqlite3.connect('database.db') as conn: + conn.row_factory = sqlite3.Row + return [dict(row) for row in conn.execute( + 'SELECT * FROM players ORDER BY score DESC, name ASC' + ).fetchall()] + +def update_player(player_id, name=None, score=None): + """Update player name and/or score""" + with db_lock: + with sqlite3.connect('database.db') as conn: + if name is not None and score is not None: + conn.execute( + 'UPDATE players SET name = ?, score = ? WHERE id = ?', + (name, score, player_id) + ) + elif name is not None: + conn.execute( + 'UPDATE players SET name = ? WHERE id = ?', + (name, player_id) + ) + elif score is not None: + conn.execute( + 'UPDATE players SET score = ? WHERE id = ?', + (score, player_id) + ) + conn.commit() + +# Web Routes +@app.route('/') +def input_page(): + """Mobile-friendly score input page""" + players = get_all_players() + return render_template('input.html', players=players) + +@app.route('/scoreboard') +def scoreboard(): + """TV display scoreboard page""" + players = get_players_by_score() # Sort by score for display + return render_template('scoreboard.html', players=players) + +@app.route('/api/players') +def api_get_players(): + """API endpoint to get all players (sorted by ID for input)""" + return jsonify(get_all_players()) + +@app.route('/api/update', methods=['POST']) +def api_update_player(): + """API endpoint to update player data""" + data = request.get_json() + player_id = data.get('id') + name = data.get('name') + score = data.get('score') + + if player_id and (name is not None or score is not None): + update_player(player_id, name, score) + return jsonify({'success': True}) + return jsonify({'success': False, 'error': 'Invalid data'}) + +@app.route('/events') +def events(): + """Server-Sent Events for real-time scoreboard updates""" + def event_stream(): + last_data = None + while True: + players = get_players_by_score() # Sort by score for real-time updates + current_data = json.dumps(players) + + # Send update only if data changed + if current_data != last_data: + yield f"data: {current_data}\n\n" + last_data = current_data + + time.sleep(1) # Check every second + + return Response( + event_stream(), + mimetype='text/plain', + headers={ + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive' + } + ) + +if __name__ == '__main__': + init_db() + app.run(host='0.0.0.0', port=8080, debug=True) diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..af22977 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,52 @@ +# nginx.conf +server { + listen 8080; + server_name localhost; + + # Static file serving + location /static/ { + alias /path/to/your/project/static/; + expires 1d; + add_header Cache-Control "public, no-transform"; + } + + # Proxy all other requests to Flask + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE specific headers + proxy_buffering off; + proxy_cache off; + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; + } + + # SSE endpoint optimization + location /events { + proxy_pass http://127.0.0.1:5000/events; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Disable buffering for real-time updates + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 24h; + proxy_send_timeout 24h; + proxy_set_header Connection ''; + proxy_http_version 1.1; + chunked_transfer_encoding off; + + # CORS headers if needed + add_header Access-Control-Allow-Origin *; + add_header Access-Control-Allow-Methods "GET, OPTIONS"; + add_header Access-Control-Allow-Headers "Origin, Content-Type"; + } +} + diff --git a/static/pricedown-b1.ttf b/static/pricedown-b1.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8ba3a976a58b3fabc21726f00fa381a8c05d5476 GIT binary patch literal 108024 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 O@Lsjozn*ZxivI-q_kGy_ literal 0 HcmV?d00001 diff --git a/static/scoreboard.js b/static/scoreboard.js new file mode 100644 index 0000000..2c9bae6 --- /dev/null +++ b/static/scoreboard.js @@ -0,0 +1,69 @@ +// static/scoreboard.js - Real-time scoreboard updates +document.addEventListener('DOMContentLoaded', function() { + + // Server-Sent Events for Real-time Updates + const eventSource = new EventSource('/events'); + + eventSource.onmessage = function(event) { + try { + const players = JSON.parse(event.data); + updateScoreboard(players); + } catch (error) { + console.error('Error parsing player data:', error); + } + }; + + eventSource.onerror = function(error) { + console.error('EventSource error:', error); + // Attempt to reconnect after 5 seconds + setTimeout(() => { + location.reload(); + }, 5000); + }; + + // Update scoreboard display with sorted players + function updateScoreboard(players) { + const columns = document.querySelectorAll('.column'); + + // Clear existing rows + columns.forEach(col => col.innerHTML = ''); + + // Rebuild grid with current rankings + players.forEach((player, index) => { + const rank = index + 1; + const columnIndex = Math.floor(index / 7); + + if (columnIndex < 2) { + const scoreRow = createScoreRow(player, rank); + columns[columnIndex].appendChild(scoreRow); + } + }); + } + + // Create a score row element + function createScoreRow(player, rank) { + const row = document.createElement('div'); + row.className = 'score-row'; + row.dataset.id = player.id; + + // Highlight leader (position 1) + if (rank === 1) { + row.classList.add('leader'); + } + + row.innerHTML = ` + ${rank} + ${player.name} + ${player.score} + `; + + return row; + } + + // Prevent screen sleep for TV display + if ('wakeLock' in navigator) { + navigator.wakeLock.request('screen').catch(err => { + console.log('Wake lock failed:', err); + }); + } +}); diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..11d61a2 --- /dev/null +++ b/static/script.js @@ -0,0 +1,65 @@ +// static/script.js - Input page functionality +document.addEventListener('DOMContentLoaded', function() { + + // Mobile Input Controls + const playerCards = document.querySelectorAll('.player-card'); + + playerCards.forEach(card => { + const updateBtn = card.querySelector('.update-btn'); + const nameInput = card.querySelector('.name-input'); + const scoreInput = card.querySelector('.score-input'); + const playerId = card.dataset.id; + + // Update player data on button click + updateBtn.addEventListener('click', function() { + const name = nameInput.value.trim(); + const score = parseInt(scoreInput.value) || 0; + + updatePlayer(playerId, name, score); + }); + + // Update on Enter key press + [nameInput, scoreInput].forEach(input => { + input.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + updateBtn.click(); + } + }); + }); + + // Auto-select score input content for easy editing + scoreInput.addEventListener('focus', function() { + this.select(); + }); + }); + + // API call to update player + async function updatePlayer(id, name, score) { + try { + const response = await fetch('/api/update', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + id: parseInt(id), + name: name, + score: score + }) + }); + + const result = await response.json(); + if (result.success) { + // Visual feedback for successful update + const card = document.querySelector(`[data-id="${id}"]`); + card.style.background = '#90EE90'; + setTimeout(() => { + card.style.background = ''; + }, 500); + } + } catch (error) { + console.error('Update failed:', error); + alert('Failed to update player data'); + } + } +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..541dc54 --- /dev/null +++ b/static/style.css @@ -0,0 +1,299 @@ +/* static/style.css */ + +/* Font Face Declaration */ +@font-face { + font-family: 'PlinkoFont'; + src: url('pricedown-b1.ttf') format('truetype'); + font-display: swap; +} + +/* static/style.css */ + +/* Price Is Right Color Palette */ +:root { + --pir-blue: #0066CC; + --pir-yellow: #FFD700; + --pir-red: #FF4444; + --pir-green: #00AA44; + --pir-orange: #FF8800; + --pir-white: #FFFFFF; + --pir-light-blue: #66AAFF; + --gradient-bg: linear-gradient(135deg, var(--pir-blue), var(--pir-light-blue)); +} + +/* Global Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + color: var(--pir-white); + overflow-x: hidden; +} + +/* Input Page Styles - Mobile Optimized */ +.input-page { + min-height: 100vh; + padding: 20px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; + background: var(--gradient-bg); +} + +/* Input Page Styles - Mobile Optimized */ +.input-page { + min-height: 100vh; + padding: 20px; +} + +.input-page .container { + max-width: 600px; + margin: 0 auto; +} + +.input-page h1 { + text-align: center; + font-size: 2.5rem; + font-weight: bold; + color: var(--pir-yellow); + text-shadow: 3px 3px 6px rgba(0,0,0,0.5); + margin-bottom: 30px; + text-transform: uppercase; +} + +.input-grid { + display: grid; + grid-template-columns: 1fr; + gap: 15px; + margin-bottom: 30px; +} + +.player-card { + background: var(--pir-white); + color: var(--pir-blue); + border-radius: 15px; + padding: 20px; + border: 4px solid var(--pir-yellow); + box-shadow: 0 6px 12px rgba(0,0,0,0.3); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; +} + +.player-card label { + display: block; + font-weight: bold; + font-size: 1.2rem; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; + margin-bottom: 10px; + text-transform: uppercase; +} + +.name-input, .score-input { + width: 100%; + padding: 15px; + font-size: 1.1rem; + border: 3px solid var(--pir-blue); + border-radius: 8px; + margin-bottom: 10px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; + font-weight: normal; +} + +.score-input { + text-align: center; + font-size: 1.5rem; +} + +.update-btn { + width: 100%; + background: var(--pir-red); + color: var(--pir-white); + border: none; + padding: 15px; + font-size: 1.2rem; + font-weight: bold; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; + border-radius: 8px; + cursor: pointer; + text-transform: uppercase; + transition: all 0.2s ease; +} + +.update-btn:hover { + background: #CC2222; + transform: translateY(-2px); +} + +.actions { + text-align: center; +} + +.view-board-btn { + display: inline-block; + background: var(--pir-green); + color: var(--pir-white); + text-decoration: none; + padding: 20px 40px; + font-size: 1.3rem; + font-weight: bold; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif; + border-radius: 12px; + text-transform: uppercase; + transition: all 0.2s ease; +} + +.view-board-btn:hover { + background: #008833; + transform: translateY(-2px); +} + +/* Scoreboard Page Styles - TV Optimized */ +.scoreboard-page { + height: 100vh; + padding: 40px; + font-family: 'PlinkoFont', 'Arial Black', Arial, sans-serif; + background: linear-gradient(135deg, + var(--pir-blue) 0%, + var(--pir-light-blue) 50%, + #4488DD 100%); +} + +.scoreboard-container { + height: 100%; + display: flex; + flex-direction: column; + max-width: 1200px; + margin: 0 auto; +} + +.scoreboard-page .title { + text-align: center; + font-size: 4rem; + color: var(--pir-yellow); + text-shadow: 4px 4px 0px var(--pir-red), + 8px 8px 16px rgba(0,0,0,0.7); + margin-bottom: 40px; + text-transform: uppercase; + letter-spacing: 3px; +} + +.scoreboard-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 30px; + flex-grow: 1; + align-items: start; +} + +.column { + display: flex; + flex-direction: column; + gap: 15px; +} + +.score-row { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 20px; + background: var(--pir-white); + color: var(--pir-blue); + padding: 25px; + border-radius: 15px; + border: 6px solid var(--pir-yellow); + box-shadow: 0 8px 16px rgba(0,0,0,0.4); + align-items: center; + transition: all 0.5s ease; + min-height: 80px; + animation: slideIn 0.3s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.score-row:hover { + transform: scale(1.02); + box-shadow: 0 12px 24px rgba(0,0,0,0.5); +} + +.score-row.leader { + border-color: var(--pir-orange); + background: linear-gradient(135deg, var(--pir-white), #FFF8DC); + box-shadow: 0 12px 24px rgba(255, 136, 0, 0.4); +} + +.player-rank { + font-size: 2rem; + font-weight: bold; + background: var(--pir-red); + color: var(--pir-white); + width: 50px; + height: 50px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + text-shadow: 2px 2px 4px rgba(0,0,0,0.3); +} + +.player-name { + font-size: 1.8rem; + font-weight: bold; + text-align: left; + text-transform: uppercase; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.player-score { + font-size: 2.5rem; + font-weight: bold; + color: var(--pir-red); + text-shadow: 2px 2px 4px rgba(0,0,0,0.2); + min-width: 80px; + text-align: right; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .input-grid { + grid-template-columns: 1fr; + } + + .input-page h1 { + font-size: 2rem; + } +} + +@media (max-width: 1024px) { + .scoreboard-page .title { + font-size: 3rem; + } + + .score-row { + padding: 20px; + min-height: 70px; + } + + .player-rank { + width: 40px; + height: 40px; + font-size: 1.5rem; + } + + .player-name { + font-size: 1.5rem; + } + + .player-score { + font-size: 2rem; + } +} diff --git a/templates/input.html b/templates/input.html new file mode 100644 index 0000000..0578a1d --- /dev/null +++ b/templates/input.html @@ -0,0 +1,35 @@ + + + + + + + Plinko Score Input + + + +
+

Fig Fam Fantasy Plinko

+
+ {% for player in players %} +
+ + + + +
+ {% endfor %} +
+ +
+ + + diff --git a/templates/scoreboard.html b/templates/scoreboard.html new file mode 100644 index 0000000..3307f61 --- /dev/null +++ b/templates/scoreboard.html @@ -0,0 +1,36 @@ + + + + + + + Plinko Scoreboard + + + +
+

Fig Fam Fantasy Plinko

+
+
+ {% for player in players[:7] %} +
+ {{ loop.index }} + {{ player.name }} + {{ player.score }} +
+ {% endfor %} +
+
+ {% for player in players[7:14] %} +
+ {{ loop.index + 7 }} + {{ player.name }} + {{ player.score }} +
+ {% endfor %} +
+
+
+ + +