Эх сурвалжийг харах

d05 Массовые исправления

SVI 2 жил өмнө
parent
commit
6b46fd9cd1
31 өөрчлөгдсөн 1323 нэмэгдсэн , 286 устгасан
  1. 3 0
      pkg/alias/alias.go
  2. 14 5
      pkg/section/down_time/down_time.go
  3. 4 3
      pkg/types/iarsenal.go
  4. 1 1
      pkg/types/ibase.go
  5. 7 1
      pkg/types/ibot.go
  6. 6 2
      pkg/types/iserv_bots.go
  7. 2 0
      pkg/types/iserver.go
  8. 11 0
      pkg/types/iserver_stat.go
  9. 27 20
      server/serv_bots/dict_warbot/dict_warbot.go
  10. 24 10
      server/serv_bots/serv_bots.go
  11. 8 0
      server/serv_bots/warbot/angar/angar.go
  12. 274 121
      server/serv_bots/warbot/angar/base/arsenal/arsenal.go
  13. 1 1
      server/serv_bots/warbot/angar/base/arsenal/arsenalnet/arsenalnet.go
  14. 34 25
      server/serv_bots/warbot/angar/base/base.go
  15. 126 0
      server/serv_bots/warbot/angar/base/labor/labor.go
  16. 74 14
      server/serv_bots/warbot/angar/base/mine/mine.go
  17. 6 1
      server/serv_bots/warbot/angar/base/polygon/polygon.go
  18. 5 11
      server/serv_bots/warbot/angar/fuel/fuel.go
  19. 134 0
      server/serv_bots/warbot/angar/tank_params/tank_params.go
  20. 21 12
      server/serv_bots/warbot/warbot.go
  21. 12 3
      server/serv_bots/warbot/warbot_config/warbot_config.go
  22. 5 14
      server/serv_web/serv_web.go
  23. 351 0
      server/serv_web/web_api/web_api.go
  24. 16 17
      server/serv_web/web_gui/web_gui.go
  25. 11 6
      server/server.go
  26. 108 0
      server/server_stat/server_stat.go
  27. 0 0
      web/static/js/htmx.min.js
  28. 3 1
      web/tmpl/footer.tmpl.html
  29. 3 1
      web/tmpl/header.tmpl.html
  30. 2 2
      web/tmpl/list_bot.tmpl.html
  31. 30 15
      web/tmpl/state_bot.tmpl.html

+ 3 - 0
pkg/alias/alias.go

@@ -30,3 +30,6 @@ type СценаРежим string
 
 // СценаРабота -- работа сцены (серебро-1. серебро-3, ремка-4 и т.п.)
 type СценаРабота string
+
+// БотНомер -- номер бота
+type БотНомер int

+ 14 - 5
pkg/section/down_time/down_time.go

@@ -17,7 +17,7 @@ import (
 )
 
 const (
-	спатьИнтервал = time.Millisecond * 100 // Малый интервал сна в 100 мсек
+	спатьИнтервал = time.Millisecond * 1000 // Малый интервал сна в 100 мсек
 )
 
 // ВремОбрат -- счётчик обратного времени для игровой зоны (анга, база, битва и т.п.)
@@ -68,12 +68,19 @@ func (сам *ВремОбрат) ПолучМилСек() alias.МилСек {
 	return сам.остатПарсер.ПолучМилСек()
 }
 
-// Запускает тикер для интервалов сна (через каждые 100 мСек)
+// Запускает тикер для интервалов сна (через каждые 1000 мСек)
 func (сам *ВремОбрат) пуск() {
 	defer close(сам.канВызов)
 	фнЖдать := func() {
+		time.Sleep(спатьИнтервал)
 		timeNow := time.Now().UTC().UnixMilli()
-		if сам.лимит.Получ() > int(timeNow) {
+		цТекущ := сам.текущ.Получ()
+		цТекущ -= 1000
+		if цТекущ < 0 {
+			цТекущ = 0
+		}
+		сам.текущ.Уст(цТекущ)
+		if сам.лимит.Получ() > int(timeNow) || цТекущ > 0 {
 			return
 		}
 		сам.канВызов <- 1
@@ -84,7 +91,6 @@ func (сам *ВремОбрат) пуск() {
 		case <-сам.кнт.Done(): // Отмена контекста тикера (а может и сцены, может и бота)
 			return
 		default:
-			time.Sleep(спатьИнтервал)
 			фнЖдать()
 		}
 	}
@@ -120,7 +126,10 @@ func (сам *ВремОбрат) Уст(время alias.Время) error {
 func (сам *ВремОбрат) String() string {
 	сам.блок.RLock()
 	defer сам.блок.RUnlock()
-	return сам.остатПарсер.String()
+	цОстат := сам.текущ.Получ()
+	остат := time.Millisecond * time.Duration(цОстат)
+	стрВрем := остат.String()
+	return стрВрем
 }
 
 // КаналСиг -- возвращает канал чтения тиков

+ 4 - 3
pkg/types/iarsenal.go

@@ -1,12 +1,13 @@
 package types
 
 /*
-	Интерфейс к объекту арсенала
+	Интерфейс к объекту оружейной
 */
 
-// ИАрсенал -- интерфейс к объекту арсенала
-type ИАрсенал interface {
+// ИОружейная -- интерфейс к объекту оружейной
+type ИОружейная interface {
 	ИСценаСтр
+	ИБазаСтроение
 	// Фугасы -- возвращает объект фугасов
 	Фугасы() ИСтатПарам
 	// Бронебойки -- возвращает объект бронебойных снарядов

+ 1 - 1
pkg/types/ibase.go

@@ -8,7 +8,7 @@ package types
 type ИБаза interface {
 	ИСценаСтр
 	// Арсенал -- возвращает объект арсенала
-	Арсенал() ИАрсенал
+	Арсенал() ИОружейная
 	// Банк -- возвращает объект банка
 	Банк() ИБанк
 	// Полигон -- возвращает объект полигона

+ 7 - 1
pkg/types/ibot.go

@@ -1,6 +1,10 @@
 package types
 
-import "context"
+import (
+	"context"
+
+	"wartank/pkg/alias"
+)
 
 // ИБот -- серверный бот с реальным состоянием
 type ИБот interface {
@@ -8,6 +12,8 @@ type ИБот interface {
 	Имя() string
 	// Пароль -- возвращает пароль бота
 	Пароль() string
+	// Номер -- возвращает номер бота
+	Номер() alias.БотНомер
 	// Ангар -- возвращает ангар бота
 	Ангар() ИАнгар
 	// Танк -- возврщает параметры танка

+ 6 - 2
pkg/types/iserv_bots.go

@@ -1,11 +1,15 @@
 package types
 
+import (
+	"wartank/pkg/alias"
+)
+
 // ИБотоФерма -- словарь серверных ботов
 type ИБотоФерма interface {
 	// Get -- возвращает бота по его имени
-	Get(name string) ИБот
+	Get(botNumber alias.БотНомер) ИБот
 	// BotStart -- запускает бота в работу
-	BotStart(name string) error
+	BotStart(botNumber alias.БотНомер) error
 	// ListBot -- возвращает список ботов
 	ListBot() []ИБот
 	// НовБот -- добавляет нового бота на бото-ферму

+ 2 - 0
pkg/types/iserver.go

@@ -15,4 +15,6 @@ type ИСервер interface {
 	ServBots() ИБотоФерма
 	// Gui -- возвращает объект графики
 	Gui() ИГуи
+	// Стат -- возвращает статистику сервера
+	Стат() ИСерверСтат
 }

+ 11 - 0
pkg/types/iserver_stat.go

@@ -0,0 +1,11 @@
+package types
+
+// ИСерверСтат -- статистика сервера
+type ИСерверСтат interface {
+	// СчётСтарт -- счётчик запусков
+	СчётСтарт() int
+	// ВремяВсего -- общее время работы
+	ВремяВсего() string
+	// ВремяСессия -- время сессии
+	ВремяСессия() string
+}

+ 27 - 20
server/serv_bots/dict_warbot/dict_warbot.go

@@ -4,9 +4,11 @@ package dict_warbot
 import (
 	"fmt"
 	"log"
+	"strconv"
 	"strings"
 	"sync"
 
+	"wartank/pkg/alias"
 	"wartank/pkg/types"
 	"wartank/server/serv_bots/warbot"
 )
@@ -19,7 +21,7 @@ const (
 type DictWarBot struct {
 	server types.ИСервер
 	store  types.ИХран
-	dict   map[string]types.ИБот
+	dict   map[alias.БотНомер]types.ИБот
 	block  sync.RWMutex
 }
 
@@ -32,7 +34,7 @@ func NewDictWarBot(server types.ИСервер) (*DictWarBot, error) {
 	сам := &DictWarBot{
 		server: server,
 		store:  server.Store(),
-		dict:   map[string]types.ИБот{},
+		dict:   map[alias.БотНомер]types.ИБот{},
 	}
 	if err := сам.load(); err != nil {
 		return nil, fmt.Errorf("NewDictBot(): in load list bots from store, err=%w", err)
@@ -52,10 +54,10 @@ func (сам *DictWarBot) ListBot() []types.ИБот {
 }
 
 // Get -- возвращает бота по имени
-func (сам *DictWarBot) Get(botName string) types.ИБот {
+func (сам *DictWarBot) Get(botNumber alias.БотНомер) types.ИБот {
 	сам.block.RLock()
 	defer сам.block.RUnlock()
-	bot := сам.dict[botName]
+	bot := сам.dict[botNumber]
 	return bot
 }
 
@@ -66,18 +68,18 @@ func (сам *DictWarBot) Add(bot types.ИБот) {
 	if bot == nil {
 		return
 	}
-	сам.dict[bot.Имя()] = bot
+	сам.dict[bot.Номер()] = bot
 	сам.save()
 }
 
 // Сохраняет словарь ботов в базе
 func (сам *DictWarBot) save() {
-	strName := ""
-	for name := range сам.dict {
-		strName += name + ";"
+	strNumber := ""
+	for botNumber := range сам.dict {
+		strNumber += fmt.Sprint(botNumber) + ";"
 	}
-	strName = strName[:len(strName)-1]
-	err := сам.store.Put(strBotList, []byte(strName))
+	strNumber = strNumber[:len(strNumber)-1]
+	err := сам.store.Put(strBotList, []byte(strNumber))
 	if err != nil {
 		сам.server.CancelApp()
 	}
@@ -85,33 +87,38 @@ func (сам *DictWarBot) save() {
 
 // Загружает всех ботов с базы
 func (сам *DictWarBot) load() error {
-	binName, err := сам.store.Get(strBotList)
+	binNumber, err := сам.store.Get(strBotList)
 	if err != nil {
 		if !strings.Contains(err.Error(), "not found") {
 			return fmt.Errorf("DictWarBot.load(): in get list bot, err=\n\t%w", err)
 		}
 	}
-	strName := string(binName)
-	if strName == "" {
+	strNumbers := string(binNumber)
+	if strNumbers == "" {
 		return nil
 	}
-	lstName := strings.Split(strName, ";")
-	for _, name := range lstName {
-		if name == "" {
+	lstNumbers := strings.Split(strNumbers, ";")
+	for _, strNumber := range lstNumbers {
+		if strNumber == "" {
 			continue
 		}
-		_, isOk := сам.dict[name]
+		iNumber, err := strconv.Atoi(strNumber)
+		if err != nil {
+			return fmt.Errorf("DictWarBot.load(): in conver number bot(%v), err=\n\t%w", strNumber, err)
+		}
+		number := alias.БотНомер(iNumber)
+		_, isOk := сам.dict[number]
 		if isOk {
 			continue
 		}
-		bot, err := warbot.ЗагрузитьВарБот(сам.server, name)
+		bot, err := warbot.ЗагрузитьВарБот(сам.server, number)
 		if err != nil {
-			return fmt.Errorf("ServBots.load(): in create bot %q, err=\n\t%w", name, err)
+			return fmt.Errorf("ServBots.load(): in create bot %q, err=\n\t%w", strNumber, err)
 		}
 		if bot.АвтоИграЕсли() {
 			go bot.Пуск()
 		}
-		сам.dict[name] = bot
+		сам.dict[number] = bot
 	}
 	return nil
 }

+ 24 - 10
server/serv_bots/serv_bots.go

@@ -4,6 +4,7 @@ package serv_bots
 import (
 	"fmt"
 
+	"wartank/pkg/alias"
 	"wartank/pkg/types"
 	"wartank/server/serv_bots/dict_warbot"
 	"wartank/server/serv_bots/warbot"
@@ -34,20 +35,20 @@ func НовБотоФерма(серв types.ИСервер) (*БотоФерм
 }
 
 // Get -- возвращает боевого бота по имени
-func (сам *БотоФерма) Get(name string) types.ИБот {
-	bot := сам.dictBot.Get(name)
+func (сам *БотоФерма) Get(botNumber alias.БотНомер) types.ИБот {
+	bot := сам.dictBot.Get(botNumber)
 	return bot
 }
 
 // BotStart -- запускает бота в работу по его имени
-func (сам *БотоФерма) BotStart(name string) error {
-	bot := сам.dictBot.Get(name)
+func (сам *БотоФерма) BotStart(botNumber alias.БотНомер) error {
+	bot := сам.dictBot.Get(botNumber)
 	if bot == nil {
-		return fmt.Errorf("ServBots.BotStart(): bot(%v) not found", name)
+		return fmt.Errorf("ServBots.BotStart(): bot(%v) not found", botNumber)
 	}
 	err := bot.Пуск()
 	if err != nil {
-		return fmt.Errorf("ServBots.BotStart(): in run bot(%v), err=\n\t%w", name, err)
+		return fmt.Errorf("ServBots.BotStart(): in run bot(%v), err=\n\t%w", botNumber, err)
 	}
 	return nil
 }
@@ -61,13 +62,26 @@ func (сам *БотоФерма) ListBot() []types.ИБот {
 // НовБот -- добавляет нового бота на ферму
 func (сам *БотоФерма) НовБот(логин, пароль string, еслиАвто bool) error {
 	{ // Существует ли такой бот
-		bot := сам.dictBot.Get(логин)
-		if bot != nil {
-			return nil
+		for _, бот := range сам.dictBot.ListBot() {
+			if бот.Имя() == логин {
+				return nil
+			}
 		}
 	}
+	номер := alias.БотНомер(len(сам.dictBot.ListBot()) + 1)
+	фнНомерПровер := func() bool {
+		for _, бот := range сам.dictBot.ListBot() {
+			if бот.Номер() == номер {
+				return false
+			}
+		}
+		return true
+	}
+	for !фнНомерПровер() {
+		номер++
+	}
 	{ // Нет такого бота, надо его создать
-		bot, err := warbot.НовВарБот(сам.серв, логин, пароль, еслиАвто)
+		bot, err := warbot.НовВарБот(сам.серв, номер, логин, пароль, еслиАвто)
 		if err != nil {
 			return fmt.Errorf("БотоФерма.НовБот(): in create bot %q, err=\n\t%w", логин, err)
 		}

+ 8 - 0
server/serv_bots/warbot/angar/angar.go

@@ -19,6 +19,7 @@ import (
 	"wartank/server/serv_bots/warbot/angar/masters"
 	"wartank/server/serv_bots/warbot/angar/missions"
 	"wartank/server/serv_bots/warbot/angar/netstat"
+	"wartank/server/serv_bots/warbot/angar/tank_params"
 	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
 
 	"github.com/sirupsen/logrus"
@@ -51,6 +52,7 @@ type Ангар struct {
 	silverOnline types.ИСтатПарам
 
 	сетьТанкСтат *netstat.NetStat
+	танкПарам    *tank_params.ТанкПараметры
 	блок         sync.Mutex
 }
 
@@ -85,6 +87,10 @@ func НовАнгар(bot types.ИБот) (*Ангар, error) {
 	if ош != nil {
 		return nil, fmt.Errorf("НовАнгар(): при создании статистики серебра заработанного за сессию, ош=\n\t%w", ош)
 	}
+	танкПарам, ош := tank_params.НовТанкПараметры(bot)
+	if ош != nil {
+		return nil, fmt.Errorf("НовАнгар(): при создании параметров танка, ош=\n\t%w", ош)
+	}
 	сам := &Ангар{
 		Секция:        section,
 		бот:           bot,
@@ -95,6 +101,7 @@ func НовАнгар(bot types.ИБот) (*Ангар, error) {
 
 		сереброВсего: сереброВсего,
 		silverOnline: сереброСессия,
+		танкПарам:    танкПарам,
 	}
 	{ // Сеть
 		сам.сеть, err = angarnet.NewAngarNet(сам)
@@ -184,6 +191,7 @@ func (сам *Ангар) Пуск() error {
 		if err := сам.миссии.Пуск(); err != nil {
 			return fmt.Errorf("Ангар.Пуск(): при пуске наград, err=\n\t%w", err)
 		}
+		сам.танкПарам.Пуск()
 		go сам.топливо.Run()
 	}
 	фнЦикл := func() bool {

+ 274 - 121
server/serv_bots/warbot/angar/base/arsenal/arsenal.go

@@ -1,6 +1,7 @@
 package arsenal
 
 import (
+	"context"
 	"fmt"
 	"log"
 	"strconv"
@@ -27,18 +28,24 @@ const (
 // Оружейная -- объект оружейной на базе
 type Оружейная struct {
 	*section.Секция
-	бот        types.ИБот
-	база       types.ИБаза
-	сеть       *arsenalnet.ArsenalNet
-	фугас      types.ИСтатПарам
-	бронебойка types.ИСтатПарам
-	кумулятив  types.ИСтатПарам
-	ремка      types.ИСтатПарам
+	бот          types.ИБот
+	база         types.ИБаза
+	сеть         *arsenalnet.ArsenalNet
+	фугас        types.ИСтатПарам
+	бронебойка   types.ИСтатПарам
+	кумулятив    types.ИСтатПарам
+	ремка        types.ИСтатПарам
+	уровень      types.ИСтатПарам
+	продуктИмя   string           // Что сейчас делается
+	продуктКол   types.ИСтатПарам // Сколько делается прямо сейчас
+	продуктВремя string           // Сколько осталось времени прямо сейчас
+	кнт          context.Context  // контекст шахты
+	фнОтмена     func()           // Функция отмены шахты
 }
 
 // НовОружейная -- возвращает новый *Arsenal
-func НовОружейная(base types.ИБаза) (*Оружейная, error) {
-	section, err := section.НовСекция(base.Бот(), "Арсенал", `<span class="green2">Ремкомплект</span><br/>`)
+func НовОружейная(база types.ИБаза) (*Оружейная, error) {
+	section, err := section.НовСекция(база.Бот(), "Арсенал", `<span class="green2">Ремкомплект</span><br/>`)
 	if err != nil {
 		return nil, fmt.Errorf("НовОружейная(): in create ISection, err=\n\t%w", err)
 	}
@@ -59,14 +66,27 @@ func НовОружейная(base types.ИБаза) (*Оружейная, error
 	if ош != nil {
 		return nil, fmt.Errorf("НовОружейная(): при создании статистики  ремок, ош=\n\t%w", ош)
 	}
+	уровень, ош := static_param.НовСтатПарам("уровень")
+	if ош != nil {
+		return nil, fmt.Errorf("НовОружейная(): при создании статистики числа добычи, ош=\n\t%w", ош)
+	}
+	продуктКол, ош := static_param.НовСтатПарам("свинец")
+	if ош != nil {
+		return nil, fmt.Errorf("НовШахта(): при создании статистики числа добычи, ош=\n\t%w", ош)
+	}
+	кнт, фнОтмена := context.WithCancel(база.Кнт())
 	сам := &Оружейная{
 		Секция:     section,
-		бот:        base.Бот(),
-		база:       base,
+		бот:        база.Бот(),
+		база:       база,
 		фугас:      фугас,
 		бронебойка: бронейбойки,
 		кумулятив:  кумулятив,
 		ремка:      ремка,
+		уровень:    уровень,
+		продуктКол: продуктКол,
+		кнт:        кнт,
+		фнОтмена:   фнОтмена,
 	}
 	{ // ArsenalNet
 		сам.сеть, err = arsenalnet.НовАрсеналСеть(сам)
@@ -74,10 +94,31 @@ func НовОружейная(base types.ИБаза) (*Оружейная, error
 			return nil, fmt.Errorf("НовОружейная(): in create NetArsenal, err=\n\t%w", err)
 		}
 	}
-	_ = types.ИАрсенал(сам)
+	_ = types.ИОружейная(сам)
 	return сам, nil
 }
 
+// Уровень -- возвращает уровень шахты
+func (сам *Оружейная) Уровень() types.ИСтатПарам {
+	return сам.уровень
+}
+
+// ПродуктКолСейчас -- возвращает количество прозводимого продукта
+func (сам *Оружейная) ПродуктКолСейчас() int {
+	return сам.продуктКол.Получ()
+}
+
+// ПродуктИмяСейчас -- возвращает имя прозводимого продукта
+func (сам *Оружейная) ПродуктИмяСейчас() string {
+	return сам.продуктИмя
+}
+
+// ПродуктВремяСейчас -- сколько осталось времени до производства продукта
+func (сам *Оружейная) ПродуктВремяСейчас() string {
+	return сам.продуктВремя
+	// return сам.Секция.ВремяОпрос().Стр()
+}
+
 func (сам *Оружейная) Пуск() error {
 	go сам.пуск()
 	return nil
@@ -88,9 +129,12 @@ func (сам *Оружейная) пуск() {
 	// сам.getTime()
 	time.Sleep(time.Second * 3)
 	фнРабота := func() {
-		defer time.Sleep(time.Minute * 5) // Интервал 5 минут (кратно интервалу производства -- от 40 минут до 1 часа)
-		if сам.построитьУлучшить() {
-			time.Sleep(time.Minute * 25)
+		defer func() {
+			for сам.ВремяОстат().ПолучМилСек() > 0 {
+				time.Sleep(time.Second * 5)
+			}
+		}()
+		if сам.уровеньОбновить() {
 			return
 		}
 		сам.СтатаОбновить()
@@ -99,7 +143,7 @@ func (сам *Оружейная) пуск() {
 	}
 	for {
 		select {
-		case <-сам.Кнт().Done():
+		case <-сам.кнт.Done():
 			return
 		// case <-сам.ВремяОпрос().КаналСиг():
 		// 	if сам.РежимТекущ().Получ() == "upgrade" {
@@ -115,6 +159,199 @@ func (сам *Оружейная) пуск() {
 	}
 }
 
+// Обновляет текущий уровень оружейки (может быть не построена)
+func (сам *Оружейная) уровеньОбновить() bool {
+	списСтр, ош := сам.сеть.Клиент().Get("http://wartank.ru/buildings")
+	if ош != nil {
+		log.Printf("Оружейная.уровеньОбновить(): in make request, err=\n\t%v\n", ош)
+		return false
+	}
+	// <span class="green2">Оружейная - 0</span><br/>
+	var (
+		еслиНайти = false
+		стр       = ""
+	)
+	for _, стр = range списСтр {
+		if strings.Contains(стр, `<span class="green2">Оружейная - `) {
+			еслиНайти = true
+			break
+		}
+	}
+	if !еслиНайти {
+		return false
+	}
+	_стр := strings.TrimPrefix(стр, `<span class="green2">Оружейная - `)
+	_стр = strings.TrimSuffix(_стр, `</span><br/>`)
+	иУровень, ош := strconv.Atoi(_стр)
+	if ош != nil {
+		log.Printf("Оружейная.уровеньОбновить(): строка уровня сбойная, стр=%q, ош=\n\t%v\n", стр, ош)
+		return false
+	}
+	сам.уровень.Уст(иУровень)
+	switch иУровень {
+	case 0: // оружейку надо построить
+		счёт := 5
+		for счёт > 0 {
+			if сам.построить(списСтр) {
+				break
+			}
+			счёт--
+		}
+	default: // Пробуем проапгрейдить
+		счёт := 5
+		for счёт > 0 {
+			if сам.проапгрейдить() {
+				break
+			}
+			счёт--
+		}
+	}
+	return true
+}
+
+// Строит оружейку при нулевом уровне
+func (сам *Оружейная) построить(списСтр []string) bool {
+	// <td style="width:50%;padding-left:1px;"><a class="simple-but border mb5" href="building-upgrade/Armory"><span><span>Построить</span></span></a></td>
+	var (
+		еслиНайти = false
+		стр       = ""
+	)
+	for _, стр = range списСтр {
+		if strings.Contains(стр, `href="building-upgrade/Armory"><span><span>Построить</span></span>`) {
+			еслиНайти = true
+			break
+		}
+	}
+	if !еслиНайти {
+		return true
+	}
+	// Пробуем построить оружейку
+	_стр := strings.TrimPrefix(стр, `<td style="width:50%;padding-left:1px;"><a class="simple-but border mb5" href="`)
+	_стр = strings.TrimSuffix(_стр, `"><span><span>Построить</span></span></a></td>`)
+	ссылка := "https://wartank.ru/" + _стр
+	списСтр, ош := сам.сеть.Клиент().Get(ссылка)
+	if ош != nil {
+		log.Printf("ERRO Оружейная.построить(): при GET-команде 'построить оружейку', err=\n\t%v\n", ош)
+		return false
+	}
+	еслиНайти = false
+	// "<a class=\"simple-but border mb5\" href=\"Armory?14-1.ILinkListener-upgradeLink-link\">"
+	for _, стр = range списСтр {
+		if strings.Contains(стр, `ILinkListener-upgradeLink-link`) {
+			еслиНайти = true
+			break
+		}
+	}
+	if !еслиНайти {
+		return true
+	}
+	_стр = strings.TrimPrefix(стр, "<a class=\"simple-but border mb5\" href=\"")
+	_стр = strings.TrimSuffix(_стр, "\">")
+	// http://wartank.ru/building-upgrade/Armory?16-1.ILinkListener-upgradeLink-link
+	ссылка = "https://wartank.ru/building-upgrade/" + _стр
+	_, ош = сам.сеть.Клиент().Get(ссылка)
+	if ош != nil {
+		log.Printf("ERRO Оружейная.построить(): при GET-команде 'купить постройку оружейки', err=\n\t%v\n", ош)
+		return false
+	}
+	return true
+}
+
+// Пытается проапгрейдить оружейку
+func (сам *Оружейная) проапгрейдить() bool {
+	time.Sleep(time.Millisecond * 1000)
+	var (
+		еслиНайти = false
+		списСтр   []string
+		стр       = ""
+		ош        error
+	)
+	фнКупить := func() bool {
+		defer time.Sleep(time.Millisecond * 1000)
+		списСтр, ош = сам.сеть.Клиент().Get("https://wartank.ru/building-upgrade/Armory")
+		if ош != nil {
+			log.Printf("Оружейная.проапгрейдить().фнКупить(): при GET-команде 'купить постройку оружейки', err=\n\t%v\n", ош)
+			return false
+		}
+		for _, стр = range списСтр {
+			// <a class="simple-but border mb5" href="Armory?5-1.ILinkListener-upgradeLink-link">
+			if strings.Contains(стр, `ILinkListener-upgradeLink-link`) {
+				еслиНайти = true
+				break
+			}
+		}
+		if !еслиНайти {
+			return true
+		}
+		// Пробуем улучшить шахту
+		_стр := strings.TrimPrefix(стр, "<a class=\"simple-but border mb5\" href=\"")
+		_стр = strings.TrimSuffix(_стр, "\">")
+		// https://wartank.ru/building-upgrade/Armory?4-1.ILinkListener-upgradeLink-link
+		// <a class="simple-but border mb5" href="Armory?50-1.ILinkListener-upgradeLink-link">
+		ссылка := "https://wartank.ru/building-upgrade/" + _стр
+		списСтр, ош = сам.сеть.Клиент().Get(ссылка)
+		if ош != nil {
+			log.Printf("Оружейная.проапгрейдить().фнКупить(): при GET-команде 'купить постройку оружейки', err=\n\t%v\n", ош)
+			return false
+		}
+		// Проверить, что постройка состоялась
+		for _, стр := range списСтр {
+			if strings.Contains(стр, "ILinkListener-upgradeLink-link") {
+				log.Printf("Оружейная.проапгрейдить().фнКупить(): покупка оружейкине прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
+				return false // Покупка не оплачена
+			}
+		}
+		log.Printf("!!!!! Оружейная.проапгрейдить().фнКупить(): покупка оружейки прошла\n")
+		return true
+	}
+
+	фнПодтверждение := func() bool {
+		for _, стр = range списСтр {
+			// <a class="simple-but border w50 mXa mb10" w:id="confirmLink" href="../wicket/page?7-1.ILinkListener-confirmLink"><span><span>да, подтверждаю</span></span></a>
+			if strings.Contains(стр, `ILinkListener-confirmLink`) {
+				еслиНайти = true
+				break
+			}
+		}
+		if !еслиНайти {
+			return true
+		}
+		// Пробуем построить шахту
+		_стр := strings.TrimPrefix(стр, `<a class="simple-but border w50 mXa mb10" w:id="confirmLink" href="..`)
+		_стр = strings.TrimSuffix(_стр, `"><span><span>да, подтверждаю</span></span></a>`)
+		// https://wartank.ru/wicket/page?6-1.ILinkListener-confirmLink
+		ссылка := "https://wartank.ru" + _стр
+		списСтр, ош = сам.сеть.Клиент().Get(ссылка)
+		if ош != nil {
+			log.Printf("Оружейная.проапгрейдить().фнПодтверждение(): при GET-команде 'подтвердить постройку склада топлива', err=\n\t%v\n", ош)
+			return false
+		}
+		// Проверить, что постройка состоялась
+		for _, стр := range списСтр {
+			if strings.Contains(стр, "<title>Вы сделали слишком большую паузу</title>") {
+				log.Printf("Оружейная.проапгрейдить().фнПодтверждение(): подтверждение покупка склада топлива не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
+				return false // Покупка не оплачена
+			}
+		}
+		log.Printf("+++++Оружейная.проапгрейдить().фнПодтверждение(): подтверждение покупка склада топлива прошла\n")
+		return true
+	}
+
+	фнКомплекс := func() {
+		count := 5
+		for count > 0 {
+			if фнКупить() {
+				if фнПодтверждение() {
+					break
+				}
+			}
+			count--
+		}
+	}
+	фнКомплекс()
+	return true
+}
+
 // Проверяет на забрать оружейную
 func (сам *Оружейная) забрать() bool {
 	var (
@@ -170,91 +407,6 @@ func (сам *Оружейная) забрать() bool {
 	return true
 }
 
-// Проверяет необходимость постройки оружейной
-func (сам *Оружейная) построитьУлучшить() bool {
-	var (
-		списПолигон []string
-		ош          error
-	)
-	{ // Зайти на страницу постройки
-		// https://wartank.ru/building-upgrade/Armory
-		списПолигон, ош = сам.сеть.Клиент().Get("https://wartank.ru/building-upgrade/Armory")
-		if ош != nil {
-			log.Printf("Оружейная.построитьУлучшить(): при чтении страницы строительства оружейки, ош=\n\t%v\n", ош)
-			return false
-		}
-		стрСсылка := ""
-		еслиНайти := false
-		// <a class="simple-but border mb5" href="Armory?163-1.ILinkListener-upgradeLink-link">
-		for _, стрСсылка = range списПолигон {
-			if strings.Contains(стрСсылка, `href="Armory?`) {
-				еслиНайти = true
-				break
-			}
-		}
-		if !еслиНайти { // Ссылка на постройку оружейной не найдена
-			return false
-		}
-		_ссылка := strings.TrimPrefix(стрСсылка, `<a class="simple-but border mb5" href="`)
-		_ссылка = strings.TrimSuffix(_ссылка, `">`)
-		ссылка := "https://wartank.ru/building-upgrade/" + _ссылка
-		// https://wartank.ru/building-upgrade/Armory?162-1.ILinkListener-upgradeLink-link
-		списПолигон, ош = сам.сеть.Клиент().Get(ссылка)
-		if ош != nil {
-			log.Printf("Оружейная.построитьУлучшить(): при выполнении запроса на строительство, ош=\n\t%v\n", ош)
-			return false
-		}
-	}
-	{ // Заказать постройку
-		// https://wartank.ru/building-upgrade/Armory
-		стрСсылка := ""
-		еслиНайти := false
-		// <a class="simple-but border mb5" href="Armory?163-1.ILinkListener-upgradeLink-link">
-		for _, стрСсылка = range списПолигон {
-			if strings.Contains(стрСсылка, `href="Armory?`) {
-				еслиНайти = true
-				break
-			}
-		}
-		if еслиНайти {
-			_ссылка := strings.TrimPrefix(стрСсылка, `<a class="simple-but border mb5" href="`)
-			_ссылка = strings.TrimSuffix(_ссылка, `">`)
-			ссылка := "https://wartank.ru/building-upgrade/" + _ссылка
-			// https://wartank.ru/building-upgrade/Armory?162-1.ILinkListener-upgradeLink-link
-			списПолигон, ош = сам.сеть.Клиент().Get(ссылка)
-			if ош != nil {
-				log.Printf("Оружейная.построитьУлучшить(): при выполнении запроса на строительство, ош=\n\t%v\n", ош)
-				return false
-			}
-		}
-	}
-	{ // подтверждение постройки
-		// <a class="simple-but border w50 mXa mb10" w:id="confirmLink" href="../wicket/page?165-1.ILinkListener-confirmLink"><span><span>да, подтверждаю</span></span></a>
-		стрСсылка := ""
-		еслиНайти := false
-		for _, стрСсылка = range списПолигон {
-			if strings.Contains(стрСсылка, `.ILinkListener-confirmLink`) {
-				еслиНайти = true
-				break
-			}
-		}
-		if !еслиНайти { // Время полигона вышло
-			return false
-		}
-		_ссылка := strings.TrimPrefix(стрСсылка, "<a class=\"simple-but border w50 mXa mb10\" w:id=\"confirmLink\" href=\"../")
-		_ссылка = strings.TrimSuffix(_ссылка, "\"><span><span>да, подтверждаю</span></span></a>")
-		ссылка := "https://wartank.ru/" + _ссылка
-		// https://wartank.ru/wicket/page?135-1.ILinkListener-confirmLink
-		_, ош = сам.сеть.Клиент().Get(ссылка)
-		if ош != nil {
-			log.Printf("Оружейная.построитьУлучшить(): при выполнении запроса на строительство, ош=\n\t%v\n", ош)
-			return false
-		}
-	}
-	log.Printf("Оружейная.построитьУлучшить(): построен упешно\n")
-	return true
-}
-
 // Фугасы -- возвращает объект числа фугасов
 func (сам *Оружейная) Фугасы() types.ИСтатПарам {
 	return сам.фугас
@@ -364,31 +516,31 @@ func (сам *Оружейная) сделать() bool {
 		return false
 	}
 	// _mt.Println("\tArsenalNet.сделать()")
-	{ // Контроль ремки по времени суток и минимальному количеству
-		iRemka := сам.Ремки().Получ()
-		if iRemka < 70 {
-			for !сам.сделатьРемку() {
-			}
-			return true
+	var (
+		ремкаКол  = сам.Ремки().Получ()
+		фугасКол  = сам.Фугасы().Получ()
+		кумульКол = сам.Кумулятивы().Получ()
+		ббКол     = сам.Бронебойки().Получ()
+		снарядТип = ""
+	)
+	if ремкаКол < 70 { // Контроль ремки по времени суток и минимальному количеству ремок
+		for !сам.сделатьРемку() {
 		}
+		сам.продуктИмя = стрРемки
+		return true
 	}
 	{ // Контроль по числу снарядов. В равных долях без приоритетов
-		iFugas := сам.Фугасы().Получ()
-		iKumul := сам.Кумулятивы().Получ()
-		iArmor := сам.Бронебойки().Получ()
-
-		typeArmor := стрФугасы
-		typeVal := iFugas
-
-		if iKumul < typeVal {
-			typeArmor = стрКумулятивы
-			typeVal = iKumul
+		// снарядТип = стрФугасы
+		снарядТип = стрБронебойки
+		if ббКол > фугасКол {
+			снарядТип = стрФугасы
 		}
 
-		if iArmor < typeVal {
-			typeArmor = стрБронебойки
+		if фугасКол > кумульКол {
+			снарядТип = стрКумулятивы
 		}
-		switch typeArmor {
+
+		switch снарядТип {
 		case стрФугасы: // Мало фугасов
 			for !сам.сделатьФугасы() {
 			}
@@ -401,6 +553,7 @@ func (сам *Оружейная) сделать() bool {
 		default:
 			// log._rintf("ERRO Оружейная.сделать(): неизвестный тип арсенала(%v)", typeArmor)
 		}
+		сам.продуктИмя = снарядТип
 	}
 	return true
 }
@@ -425,7 +578,7 @@ func (сам *Оружейная) сделатьБронебойки() bool {
 	стрВых = lstArsenal[инд+10]
 	// Получить ссылку на бронебойные
 	lstArmor := strings.Split(стрВых, `<a class="simple-but border" href="`)
-	if len(lstArmor) == 0 {
+	if len(lstArmor) <= 1 { // Тут возможно есть пустая строка
 		return true // Считаем, что производство уже запущено
 	}
 	strLink := lstArmor[1]

+ 1 - 1
server/serv_bots/warbot/angar/base/arsenal/arsenalnet/arsenalnet.go

@@ -17,7 +17,7 @@ type ArsenalNet struct {
 }
 
 // НовАрсеналСеть -- возвращает новый *ArsenalNet
-func НовАрсеналСеть(arsenal types.ИАрсенал) (*ArsenalNet, error) {
+func НовАрсеналСеть(arsenal types.ИОружейная) (*ArsenalNet, error) {
 	sectionNet, err := scene_net.НовСекцияСеть(arsenal, "https://wartank.ru/production/Armory")
 	if err != nil {
 		return nil, fmt.Errorf("NewArsenalNet(): in create SectionNet, err=\n\t%w", err)

+ 34 - 25
server/serv_bots/warbot/angar/base/base.go

@@ -16,6 +16,7 @@ import (
 	"wartank/server/serv_bots/warbot/angar/base/bank"
 	"wartank/server/serv_bots/warbot/angar/base/basenet"
 	"wartank/server/serv_bots/warbot/angar/base/fuel"
+	"wartank/server/serv_bots/warbot/angar/base/labor"
 	"wartank/server/serv_bots/warbot/angar/base/market"
 	"wartank/server/serv_bots/warbot/angar/base/mine"
 	"wartank/server/serv_bots/warbot/angar/base/polygon"
@@ -41,60 +42,67 @@ type База struct {
 	шахта        *mine.Шахта
 	ранок        *market.Рынок
 	складТоплива *fuel.СкладТоплива
+	лаборатория  *labor.Лаборатория
 	времОстат    int // Сколько времени спать до опроса базы
 	блок         sync.Mutex
 }
 
 // НовБаза -- возвращает новую базу бота
 func НовБаза(ангар types.ИАнгар) (*База, error) {
-	section, err := section.НовСекция(ангар.Бот(), "База", `<title>База</title>`)
-	if err != nil {
-		return nil, fmt.Errorf("НовБаза(): in create ISection, err=\n\t%w", err)
+	section, ош := section.НовСекция(ангар.Бот(), "База", `<title>База</title>`)
+	if ош != nil {
+		return nil, fmt.Errorf("НовБаза(): in create ISection, err=\n\t%w", ош)
 	}
 	log.Printf("НовБаза(): %q\n", section.Бот().Имя())
 	сам := &База{
 		Секция: section,
 	}
 	{ // База в сети
-		сам.сеть, err = basenet.NewBaseNet(сам)
-		if err != nil {
-			return nil, fmt.Errorf("NewBase(): in create NetBase, err=\n\t%w", err)
+		сам.сеть, ош = basenet.NewBaseNet(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("NewBase(): in create NetBase, err=\n\t%w", ош)
 		}
 	}
 	{ // Arsenal
-		сам.арсенал, err = arsenal.НовОружейная(сам)
-		if err != nil {
-			return nil, fmt.Errorf("NewBase(): in create IArsenal, err=\n\t%w", err)
+		сам.арсенал, ош = arsenal.НовОружейная(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("NewBase(): in create IArsenal, err=\n\t%w", ош)
 		}
 	}
 	{ // Bank
-		сам.банк, err = bank.НовБанк(сам)
-		if err != nil {
-			return nil, fmt.Errorf("NewBase(): in create IBank, err=\n\t%w", err)
+		сам.банк, ош = bank.НовБанк(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("NewBase(): in create IBank, err=\n\t%w", ош)
 		}
 	}
 	{ // Mine
-		сам.шахта, err = mine.НовШахта(сам)
-		if err != nil {
-			return nil, fmt.Errorf("NewBase(): in create IMine, err=\n\t%w", err)
+		сам.шахта, ош = mine.НовШахта(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("NewBase(): in create IMine, err=\n\t%w", ош)
 		}
 	}
 	{ // Market
-		сам.ранок, err = market.НовРынок(сам)
-		if err != nil {
-			return nil, fmt.Errorf("NewBase(): при создании IMarket, err=\n\t%w", err)
+		сам.ранок, ош = market.НовРынок(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("NewBase(): при создании IMarket, err=\n\t%w", ош)
 		}
 	}
 	{ // Polygon
-		сам.полигон, err = polygon.НовПолигон(сам)
-		if err != nil {
-			return nil, fmt.Errorf("NewBase(): in create IPolygon, err=\n\t%w", err)
+		сам.полигон, ош = polygon.НовПолигон(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("NewBase(): in create IPolygon, err=\n\t%w", ош)
+		}
+	}
+	{ // Лаборатория
+		сам.лаборатория, ош = labor.НоваяЛаборатория(ангар.Бот())
+		if ош != nil {
+			return nil, fmt.Errorf("НовБаза(): при создании лаборатории, err=\n\t%w", ош)
 		}
 	}
 	{ // Склад топлива
-		сам.складТоплива, err = fuel.НовСкладТоплива(сам)
-		if err != nil {
-			return nil, fmt.Errorf("НовБаза(): при создании склада топлива, err=\n\t%w", err)
+		сам.складТоплива, ош = fuel.НовСкладТоплива(сам)
+		if ош != nil {
+			return nil, fmt.Errorf("НовБаза(): при создании склада топлива, err=\n\t%w", ош)
 		}
 	}
 	return сам, nil
@@ -162,6 +170,7 @@ func (сам *База) runComponent() error {
 	if err := сам.складТоплива.Пуск(); err != nil {
 		return fmt.Errorf("Base.runComponent(): при запуске склада топлива, err=\n\t%w", err)
 	}
+	сам.лаборатория.Пуск()
 	return nil
 }
 
@@ -188,7 +197,7 @@ func (сам *База) setCountDown() {
 }
 
 // Арсенал -- возвращает объект арсенала
-func (сам *База) Арсенал() types.ИАрсенал {
+func (сам *База) Арсенал() types.ИОружейная {
 	return сам.арсенал
 }
 

+ 126 - 0
server/serv_bots/warbot/angar/base/labor/labor.go

@@ -0,0 +1,126 @@
+// package labor -- лаборатория на базе
+package labor
+
+import (
+	"fmt"
+	"log"
+	"strings"
+	"time"
+
+	"wartank/pkg/types"
+)
+
+// Лаборатория на базе
+type Лаборатория struct {
+	бот types.ИБот
+}
+
+// НоваяЛаборатория -- возвращает новую лабораторию
+func НоваяЛаборатория(бот types.ИБот) (*Лаборатория, error) {
+	if бот == nil {
+		return nil, fmt.Errorf("НоваяЛаборатория(): ИБот == nil")
+	}
+	сам := &Лаборатория{
+		бот: бот,
+	}
+	return сам, nil
+}
+
+// Пуск -- запуск в работу
+func (сам *Лаборатория) Пуск() {
+	go сам.пуск()
+}
+
+// Запукает в работу в отдельном потоке
+func (сам *Лаборатория) пуск() {
+	time.Sleep(time.Millisecond * 4500)
+	for {
+		select {
+		case <-сам.бот.Кнт().Done():
+			return
+		default:
+			сам.работать()
+		}
+	}
+}
+
+// Основной метод работы
+func (сам *Лаборатория) работать() {
+	defer time.Sleep(time.Second * 300)
+	if ош := сам.улучшить(); ош != nil {
+		log.Printf("Лаборатория.работать(): ош=\n\t%v\n", ош)
+		return
+	}
+}
+
+// Улучшает параметры лаборатории
+func (сам *Лаборатория) улучшить() error {
+	// https://wartank.ru/buildings
+	клиент := сам.бот.Сеть().КлиентСеть()
+	фнПостроить := func() error {
+		лстСтр, ош := клиент.Get("https://wartank.ru/buildings")
+		if ош != nil {
+			return fmt.Errorf("Лаборатория.улучшить(): при получении страницы постройки лаборатории с сервера, ош=\n\t%w", ош)
+		}
+		еслиНашли := false
+		// <td style="width:50%;padding-left:1px;"><a class="simple-but border mb5" href="building-upgrade/Laboratory"><span><span>Построить</span></span></a></td>
+		for _, стр := range лстСтр {
+			if strings.Contains(стр, `<td style="width:50%;padding-left:1px;"><a class="simple-but border mb5" href="building-upgrade/Laboratory"><span><span>Построить</span></span></a></td>`) {
+				еслиНашли = true
+				break
+			}
+		}
+		if !еслиНашли {
+			return nil
+		}
+		// https://wartank.ru/building-upgrade/Laboratory
+		_, ош = клиент.Get("https://wartank.ru/building-upgrade/Laboratory")
+		if ош != nil {
+			return fmt.Errorf("Лаборатория.улучшить(): при построении лаборатории, ош=\n\t%w", ош)
+		}
+		return nil
+	}
+	фнКупить := func() error {
+		лстСтр, ош := клиент.Get("https://wartank.ru/building-upgrade/Laboratory")
+		if ош != nil {
+			return fmt.Errorf("Лаборатория.улучшить(): при получении страницы покупки лаборатории с сервера, ош=\n\t%w", ош)
+		}
+		стрВых := ""
+		// <a class="simple-but border mb5" href="Laboratory?118-1.ILinkListener-upgradeLink-link">
+		for _, стрВых = range лстСтр {
+			if strings.Contains(стрВых, `<a class="simple-but border mb5" href="Laboratory?`) {
+				break
+			}
+		}
+		if стрВых == "" {
+			return nil
+		}
+		стрВых = strings.TrimPrefix(стрВых, `<a class="simple-but border mb5" href="`)
+		стрВых = strings.TrimSuffix(стрВых, `">`)
+		// https://wartank.ru/building-upgrade/Laboratory?117-1.ILinkListener-upgradeLink-link
+		стрВых = "https://wartank.ru/building-upgrade/" + стрВых
+		_, ош = клиент.Get(стрВых)
+		if ош != nil {
+			return fmt.Errorf("Лаборатория.улучшить(): при покупки лаборатории, ош=\n\t%w", ош)
+		}
+		return nil
+	}
+	счётОш := 5
+	for счётОш > 0 {
+		time.Sleep(time.Millisecond * 350)
+		счётОш--
+		ош := фнПостроить()
+		if ош != nil {
+			log.Printf("Лаборатория.улучшить(): получить, ош=\n\t%v\n", ош)
+			continue
+		}
+		ош = фнКупить()
+		if ош != nil {
+			log.Printf("Лаборатория.улучшить(): оплатить, ош=\n\t%v\n", ош)
+			continue
+		}
+		// FIXME: надо сделать подтверждение оплаты
+		break
+	}
+	return nil
+}

+ 74 - 14
server/serv_bots/warbot/angar/base/mine/mine.go

@@ -59,7 +59,7 @@ func НовШахта(база types.ИБаза) (*Шахта, error) {
 	if ош != nil {
 		return nil, fmt.Errorf("НовШахта(): при создании статистики свинца, ош=\n\t%w", ош)
 	}
-	добычаЧисло, ош := static_param.НовСтатПарам("свинец")
+	продуктКол, ош := static_param.НовСтатПарам("свинец")
 	if ош != nil {
 		return nil, fmt.Errorf("НовШахта(): при создании статистики числа добычи, ош=\n\t%w", ош)
 	}
@@ -76,7 +76,7 @@ func НовШахта(база types.ИБаза) (*Шахта, error) {
 		железо:     железо,
 		сталь:      сталь,
 		свинец:     свинец,
-		продуктКол: добычаЧисло,
+		продуктКол: продуктКол,
 		уровень:    уровень,
 		кнт:        кнт,
 		фнОтмена:   фнОтмена,
@@ -99,9 +99,14 @@ func (сам *Шахта) Пуск() error {
 func (сам *Шахта) пуск() {
 	time.Sleep(time.Second * 3)
 	фнРабота := func() {
-		defer time.Sleep(time.Minute * 5)
+		defer func() {
+			for сам.ВремяОстат().ПолучМилСек() > 0 {
+				time.Sleep(time.Second * 5)
+			}
+		}()
+		сам.бот.Ангар().РесурсыОбновить()
 		счёт := 5
-		for счёт > 0 {
+		for счёт > 0 { // Забрать из шахты
 			if сам.шахтаЗабрать() {
 				break
 			}
@@ -119,12 +124,14 @@ func (сам *Шахта) пуск() {
 		сам.количествоПолучить()
 		сам.бот.Ангар().РесурсыОбновить()
 		сам.Сделать()
+
 	}
 	for {
 		select {
 		case <-сам.кнт.Done():
 			return
 		case <-сам.ВремяОстат().КаналСиг():
+			фнРабота()
 		default:
 			log.Printf("Шахта.пуск()\n")
 			фнРабота()
@@ -153,13 +160,34 @@ func (сам *Шахта) количествоПолучить() {
 		<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/ore.png?2" alt="ore"/>&nbsp;1</div></td>
 	*/
 	for ind, strOut = range lstMine {
-		// Руда
+		// Руда текущее
 		if strings.Contains(strOut, `src="/images/icons/ore.png?2" alt="ore"`) {
 			// <td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/ore.png?2" alt="ore"/>&nbsp;1</div></td>
 			еслиНайдено = true
 			режим = "руда"
 			break
 		}
+		// Железо текущее
+		if strings.Contains(strOut, `src="/images/icons/iron.png?2" alt="iron"`) {
+			// <td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/iron.png?2" alt="iron"/>&nbsp;2</div></td>
+			еслиНайдено = true
+			режим = "железо"
+			break
+		}
+		// Сталь текущее
+		if strings.Contains(strOut, `src="/images/icons/steel.png?2" alt="steel"`) {
+			// <td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/steel.png?2" alt="iron"/>&nbsp;2</div></td>
+			еслиНайдено = true
+			режим = "сталь"
+			break
+		}
+		// Свинец текущее
+		if strings.Contains(strOut, `src="/images/icons/plumbum.png?2" alt="plumbum"`) {
+			// <td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/plumbum.png?2" alt="iron"/>&nbsp;2</div></td>
+			еслиНайдено = true
+			режим = "свинец"
+			break
+		}
 	}
 	if !еслиНайдено {
 		return
@@ -175,7 +203,39 @@ func (сам *Шахта) количествоПолучить() {
 		}
 		сам.продуктКол.Уст(iNum)
 		сам.продуктИмя = "руда"
+	case "железо":
+		_число := strings.TrimPrefix(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/iron.png?2" alt="iron"/>&nbsp;`)
+		_число = strings.TrimSuffix(_число, `</div></td>`)
+		iNum, err := strconv.Atoi(_число)
+		if err != nil {
+			log.Printf("Шахта.количествоПолучить(): кол-во железа (%v) не число, err=\n\t%v\n", _число, err)
+			return
+		}
+		сам.продуктКол.Уст(iNum)
+		сам.продуктИмя = "железо"
+	case "сталь":
+		_число := strings.TrimPrefix(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/steel.png?2" alt="steel"/>&nbsp;`)
+		_число = strings.TrimSuffix(_число, `</div></td>`)
+		iNum, err := strconv.Atoi(_число)
+		if err != nil {
+			log.Printf("Шахта.количествоПолучить(): кол-во стали (%v) не число, err=\n\t%v\n", _число, err)
+			return
+		}
+		сам.продуктКол.Уст(iNum)
+		сам.продуктИмя = "сталь"
+	case "свинец":
+		_число := strings.TrimPrefix(strOut, `<td class="vam"><div class="nwr pr5 gray1"><img class="rico vm" src="/images/icons/plumbum.png?2" alt="plumbum"/>&nbsp;`)
+		_число = strings.TrimSuffix(_число, `</div></td>`)
+		iNum, err := strconv.Atoi(_число)
+		if err != nil {
+			log.Printf("Шахта.количествоПолучить(): кол-во свинца (%v) не число, err=\n\t%v\n", _число, err)
+			return
+		}
+		сам.продуктКол.Уст(iNum)
+		сам.продуктИмя = "свинец"
 	default:
+		log.Printf("Шахта.количествоПолучить(): неизвестный режим (%v)\n", режим)
+		return
 	}
 	// <td><div class="value-block lh1"><span><span>00:00:34</span></span></div></td>
 	strTime := lstMine[ind+3]
@@ -364,11 +424,11 @@ func (сам *Шахта) проапгрейдить() bool {
 		defer time.Sleep(time.Millisecond * 1000)
 		списСтр, ош = сам.сеть.Клиент().Get("https://wartank.ru/building-upgrade/Mine")
 		if ош != nil {
-			log.Printf("Шахта.проапгрейдить().фнКупить(): при GET-команде 'купить постройку склада топлива', err=\n\t%v\n", ош)
+			log.Printf("Шахта.проапгрейдить().фнКупить(): при GET-команде 'купить постройку шахты', err=\n\t%v\n", ош)
 			return false
 		}
 		for _, стр = range списСтр {
-			// <a class="simple-but border mb5" href="FuelStorage?5-1.ILinkListener-upgradeLink-link">
+			// <a class="simple-but border mb5" href="Mine?5-1.ILinkListener-upgradeLink-link">
 			if strings.Contains(стр, `ILinkListener-upgradeLink-link`) {
 				еслиНайти = true
 				break
@@ -385,17 +445,17 @@ func (сам *Шахта) проапгрейдить() bool {
 		ссылка := "https://wartank.ru/building-upgrade/" + _стр
 		списСтр, ош = сам.сеть.Клиент().Get(ссылка)
 		if ош != nil {
-			log.Printf("Шахта.проапгрейдить().фнКупить(): при GET-команде 'купить постройку склада топлива', err=\n\t%v\n", ош)
+			log.Printf("Шахта.проапгрейдить().фнКупить(): при GET-команде 'купить постройку шахты', err=\n\t%v\n", ош)
 			return false
 		}
 		// Проверить, что постройка состоялась
 		for _, стр := range списСтр {
 			if strings.Contains(стр, "ILinkListener-upgradeLink-link") {
-				log.Printf("Шахта.проапгрейдить().фнКупить(): покупка склада топлива не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
+				log.Printf("Шахта.проапгрейдить().фнКупить(): покупка шахты не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
 				return false // Покупка не оплачена
 			}
 		}
-		log.Printf("+++++Шахта.проапгрейдить().фнКупить(): покупка склада топлива прошла\n")
+		log.Printf("+++++Шахта.проапгрейдить().фнКупить(): покупка шахты прошла\n")
 		return true
 	}
 
@@ -417,17 +477,17 @@ func (сам *Шахта) проапгрейдить() bool {
 		ссылка := "https://wartank.ru" + _стр
 		списСтр, ош = сам.сеть.Клиент().Get(ссылка)
 		if ош != nil {
-			log.Printf("СкладТоплива.проапгрейдить().фнПодтверждение(): при GET-команде 'подтвердить постройку склада топлива', err=\n\t%v\n", ош)
+			log.Printf("Шахта.проапгрейдить().фнПодтверждение(): при GET-команде 'подтвердить постройку шахты', err=\n\t%v\n", ош)
 			return false
 		}
 		// Проверить, что постройка состоялась
 		for _, стр := range списСтр {
 			if strings.Contains(стр, "<title>Вы сделали слишком большую паузу</title>") {
-				log.Printf("СкладТоплива.проапгрейдить().фнПодтверждение(): подтверждение покупка склада топлива не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
+				log.Printf("Шахта.проапгрейдить().фнПодтверждение(): подтверждение покупка шахты не прошла\n\tlink=%v\n\tстр=\n\t%v\n", ссылка, стр)
 				return false // Покупка не оплачена
 			}
 		}
-		log.Printf("+++++СкладТоплива.проапгрейдить().фнПодтверждение(): подтверждение покупка склада топлива прошла\n")
+		log.Printf("+++++Шахта.проапгрейдить().фнПодтверждение(): подтверждение покупка шахты прошла\n")
 		return true
 	}
 
@@ -507,7 +567,7 @@ func (сам *Шахта) ПродуктИмяСейчас() string {
 
 // ПродуктВремяСейчас -- сколько осталось времени до производства продукта
 func (сам *Шахта) ПродуктВремяСейчас() string {
-	сам.количествоПолучить()
+	// сам.количествоПолучить()
 	return сам.продуктВремя
 	// return сам.Секция.ВремяОпрос().Стр()
 }

+ 6 - 1
server/serv_bots/warbot/angar/base/polygon/polygon.go

@@ -101,7 +101,12 @@ const (
 func (сам *Полигон) пуск() {
 	сам.ОбратВремяУст("02")
 	фнРабота := func() {
-		defer time.Sleep(time.Minute * 20)
+		defer func() {
+			for сам.ВремяОстат().ПолучМилСек() > 0 {
+				time.Sleep(time.Second * 5)
+				continue
+			}
+		}()
 		сам.усилениеДобавить()
 		сам.усилениеПровер()
 		сам.времяОбнов()

+ 5 - 11
server/serv_bots/warbot/angar/fuel/fuel.go

@@ -12,7 +12,7 @@ import (
 	"wartank/server/serv_bots/warbot/tank/tankstat/static_param"
 )
 
-// Топливо -- топливо в баке
+// Топливо -- топливо в баке, +1 каждые 15 сек
 type Топливо struct {
 	ангар   types.ИАнгар
 	топливо types.ИСтатПарам
@@ -36,18 +36,12 @@ func НовТопливо(ангар types.ИАнгар) (*Топливо, error
 
 // Run -- должен работать в отдельном потоке, контролит топливо
 func (сам *Топливо) Run() {
-	count := 0 // Каждые 1500 сек (100 топлива проверять принудительно)
+	// Каждые 15 сек +1 топливо
 	for {
 		time.Sleep(time.Second * 15)
-		if сам.топливо.Получ() < 314 {
-			сам.Обновить()
-			count = 0
-			continue
-		}
-		count++
-		fuel := сам.топливо.Получ()
-		fuel++
-		сам.топливо.Уст(fuel)
+		топливо := сам.топливо.Получ()
+		топливо++
+		сам.топливо.Уст(топливо)
 		// log.Printf("Fuel.Run: val=%v\n", fuel)
 	}
 }

+ 134 - 0
server/serv_bots/warbot/angar/tank_params/tank_params.go

@@ -0,0 +1,134 @@
+// package tank_params -- параметры танка повышение
+package tank_params
+
+import (
+	"fmt"
+	"log"
+	"strconv"
+	"strings"
+	"time"
+
+	"wartank/pkg/types"
+)
+
+// ТанкПараметры -- параметры танка повышение
+type ТанкПараметры struct {
+	бот   types.ИБот
+	номер string // Номер танка в игре
+}
+
+// НовТанкПараметры -- возвращает новые параметры танка
+func НовТанкПараметры(бот types.ИБот) (*ТанкПараметры, error) {
+	if бот == nil {
+		return nil, fmt.Errorf("НовТанкПараметры(): ИБот == nil")
+	}
+	сам := &ТанкПараметры{
+		бот: бот,
+	}
+	return сам, nil
+}
+
+// Пуск -- запуск в работу
+func (сам *ТанкПараметры) Пуск() {
+	go сам.пуск()
+}
+
+// Запукает в работу в отдельном потоке
+func (сам *ТанкПараметры) пуск() {
+	time.Sleep(time.Second * 4)
+	сам.номерПолуч()
+	for {
+		select {
+		case <-сам.бот.Кнт().Done():
+			return
+		default:
+			сам.работать()
+		}
+	}
+}
+
+// Основной метод работы
+func (сам *ТанкПараметры) работать() {
+	defer time.Sleep(time.Second * 300)
+	if ош := сам.улучшить(); ош != nil {
+		log.Printf("ТанкПараметры.работать(): ош=\n\t%v\n", ош)
+		return
+	}
+}
+
+// Улучшает параметры танка
+func (сам *ТанкПараметры) улучшить() error {
+	// https://wartank.ru/pimp/34479487
+	клиент := сам.бот.Сеть().КлиентСеть()
+	фнУлучшить := func() error {
+		лстСтр, ош := клиент.Get("https://wartank.ru/pimp/" + сам.номер)
+		if ош != nil {
+			return fmt.Errorf("ТанкПараметры.улучшить(): при получении страницы улучшения танка с сервера, ош=\n\t%w", ош)
+		}
+		var (
+			стрВых    string
+			еслиНашли bool
+		)
+		// <a class="simple-but border mb5" href="34479487?22-1.ILinkListener-modules-slots-0-slot-root-pimpLink-link">
+		for _, стрВых = range лстСтр {
+			if strings.Contains(стрВых, `<a class="simple-but border mb5" href="`+сам.номер) {
+				еслиНашли = true
+				break
+			}
+		}
+		if !еслиНашли {
+			return fmt.Errorf("ТанкПараметры.улучшить(): не нашёл кнопку улучшения")
+		}
+		стрВых = strings.TrimPrefix(стрВых, `<a class="simple-but border mb5" href="`)
+		стрВых = strings.TrimSuffix(стрВых, `">`)
+		// https://wartank.ru/pimp/34479487?21-1.ILinkListener-modules-slots-0-slot-root-pimpLink-link
+		стрСсыль := "https://wartank.ru/pimp/" + стрВых
+		_, ош = клиент.Get(стрСсыль)
+		if ош != nil {
+			return fmt.Errorf("ТанкПараметры.улучшить(): при улучшении танка с сервера, ош=\n\t%w", ош)
+		}
+		return nil
+	}
+	счётОш := 5
+	for счётОш > 0 {
+		ош := фнУлучшить()
+		if ош == nil {
+			return nil
+		}
+		log.Printf("ТанкПараметры.улучшить(): ош=\n\t%v\n", ош)
+		счётОш--
+		time.Sleep(time.Millisecond * 350)
+	}
+	return nil
+}
+
+// Получает собственный номер танка с сервера
+func (сам *ТанкПараметры) номерПолуч() error {
+	клиент := сам.бот.Сеть().КлиентСеть()
+	лстСтр, ош := клиент.Get("https://wartank.ru/angar")
+	if ош != nil {
+		return fmt.Errorf("ТанкПараметры.номерПолуч(): при получении страницы ангара с сервера, ош=\n\t%w", ош)
+	}
+	var (
+		стрНомер  string
+		еслиНашёл bool
+	)
+	// https://wartank.ru/power/34479487
+	for _, стрНомер = range лстСтр {
+		if strings.Contains(стрНомер, `href="power/`) {
+			еслиНашёл = true
+			break
+		}
+	}
+	if !еслиНашёл {
+		return fmt.Errorf("ТанкПараметры.номерПолуч(): не нашёл собственный номер на сервере")
+	}
+	стрВыход := strings.TrimPrefix(стрНомер, `<a class="simple-but border" href="power/`)
+	стрВыход = strings.TrimSuffix(стрВыход, `"><span><span>Повысить параметры</span></span></a>`)
+	_, ош = strconv.Atoi(стрВыход)
+	if ош != nil {
+		return fmt.Errorf("ТанкПараметры.номерПолуч(): ошибка преобразования собственного номера(%s), ош=\n\t%w", стрВыход, ош)
+	}
+	сам.номер = стрВыход
+	return nil
+}

+ 21 - 12
server/serv_bots/warbot/warbot.go

@@ -8,6 +8,7 @@ import (
 	"strings"
 	"time"
 
+	"wartank/pkg/alias"
 	"wartank/pkg/components/safe_bool"
 	"wartank/pkg/types"
 	"wartank/server/serv_bots/warbot/angar"
@@ -32,31 +33,32 @@ type ВарБот struct {
 }
 
 // ЗагрузитьВарБот -- загружает бота из базы
-func ЗагрузитьВарБот(сервер types.ИСервер, логин string) (*ВарБот, error) {
+func ЗагрузитьВарБот(сервер types.ИСервер, номер alias.БотНомер) (*ВарБот, error) {
 	{ // Предусловия
 		if сервер == nil {
 			return nil, fmt.Errorf("ЗагрузитьВарБот(): IApp is nil")
 		}
-		if логин == "" {
-			return nil, fmt.Errorf("ЗагрузитьВарБот(): name is empty")
+		if номер == 0 {
+			return nil, fmt.Errorf("ЗагрузитьВарБот(): номер пустой")
 		}
 	}
-	log.Printf("ЗагрузитьВарБот(): name=%q\n", логин)
+	стрНомер := fmt.Sprint(номер)
+	log.Printf("ЗагрузитьВарБот(): номер=%q\n", стрНомер)
 	store := сервер.Store()
-	binData, err := store.Get("/bots/" + логин)
+	binData, err := store.Get("/bots/" + стрНомер)
 	if err != nil {
 		if !strings.Contains(err.Error(), "not found") {
-			return nil, fmt.Errorf("ЗагрузитьВарБот(): in load bot %q from store, err=\n\t%w", логин, err)
+			return nil, fmt.Errorf("ЗагрузитьВарБот(): in load bot '%v' from store, err=\n\t%w", номер, err)
 		}
-		return nil, fmt.Errorf("ЗагрузитьВарБот(): bot %q not found in store", логин)
+		return nil, fmt.Errorf("ЗагрузитьВарБот(): bot '%v' not found in store", номер)
 	}
 	конфиг := &warbot_config.ВарБотКонфиг{}
 	if err = конфиг.Unmarshall(binData); err != nil {
-		return nil, fmt.Errorf("ЗагрузитьВарБот(): in unmarshall WarBotConfig(%q) from store, err=\n\t%w", логин, err)
+		return nil, fmt.Errorf("ЗагрузитьВарБот(): in unmarshall WarBotConfig(%v) from store, err=\n\t%w", номер, err)
 	}
 	сам, err := создатьЯдроВарБот(сервер, конфиг)
 	if err != nil {
-		return nil, fmt.Errorf("ЗагрузитьВарБот(): in make core for bot %q, err=\n\t%w", логин, err)
+		return nil, fmt.Errorf("ЗагрузитьВарБот(): in make core for bot '%v', err=\n\t%w", номер, err)
 	}
 	// go сам.рестарт()
 	return сам, nil
@@ -66,7 +68,7 @@ func ЗагрузитьВарБот(сервер types.ИСервер, логи
 func (сам *ВарБот) рестарт() {
 	time.Sleep(time.Hour * 2)
 	сам.Закончить()
-	сам, ош := ЗагрузитьВарБот(сам.сервер, сам.конфиг.Логин_)
+	сам, ош := ЗагрузитьВарБот(сам.сервер, сам.конфиг.Номер_)
 	if ош != nil {
 		panic(fmt.Errorf("ВарБот.рестарт(): при загрузке своего тела, ош=\n\t%w", ош))
 	}
@@ -74,7 +76,7 @@ func (сам *ВарБот) рестарт() {
 }
 
 // НовВарБот -- возвращает новый WarBot
-func НовВарБот(сервер types.ИСервер, логин, пароль string, еслиАвто bool) (*ВарБот, error) {
+func НовВарБот(сервер types.ИСервер, номер alias.БотНомер, логин, пароль string, еслиАвто bool) (*ВарБот, error) {
 	{ // Предусловия
 		if сервер == nil {
 			return nil, fmt.Errorf("НовВарБот(): IApp is nil")
@@ -91,6 +93,7 @@ func НовВарБот(сервер types.ИСервер, логин, паро
 		ЕслиАвтозапуск_: еслиАвто,
 		Логин_:          логин,
 		Пароль_:         пароль,
+		Номер_:          номер,
 	}
 	сам, err := создатьЯдроВарБот(сервер, config)
 	if err != nil {
@@ -155,6 +158,11 @@ func (сам *ВарБот) ЕслиПуск() bool {
 	return сам.еслиРаботает.Получ()
 }
 
+// Номер -- возвращает номер бота
+func (сам *ВарБот) Номер() alias.БотНомер {
+	return сам.конфиг.Номер()
+}
+
 // Имя -- возвращает имя бота
 func (сам *ВарБот) Имя() string {
 	return сам.конфиг.Логин()
@@ -240,7 +248,8 @@ func (сам *ВарБот) Закончить() {
 
 // Сохраняет себя в базу
 func (сам *ВарБот) сохр() error {
-	err := сам.store.Put("/bots/"+сам.Имя(), сам.конфиг.Marshall())
+	стрНомер := fmt.Sprint(сам.Номер())
+	err := сам.store.Put("/bots/"+стрНомер, сам.конфиг.Marshall())
 	if err != nil {
 		return fmt.Errorf("WarBot.save(): in self save to store bot(%q), err=\n\t%w", сам.Имя(), err)
 	}

+ 12 - 3
server/serv_bots/warbot/warbot_config/warbot_config.go

@@ -5,13 +5,15 @@ import (
 	"encoding/json"
 	"fmt"
 	"sync"
+	"wartank/pkg/alias"
 )
 
 // ВарБотКонфиг -- конфиг бота для хранения в базе
 type ВарБотКонфиг struct {
-	ЕслиАвтозапуск_ bool   `json:"is_auto_run,omitempty"` // Признак автостарта при загрузке
-	Логин_          string `json:"login"`                 // Логин бота
-	Пароль_         string `json:"password"`              // Пароль бота
+	ЕслиАвтозапуск_ bool           `json:"is_auto_run,omitempty"` // Признак автостарта при загрузке
+	Логин_          string         `json:"login"`                 // Логин бота
+	Пароль_         string         `json:"password"`              // Пароль бота
+	Номер_          alias.БотНомер `json:"number"`                // Номер бота
 	блок            sync.RWMutex
 }
 
@@ -36,3 +38,10 @@ func (сам *ВарБотКонфиг) Логин() string {
 	defer сам.блок.RUnlock()
 	return сам.Логин_
 }
+
+// Номер -- возвращает номер бота
+func (сам *ВарБотКонфиг) Номер() alias.БотНомер {
+	сам.блок.RLock()
+	defer сам.блок.RUnlock()
+	return сам.Номер_
+}

+ 5 - 14
server/serv_web/serv_web.go

@@ -11,6 +11,7 @@ import (
 	"github.com/gofiber/fiber/v2/middleware/compress"
 	"github.com/gofiber/template/html/v2"
 
+	"wartank/pkg/alias"
 	"wartank/pkg/types"
 	"wartank/server/serv_web/web_api"
 	"wartank/server/serv_web/web_gui"
@@ -145,7 +146,7 @@ func (сам *СервВеб) Пуск() {
 }
 
 type ПостБотСтат struct {
-	Логин string `form:"name"`
+	Номер alias.БотНомер `form:"bot_number"`
 }
 
 // постБотСтат -- возвращает статистику бота
@@ -157,22 +158,12 @@ func (сам *СервВеб) постБотСтат(кнт *fiber.Ctx) error {
 			"error": "СервВеб.постБотСтат(): при парсинге тела запроса, ош=\n\t%" + err.Error(),
 		})
 	}
-	if ботСт.Логин == "" {
-		return кнт.JSON(fiber.Map{
-			"error": "СервВеб.постБотСтат(): пустой логин",
-		})
-	}
-	имя := ботСт.Логин
-	if имя == "" {
-		return кнт.JSON(fiber.Map{
-			"error": "СервВеб.постБотСтат(): пустое имя бота",
-		})
-	}
+	номер := ботСт.Номер
 	ботоФерма := сам.серв.ServBots()
-	бот := ботоФерма.Get(имя)
+	бот := ботоФерма.Get(номер)
 	if бот == nil {
 		return кнт.JSON(fiber.Map{
-			"error": fmt.Sprintf("СервВеб.постБотСтат(): бот c имнем %q не существует", имя),
+			"error": fmt.Sprintf("СервВеб.постБотСтат(): бот c имнем %q не существует", номер),
 		})
 	}
 	диктБот := map[string]string{}

+ 351 - 0
server/serv_web/web_api/web_api.go

@@ -8,6 +8,7 @@ import (
 
 	"github.com/gofiber/fiber/v2"
 
+	"wartank/pkg/alias"
 	"wartank/pkg/types"
 )
 
@@ -28,9 +29,359 @@ func НовВебАпи(вебСервер types.ИВебСервер) (*Веб
 	}
 	сам.файбер.Post("/api/login", сам.логин)
 	сам.файбер.Post("/api/add_bot", сам.добавитьБота)
+	сам.файбер.Get("/api/uptime", сам.аптаймСервер)
+	сам.файбер.Get("/api/count_start", сам.стартНомер)
+
+	сам.файбер.Get("/api/bot/:number/mine/level", сам.шахтаУровень)
+	сам.файбер.Get("/api/bot/:number/mine/mode", сам.шахтаРежим)
+	сам.файбер.Get("/api/bot/:number/mine/count_product", сам.шахтаРаботаКоличество)
+	сам.файбер.Get("/api/bot/:number/mine/name_product", сам.шахтаРаботаИмя)
+	сам.файбер.Get("/api/bot/:number/mine/back_time", сам.шахтаВремяОсталось)
+
+	сам.файбер.Get("/api/bot/:number/tank/fuel", сам.танкТопливо)
+	сам.файбер.Get("/api/bot/:number/angar/silver", сам.ангарСеребро)
+
+	сам.файбер.Get("/api/bot/:number/polygon/level", сам.полигонУровень)
+	сам.файбер.Get("/api/bot/:number/polygon/mode", сам.полигонРежим)
+	сам.файбер.Get("/api/bot/:number/polygon/count_product", сам.полигонРаботаКоличество)
+	сам.файбер.Get("/api/bot/:number/polygon/name_product", сам.полигонРаботаИмя)
+	сам.файбер.Get("/api/bot/:number/polygon/back_time", сам.полигонВремяОсталось)
+
+	сам.файбер.Get("/api/bot/:number/arsenal/level", сам.арсеналУровень)
+	сам.файбер.Get("/api/bot/:number/arsenal/mode", сам.арсеналРежим)
+	сам.файбер.Get("/api/bot/:number/arsenal/count_product", сам.полигонРаботаКоличество)
+	сам.файбер.Get("/api/bot/:number/arsenal/name_product", сам.арсеналРаботаИмя)
+	сам.файбер.Get("/api/bot/:number/arsenal/back_time", сам.ареналВремяОсталось)
 	return сам, nil
 }
 
+// Возвращает имя производства снаряда на оружейке
+func (сам *ВебАпи) арсеналРаботаИмя(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Тип: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Тип: нет такого бота]")
+	}
+	имя := бот.Ангар().База().Арсенал().ПродуктИмяСейчас()
+	if имя == "" {
+		return кнт.SendString("[Тип: пустое имя]")
+	}
+	return кнт.SendString("[Тип: " + имя + "]")
+}
+
+// Возвращает режим оружейки
+func (сам *ВебАпи) арсеналРежим(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Режим: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Режим: нет такого бота]")
+	}
+	сценаРежим := бот.Ангар().База().Арсенал().СценаРежим()
+	стрРежим := fmt.Sprint(сценаРежим.Режим())
+	if стрРежим == "" {
+		return кнт.SendString("[Режим: пустой режим]")
+	}
+	return кнт.SendString("[Режим: " + стрРежим + "]")
+}
+
+// Возвращает уровень арсенала
+func (сам *ВебАпи) арсеналУровень(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Уровень: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Уровень: нет такого бота]")
+	}
+	уровень := бот.Ангар().База().Арсенал().Уровень()
+	стрУровень := fmt.Sprint(уровень.Получ())
+	if стрУровень == "" {
+		return кнт.SendString("[Уровень: пустой уровень]")
+	}
+	return кнт.SendString("[Уровень: " + стрУровень + "]")
+}
+
+// Возвращает время, которое осталось на арсенале
+func (сам *ВебАпи) ареналВремяОсталось(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Время: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Время: нет такого бота]")
+	}
+	время := бот.Ангар().База().Арсенал().ВремяОстат()
+	стрВремя := время.String()
+	if стрВремя == "" {
+		return кнт.SendString("[Время: пустой остаток времени]")
+	}
+	return кнт.SendString("[Время: " + стрВремя + "]")
+}
+
+// Возвращает время, которое осталось на полигоне
+func (сам *ВебАпи) полигонВремяОсталось(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Время: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Время: нет такого бота]")
+	}
+	время := бот.Ангар().База().Полигон().ВремяОстат()
+	стрВремя := время.String()
+	if стрВремя == "" {
+		return кнт.SendString("[Время: пустой остаток времени]")
+	}
+	return кнт.SendString("[Время: " + стрВремя + "]")
+}
+
+// Возвращает имя добычи ресурса на полигоне
+func (сам *ВебАпи) полигонРаботаИмя(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Тип: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Тип: нет такого бота]")
+	}
+	имя := бот.Ангар().База().Полигон().ПродуктИмяСейчас()
+	if имя == "" {
+		return кнт.SendString("[Тип: пустое имя]")
+	}
+	return кнт.SendString("[Тип: " + имя + "]")
+}
+
+// Возвращает количесто добычи на полигоне
+func (сам *ВебАпи) полигонРаботаКоличество(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Кол: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Кол: нет такого бота]")
+	}
+	колич := бот.Ангар().База().Полигон().ПродуктКолСейчас()
+	стрКолич := fmt.Sprint(колич)
+	if стрКолич == "" {
+		return кнт.SendString("[Кол: пустое кол]")
+	}
+	return кнт.SendString("[Кол: +" + стрКолич + "]")
+}
+
+// Возвращает режим полигона
+func (сам *ВебАпи) полигонРежим(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Режим: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Режим: нет такого бота]")
+	}
+	сценаРежим := бот.Ангар().База().Полигон().СценаРежим()
+	стрРежим := fmt.Sprint(сценаРежим.Режим())
+	if стрРежим == "" {
+		return кнт.SendString("[Режим: пустой режим]")
+	}
+	return кнт.SendString("[Режим: " + стрРежим + "]")
+}
+
+// Возвращает уровень полигона
+func (сам *ВебАпи) полигонУровень(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Уровень: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Уровень: нет такого бота]")
+	}
+	уровень := бот.Ангар().База().Полигон().Уровень()
+	стрУровень := fmt.Sprint(уровень.Получ())
+	if стрУровень == "" {
+		return кнт.SendString("[Уровень: пустой уровень]")
+	}
+	return кнт.SendString("[Уровень: " + стрУровень + "]")
+}
+
+// Возвращает количество серебра в ангаре
+func (сам *ВебАпи) ангарСеребро(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Серебро: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Серебро: нет такого бота]")
+	}
+	серебро := бот.Ангар().СереброВсего().Получ()
+	if серебро == 0 {
+		return кнт.SendString("[Серебро: пустое кол]")
+	}
+	стрСеребро := fmt.Sprint(серебро)
+	return кнт.SendString("[Серебро: " + стрСеребро + "]")
+}
+
+// Возвращает количество топлива в танке
+func (сам *ВебАпи) танкТопливо(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Топливо: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Топливо: нет такого бота]")
+	}
+	топливо := бот.Ангар().Топливо().Получ()
+	if топливо == 0 {
+		return кнт.SendString("[Топливо: пустое кол]")
+	}
+	стрТопливо := fmt.Sprint(топливо)
+	return кнт.SendString("[Топливо: " + стрТопливо + "]")
+}
+
+// Возвращает имя добычи ресурса в шахте
+func (сам *ВебАпи) шахтаРаботаИмя(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Тип: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Тип: нет такого бота]")
+	}
+	имя := бот.Ангар().База().Шахта().ПродуктИмяСейчас()
+	if имя == "" {
+		return кнт.SendString("[Тип: пустое имя]")
+	}
+	return кнт.SendString("[Тип: " + имя + "]")
+}
+
+// Возвращает количесто добычи в шахте
+func (сам *ВебАпи) шахтаРаботаКоличество(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Кол: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Кол: нет такого бота]")
+	}
+	колич := бот.Ангар().База().Шахта().ПродуктКолСейчас()
+	стрКолич := fmt.Sprint(колич)
+	if стрКолич == "" {
+		return кнт.SendString("[Кол: пустое кол]")
+	}
+	return кнт.SendString("[Кол: " + стрКолич + "]")
+}
+
+// Возвращает режим шахты
+func (сам *ВебАпи) шахтаРежим(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Режим: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Режим: нет такого бота]")
+	}
+	сценаРежим := бот.Ангар().База().Шахта().СценаРежим()
+	стрРежим := fmt.Sprint(сценаРежим.Режим())
+	if стрРежим == "" {
+		return кнт.SendString("[Режим: пустой режим]")
+	}
+	return кнт.SendString("[Режим: " + стрРежим + "]")
+}
+
+// Возвращает уровень шахты
+func (сам *ВебАпи) шахтаУровень(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Уровень: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Уровень: нет такого бота]")
+	}
+	уровень := бот.Ангар().База().Шахта().Уровень()
+	стрУровень := fmt.Sprint(уровень.Получ())
+	if стрУровень == "" {
+		return кнт.SendString("[Уровень: пустой уровень]")
+	}
+	return кнт.SendString("[Уровень: " + стрУровень + "]")
+}
+
+// Возвращает время, которое осталось на шахте
+func (сам *ВебАпи) шахтаВремяОсталось(кнт *fiber.Ctx) error {
+	номер, ош := кнт.ParamsInt("number")
+	if ош != nil {
+		сообщ := fmt.Sprintf("[Время: неправильный номер бота(%q), err=%v]", номер, ош.Error())
+		return кнт.SendString(сообщ)
+	}
+	ботНомер := alias.БотНомер(номер)
+	бот := сам.серв.ServBots().Get(ботНомер)
+	if бот == nil {
+		return кнт.SendString("[Время: нет такого бота]")
+	}
+	время := бот.Ангар().База().Шахта().ВремяОстат()
+	стрВремя := время.String()
+	if стрВремя == "" {
+		return кнт.SendString("[Время: пустой остаток времени]")
+	}
+	return кнт.SendString("[Время: " + стрВремя + "]")
+}
+
+// Возвращает счётчик запусков сервера
+func (сам *ВебАпи) стартНомер(кнт *fiber.Ctx) error {
+	return кнт.SendString("[Старт: " + fmt.Sprint(сам.серв.Стат().СчётСтарт()) + "]")
+}
+
+// Возвращает аптайм сервера
+func (сам *ВебАпи) аптаймСервер(кнт *fiber.Ctx) error {
+	return кнт.SendString("[Аптайм: " + сам.серв.Стат().ВремяВсего() + "]")
+}
+
 type AddBotRequest struct {
 	Логин_ string `json:"login" form:"login_bot"`
 	Пароль string `json:"password" form:"password_bot"`

+ 16 - 17
server/serv_web/web_gui/web_gui.go

@@ -4,10 +4,11 @@ package web_gui
 import (
 	"fmt"
 	"log"
-	"net/url"
+	"strconv"
 
 	"github.com/gofiber/fiber/v2"
 
+	"wartank/pkg/alias"
 	"wartank/pkg/types"
 )
 
@@ -29,37 +30,35 @@ func НовВебГуи(вебСервер types.ИВебСервер) (*Веб
 	сам.файбер.Get("/", сам.индекс)
 	сам.файбер.Get("/gui/list_bot", сам.списокБотов)
 	сам.файбер.Get("/gui/add_bot", сам.гетБотНов)
-	сам.файбер.Get("/gui/bot/:name/state", сам.состояниеБота)
+	сам.файбер.Get("/gui/bot/:number/state", сам.состояниеБота)
 	return сам, nil
 }
 
 // Показывает состояние бота по имени
 func (сам *ВебГуи) состояниеБота(кнт *fiber.Ctx) error {
-	имя := кнт.Params("name")
-	имя, ош := url.QueryUnescape(имя)
+	стрНомер := кнт.Params("number")
+	иНомер, ош := strconv.Atoi(стрНомер)
 	if ош != nil {
 		return кнт.Render("list_bot", fiber.Map{
 			"Title": "WarTank",
 			"err":   fmt.Sprintf("ВебГуи.состояниеБота(): ошибка декодирования имени бота: %v", ош.Error()),
 		})
 	}
-	if имя == "" {
-		return кнт.Render("list_bot", fiber.Map{
-			"Title": "WarTank",
-			"err":   "Не задано имя бота",
-		})
-	}
-	log.Printf("ВебГуи.состояниеБота(): имя=%s\n", имя)
-	бот := сам.серв.ServBots().Get(имя)
+	ботНомер := alias.БотНомер(иНомер)
+	log.Printf("ВебГуи.состояниеБота(): имя=%s\n", стрНомер)
+	бот := сам.серв.ServBots().Get(ботНомер)
 	if бот == nil {
 		return кнт.Render("list_bot", fiber.Map{
 			"Title": "WarTank",
 			"err":   "Бот не найден",
 		})
 	}
+	уровень := бот.Ангар().База().Шахта().Уровень()
+	стрУровень := fmt.Sprint(уровень.Получ())
 	return кнт.Render("state_bot", fiber.Map{
 		"Title":   "WarTank",
-		"имя":     имя,
+		"имя":     бот.Имя(),
+		"number":  бот.Номер(),
 		"топливо": бот.Ангар().Топливо().Получ(),
 		"золото":  бот.Ангар().Золото().Получ(),
 		"серебро": бот.Ангар().СереброВсего().Получ(),
@@ -71,7 +70,7 @@ func (сам *ВебГуи) состояниеБота(кнт *fiber.Ctx) error
 		"прочность": бот.Танк().ТанкСтат().Прочность().Получ(),
 		"мощь":      бот.Танк().ТанкСтат().Мощь().Получ(),
 
-		"шахта_уровень":       бот.Ангар().База().Шахта().Уровень().Получ(),
+		"шахта_уровень":       стрУровень,
 		"шахта_режим":         бот.Ангар().База().Шахта().СценаРежим().Режим(),
 		"шахта_сделать_кол":   бот.Ангар().База().Шахта().ПродуктКолСейчас(),
 		"шахта_сделать_назв":  бот.Ангар().База().Шахта().ПродуктИмяСейчас(),
@@ -117,13 +116,13 @@ func (сам *ВебГуи) списокБотов(кнт *fiber.Ctx) error {
 	}
 	log.Printf("ВебГуи.списокБотов(): логин=%s\n", имя)
 	списокБотов := сам.серв.ServBots().ListBot()
-	списокИмёнБотов := make([]string, 0)
+	списокБот := make(map[alias.БотНомер]string, 0)
 	for _, бот := range списокБотов {
-		списокИмёнБотов = append(списокИмёнБотов, бот.Имя())
+		списокБот[бот.Номер()] = бот.Имя()
 	}
 	return кнт.Render("list_bot", fiber.Map{
 		"Title": "WarTank",
-		"bots":  списокИмёнБотов,
+		"bots":  списокБот,
 	})
 }
 

+ 11 - 6
server/server.go

@@ -8,6 +8,7 @@ import (
 	"wartank/pkg/types"
 	"wartank/server/serv_bots"
 	"wartank/server/serv_web"
+	"wartank/server/server_stat"
 )
 
 // Сервер -- главный тип приложения
@@ -15,6 +16,7 @@ type Сервер struct {
 	types.ИЯдро
 	ботоФерма *serv_bots.БотоФерма
 	сервВеб   *serv_web.СервВеб
+	сервСтат  types.ИСерверСтат
 }
 
 // НовСервер -- возвращает новый объект приложения
@@ -30,16 +32,14 @@ func НовСервер() (*Сервер, error) {
 	if ош != nil {
 		return nil, fmt.Errorf("НовСервер(): in create ServBots, err=\n\t%w", ош)
 	}
-	/*
-		сам.gui, err = serv_desktop.НовСерверДесктоп(сам)
-		if err != nil {
-			return nil, fmt.Errorf("НовСервер(): in make Gui, err=\n\t%w", err)
-		}
-	*/
 	сам.сервВеб, ош = serv_web.НовСервВеб(сам)
 	if ош != nil {
 		return nil, fmt.Errorf("НовСервер(): при создании СервВеб, ош=\n\t%w", ош)
 	}
+	сам.сервСтат, ош = server_stat.НовСерверСтат(сам)
+	if ош != nil {
+		return nil, fmt.Errorf("НовСервер(): при создании ИСервСтат, ош=\n\t%w", ош)
+	}
 	return сам, nil
 }
 
@@ -50,6 +50,11 @@ func (сам *Сервер) Run() error {
 	return nil
 }
 
+// Стат -- возвращает статистику сервера
+func (сам *Сервер) Стат() types.ИСерверСтат {
+	return сам.сервСтат
+}
+
 // ServBots -- возвращает словарь ботов
 func (сам *Сервер) ServBots() types.ИБотоФерма {
 	return сам.ботоФерма

+ 108 - 0
server/server_stat/server_stat.go

@@ -0,0 +1,108 @@
+// package server_stat -- глобальная статистика сервера
+package server_stat
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+	"time"
+
+	"wartank/pkg/types"
+)
+
+// СерверСтат -- структура статистики сервера
+type СерверСтат struct {
+	серв         types.ИСервер
+	CчётСтарт_   int           `json:"count_start"`  // Количество запусков
+	ВремяВсего_  time.Duration `json:"time_total"`   // Общее время работы в секундах
+	ВремяСессия_ time.Duration `json:"time_session"` // Время сессии в секундах
+}
+
+// НовСерверСтат -- возвращает структуру статистики сервера
+func НовСерверСтат(серв types.ИСервер) (*СерверСтат, error) {
+	if серв == nil {
+		return nil, fmt.Errorf("НовСерверСтат: ИСервер == nil")
+	}
+	сам := &СерверСтат{
+		серв:         серв,
+		CчётСтарт_:   0,
+		ВремяВсего_:  0,
+		ВремяСессия_: 0,
+	}
+	if ош := сам.загр(); ош != nil {
+		return nil, fmt.Errorf("НовСерверСтат: при загрузке статистики, ош=\n\t%w", ош)
+	}
+	сам.CчётСтарт_++
+	if ош := сам.сохр(); ош != nil {
+		return nil, fmt.Errorf("НовСерверСтат: при сохранении статистики, ош=\n\t%w", ош)
+	}
+	go сам.пуск()
+	return сам, nil
+}
+
+// Загружает статистику сервера
+func (сам *СерверСтат) загр() error {
+	бинДанные, ош := сам.серв.Store().Get("server_stat")
+	if ош != nil {
+		if strings.Contains(ош.Error(), "not found") {
+			return nil
+		}
+		return fmt.Errorf("СерверСтат.загр(): при загрузке статистики из хранилища, ош=\n\t%w", ош)
+	}
+	ош = json.Unmarshal(бинДанные, сам)
+	if ош != nil {
+		return fmt.Errorf("СерверСтат.загр(): при декодировании статистики из JSON, ош=\n\t%w", ош)
+	}
+	go сам.пуск()
+	return nil
+}
+
+// Работает в отдельном потоке, считает время работы и счетчик запусков
+func (сам *СерверСтат) пуск() {
+	фнПуск := func() {
+		сам.ВремяВсего_ += time.Second
+		сам.ВремяСессия_ += time.Second
+		if ош := сам.сохр(); ош != nil {
+			fmt.Printf("СерверСтат.пуск(): при сохранении статистики в хранилище, ош=\n\t%v\n", ош)
+			сам.серв.CancelApp()
+			return
+		}
+		time.Sleep(time.Second)
+	}
+	for {
+		select {
+		case <-сам.серв.Done():
+			return
+		default:
+			фнПуск()
+		}
+	}
+}
+
+// Сохраняет статистику сервера
+func (сам *СерверСтат) сохр() error {
+	бинДанные, ош := json.Marshal(сам)
+	if ош != nil {
+		return fmt.Errorf("СерверСтат.сохр(): при кодировании статистики в JSON, ош=\n\t%w", ош)
+	}
+	ош = сам.серв.Store().Put("server_stat", бинДанные)
+	if ош != nil {
+		return fmt.Errorf("СерверСтат.сохр(): при сохранении статистики в хранилище, ош=\n\t%w", ош)
+	}
+	return nil
+}
+
+// СчётСтарт -- счётчик запусков
+func (сам *СерверСтат) СчётСтарт() int {
+	return сам.CчётСтарт_
+}
+
+// ВремяСессия -- возвращает время сессии
+func (сам *СерверСтат) ВремяСессия() string {
+	return сам.ВремяСессия_.String()
+}
+
+// ВремяВсего -- возвращает общее время работы
+func (сам *СерверСтат) ВремяВсего() string {
+	return сам.ВремяВсего_.String()
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
web/static/js/htmx.min.js


+ 3 - 1
web/tmpl/footer.tmpl.html

@@ -2,7 +2,9 @@
 <br>
 <!-- Контейнер подвала -->
 <div class="container-fluid border rounded text-info bg-dark">
-FunnySoft 2023 (c)
+<span>FunnySoft 2023 (c)</span>
+<span hx-get="/api/uptime" hx-trigger="every 5s"></span>
+<span hx-get="/api/count_start" hx-trigger="every 30s"></span>
 </div>
 
 <!-- конец боди-контейнера -->

+ 3 - 1
web/tmpl/header.tmpl.html

@@ -9,9 +9,11 @@
         <meta pragma="no-cache">
         <!-- Bootstrap CSS -->
         <link href="/static/css/bootstrap.min.css" rel="stylesheet">
+        <!-- htmx -->
+        <script src="/static/js/htmx.min.js"></script>
     </head>
 
-    <body>
+    <body hx-boost="true">
         <div class="container-fluid"><!-- начало боди-контейнера -->
             <div class="container-fluid align-middle"><!-- контейнер заголовка -->
                 <div class="row border align-items-center">

+ 2 - 2
web/tmpl/list_bot.tmpl.html

@@ -2,9 +2,9 @@
 <!-- web/tmpl/list_bot.tmpl.html -->
 {{ template "header" . }}
 <h1>Страница списка ботов вартанк</h1>
-{{ range $key, $name := .bots }}
+{{ range $number, $name := .bots }}
 <div>
-    <a href="/gui/bot/{{ $name }}/state">{{ $name }}</a><br><br>
+    <a href="/gui/bot/{{ $number }}/state">{{ $name }}</a><br><br>
 </div>
 {{ else }}
 Нет ботов

+ 30 - 15
web/tmpl/state_bot.tmpl.html

@@ -10,8 +10,10 @@
         </div>
         <div class="card-body">
             <p class="card-text">Золото: {{.золото}}</p>
-            <p class="card-text">Серебро: {{.серебро}}</p>
-            <p class="card-text">Топливо: {{.топливо}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/angar/silver" hx-trigger="every 60s">Серебро: {{.серебро}}
+            </p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/tank/fuel" hx-trigger="every 15s">Топливо: {{.топливо}}
+            </p>
             <p class="card-text">Слава: {{.слава}}</p>
         </div>
     </div>
@@ -35,12 +37,17 @@
             <h3>Шахта</h3>
         </div>
         <div class="card-body">
-            <p class="card-text">Уровень: {{.шахта_уровень}}</p>
-            <p class="card-text">Режим: {{.шахта_режим}}</p>
-            <p class="card-text">Кол: {{.шахта_сделать_кол}}</p>
-            <p class="card-text">Тип: {{.шахта_сделать_назв}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/mine/level" hx-trigger="every 30s">[Уровень:
+                {{.шахта_уровень}}]</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/mine/mode" hx-trigger="every 30s">Режим: {{.шахта_режим}}
+            </p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/mine/count_product" hx-trigger="every 30s">Кол:
+                {{.шахта_сделать_кол}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/mine/name_product" hx-trigger="every 30s">Тип:
+                {{.шахта_сделать_назв}}</p>
         </div>
-        <div class="card-footer border-success">Время: {{.шахта_сделать_время}}</div>
+        <div class="card-footer border-success" hx-get="/api/bot/{{.number}}/mine/back_time/" hx-trigger="every 5s">
+            Время: {{.шахта_сделать_время}}</div>
     </div>
 
     <!-- статистика по полигону-->
@@ -49,12 +56,17 @@
             <h3>Полигон</h3>
         </div>
         <div class="card-body">
-            <p class="card-text">Уровень: {{.полигон_уровень}}</p>
-            <p class="card-text">Режим: {{.полигон_режим}}</p>
-            <p class="card-text">Кол: {{.полигон_сделать_кол}}</p>
-            <p class="card-text">Тип: {{.полигон_сделать_назв}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/polygon/level" hx-trigger="every 30s">Уровень:
+                {{.полигон_уровень}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/polygon/mode" hx-trigger="every 30s">Режим:
+                {{.полигон_режим}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/polygon/count_product" hx-trigger="every 30s">Кол:
+                {{.полигон_сделать_кол}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/polygon/name_product" hx-trigger="every 30s">Тип:
+                {{.полигон_сделать_назв}}</p>
         </div>
-        <div class="card-footer border-success">Время: {{.полигон_сделать_время}}</div>
+        <div class="card-footer border-success" hx-get="/api/bot/{{.number}}/polygon/back_time/" hx-trigger="every 5s">
+            Время: {{.полигон_сделать_время}}</div>
     </div>
 
     <!-- статистика по оружейной-->
@@ -63,12 +75,15 @@
             <h3>Оружейная</h3>
         </div>
         <div class="card-body">
-            <p class="card-text">Работа: {{.оружейная_работа}}</p>
-            <p class="card-text">Режим: {{.оружейная_режим}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/arsenal/level" hx-trigger="every 30s">Уровень:
+                {{.арсенал_уровень}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/arsenal/mode" hx-trigger="every 30s">Работа: {{.оружейная_работа}}</p>
+            <p class="card-text" hx-get="/api/bot/{{.number}}/arsenal/name_product" hx-trigger="every 30s">Тип: {{.оружейная_режим}}</p>
             <p class="card-text">Кумул: {{.оружейная_кумул}}</p>
             <p class="card-text">Ремки: {{.оружейная_ремки}}</p>
         </div>
-        <div class="card-footer border-success">Время: {{.оружейная_время}}</div>
+        <div class="card-footer border-success" hx-get="/api/bot/{{.number}}/arsenal/back_time/" hx-trigger="every 5s">
+            Время: {{.оружейная_время}}</div>
     </div>
 </div>
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно