diff --git a/store/codex.go b/store/codex.go new file mode 100644 index 0000000..8f7226d --- /dev/null +++ b/store/codex.go @@ -0,0 +1,88 @@ +package store + +import ( + "encoding/json" + + bolt "go.etcd.io/bbolt" +) + +var bucketCodex = []byte("codex") + +type Codex struct { + Monsters map[string]bool `json:"monsters"` + Items map[string]bool `json:"items"` + Events map[string]bool `json:"events"` +} + +func newCodex() Codex { + return Codex{ + Monsters: make(map[string]bool), + Items: make(map[string]bool), + Events: make(map[string]bool), + } +} + +func (d *DB) RecordCodexEntry(fingerprint, category, id string) error { + return d.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketCodex) + key := []byte(fingerprint) + codex := newCodex() + + v := b.Get(key) + if v != nil { + if err := json.Unmarshal(v, &codex); err != nil { + return err + } + // Ensure maps are initialized after unmarshal + if codex.Monsters == nil { + codex.Monsters = make(map[string]bool) + } + if codex.Items == nil { + codex.Items = make(map[string]bool) + } + if codex.Events == nil { + codex.Events = make(map[string]bool) + } + } + + switch category { + case "monster": + codex.Monsters[id] = true + case "item": + codex.Items[id] = true + case "event": + codex.Events[id] = true + } + + data, err := json.Marshal(codex) + if err != nil { + return err + } + return b.Put(key, data) + }) +} + +func (d *DB) GetCodex(fingerprint string) (Codex, error) { + codex := newCodex() + err := d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketCodex) + v := b.Get([]byte(fingerprint)) + if v == nil { + return nil + } + if err := json.Unmarshal(v, &codex); err != nil { + return err + } + if codex.Monsters == nil { + codex.Monsters = make(map[string]bool) + } + if codex.Items == nil { + codex.Items = make(map[string]bool) + } + if codex.Events == nil { + codex.Events = make(map[string]bool) + } + return nil + }) + return codex, err +} diff --git a/store/codex_test.go b/store/codex_test.go new file mode 100644 index 0000000..65f8de3 --- /dev/null +++ b/store/codex_test.go @@ -0,0 +1,71 @@ +package store + +import ( + "testing" +) + +func TestRecordCodexEntry(t *testing.T) { + dir := t.TempDir() + db, err := Open(dir + "/test_codex.db") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + // Record some entries + db.RecordCodexEntry("fp1", "monster", "goblin") + db.RecordCodexEntry("fp1", "monster", "skeleton") + db.RecordCodexEntry("fp1", "item", "health_potion") + db.RecordCodexEntry("fp1", "event", "altar") + + codex, err := db.GetCodex("fp1") + if err != nil { + t.Fatal(err) + } + + if len(codex.Monsters) != 2 { + t.Errorf("expected 2 monsters, got %d", len(codex.Monsters)) + } + if !codex.Monsters["goblin"] { + t.Error("expected goblin in monsters") + } + if !codex.Monsters["skeleton"] { + t.Error("expected skeleton in monsters") + } + if len(codex.Items) != 1 { + t.Errorf("expected 1 item, got %d", len(codex.Items)) + } + if !codex.Items["health_potion"] { + t.Error("expected health_potion in items") + } + if len(codex.Events) != 1 { + t.Errorf("expected 1 event, got %d", len(codex.Events)) + } + if !codex.Events["altar"] { + t.Error("expected altar in events") + } + + // Duplicate entry should not increase count + db.RecordCodexEntry("fp1", "monster", "goblin") + codex2, _ := db.GetCodex("fp1") + if len(codex2.Monsters) != 2 { + t.Errorf("expected still 2 monsters after duplicate, got %d", len(codex2.Monsters)) + } +} + +func TestGetCodexEmpty(t *testing.T) { + dir := t.TempDir() + db, err := Open(dir + "/test_codex_empty.db") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + codex, err := db.GetCodex("fp_unknown") + if err != nil { + t.Fatal(err) + } + if len(codex.Monsters) != 0 || len(codex.Items) != 0 || len(codex.Events) != 0 { + t.Error("expected empty codex for unknown player") + } +}