Go Adventure – Store
Hey. Alright, today I actually implemented the store feature. Now, we can spend that sweet gold. Or we can tell the store to buy our junk. If only we could do that in real life.
Go
So, the store model is very simple. It uses a simple struct that has a location ID, a name, how much gold it currently carries, it’s inventory and when it was last updated. The last updated field will help with gold and inventory refresh. The store carries only 1000 gold when created or refreshed.
type Store struct {
locationID int32
name string
gold int32
inventory map[int32]int32 // map[itemID]quantity
last_updated time.Time
}
func InitStore(locationID int32, name string, itemList map[int32]*Item) (*Store, error) {
if locationID < 1 {
return nil, errors.New("invalid location id")
}
if name == "" {
return nil, errors.New("invalid store name")
}
store := Store{
locationID: locationID,
name: name,
gold: 1000,
inventory: generateInventory(itemList),
last_updated: time.Now().UTC(),
}
return &store, nil
}
Now, the inventory is randomly generated from the item list. Each item has a 50% chance of appearing in the store inventory. I also use a random number between 1 and 10 for the quantity of that item.
func generateInventory(items map[int32]*Item) map[int32]int32 {
inventory := make(map[int32]int32)
for key := range items {
randNum := rand.IntN(100) + 1
if randNum > 49 {
randAmount := rand.IntN(10) + 1
inventory[key] = int32(randAmount)
}
}
return inventory
}
I added the basic Get functions for the location ID, name, gold and inventory. Nothing complicated.
func (s *Store) GetLocationID() int32 {
return s.locationID
}
func (s *Store) GetName() string {
return s.name
}
func (s *Store) GetGold() int32 {
return s.gold
}
func (s *Store) GetInventory() map[int32]int32 {
return s.inventory
}
For the gold, I added some extra functions. I added the Increase and Decrease functions. The functions do a simple check to make sure the amount is not negative. The Decrease function has an extra check to verify that the amount is not greater than what the store has. Can’t have the store go in debt.
func (s *Store) IncreaseGold(amount int32) error {
if amount < 1 {
return errors.New("invalid gold amount")
}
s.gold += amount
s.last_updated = time.Now().UTC()
return nil
}
func (s *Store) DecreaseGold(amount int32) error {
if amount < 1 || amount > s.gold {
return errors.New("invalid gold amount")
}
s.gold -= amount
s.last_updated = time.Now().UTC()
return nil
}
I also added is a Refresh function to keep the inventory and gold amount constantly stock to avoid having the store go out of business. The function does is a simple check to verify when it was last updated. Every increase and decrease of gold updates the last time the store was updated. If the store hasn’t been updated for over an hour, the store will refresh its stock and gold.
func (s *Store) RefreshStore(itemList map[int32]*Item) {
refreshTime := s.last_updated.Add(time.Hour)
if time.Now().UTC().After(refreshTime) {
s.gold = 1000
s.inventory = generateInventory(itemList)
s.last_updated = time.Now().UTC()
}
}
The last 2 functions are the Buy Item and Sell Item. Both functions do 2 simple checks. The first being to check if the item ID actually exists. And the second that the quantity is not negative or zero. The buy item function will check the store inventory to see if the store has it in stock. The sell item function on the other hand, will check the player inventory to see if they have it. Then the gold transfer and item transfer are processed with simple check that there is enough gold or that they have enough of the item. One thing that differs is that during the sell item process, if the store doesn’t have enough gold, the player will be selling the items at a lost. Because the Store always win. I mean can’t get into debt.
func (s *Store) BuyItem(itemList map[int32]*Item, itemID, quantity int32, player *Player) (n int32, err error) {
// Check Item Exists
item, ok := itemList[itemID]
if itemID < 1 || !ok {
return 0, errors.New("invalid item id")
}
if quantity < 1 {
return 0, errors.New("invalid quantity")
}
// Check Store Inventory
storeQuantity, ok := s.inventory[itemID]
if !ok {
return 0, fmt.Errorf("I'm sorry. I don't carry %s.", itemList[itemID].GetName())
}
if quantity > storeQuantity {
return 0, fmt.Errorf("I'm sorry. I only have %d in stock.", storeQuantity)
}
// Get total amount
totalAmount := item.GetValue() * quantity
// Gold Transfer
err = player.RemoveGold(totalAmount)
if err != nil {
return 0, err
}
err = s.IncreaseGold(totalAmount)
if err != nil {
return 0, err
}
// Item Transfer
err = player.AddItem(itemID, quantity)
if err != nil {
return 0, err
}
if storeQuantity == quantity {
delete(s.inventory, itemID)
} else {
s.inventory[itemID] -= quantity
}
// Return
return quantity, nil
}
func (s *Store) SellItem(itemList map[int32]*Item, itemID, quantity int32, player *Player) (int32, error) {
// Check Item Exist
item, ok := itemList[itemID]
if itemID < 1 || !ok {
return 0, errors.New("invalid item ID")
}
if quantity < 1 {
return 0, errors.New("invalid quantity")
}
// Check Player Inventory
amount, ok := player.GetInventory()[itemID]
if !ok {
return 0, fmt.Errorf("Oh. Looks like you don't have any %s.", item.GetName())
}
if amount < quantity {
return 0, fmt.Errorf("Oh. Looks like you don't only have %d in your inventory.", amount)
}
// Get Total Amount
totalAmount := item.GetValue() * quantity
// Gold Transfer
if totalAmount > s.GetGold() {
// Store always wins
totalAmount = s.GetGold()
}
err := s.DecreaseGold(totalAmount)
if err != nil {
return 0, err
}
err = player.AddGold(totalAmount)
if err != nil {
return 0, err
}
// Item Transfer
_, ok = s.inventory[itemID]
if !ok {
s.inventory[itemID] = quantity
} else {
s.inventory[itemID] += quantity
}
_, err = player.RemoveItem(itemID, quantity)
if err != nil {
return 0, err
}
// Return
return totalAmount, nil
}
Now, for the store command. When you enter the store, there is a few stuff you can do. First is to check your inventory or that of the store. It also shows you how much gold you have or the store has. I also added a help menu in case you forget. I’m so helpful! And the most important parts are buying and selling. Each will check the format of the command. And verify that the quantity is valid.
func store(scanner *bufio.Scanner) error {
store := Assets.Stores[Assets.Player.GetLocation()]
fmt.Println("\n=== Store ===")
fmt.Printf("Welcome to %s!\n", store.GetName())
fmt.Println("What can I get you today?")
store.RefreshStore(Assets.Items)
outer:
for {
fmt.Println()
fmt.Print("What are we doing?> ")
if scanner.Scan() {
input := scanner.Text()
parts := strings.Split(strings.ToLower(strings.TrimSpace(input)), " ")
switch parts[0] {
case "inv":
if len(parts) > 1 {
if parts[1] == "store" {
displayInventory(store)
} else {
displayInventory(Assets.Player)
}
} else {
displayInventory(Assets.Player)
}
case "buy":
if len(parts) < 3 {
fmt.Println("I'm sorry. What are you trying to buy exactly and how much?")
continue
}
quantity, err := strconv.ParseInt(parts[len(parts)-1], 10, 32)
if err != nil || quantity < 1 {
fmt.Println("Sorry. How many did you want to buy?")
continue
}
itemName := strings.Join(parts[1:len(parts)-1], " ")
itemFound := false
for key, item := range Assets.Items {
if strings.ToLower(item.GetName()) == itemName {
itemFound = true
n, err := store.BuyItem(Assets.Items, key, int32(quantity), Assets.Player)
if err != nil {
fmt.Println(err)
break
}
fmt.Printf("You have bought %d %s.\n", n, item.GetName())
}
}
if !itemFound {
fmt.Printf("I'm sorry. I don't know what '%s' is.\n", cases.Title(language.English).String(itemName))
}
case "sell":
if len(parts) < 3 {
fmt.Println("I'm sorry. What are you trying to sell exactly and how much?")
continue
}
quantity, err := strconv.ParseInt(parts[len(parts)-1], 10, 32)
if err != nil || quantity < 1 {
fmt.Println("Sorry. How may did you want to sell?")
continue
}
itemName := strings.Join(parts[1:len(parts)-1], " ")
itemFound := false
for key, item := range Assets.Items {
if strings.ToLower(item.GetName()) == itemName {
itemFound = true
n, err := store.SellItem(Assets.Items, key, int32(quantity), Assets.Player)
if err != nil {
fmt.Println(err)
break
}
fmt.Printf("You have sold %d %s for %d gold.\n", quantity, item.GetName(), n)
}
}
if !itemFound {
fmt.Printf("I'm sorry. I don't know what '%s' is.\n", cases.Title(language.English).String(itemName))
}
case "help":
help_menu := `
+----------------------------+--------------------------+
| Command | Description |
+----------------------------+--------------------------+
| inv [store] | Check inventory |
+----------------------------+--------------------------+
| buy <itemName> <quantity> | Buy from the store |
+----------------------------+--------------------------+
| sell <itemName> <quantity> | Sell from your inventory |
+----------------------------+--------------------------+
| exit | Exit store |
+----------------------------+--------------------------+
`
fmt.Println(help_menu)
case "exit":
fmt.Println("Of course. Come back again!")
break outer
default:
fmt.Println("I'm sorry I don't understand.")
}
}
if err := scanner.Err(); err != nil {
return err
}
}
return nil
}
That’s it for today. See you in the next one.
God bless.

Woohoohoo