Untitled

 avatar
unknown
plain_text
a month ago
10 kB
3
Indexable
package main

/*
	Домашнее задание: Инвентарь персонажа

	После первой лекции по ООП нужно доделать учебную систему инвентаря.

	Что нужно сделать:
	1. Самостоятельно добавить новый тип предмета Potion с нуля.
	2. Для Potion нужно продумать поля, реализовать методы Use, GetName, GetWeight.
	3. Сделать так, чтобы Potion тоже можно было сохранять и загружать.
	4. Реализовать generic-функции Filter и Map.
	5. Дописать метод Load у Inventory, чтобы он умел загружать все типы предметов.
	6. В main показать работу нового типа предмета вместе с остальными.

	Важно:
	- обработку ошибок пока можно пропустить;
	- тесты писать не нужно;
	- цель домашки — потренировать интерфейсы, методы, структуры и generics.
*/

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"slices"
	"strconv"
	"strings"
)

type Item interface {
	Use() string
	GetName() string
	GetWeight() float64
}

type Storable interface {
	Serialize(w io.Writer)
	Deserialize(r io.Reader)
}

type Weapon struct {
	Name       string
	Damage     int
	Durability int
}

func (w *Weapon) Use() string {
	if w.Durability <= 0 {
		return w.Name + " сломано!"
	}

	w.Durability--
	return fmt.Sprintf("Атаковали %s (%d урона)", w.Name, w.Damage)
}

func (w *Weapon) GetName() string {
	return w.Name
}

func (w *Weapon) GetWeight() float64 {
	return 2.5
}

func (w *Weapon) Serialize(wr io.Writer) {
	_, _ = fmt.Fprintf(wr, "Weapon|%s|%d|%d", w.Name, w.Damage, w.Durability)
}

func (w *Weapon) Deserialize(r io.Reader) {
	data, _ := io.ReadAll(r)
	parts := strings.Split(strings.TrimSpace(string(data)), "|")

	w.Name = parts[1]
	w.Damage, _ = strconv.Atoi(parts[2])
	w.Durability, _ = strconv.Atoi(parts[3])
}

type Armor struct {
	Name    string
	Defense int
	Weight  float64
}

func (a *Armor) Use() string {
	return fmt.Sprintf("Надели %s (+%d защиты)", a.Name, a.Defense)
}

func (a *Armor) GetName() string {
	return a.Name
}

func (a *Armor) GetWeight() float64 {
	return a.Weight
}

func (a *Armor) Serialize(wr io.Writer) {
	_, _ = fmt.Fprintf(wr, "Armor|%s|%d|%f", a.Name, a.Defense, a.Weight)
}

func (a *Armor) Deserialize(r io.Reader) {
	data, _ := io.ReadAll(r)
	parts := strings.Split(strings.TrimSpace(string(data)), "|")

	a.Name = parts[1]
	a.Defense, _ = strconv.Atoi(parts[2])
	a.Weight, _ = strconv.ParseFloat(parts[3], 64)
}

// TODO:
// 1. Добавить здесь структуру Potion.
// 2. Реализовать для неё все нужные методы.
// 3. Сделать так, чтобы Potion удовлетворял интерфейсам Item и Storable.

const glassSize = float64(0.1)

type WineColor int

const (
	WineColorRed WineColor = iota
	WineColorWhite
	WineColorRose
	WineColorOrange
)

type WineSweetness int

const (
	WineSweetnessDry WineSweetness = iota
	WineSweetnessSemiDry
	WineSweetnessSemiSweet
	WineSweetnessSweet
)

type WineRank string

const (
	WineRankAOC WineRank = "AOC"
	WineRankIGP WineRank = "IGP"
	WineRankVDF WineRank = "VdF"
)

type Wine struct {
	Name      string
	Color     WineColor
	Sweetness WineSweetness
	ABV       float64
	Region    string
	Rank      WineRank
	Year      uint
	Volume    float64
}

func (c WineColor) String() string {
	names := [...]string{"Красное", "Белое", "Розовое", "Оранжевое"}
	if c < WineColorRed || c > WineColorOrange {
		return "Неизвестный цвет"
	}
	return names[c]
}

func (s WineSweetness) String() string {
	names := [...]string{"Сухое", "Полусухое", "Полусладкое", "Сладкое"}
	if s < WineSweetnessDry || s > WineSweetnessSweet {
		return "Неопределенная сладость"
	}
	return names[s]
}

var rankNames = map[WineRank]string{
	WineRankAOC: "Высшая категория (AOC)",
	WineRankIGP: "Местное вино (IGP)",
	WineRankVDF: "Столовое вино (VdF)",
}

func (r WineRank) String() string {
	if val, ok := rankNames[r]; ok {
		return val
	}
	return string(r)
}

type Potion struct {
	Wine
}

func (p *Potion) Serialize(wr io.Writer) {
	_, _ = fmt.Fprintf(wr, "Potion|%s|%d|%d|%.1f|%s|%s|%d|%.1f", p.Name, p.Color, p.Sweetness, p.ABV, p.Region, p.Rank, p.Year, p.Volume)
}

func (p *Potion) Deserialize(r io.Reader) {
	data, _ := io.ReadAll(r)
	parts := strings.Split(strings.TrimSpace(string(data)), "|")

	// Пропускаем parts[0], так как там строка "Potion"
	p.Name = parts[1]

	colorVal, _ := strconv.Atoi(parts[2])
	p.Color = WineColor(colorVal)

	sweetVal, _ := strconv.Atoi(parts[3])
	p.Sweetness = WineSweetness(sweetVal)

	p.ABV, _ = strconv.ParseFloat(parts[4], 64)
	p.Region = parts[5]
	p.Rank = WineRank(parts[6])

	yearVal, _ := strconv.Atoi(parts[7])
	p.Year = uint(yearVal)

	p.Volume, _ = strconv.ParseFloat(parts[8], 64)
}

func (p *Potion) Use() string {
	if p.Volume < glassSize {
		return p.Name + " кончилось!"
	}
	p.Volume -= glassSize

	return fmt.Sprintf("Вы приняли %.1f %s (%s, %s). Эффект: %d%%", glassSize, p.Name, p.Color, p.Sweetness, int(p.ABV))
}

func (p *Potion) GetName() string {
	return p.Name
}

func (p *Potion) GetWeight() float64 {
	return p.Volume
}

func DescribeItem(i Item) string {
	if i == nil {
		return "Предмет отсутствует"
	}

	return fmt.Sprintf("%s (вес: %.1f)", i.GetName(), i.GetWeight())
}

func Filter[T any](items []T, predicate func(T) bool) []T {
	result := make([]T, 0, len(items))
	for item := range slices.Values(items) {
		if predicate(item) {
			result = append(result, item)
		}
	}
	return result
}

func Map[T any, R any](items []T, transform func(T) R) []R {
	result := make([]R, 0, len(items))
	for item := range slices.Values(items) {
		result = append(result, transform(item))
	}
	return result
}

func Find[T any](items []T, condition func(T) bool) (T, bool) {
	for _, item := range items {
		if condition(item) {
			return item, true
		}
	}

	var zero T
	return zero, false
}

type Inventory struct {
	Items []Item
}

func (inv *Inventory) AddItem(item Item) {
	if item == nil {
		return
	}
	inv.Items = append(inv.Items, item)
}

func (inv *Inventory) GetWeapons() []*Weapon {
	weapons := Filter(inv.Items, func(item Item) bool {
		_, ok := item.(*Weapon)
		return ok
	})

	return Map(weapons, func(item Item) *Weapon {
		return item.(*Weapon)
	})
}

func (inv *Inventory) GetBrokenItems() []Item {
	return Filter(inv.Items, func(item Item) bool {
		switch v := item.(type) {
		case *Weapon:
			return v.Durability <= 0
		case *Potion:
			return v.Volume < glassSize
		default:
			return false
		}
	})
}

func (inv *Inventory) GetItemNames() []string {
	return Map(inv.Items, func(item Item) string {
		return item.GetName()
	})
}

func (inv *Inventory) FindItemByName(name string) (Item, bool) {
	return Find(inv.Items, func(item Item) bool {
		return item.GetName() == name
	})
}

func (inv *Inventory) Save(w io.Writer) {
	for _, item := range inv.Items {
		if storable, ok := item.(Storable); ok {
			storable.Serialize(w)
			_, _ = fmt.Fprintln(w)
		}
	}
}

func (inv *Inventory) Load(r io.Reader) {
	scanner := bufio.NewScanner(r)

	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == "" {
			continue
		}

		// TODO: дописать загрузку всех типов предметов
		if strings.HasPrefix(line, "Weapon|") {
			var w Weapon
			w.Deserialize(strings.NewReader(line))
			inv.AddItem(&w)
		} else if strings.HasPrefix(line, "Armor|") {
			var a Armor
			a.Deserialize(strings.NewReader(line))
			inv.AddItem(&a)
		} else if strings.HasPrefix(line, "Potion|") {
			var p Potion
			p.Deserialize(strings.NewReader(line))
			inv.AddItem(&p)
		}
	}
}

func main() {
	inv := Inventory{}

	sword := &Weapon{Name: "Меч", Damage: 10, Durability: 2}
	armor := &Armor{Name: "Кираса", Defense: 7, Weight: 6.5}
	abrau := &Potion{
		Wine: Wine{
			Name:      "Абрау-Дюрсо Виктор Дравиньи",
			Color:     WineColorWhite,
			Sweetness: WineSweetnessDry, // Брют
			ABV:       12.0,
			Region:    "Краснодарский край (Абрау-Дюрсо)",
			Rank:      WineRankAOC,
			Year:      2020,
			Volume:    0.7,
		},
	}
	inv.AddItem(sword)
	inv.AddItem(armor)
	inv.AddItem(abrau)

	// TODO:
	// 1. Создать Potion (или что то свое) и добавить его в инвентарь.
	// 2. Показать его использование.
	// 3. Проверить, что он сохраняется и загружается.

	fmt.Println("=== Все предметы ===")
	for _, item := range inv.Items {
		fmt.Println("-", DescribeItem(item))
	}

	fmt.Println("\n=== Использование предметов ===")
	fmt.Println(sword.Use())
	fmt.Println(sword.Use())
	fmt.Println(sword.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())
	fmt.Println(abrau.Use())

	fmt.Println("\n=== Сохранение инвентаря ===")
	var buf bytes.Buffer
	inv.Save(&buf)
	fmt.Println(buf.String())

	fmt.Println("=== Загрузка инвентаря ===")
	loaded := Inventory{}
	loaded.Load(strings.NewReader(buf.String()))

	fmt.Println("\n=== Имена предметов ===")
	fmt.Println(loaded.GetItemNames())

	fmt.Println("\n=== Сломанные предметы ===")
	for _, item := range loaded.GetBrokenItems() {
		fmt.Println("-", item.GetName())
	}
}
Editor is loading...
Leave a Comment