Files
Catacombs/store/db.go
tolelom 087ce31164 feat: show party members in rankings and result screen
- Add Members field to RunRecord for party member names
- Save all party member names when recording a run
- Display party members in leaderboard (floor/gold tabs)
- Display party members in result screen rankings
- Solo runs show no party info, party runs show "(Alice, Bob, ...)"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 00:03:08 +09:00

180 lines
3.9 KiB
Go

package store
import (
"encoding/json"
"fmt"
"sort"
bolt "go.etcd.io/bbolt"
)
var (
bucketProfiles = []byte("profiles")
bucketRankings = []byte("rankings")
)
type DB struct {
db *bolt.DB
}
type RunRecord struct {
Player string `json:"player"`
Floor int `json:"floor"`
Score int `json:"score"`
Class string `json:"class,omitempty"`
Members []string `json:"members,omitempty"` // party member names (empty for solo)
}
func Open(path string) (*DB, error) {
db, err := bolt.Open(path, 0600, nil)
if err != nil {
return nil, err
}
err = db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucketIfNotExists(bucketProfiles); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(bucketRankings); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(bucketAchievements); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(bucketDailyRuns); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(bucketUnlocks); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(bucketTitles); err != nil {
return err
}
if _, err := tx.CreateBucketIfNotExists(bucketCodex); err != nil {
return err
}
return nil
})
return &DB{db: db}, err
}
func (d *DB) Close() error {
return d.db.Close()
}
func (d *DB) SaveProfile(fingerprint, name string) error {
return d.db.Update(func(tx *bolt.Tx) error {
return tx.Bucket(bucketProfiles).Put([]byte(fingerprint), []byte(name))
})
}
func (d *DB) GetProfile(fingerprint string) (string, error) {
var name string
err := d.db.View(func(tx *bolt.Tx) error {
v := tx.Bucket(bucketProfiles).Get([]byte(fingerprint))
if v == nil {
return fmt.Errorf("profile not found")
}
name = string(v)
return nil
})
return name, err
}
func (d *DB) SaveRun(player string, floor, score int, class string, members []string) error {
return d.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketRankings)
id, _ := b.NextSequence()
record := RunRecord{Player: player, Floor: floor, Score: score, Class: class, Members: members}
data, err := json.Marshal(record)
if err != nil {
return err
}
return b.Put([]byte(fmt.Sprintf("%010d", id)), data)
})
}
func (d *DB) TopRunsByGold(limit int) ([]RunRecord, error) {
var runs []RunRecord
err := d.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketRankings)
return b.ForEach(func(k, v []byte) error {
var r RunRecord
if json.Unmarshal(v, &r) == nil {
runs = append(runs, r)
}
return nil
})
})
if err != nil {
return nil, err
}
sort.Slice(runs, func(i, j int) bool {
return runs[i].Score > runs[j].Score
})
if len(runs) > limit {
runs = runs[:limit]
}
return runs, nil
}
type PlayerStats struct {
TotalRuns int
BestFloor int
TotalGold int
TotalKills int
Victories int
}
func (d *DB) GetStats(player string) (PlayerStats, error) {
var stats PlayerStats
err := d.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketRankings)
if b == nil {
return nil
}
return b.ForEach(func(k, v []byte) error {
var r RunRecord
if json.Unmarshal(v, &r) == nil && r.Player == player {
stats.TotalRuns++
if r.Floor > stats.BestFloor {
stats.BestFloor = r.Floor
}
stats.TotalGold += r.Score
if r.Floor >= 20 {
stats.Victories++
}
}
return nil
})
})
return stats, err
}
func (d *DB) TopRuns(limit int) ([]RunRecord, error) {
var runs []RunRecord
err := d.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketRankings)
return b.ForEach(func(k, v []byte) error {
var r RunRecord
if err := json.Unmarshal(v, &r); err != nil {
return err
}
runs = append(runs, r)
return nil
})
})
if err != nil {
return nil, err
}
sort.Slice(runs, func(i, j int) bool {
if runs[i].Floor != runs[j].Floor {
return runs[i].Floor > runs[j].Floor
}
return runs[i].Score > runs[j].Score
})
if len(runs) > limit {
runs = runs[:limit]
}
return runs, nil
}