3cb-data

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.alexwennerberg.com/3cb-data.git
Log | Files | Refs | README | LICENSE

commit 3ac5a213ea481d1291168520780e2f8becd54b8e
parent f7cd2d7cb698e4aa0dbd2e90c1538d87b083d439
Author: alex wennerberg <alex@alexwennerberg.com>
Date:   Fri,  7 Feb 2025 16:49:50 -0500

ruby rewrite

Diffstat:
AGemfile.lock | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MREADME.md | 7+------
Dbuildpage.py | 33---------------------------------
Amain.rb | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apublic/style.css | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Drequirements.txt | 5-----
Mrunround.sh | 2+-
Aschema.sql | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msql/delete-last.sql | 6+++---
Dsql/export.sql | 35-----------------------------------
Dsql/players.sql | 26--------------------------
Dsql/topdecks.sql | 13-------------
Dtemplates/index.html | 49-------------------------------------------------
Aviews/card.erb | 3+++
Aviews/footer.erb | 6++++++
Aviews/header.erb | 14++++++++++++++
Aviews/index.erb | 9+++++++++
Aviews/matches.erb | 24++++++++++++++++++++++++
Aviews/player.erb | 15+++++++++++++++
Aviews/round.erb | 21+++++++++++++++++++++
Aviews/submission.erb | 11+++++++++++
21 files changed, 359 insertions(+), 171 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock @@ -0,0 +1,59 @@ +GEM + remote: https://rubygems.org/ + specs: + base64 (0.2.0) + logger (1.6.4) + mustermann (3.0.3) + ruby2_keywords (~> 0.0.1) + nio4r (2.7.4) + puma (6.5.0) + nio4r (~> 2.0) + rack (3.1.8) + rack-protection (4.1.1) + base64 (>= 0.1.0) + logger (>= 1.6.0) + rack (>= 3.0.0, < 4) + rack-session (2.0.0) + rack (>= 3.0.0) + rackup (2.2.1) + rack (>= 3) + ruby2_keywords (0.0.5) + sinatra (4.1.1) + logger (>= 1.6.0) + mustermann (~> 3.0) + rack (>= 3.0.0, < 4) + rack-protection (= 4.1.1) + rack-session (>= 2.0.0, < 3) + tilt (~> 2.0) + sqlite3 (2.4.1-aarch64-linux-gnu) + sqlite3 (2.4.1-aarch64-linux-musl) + sqlite3 (2.4.1-arm-linux-gnu) + sqlite3 (2.4.1-arm-linux-musl) + sqlite3 (2.4.1-arm64-darwin) + sqlite3 (2.4.1-x86-linux-gnu) + sqlite3 (2.4.1-x86-linux-musl) + sqlite3 (2.4.1-x86_64-darwin) + sqlite3 (2.4.1-x86_64-linux-gnu) + sqlite3 (2.4.1-x86_64-linux-musl) + tilt (2.5.0) + +PLATFORMS + aarch64-linux-gnu + aarch64-linux-musl + arm-linux-gnu + arm-linux-musl + arm64-darwin + x86-linux-gnu + x86-linux-musl + x86_64-darwin + x86_64-linux-gnu + x86_64-linux-musl + +DEPENDENCIES + puma + rackup + sinatra + sqlite3 + +BUNDLED WITH + 2.5.9 diff --git a/README.md b/README.md @@ -5,15 +5,10 @@ Utilities for pulling data from 3 card blind metashape: https://sites.google.com/view/3cb-metashape/home -Dependencies: - - python - - sqlite +(Working on Ruby rewrite) Pull all cards from mtgjson https://mtgjson.com/downloads/all-files/ wget https://mtgjson.com/api/v5/AtomicCards.json cat AtomicCards.json | jq -r '.data | keys[]' > allcards.txt - -Then: - python3 getdata.py diff --git a/buildpage.py b/buildpage.py @@ -1,33 +0,0 @@ -import sqlite3,urllib -from jinja2 import Environment, PackageLoader, select_autoescape - -con = sqlite3.connect("3cb.db") -env = Environment( - loader=PackageLoader("buildpage"), - autoescape=select_autoescape() - ) - -def card_link(card): - return f"https://scryfall.com/search?q={urllib.parse.quote_plus(card)}" - -env.filters['card_link'] = card_link -env.filters['zip'] = zip - -def rows_to_dict(res): - desc = res.description - column_names = [col[0] for col in desc] - return [dict(zip(column_names, row)) for row in res.fetchall()] - -def export_html(): - with open("sql/export.sql") as f: - query = f.read() - res = con.cursor().execute(query) - decks = rows_to_dict(res) - template = env.get_template("index.html") - print(template.render(decks=decks)) - -if __name__ == "__main__": - export_html() - - - diff --git a/main.rb b/main.rb @@ -0,0 +1,63 @@ +require 'sinatra' +require 'sqlite3' +require 'erb' +include ERB::Util + +db = SQLite3::Database.new "3cb.db" +db.results_as_hash = true +set :server_settings, :timeout => 5 + +configure :production do + require 'prometheus/middleware/collector' + require 'prometheus/middleware/exporter' + use Rack::Deflater + use Prometheus::Middleware::Collector + use Prometheus::Middleware::Exporter +end + + +get '/' do + @rounds = db.execute "select * from round order by id desc" + @subtitle = "rounds" + erb :index +end + +get '/round/:id' do |id| + @round = db.execute("select * from round where id = ?", id)[0] + @matches = db.execute "select * from round_score where round = ? order by final_score desc,prelim_score desc", id + @subtitle = "round " + id + erb :matches +end + +get '/card' do + name = params[:name] + @matches = db.execute "select * from round_score where + card1 = ?1 or + card2 = ?1 or + card3 = ?1 order by round desc", name + @subtitle = name + erb :matches +end + +get '/player' do + name = params[:name] + @matches = db.execute "select * from round_score where player = ? order by round desc", name + @subtitle = "player " + name + erb :matches # rename round -> matches +end + +get '/deck' do + cards = [params[:c1], params[:c2], params[:c3]].sort + # i am lazy + @matches = db.execute(" + select * from round_score where + card1 = ?1 and card2 = ?2 and card3 = ?3 or + card1 = ?1 and card2 = ?3 and card3 = ?2 or + card1 = ?2 and card2 = ?1 and card3 = ?3 or + card1 = ?2 and card2 = ?3 and card3 = ?1 or + card1 = ?3 and card2 = ?1 and card3 = ?2 or + card1 = ?3 and card2 = ?2 and card3 = ?1 + order by round desc", [cards[0], cards[1], cards[2]]) + @subtitle = cards.join("; ") + erb :matches +end diff --git a/public/style.css b/public/style.css @@ -0,0 +1,75 @@ +:root { + --text: #000; + --link: #00a; +} + +body { + font-size:16px; + background: var(--bg); + color: var(--text); + margin: 0; + font-family: Courier, monospace; + word-wrap: break-word; + line-height: 1.5; +} + +.flextable { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +img { + margin: 0; +} + +.deck-box { + display: flex; + column-gap: 0.5rem; + flex-direction: row; + justify-content: space-between; + margin: 0.2rem; + padding: 0.2rem; + background: #eee; +} + +@media only screen and (max-width: 767px) { + .deck-box { + flex-direction: column; + } +} + +header { + font-size:1.4rem; + padding-left: 0.3rem; + border-bottom: 1px solid black; +} +footer { + border-top: 1px solid black; + text-align:right; +} + +main { + padding: 0.3rem; +} + +a { + color: var(--link); +} +a:hover { + text-decoration: none; +} +.hover_img a { position:relative; } +.hover_img a span { position:absolute; display:none; z-index:99; } +.hover_img a:hover span { display:block; } + +@media only screen and (max-width: 600px) { + .hide-mobile { + display:none; + } + body { + font-size: 16px; + border: 0px; + } +} + diff --git a/requirements.txt b/requirements.txt @@ -1,5 +0,0 @@ -requests -titlecase -openpyxl -Levenshtein -jinja2 diff --git a/runround.sh b/runround.sh @@ -3,5 +3,5 @@ python getdata.py sqlite3 3cb.db < sql/new.sql sqlite3 3cb.db < sql/export.sql > _site/export.tsv python3 buildpage.py > _site/index.html -rsync -r _site/ root@pi:/var/www/data.3cardmagic.org +rsync -r _site/ alpine@fishbb.org:/var/www/data.3cardmagic.org diff --git a/schema.sql b/schema.sql @@ -0,0 +1,54 @@ +CREATE TABLE round ( + id integer primary key, + fileid text not null +); +CREATE TABLE deck ( + round integer not null, + player varchar not null, + card1 text, + card2 text, + card3 text +); +CREATE TABLE match ( + round integer, + group_name text, + player text, + opp_player text, + score integer +); +CREATE VIEW card as + select round,player,card1 as card from deck + union select round,player,card2 as card from deck + union select round,player,card3 as card from deck +/* card(round,player,card) */; +CREATE INDEX matchidx on match(round); +CREATE INDEX deckidx on deck(round); +CREATE INDEX matchidx2 on match(player); +CREATE INDEX deckidx2 on deck(player); +CREATE VIEW rank as +select round,player,rank() over (partition by round order by +sum(case when match.group_name = 'final' then match.score else null end) desc nulls last, +sum(case when match.group_name != 'final' then match.score else null end) desc +) as rank +from match group by 1,2 order by 3 desc +/* rank(round,player,rank) */; +CREATE VIEW round_score as +select deck.round, deck.player, +card1, +card2, +card3, +min(match.group_name) as prelim_group, +sum(case when match.group_name != 'final' then match.score else 0 end) as prelim_score, +sum(case when match.group_name = 'final' then match.score else null end) as final_score, +rank.rank, +count(*) as num_players +from deck join match +on deck.round = match.round and deck.player = match.player +join rank +on deck.round = rank.round and deck.player = rank.player +group by 1,2 +/* round_score(round,player,card1,card2,card3,prelim_group,prelim_score,final_score,rank,num_players) */; +CREATE TABLE ban (name text); +CREATE INDEX c1idx on deck(card1); +CREATE INDEX c2idx on deck(card2); +CREATE INDEX c3idx on deck(card3); diff --git a/sql/delete-last.sql b/sql/delete-last.sql @@ -1,3 +1,3 @@ -delete from round where id > 68; -delete from deck where round > 68; -delete from match where round > 68; +delete from round where id > 72; +delete from deck where round > 72; +delete from match where round > 72; diff --git a/sql/export.sql b/sql/export.sql @@ -1,35 +0,0 @@ -/* best-per-round */ -/* .headers off */ -/* .mode csv */ -/* .mode list */ -/* .separator "\t" */ - -with firsts as ( - select card,min(round) as first_round from card - group by 1 -), - -summary as (select deck.round,deck.player, -card1, -card2, -card3, -f1.card is not null as card1new, -f2.card is not null as card2new, -f3.card is not null as card3new, -min(match.group_name) as prelim_group, -sum(case when match.group_name != 'final' then match.score else 0 end) as prelim_score, -sum(case when match.group_name = 'final' then match.score else null end) as final_score -from deck join match -on deck.round = match.round and deck.player = match.player -left join firsts f1 -on f1.card = deck.card1 -and f1.first_round = deck.round -left join firsts f2 -on f2.card = deck.card2 -and f2.first_round = deck.round -left join firsts f3 -on f3.card = deck.card3 -and f3.first_round = deck.round -group by 1,2 order by 1 desc, prelim_group,2) - -select * from summary order by round desc,final_score desc,prelim_score desc,prelim_group,player; diff --git a/sql/players.sql b/sql/players.sql @@ -1,26 +0,0 @@ -/* TODO slightly off -- jsut sums finals and original together */ -with rank as ( -select round,player, percent_rank() over ( - partition by round - order by sum(score) -) as rank from match -group by 1,2 order by 1 -), - -new as ( -select min(card.round) as rnd, card, card.player -from match join card on match.round = card.round and match.player = card.player -group by 2 order by 1), - -newcnt as (select player,count(1) as count from new group by 1), -/* select * from newcnt; */ -/* TODO -- median percentile, not mean */ -allpl as ( -select deck.player, count(1) as cnt, printf('%.2f', avg(rank.rank)) as avg_rank, -group_concat(printf('%.2f', rank.rank), ',') as ranks -from deck -join rank on deck.round = rank.round -and deck.player = rank.player -group by 1 order by 3 desc) - -select * from allpl where cnt > 10; diff --git a/sql/topdecks.sql b/sql/topdecks.sql @@ -1,13 +0,0 @@ -select deck.card1, deck.card2, deck.card3, cast(avg(score) * 100 as int) / 100.0 as avgscore, count(1) from -deck -join match on deck.round = match.round and deck.player = match.player -join deck d2 on deck.round = d2.round and match.opp_player = d2.player -left join ban on deck.card1 = ban.name -left join ban b2 on deck.card2 = b2.name -left join ban b3 on deck.card3 = b3.name -where d2.player is not null -and (ban.name is null and b2.name is null and b3.name is null) -group by 1 -having count(1) > 30 -order by avgscore desc -limit 5; diff --git a/templates/index.html b/templates/index.html @@ -1,49 +0,0 @@ -<head> - <meta charset="utf-8"> -<style> -table { font-family: Arial, Helvetica, sans-serif; border-collapse: collapse; width: 100%; } -td, th { border: 1px solid #ddd; padding: 8px; } -tr:nth-child(even){background-color: #f2f2f2;} -tr:hover {background-color: #ddd;} -#th { padding-top: 12px; padding-bottom: 12px; text-align: left; background-color: #04AA6D; color: white; } -.hover_img a { position:relative; } -.hover_img a span { position:absolute; display:none; z-index:99; } -.hover_img a:hover span { display:block; } -</style> -<title>3 card blind data analysis</title> -<script src="sorttable.js"></script> -</head> -<body> - <h1>3 Card Blind Data Analysis</h1> - Full, cleaned data for <a href="//3cardmagic.org">3 card blind metashape</a>. Source code can be found <a href="https://git.sr.ht/~aw/3cb-data">here</a>. <a href="export.tsv">Download</a> an export as tsv-formatted data.<br> - Maintained by aw on the 3cb discord. Tag him for questions/comments/feature requests.<br> - <b>Bold</b> represents that that round is the first time this card was played. - <p>Click on a header to sort by that value.</p> - <table class="sortable"> - <th><td>player</td><td>card 1</td><td>card 2</td> - <td>card 3</td><td>group</td><td>score</td><td>final</td> - </th> - {% for deck in decks %} - <tr> - <td>{{deck.round}}</td> - <td>{{deck.player}}</td> - {% set cards = [deck.card1, deck.card2, deck.card3] %} - {% set new = [deck.card1new, deck.card2new, deck.card3new] %} - {% for card, new in cards|zip(new) %} - <td> - <div class="hover_img"> - <a href="https://scryfall.com/search?q={{card}}"> - {% if new %}<b>{{card}}</b>{% else %}{{card}}{% endif %} - <span> - <img loading=lazy src="https://api.scryfall.com/cards/named?exact={{card}}&format=image&version=small" /></span></a> - </div> - </td> - {% endfor %} - <td>{{deck.prelim_group}}</td> - <td>{{deck.prelim_score}}</td> - <td>{{deck.final_score or ""}}</td> - </tr> - {% endfor %} - </table> -</body> -</html> diff --git a/views/card.erb b/views/card.erb @@ -0,0 +1,3 @@ +<%= erb :header %> +<%= erb :footer%> + diff --git a/views/footer.erb b/views/footer.erb @@ -0,0 +1,6 @@ +<footer> + Made by aw for <a href="https://sites.google.com/view/3cb-metashape/home">3 Card Blind Metashape</a>. <a href="https://git.sr.ht/~aw/3cb-data">Source</a> +</footer> +</main> +</body> +</html> diff --git a/views/header.erb b/views/header.erb @@ -0,0 +1,14 @@ +<html> +<html dir="ltr" lang="en"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1" /> +<meta name="description" content="Data analysis for 3 card blind metashape" /> +<link rel="stylesheet" type="text/css" href="/style.css"> +<title>3 card blind data analysis</title> +</head> +<body> + <header> + <b><a href="/">3cb-data</a></b> &gt; <b><%= @subtitle %></b> + </header> +<main> diff --git a/views/index.erb b/views/index.erb @@ -0,0 +1,9 @@ +<%= erb :header %> +<div class="flextable"> +<% @rounds.each do |round| %> + <div class="flextable-item"> + <a href="/round/<%= round["id"] %>"><%= round["id"].to_s.rjust(2, "0") %></a>&nbsp; + </div> + <% end %> +</div> +<%= erb :footer %> diff --git a/views/matches.erb b/views/matches.erb @@ -0,0 +1,24 @@ +<%= erb :header %> + <% if @round %> + <a href='https://docs.google.com/spreadsheets/d/<%= @round["fileid"] %>/'>Google Sheet</a><% end %> +<div class="flextable"> + <% @matches.each do |match| %> + <div class="deck-box" + <% if match["rank"] == 1 %> style="background:#c5ffc5" <% elsif match["final_score"] %> style="background:#ffffc5"<% end %>> + <div style="width:160px"> + <b><a href='/player?name=<%= match["player"] %>'><%= match["player"] %></a></b><br> + <a href="/round/<%= match["round"]%>">Round <%= match["round"] %></a><br> + Rank: &nbsp;&nbsp;<%= match["rank"] %><br> + Groups<%= match["group_name"] %>: <%= match["prelim_score"] %> +<% if match["final_score"] %><br> + Finals: <%= match["final_score"] %> +<% end %><br> + <a href="/deck?c1=<%= match["card1"] %>&c2=<%= match["card2"] %>&c3=<%= match["card3"]%>">deck</a><br> +</div> +<div> + <% [match["card1"], match["card2"], match["card3"]].each do |card| %><a href="/card?name=<%= card %>"><img alt='<%= card %>' width=146 height=204px loading=lazy src="https://api.scryfall.com/cards/named?exact=<%= url_encode(card) %>&format=image&version=medium" /></a><% end %> +</div> +</div> +<% end %> +</div> +<%= erb :footer %> diff --git a/views/player.erb b/views/player.erb @@ -0,0 +1,15 @@ +<%= erb :header %> +Submissions: +<table> +<% @matches.each do |match| %> + <tr> + <td><%= match["round"] %></td> + <td><%= match["prelim_score"] %></td> + <td><%= match["final_score"] %></td> + <td><%= match["card1"] %></td> + <td><%= match["card2"] %></td> + <td><%= match["card3"] %></td> + </tr> +<% end %> +</table> +<%= erb :footer%> diff --git a/views/round.erb b/views/round.erb @@ -0,0 +1,21 @@ +<%= erb :header %> + <% if @round %> + <a href='https://docs.google.com/spreadsheets/d/<%= @round["fileid"] %>/'>Google Sheet</a><% end %> +<div class="flextable"> + <% @matches.each do |match| %> + <div class="deck-box" + <% if match["final_score"] %> style="background:#ffffc5"<% end %>> + <div style="width:160px"> + <b><a href='/player/<%= match["player"] %>'><%= match["player"] %></a></b><br> + <a href="/round/<%= match["round"]%>"><%= match["round"] %></a> + <a href="/deck?card=<%= match["card1"] %>&card=<%= match["card2"] %>card= <%= match["card3"]%>>deck</a><br> + Group <%= match["prelim_group"] %>: <%=match["prelim_score"]%><br> + <% if match["final_score"] %>Finals: &nbsp;<%= match["final_score"] %><% end %> +</div> +<div> + <% [match["card1"], match["card2"], match["card3"]].each do |card| %><img alt='<%= card %>' width=146height=204px loading=lazy src="https://api.scryfall.com/cards/named?exact=<%= card %>&format=image&version=small" /><% end %> +</div> +</div> +<% end %> +</div> +<%= erb :footer %> diff --git a/views/submission.erb b/views/submission.erb @@ -0,0 +1,11 @@ +<div class="deck-match"> + <div> + playername<br> + Group A: 43<br> + Finals: 10<br> + </div> + <div> + card123 + deck link + </div> +</div>