diff --git a/store/unlocks.go b/store/unlocks.go new file mode 100644 index 0000000..058c3ac --- /dev/null +++ b/store/unlocks.go @@ -0,0 +1,47 @@ +package store + +import ( + bolt "go.etcd.io/bbolt" +) + +var bucketUnlocks = []byte("unlocks") + +type UnlockDef struct { + ID string + Name string + Description string + Condition string +} + +var UnlockDefs = []UnlockDef{ + {ID: "fifth_class", Name: "Fifth Class", Description: "Reach floor 10 or higher", Condition: "floor 10+"}, + {ID: "hard_mode", Name: "Hard Mode", Description: "Clear the game with 3+ players", Condition: "3+ player clear"}, + {ID: "mutations", Name: "Mutations", Description: "Achieve victory on floor 20", Condition: "floor 20 victory"}, +} + +func (d *DB) UnlockContent(fingerprint, unlockID string) (bool, error) { + key := []byte(fingerprint + ":" + unlockID) + alreadyUnlocked := false + err := d.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketUnlocks) + if b.Get(key) != nil { + alreadyUnlocked = true + return nil + } + return b.Put(key, []byte("1")) + }) + return !alreadyUnlocked, err +} + +func (d *DB) IsUnlocked(fingerprint, unlockID string) bool { + key := []byte(fingerprint + ":" + unlockID) + unlocked := false + d.db.View(func(tx *bolt.Tx) error { + b := tx.Bucket(bucketUnlocks) + if b.Get(key) != nil { + unlocked = true + } + return nil + }) + return unlocked +} diff --git a/store/unlocks_test.go b/store/unlocks_test.go new file mode 100644 index 0000000..cbf25a5 --- /dev/null +++ b/store/unlocks_test.go @@ -0,0 +1,47 @@ +package store + +import ( + "testing" +) + +func TestUnlockContent(t *testing.T) { + dir := t.TempDir() + db, err := Open(dir + "/test_unlocks.db") + if err != nil { + t.Fatal(err) + } + defer db.Close() + + // Initially not unlocked + if db.IsUnlocked("fp1", "fifth_class") { + t.Error("should not be unlocked initially") + } + + // Unlock returns true for new unlock + newlyUnlocked, err := db.UnlockContent("fp1", "fifth_class") + if err != nil { + t.Fatal(err) + } + if !newlyUnlocked { + t.Error("should be newly unlocked") + } + + // Now it should be unlocked + if !db.IsUnlocked("fp1", "fifth_class") { + t.Error("should be unlocked after UnlockContent") + } + + // Second unlock returns false (already unlocked) + newlyUnlocked2, err := db.UnlockContent("fp1", "fifth_class") + if err != nil { + t.Fatal(err) + } + if newlyUnlocked2 { + t.Error("should not be newly unlocked on second call") + } + + // Different player still not unlocked + if db.IsUnlocked("fp2", "fifth_class") { + t.Error("different player should not have unlock") + } +}