add autocomplete for the claim function

absences
William Perron 2 years ago
parent 2a8a36058f
commit 702f3c9da3

@ -90,11 +90,17 @@ func main() {
Name: "claim-type", Name: "claim-type",
Description: "one of `area`, `region` or `trade`", Description: "one of `area`, `region` or `trade`",
Type: discordgo.ApplicationCommandOptionString, Type: discordgo.ApplicationCommandOptionString,
Choices: []*discordgo.ApplicationCommandOptionChoice{
{Name: "Area", Value: themis.CLAIM_TYPE_AREA},
{Name: "Region", Value: themis.CLAIM_TYPE_REGION},
{Name: "Trade Node", Value: themis.CLAIM_TYPE_TRADE},
},
}, },
{ {
Name: "name", Name: "name",
Description: "the name of zone claimed", Description: "the name of zone claimed",
Type: discordgo.ApplicationCommandOptionString, Type: discordgo.ApplicationCommandOptionString,
Autocomplete: true,
}, },
}, },
}, },
@ -142,6 +148,11 @@ func main() {
} }
}, },
"claim": func(s *discordgo.Session, i *discordgo.InteractionCreate) { "claim": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
if i.Type == discordgo.InteractionApplicationCommandAutocomplete {
handleClaimAutocomplete(ctx, store, s, i)
return
}
opts := i.ApplicationCommandData().Options opts := i.ApplicationCommandData().Options
claimType, err := themis.ClaimTypeFromString(opts[0].StringValue()) claimType, err := themis.ClaimTypeFromString(opts[0].StringValue())
if err != nil { if err != nil {
@ -306,6 +317,38 @@ func formatClaimsTable(claims []themis.Claim) string {
return sb.String() return sb.String()
} }
func handleClaimAutocomplete(ctx context.Context, store *themis.Store, s *discordgo.Session, i *discordgo.InteractionCreate) {
opts := i.ApplicationCommandData().Options
claimType, err := themis.ClaimTypeFromString(opts[0].StringValue())
if err != nil {
log.Printf("[error]: %s\n", err)
return
}
availability, err := store.ListAvailability(ctx, claimType, opts[1].StringValue())
if err != nil {
log.Printf("[error]: %s\n", err)
return
}
choices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(availability))
for _, s := range availability {
choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
Name: s,
Value: s,
})
}
if err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
Data: &discordgo.InteractionResponseData{
Choices: choices[:min(len(choices), 25)],
},
}); err != nil {
log.Printf("[error]: %s\n", err)
}
}
func serve(address string) error { func serve(address string) error {
http.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK")) w.Write([]byte("OK"))
@ -313,3 +356,10 @@ func serve(address string) error {
return http.ListenAndServe(address, nil) return http.ListenAndServe(address, nil)
} }
func min(a, b int) int {
if a < b {
return a
}
return b
}

@ -3940,7 +3940,7 @@ INSERT OR IGNORE INTO provinces VALUES('4937','Lau','3','1','1','1','','Polynesi
INSERT OR IGNORE INTO provinces VALUES('4938','Vanua Levu','5','2','2','1','','Polynesian Triangle','','Land','Oceania','Oceania','Oceania','Fiji'); INSERT OR IGNORE INTO provinces VALUES('4938','Vanua Levu','5','2','2','1','','Polynesian Triangle','','Land','Oceania','Oceania','Oceania','Fiji');
INSERT OR IGNORE INTO provinces VALUES('4939','Te Urewera','8','3','4','1','','Polynesian Triangle','','Land','Oceania','Oceania','Oceania','Te Ika a Maui Waho'); INSERT OR IGNORE INTO provinces VALUES('4939','Te Urewera','8','3','4','1','','Polynesian Triangle','','Land','Oceania','Oceania','Oceania','Te Ika a Maui Waho');
INSERT OR IGNORE INTO provinces VALUES('4940','Lake Tulare','','','','','','','','Lake','','','',''); INSERT OR IGNORE INTO provinces VALUES('4940','Lake Tulare','','','','','','','','Lake','','','','');
INSERT OR IGNORE INTO provinces VALUES('4941','Lake Cahuilla','','','','','','','','Lake','','','',NULL); INSERT OR IGNORE INTO provinces VALUES('4941','Lake Cahuilla','','','','','','','','Lake','','','','');
CREATE TABLE IF NOT EXISTS claim_types ( CREATE TABLE IF NOT EXISTS claim_types (
claim_type TEXT PRIMARY KEY claim_type TEXT PRIMARY KEY

@ -144,6 +144,40 @@ func (s *Store) Claim(ctx context.Context, player, province string, claimType Cl
return nil return nil
} }
func (s *Store) ListAvailability(ctx context.Context, claimType ClaimType, search ...string) ([]string, error) {
queryParams := []any{string(claimType)}
queryPattern := `SELECT DISTINCT(provinces.%[1]s)
FROM provinces LEFT JOIN claims ON provinces.%[1]s = claims.val AND claims.claim_type = ?
WHERE claims.val IS NULL
AND provinces.typ = 'Land'`
if len(search) > 0 && search[0] != "" {
// only take one search param, ignore the rest
queryPattern += `AND provinces.%[1]s LIKE ?`
queryParams = append(queryParams, fmt.Sprintf("%%%s%%", search[0]))
}
stmt, err := s.db.PrepareContext(ctx, fmt.Sprintf(queryPattern, claimTypeToColumn[claimType]))
if err != nil {
return nil, fmt.Errorf("failed to prepare query: %w", err)
}
rows, err := stmt.QueryContext(ctx, queryParams...)
if err != nil {
return nil, fmt.Errorf("failed to execute query: %w", err)
}
avail := make([]string, 0)
for rows.Next() {
var s string
if err := rows.Scan(&s); err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
avail = append(avail, s)
}
return avail, nil
}
func (s *Store) ListClaims(ctx context.Context) ([]Claim, error) { func (s *Store) ListClaims(ctx context.Context) ([]Claim, error) {
stmt, err := s.db.PrepareContext(ctx, `SELECT id, player, claim_type, val FROM claims`) stmt, err := s.db.PrepareContext(ctx, `SELECT id, player, claim_type, val FROM claims`)
if err != nil { if err != nil {

@ -68,3 +68,50 @@ func TestStore_Claim(t *testing.T) {
}) })
} }
} }
func TestAvailability(t *testing.T) {
store, err := NewStore("file::memory:?cache=shared")
assert.NoError(t, err)
store.Claim(context.TODO(), "foo", "Genoa", CLAIM_TYPE_TRADE)
store.Claim(context.TODO(), "foo", "Venice", CLAIM_TYPE_TRADE)
store.Claim(context.TODO(), "foo", "English Channel", CLAIM_TYPE_TRADE)
// There's a total of 80 distinct trade nodes, there should be 77 available
// after the three claims above
availability, err := store.ListAvailability(context.TODO(), CLAIM_TYPE_TRADE)
assert.NoError(t, err)
assert.Equal(t, 77, len(availability))
store.Claim(context.TODO(), "foo", "France", CLAIM_TYPE_REGION)
store.Claim(context.TODO(), "foo", "Italy", CLAIM_TYPE_REGION)
// There's a total of 73 distinct regions, there should be 71 available
// after the two claims above
availability, err = store.ListAvailability(context.TODO(), CLAIM_TYPE_REGION)
assert.NoError(t, err)
assert.Equal(t, 71, len(availability))
store.Claim(context.TODO(), "foo", "Normandy", CLAIM_TYPE_AREA)
store.Claim(context.TODO(), "foo", "Champagne", CLAIM_TYPE_AREA)
store.Claim(context.TODO(), "foo", "Lorraine", CLAIM_TYPE_AREA)
store.Claim(context.TODO(), "foo", "Provence", CLAIM_TYPE_AREA)
// There's a total of 823 distinct regions, there should be 819 available
// after the four claims above
availability, err = store.ListAvailability(context.TODO(), CLAIM_TYPE_AREA)
assert.NoError(t, err)
assert.Equal(t, 819, len(availability))
// There is both a Trade Node and an Area called 'Valencia', while the trade
// node is claimed, the area should show up in the availability list (even
// though there are conflicting provinces)
store.Claim(context.TODO(), "foo", "Valencia", CLAIM_TYPE_TRADE)
availability, err = store.ListAvailability(context.TODO(), CLAIM_TYPE_AREA)
assert.NoError(t, err)
assert.Equal(t, 819, len(availability)) // availability for areas should be the same as before
availability, err = store.ListAvailability(context.TODO(), CLAIM_TYPE_AREA, "bay")
assert.NoError(t, err)
assert.Equal(t, 3, len(availability)) // availability for areas should be the same as before
}

Loading…
Cancel
Save