Part 6 mini-project: Inventory System

This project uses the Part 6 chapters: structs, methods, pointers, slices, maps, and a package with exported names. An Item and an Inventory manage a bag of loot.

What to build

Two types in one package, inventory.

Item, one collectable thing:

  • NewItem(name, weight, value) returns a *Item and an error. The name must not be empty, and the weight and the value must not be below 0.
  • Unexported fields: name (string), weight (float64), value (int).
  • Name() returns the name.
  • Describe() returns a one-line summary, such as sword (3.5kg, 50g).

Inventory, a container of items:

  • New() returns an empty inventory, as a *Inventory.
  • An unexported field: items, a slice of *Item.
  • Add(item) appends an item.
  • Remove(name) removes the first item with that name, and returns it. It returns nil if none matches.
  • List() prints each item's Describe() on its own line, then a line of totals.
  • TotalWeight() returns the sum of the weights.
  • TotalValue() returns the sum of the values.
  • Counts() returns a map[string]int from a name to the number of items with that name.

main.go is written for you. It:

  1. Makes a few items, and adds them to an inventory.
  2. Lists the inventory.
  3. Removes one item by name.
  4. Lists it again.
  5. Tries to remove an item that is not there, tries to make an item with a bad weight, and adds a second potion.
  6. Prints the counts, with the names sorted.

The finished program prints:

Inventory (3 items):
  - sword (3.5kg, 50g)
  - shield (5.0kg, 30g)
  - potion (0.2kg, 5g)
  Totals: 8.70kg, 85g

(removed sword)

Inventory (2 items):
  - shield (5.0kg, 30g)
  - potion (0.2kg, 5g)
  Totals: 5.20kg, 35g

(no lantern to remove)
Error: item rock: weight -1.0 is below 0

Counts:
  potion: 2
  shield: 1

File layout

This project is a module with two packages, as in Part 5. Both versions live under projects/05-inventory/:

starter/
    go.mod
    main.go
    inventory/
        inventory.go
finished/
    go.mod
    main.go
    inventory/
        inventory.go

To work on the starter, go into its folder and use go run .:

cd projects/05-inventory/starter
go run .

To run the finished version:

cd projects/05-inventory/finished
go run .

The starter runs as it is. It prints placeholder lines. main.go is finished, and the TODOs are all in inventory/inventory.go. Run go vet ./... in the same folder to check both packages.

Hints

  • inventory is both the package name and the folder. The module is called inventorysystem, so the import line is "inventorysystem/inventory".
  • The fields are lower case, so main cannot change an item's weight. That is why Name() exists, and why main writes removed.Name().
  • Item values do not change, but Inventory values do, so Add and Remove need pointer receivers. The rules from Chapter 27 then give pointer receivers to the rest of Inventory. No Item method changes it, so rule 3 would allow value receivers there. It has pointer receivers to match NewItem, which returns a *Item. The slice holds *Item, so that Remove can return nil when there is no match.
  • Remove walks inv.items with range, looking for a matching name, then removes it with the append line from Chapter 22. Assign the result back to inv.items.
  • Use fmt.Printf for the totals line: " Totals: %.2fkg, %dg\n".
  • Describe returns the string instead of printing it, so that List controls how each line is displayed. fmt.Sprintf builds it.
  • counts[item.name]++ works on a missing key, because the zero value of an int is 0, as in Chapter 23's word count.

What is fair game

Anything from Parts 2 to 6: variables, control flow, functions, errors, packages, slices, maps, structs, methods and pointers.

Done?

When the output of your go run . matches the one above, you are done. The next project is the Monster Battle.