We use an sqlite GROUP SELECT to automatically rank and return lists of boxes that have been assigned categories by players. The whole list of category rankings sits in a single json file.
749 lines
18 KiB
Go
749 lines
18 KiB
Go
//
|
|
// itb-ranking - create rankings from scoring data
|
|
//
|
|
// Copyright (c) 2017 - Auke Kok <sofar@foo-projects.org>
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject
|
|
// to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included
|
|
// in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
|
// KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/jmoiron/jsonq"
|
|
_ "github.com/mattn/go-sqlite3" // MIT licensed.
|
|
"log"
|
|
"math"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
type Score struct {
|
|
player_id int
|
|
box_id int
|
|
stype string
|
|
score float64
|
|
}
|
|
|
|
var scores []Score
|
|
|
|
type Bmeta struct {
|
|
box_id int
|
|
btype int
|
|
meta string
|
|
builder string
|
|
name string
|
|
// used for player rankings
|
|
worst_time float64
|
|
worst_damage float64
|
|
worst_deaths float64
|
|
// box ranking elements
|
|
completed_players int
|
|
completed_players_rank float64
|
|
completed_times int
|
|
completed_times_rank float64
|
|
avg_rating float64
|
|
avg_rating_rank float64
|
|
avg_time float64
|
|
avg_time_rank float64
|
|
avg_damage float64
|
|
avg_damage_rank float64
|
|
avg_deaths float64
|
|
avg_deaths_rank float64
|
|
combined float64
|
|
}
|
|
|
|
var boxes []Bmeta
|
|
|
|
type Player struct {
|
|
player_id int
|
|
completed_boxes map[int]bool
|
|
completed int
|
|
completed_rank float64
|
|
time float64
|
|
time_rank float64
|
|
damage float64
|
|
damage_rank float64
|
|
deaths float64
|
|
deaths_rank float64
|
|
combined float64
|
|
}
|
|
|
|
var players []Player
|
|
|
|
type Builder struct {
|
|
player_id int
|
|
name string
|
|
combined float64
|
|
box_count int
|
|
box_count_rank float64
|
|
box_rank_avg float64
|
|
box_rank_avg_rank float64
|
|
}
|
|
|
|
var builders []Builder
|
|
|
|
type Series struct {
|
|
series_id int
|
|
name string
|
|
}
|
|
|
|
var series []Series
|
|
|
|
func main() {
|
|
if len(os.Args) < 1 {
|
|
log.Fatal("Not enough arguments: sqlite_file")
|
|
}
|
|
f := os.Args[1]
|
|
|
|
db, err := sql.Open("sqlite3", f)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
//
|
|
// sqlite data fetching - for all data needed
|
|
//
|
|
|
|
// fetch Box meta
|
|
rows, err := db.Query("select id, type, meta from box_meta")
|
|
for rows.Next() {
|
|
var b Bmeta
|
|
|
|
err = rows.Scan(&b.box_id, &b.btype, &b.meta)
|
|
if b.btype == 0 {
|
|
data := map[string]interface{}{}
|
|
dec := json.NewDecoder(strings.NewReader(b.meta))
|
|
dec.Decode(&data)
|
|
jq := jsonq.NewQuery(data)
|
|
status, _ := jq.Int("status")
|
|
b.builder, _ = jq.String("builder")
|
|
b.name, _ = jq.String("box_name")
|
|
|
|
if status == 2 {
|
|
boxes = append(boxes, b)
|
|
}
|
|
}
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// fetch player data
|
|
rows, err = db.Query("select player_id, box_id, type, score from points")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
for rows.Next() {
|
|
var s Score
|
|
var player_id int
|
|
var box_id int
|
|
var stype string
|
|
var score float64
|
|
|
|
err = rows.Scan(&player_id, &box_id, &stype, &score)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
s.player_id = player_id
|
|
s.box_id = box_id
|
|
s.stype = stype
|
|
s.score = score
|
|
|
|
found := false
|
|
for i := range boxes {
|
|
if boxes[i].box_id == s.box_id {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
scores = append(scores, s)
|
|
}
|
|
}
|
|
rows.Close()
|
|
|
|
// write out series data
|
|
rows, err = db.Query("select id, name from series")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
for rows.Next() {
|
|
var s Series
|
|
err = rows.Scan(&s.series_id, &s.name)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
series = append(series, s)
|
|
}
|
|
rows.Close()
|
|
|
|
// make main slice
|
|
series_data := make(map[string]interface{})
|
|
for i := range series {
|
|
c := 0
|
|
s := make(map[string]interface{})
|
|
d := make(map[string]interface{})
|
|
rows, err = db.Query(fmt.Sprintf("select box_id from series_box where series_id=%v order by box_order ASC", series[i].series_id))
|
|
for rows.Next() {
|
|
var id int
|
|
dd := make(map[string]interface{})
|
|
err = rows.Scan(&id)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
c += 1
|
|
dd["id"] = id
|
|
for j := range boxes {
|
|
if boxes[j].box_id == id {
|
|
dd["builder"] = boxes[j].builder
|
|
dd["name"] = boxes[j].name
|
|
break
|
|
}
|
|
}
|
|
d[fmt.Sprintf("%v", c)] = dd
|
|
}
|
|
s["name"] = series[i].name
|
|
s["id"] = series[i].series_id
|
|
s["boxes"] = d
|
|
series_data[fmt.Sprintf("%v", i)] = s
|
|
}
|
|
sd, err := json.Marshal(series_data)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// write to disc!
|
|
of, err := os.Create("series.json")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
of.Write(sd)
|
|
of.Close()
|
|
|
|
//
|
|
// PLAYER RANKING
|
|
//
|
|
|
|
// player ranking - create base player list first, and add boxes completed
|
|
for _, p := range scores {
|
|
if p.stype == "time" {
|
|
// prune admins!
|
|
if (p.player_id == 1596) || (p.player_id == 2981) {
|
|
continue
|
|
}
|
|
|
|
found := false
|
|
for i := range players {
|
|
if players[i].player_id == p.player_id {
|
|
found = true
|
|
if !players[i].completed_boxes[p.box_id] {
|
|
players[i].completed_boxes[p.box_id] = true
|
|
players[i].completed += 1
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
var n Player
|
|
n.player_id = p.player_id
|
|
n.completed_boxes = make(map[int]bool)
|
|
n.completed_boxes[p.box_id] = true
|
|
n.completed = 1
|
|
|
|
players = append(players, n)
|
|
}
|
|
}
|
|
}
|
|
|
|
// rank boxes completed
|
|
sort.Slice(players, func(a, b int) bool { return players[a].completed > players[b].completed })
|
|
step := 1.0 / float64(len(players))
|
|
lastrank := 1.0
|
|
last := 0
|
|
for i := range players {
|
|
c := players[i].completed
|
|
if c == last {
|
|
players[i].completed_rank = lastrank
|
|
} else {
|
|
last = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
players[i].completed_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// time/deaths/damage ranking
|
|
// for this, we need to know the WORST scores for each box, since that
|
|
// will be the ranking value for players that have not completed that box
|
|
for i := range boxes {
|
|
for j := range scores {
|
|
if scores[j].box_id == boxes[i].box_id {
|
|
if scores[j].stype == "time" {
|
|
if scores[j].score > boxes[i].worst_time {
|
|
boxes[i].worst_time = scores[j].score
|
|
}
|
|
}
|
|
if scores[j].stype == "damage" {
|
|
if scores[j].score > boxes[i].worst_damage {
|
|
boxes[i].worst_damage = scores[j].score
|
|
}
|
|
}
|
|
if scores[j].stype == "deaths" {
|
|
if scores[j].score > boxes[i].worst_deaths {
|
|
boxes[i].worst_deaths = scores[j].score
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := range players {
|
|
for j := range boxes {
|
|
time := boxes[j].worst_time
|
|
damage := boxes[j].worst_damage
|
|
deaths := boxes[j].worst_deaths
|
|
|
|
for k := range scores {
|
|
if scores[k].box_id == boxes[j].box_id &&
|
|
scores[k].player_id == players[i].player_id {
|
|
if scores[k].stype == "time" {
|
|
time = math.Min(time, scores[k].score)
|
|
}
|
|
if scores[k].stype == "damage" {
|
|
damage = math.Min(damage, scores[k].score)
|
|
}
|
|
if scores[k].stype == "deaths" {
|
|
deaths = math.Min(deaths, scores[k].score)
|
|
}
|
|
}
|
|
}
|
|
|
|
players[i].time += time
|
|
players[i].damage += damage
|
|
players[i].deaths += deaths
|
|
}
|
|
}
|
|
|
|
// rank by time
|
|
sort.Slice(players, func(a, b int) bool { return players[a].time < players[b].time })
|
|
step = 1.0 / float64(len(players))
|
|
lastrank = 1.0
|
|
lastf := .0
|
|
for i := range players {
|
|
c := players[i].time
|
|
if c == lastf {
|
|
players[i].time_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
players[i].time_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// rank by damage
|
|
sort.Slice(players, func(a, b int) bool { return players[a].damage < players[b].damage })
|
|
step = 1.0 / float64(len(players))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range players {
|
|
c := players[i].damage
|
|
if c == lastf {
|
|
players[i].damage_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
players[i].damage_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// rank by deaths
|
|
sort.Slice(players, func(a, b int) bool { return players[a].deaths < players[b].deaths })
|
|
step = 1.0 / float64(len(players))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range players {
|
|
c := players[i].deaths
|
|
if c == lastf {
|
|
players[i].deaths_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
players[i].deaths_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// rank all together now
|
|
for i := range players {
|
|
players[i].combined =
|
|
players[i].completed_rank +
|
|
players[i].time_rank +
|
|
players[i].damage_rank +
|
|
players[i].deaths_rank
|
|
}
|
|
|
|
// sort and we're done, no need to normalize ranking values
|
|
sort.Slice(players, func(a, b int) bool { return players[a].combined > players[b].combined })
|
|
|
|
// write out ranking JSON
|
|
var player_topranks = make(map[string]string)
|
|
var ranklen = 30
|
|
if len(players) < ranklen {
|
|
ranklen = len(players)
|
|
}
|
|
|
|
for i := 0; i < ranklen; i++ {
|
|
var name string
|
|
err = db.QueryRow(fmt.Sprintf("SELECT name FROM player WHERE id = \"%v\"", players[i].player_id)).Scan(&name)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
player_topranks[fmt.Sprintf("%v", i+1)] = name
|
|
}
|
|
j, err := json.Marshal(player_topranks)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// write to disc!
|
|
of, err = os.Create("top_players.json")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
of.Write(j)
|
|
of.Close()
|
|
|
|
//
|
|
// BOX RANKING
|
|
//
|
|
|
|
// completed_times, completed_players
|
|
for i := range boxes {
|
|
// make sure to filter out builder runs
|
|
var builder int
|
|
err = db.QueryRow(fmt.Sprintf("SELECT id FROM player WHERE name = \"%v\"", boxes[i].builder)).Scan(&builder)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
count := 0
|
|
err = db.QueryRow(fmt.Sprintf("SELECT COUNT(player_id) FROM POINTS WHERE box_id = \"%v\" AND type = \"time\" AND player_id <> \"%v\"", boxes[i].box_id, builder)).Scan(&count)
|
|
if (err != nil) && (err != sql.ErrNoRows) {
|
|
log.Fatal(err)
|
|
}
|
|
boxes[i].completed_times = count
|
|
if count == 0 {
|
|
continue
|
|
}
|
|
|
|
pcount := 0
|
|
err = db.QueryRow(fmt.Sprintf("SELECT COUNT(DISTINCT player_id) FROM POINTS WHERE box_id = \"%v\" AND type = \"time\" AND player_id <> \"%v\"", boxes[i].box_id, builder)).Scan(&pcount)
|
|
if (err != nil) && (err != sql.ErrNoRows) {
|
|
log.Fatal(err)
|
|
}
|
|
boxes[i].completed_players = pcount
|
|
|
|
rating := .0
|
|
err = db.QueryRow(fmt.Sprintf("SELECT AVG(score) FROM points WHERE type='rating' AND box_id = \"%v\" AND player_id <> \"%v\"", boxes[i].box_id, builder)).Scan(&rating)
|
|
//if (err != nil) && (err != sql.ErrNoRows) {
|
|
// log.Fatal(err)
|
|
//}
|
|
boxes[i].avg_rating = rating
|
|
|
|
time := .0
|
|
err = db.QueryRow(fmt.Sprintf("SELECT AVG(score) FROM points WHERE type='time' AND box_id = \"%v\" AND player_id <> \"%v\"", boxes[i].box_id, builder)).Scan(&time)
|
|
if (err != nil) && (err != sql.ErrNoRows) {
|
|
log.Fatal(err)
|
|
}
|
|
boxes[i].avg_time = time
|
|
|
|
deaths := .0
|
|
err = db.QueryRow(fmt.Sprintf("SELECT AVG(score) FROM points WHERE type='deaths' AND box_id = \"%v\" AND player_id <> \"%v\"", boxes[i].box_id, builder)).Scan(&deaths)
|
|
if (err != nil) && (err != sql.ErrNoRows) {
|
|
log.Fatal(err)
|
|
}
|
|
boxes[i].avg_deaths = deaths
|
|
|
|
damage := .0
|
|
err = db.QueryRow(fmt.Sprintf("SELECT AVG(score) FROM points WHERE type='damage' AND box_id = \"%v\" AND player_id <> \"%v\"", boxes[i].box_id, builder)).Scan(&damage)
|
|
if (err != nil) && (err != sql.ErrNoRows) {
|
|
log.Fatal(err)
|
|
}
|
|
boxes[i].avg_damage = damage
|
|
}
|
|
|
|
// rank box data
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].completed_times > boxes[b].completed_times })
|
|
step = 1.0 / float64(len(boxes))
|
|
lastrank = 1.0
|
|
last = 0
|
|
for i := range boxes {
|
|
c := boxes[i].completed_times
|
|
if c == last {
|
|
boxes[i].completed_times_rank = lastrank
|
|
} else {
|
|
last = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
boxes[i].completed_times_rank = lastrank
|
|
}
|
|
}
|
|
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].completed_players > boxes[b].completed_players })
|
|
step = 1.0 / float64(len(boxes))
|
|
lastrank = 1.0
|
|
last = 0
|
|
for i := range boxes {
|
|
c := boxes[i].completed_players
|
|
if c == last {
|
|
boxes[i].completed_players_rank = lastrank
|
|
} else {
|
|
last = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
boxes[i].completed_players_rank = lastrank
|
|
}
|
|
}
|
|
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].avg_rating > boxes[b].avg_rating })
|
|
step = 1.0 / float64(len(boxes))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range boxes {
|
|
c := boxes[i].avg_rating
|
|
if c == lastf {
|
|
boxes[i].avg_rating_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
boxes[i].avg_rating_rank = lastrank
|
|
}
|
|
}
|
|
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].avg_time > boxes[b].avg_time })
|
|
step = 1.0 / float64(len(boxes))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range boxes {
|
|
c := boxes[i].avg_time
|
|
if c == lastf {
|
|
boxes[i].avg_time_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
boxes[i].avg_time_rank = lastrank
|
|
}
|
|
}
|
|
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].avg_deaths > boxes[b].avg_deaths })
|
|
step = 1.0 / float64(len(boxes))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range boxes {
|
|
c := boxes[i].avg_deaths
|
|
if c == lastf {
|
|
boxes[i].avg_deaths_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
boxes[i].avg_deaths_rank = lastrank
|
|
}
|
|
}
|
|
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].avg_damage > boxes[b].avg_damage })
|
|
step = 1.0 / float64(len(boxes))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range boxes {
|
|
c := boxes[i].avg_damage
|
|
if c == lastf {
|
|
boxes[i].avg_damage_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
boxes[i].avg_damage_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// glob all the rankings
|
|
for i := range boxes {
|
|
boxes[i].combined =
|
|
(.5 * boxes[i].avg_damage_rank) +
|
|
(.5 * boxes[i].avg_deaths_rank) +
|
|
boxes[i].avg_time_rank +
|
|
boxes[i].avg_rating_rank +
|
|
(.5 * boxes[i].completed_players_rank) +
|
|
(.5 * boxes[i].completed_times_rank)
|
|
}
|
|
// sort and output
|
|
sort.Slice(boxes, func(a, b int) bool { return boxes[a].combined > boxes[b].combined })
|
|
c := 0
|
|
var boxes_topranks = make(map[string]string)
|
|
for i := range boxes {
|
|
if (boxes[i].builder == "sofar") || (boxes[i].builder == "nore") {
|
|
continue
|
|
}
|
|
c = c + 1
|
|
boxes_topranks[fmt.Sprintf("%v", c)] = fmt.Sprintf("%v: \"%v\" by %v", boxes[i].box_id, boxes[i].name, boxes[i].builder)
|
|
if c > ranklen {
|
|
break
|
|
}
|
|
}
|
|
j, err = json.Marshal(boxes_topranks)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// write to disc!
|
|
of, err = os.Create("top_boxes.json")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
of.Write(j)
|
|
of.Close()
|
|
|
|
//
|
|
// BUILDER RANKING
|
|
//
|
|
for i := range boxes {
|
|
found := false
|
|
for b := range builders {
|
|
if builders[b].name == boxes[i].builder {
|
|
found = true
|
|
s := builders[b].box_rank_avg*float64(builders[b].box_count) + boxes[i].combined
|
|
builders[b].box_count += 1
|
|
builders[b].box_rank_avg = s / float64(builders[b].box_count)
|
|
break
|
|
}
|
|
}
|
|
if found {
|
|
continue
|
|
}
|
|
|
|
if (boxes[i].builder == "sofar") || (boxes[i].builder == "nore") {
|
|
continue
|
|
}
|
|
|
|
var n Builder
|
|
n.name = boxes[i].builder
|
|
n.box_count = 1
|
|
n.box_rank_avg = boxes[i].combined
|
|
|
|
builders = append(builders, n)
|
|
}
|
|
|
|
// rank box count
|
|
sort.Slice(builders, func(a, b int) bool { return builders[a].box_count > builders[b].box_count })
|
|
step = 1.0 / float64(len(builders))
|
|
lastrank = 1.0
|
|
last = 0
|
|
for i := range builders {
|
|
c := builders[i].box_count
|
|
if c == last {
|
|
builders[i].box_count_rank = lastrank
|
|
} else {
|
|
last = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
builders[i].box_count_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// rank boxes scoring
|
|
sort.Slice(builders, func(a, b int) bool { return builders[a].box_rank_avg > builders[b].box_rank_avg })
|
|
step = 1.0 / float64(len(builders))
|
|
lastrank = 1.0
|
|
lastf = .0
|
|
for i := range builders {
|
|
c := builders[i].box_rank_avg
|
|
if c == lastf {
|
|
builders[i].box_rank_avg_rank = lastrank
|
|
} else {
|
|
lastf = c
|
|
lastrank = 1.0 - step*float64(i)
|
|
builders[i].box_rank_avg_rank = lastrank
|
|
}
|
|
}
|
|
|
|
// combine
|
|
for i := range builders {
|
|
builders[i].combined =
|
|
builders[i].box_rank_avg_rank +
|
|
builders[i].box_count_rank
|
|
}
|
|
sort.Slice(builders, func(a, b int) bool { return builders[a].combined > builders[b].combined })
|
|
var builder_topranks = make(map[string]string)
|
|
for i := range builders {
|
|
if i > 30 {
|
|
break
|
|
}
|
|
builder_topranks[fmt.Sprintf("%v", i+1)] = fmt.Sprintf("%v", builders[i].name)
|
|
}
|
|
j, err = json.Marshal(builder_topranks)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// write to disc!
|
|
of, err = os.Create("top_builders.json")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
of.Write(j)
|
|
of.Close()
|
|
|
|
//
|
|
// CATEGORY RANKING
|
|
//
|
|
|
|
// category icons go from 2..19 atm.
|
|
var cat_series = make(map[string]map[string]string) // integer indexed array of arrays
|
|
for i := 2; i < 19; i++ {
|
|
cat_series[fmt.Sprintf("%v", i)] = make(map[string]string)
|
|
cat_rank := 1
|
|
|
|
// Must have min 3x chosen category, limit to 25 max per cat series
|
|
rows, err = db.Query(fmt.Sprintf("SELECT box_id, COUNT(box_id) AS freq FROM points WHERE type='category' AND score=%v GROUP BY box_id HAVING freq > 3 ORDER BY COUNT(box_id) DESC LIMIT 25;", i))
|
|
for rows.Next() {
|
|
var box int
|
|
var count int
|
|
|
|
err = rows.Scan(&box, &count)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
cat_series[fmt.Sprintf("%v", i)][fmt.Sprintf("%v", cat_rank)] = fmt.Sprintf("%v", box)
|
|
cat_rank++
|
|
}
|
|
}
|
|
j, err = json.Marshal(cat_series)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
of, err = os.Create("category_series.json")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// write out
|
|
of.Write(j)
|
|
of.Close()
|
|
|
|
defer db.Close()
|
|
}
|