Kaynağa Gözat

Добавление кода

SVI 3 yıl önce
ebeveyn
işleme
b99da90155
91 değiştirilmiş dosya ile 4536 ekleme ve 0 silme
  1. 1 0
      doc/index.md
  2. 11 0
      pkg/alias/alias.go
  3. 173 0
      pkg/components/counttime/counttime.go
  4. 256 0
      pkg/components/counttime/counttime_test.go
  5. 56 0
      pkg/components/kernel/keeper/keeper.go
  6. 85 0
      pkg/components/kernel/keeper/keeper_test.go
  7. 79 0
      pkg/components/kernel/kernel.go
  8. 43 0
      pkg/components/kernel/kernel_test.go
  9. 144 0
      pkg/components/kernel/slog/slog.go
  10. 110 0
      pkg/components/kernel/slog/slog_file/slog_file.go
  11. 110 0
      pkg/components/kernel/slog/slog_file/slog_file_test.go
  12. 28 0
      pkg/components/kernel/slog/slog_term/slog_term.go
  13. 12 0
      pkg/components/kernel/slog/slog_term/slog_term_test.go
  14. 84 0
      pkg/components/kernel/slog/slog_test.go
  15. 64 0
      pkg/components/kernel/wgname/wgname.go
  16. 88 0
      pkg/components/kernel/wgname/wgname_test.go
  17. 66 0
      pkg/components/lststring/lststring.go
  18. 77 0
      pkg/components/parsetime/parsehour/parsehour.go
  19. 117 0
      pkg/components/parsetime/parsehour/parsehour_test.go
  20. 77 0
      pkg/components/parsetime/parsemin/parsemin.go
  21. 112 0
      pkg/components/parsetime/parsemin/parsemin_test.go
  22. 77 0
      pkg/components/parsetime/parsesec/parsesec.go
  23. 139 0
      pkg/components/parsetime/parsesec/parsesec_test.go
  24. 105 0
      pkg/components/parsetime/parsetime.go
  25. 164 0
      pkg/components/parsetime/parsetime_test.go
  26. 39 0
      pkg/components/safebool/safebool.go
  27. 46 0
      pkg/components/safebool/safebool_test.go
  28. 32 0
      pkg/components/safeint/safeint.go
  29. 44 0
      pkg/components/safeint/safeint_test.go
  30. 32 0
      pkg/components/safestring/safestring.go
  31. 44 0
      pkg/components/safestring/safestring_test.go
  32. 61 0
      pkg/components/section/section.go
  33. 51 0
      pkg/components/section/sectionmode/sectionmode.go
  34. 145 0
      pkg/components/sectionnet/netclient/netclient.go
  35. 84 0
      pkg/components/sectionnet/netstat/netstat.go
  36. 88 0
      pkg/components/sectionnet/sectionnet.go
  37. 130 0
      pkg/components/sound/sound.go
  38. 67 0
      pkg/components/wrag/wrag.go
  39. 16 0
      pkg/cons/cons.go
  40. 77 0
      pkg/mock/mockapp/mockapp.go
  41. 146 0
      pkg/mock/mockenv/mockenv.go
  42. 256 0
      pkg/mock/mockenv/mockenv_test.go
  43. 62 0
      pkg/mock/mockkernel/mockkernel.go
  44. 51 0
      pkg/mock/mockkernel/mockkernel_test.go
  45. 10 0
      pkg/net_struct/net_arsenal.go
  46. 11 0
      pkg/net_struct/net_mine.go
  47. 10 0
      pkg/net_struct/net_resource.go
  48. 101 0
      pkg/store/store.go
  49. 36 0
      pkg/types/iangar.go
  50. 18 0
      pkg/types/iarsenal.go
  51. 16 0
      pkg/types/ibank.go
  52. 15 0
      pkg/types/ibankmode.go
  53. 22 0
      pkg/types/ibase.go
  54. 12 0
      pkg/types/ibattle.go
  55. 26 0
      pkg/types/ibattleon.go
  56. 12 0
      pkg/types/ibot.go
  57. 13 0
      pkg/types/ibot_cookie.go
  58. 22 0
      pkg/types/ibot_net.go
  59. 12 0
      pkg/types/iconvoy.go
  60. 25 0
      pkg/types/icounttime.go
  61. 14 0
      pkg/types/idesktop.go
  62. 7 0
      pkg/types/idict_bot.go
  63. 12 0
      pkg/types/idivwar.go
  64. 31 0
      pkg/types/idivwaron.go
  65. 15 0
      pkg/types/iguiweb.go
  66. 31 0
      pkg/types/ikernel.go
  67. 10 0
      pkg/types/imarket.go
  68. 20 0
      pkg/types/imine.go
  69. 10 0
      pkg/types/imissions.go
  70. 17 0
      pkg/types/imode.go
  71. 7 0
      pkg/types/inet_client.go
  72. 10 0
      pkg/types/inetangar.go
  73. 10 0
      pkg/types/inetbase.go
  74. 8 0
      pkg/types/inetmarket.go
  75. 9 0
      pkg/types/ipassword.go
  76. 10 0
      pkg/types/ipolygon.go
  77. 4 0
      pkg/types/iroot.go
  78. 17 0
      pkg/types/isection.go
  79. 13 0
      pkg/types/isectionnet.go
  80. 15 0
      pkg/types/iserv_bot.go
  81. 7 0
      pkg/types/iserv_bots.go
  82. 11 0
      pkg/types/iserv_web.go
  83. 18 0
      pkg/types/iserver.go
  84. 17 0
      pkg/types/islog.go
  85. 17 0
      pkg/types/istatparam.go
  86. 12 0
      pkg/types/istore.go
  87. 11 0
      pkg/types/itank.go
  88. 21 0
      pkg/types/itankstat.go
  89. 13 0
      pkg/types/iweb_socket.go
  90. 16 0
      pkg/types/iwin.go
  91. 13 0
      pkg/types/types_test.go

+ 1 - 0
doc/index.md

@@ -0,0 +1 @@
+# Документация wartank

+ 11 - 0
pkg/alias/alias.go

@@ -0,0 +1,11 @@
+package alias
+
+/*
+	Содержит алиасы типов, необходимые для работы.
+*/
+
+// AMilSec -- время в миллисекундах
+type AMilSec int
+
+// ADAmage -- урон от выстрела
+type ADamage int

+ 173 - 0
pkg/components/counttime/counttime.go

@@ -0,0 +1,173 @@
+package counttime
+
+import (
+	"fmt"
+	// "log"
+	"sync"
+	"time"
+
+	"wartank/pkg/components/parsetime"
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/components/safeint"
+	"wartank/pkg/types"
+)
+
+/*
+	Счётчик обратного временив мсек
+*/
+
+const (
+	iSleep = time.Millisecond * 100
+)
+
+// CountTime -- счётчик обратного времени
+type CountTime struct {
+	app    types.IServer
+	val    *safeint.SafeInt
+	parser *parsetime.ParseTime
+
+	chTick     chan int
+	chCall     chan int           // Канал для отправки сигналов
+	isWork     *safebool.SafeBool // Признак работы
+	timeTarget *safeint.SafeInt   // Целевое время срабатывания
+
+	block sync.RWMutex
+}
+
+// NewCountTime -- возвращает новый *CountTime
+func NewCountTime(app types.IServer) *CountTime {
+	if app == nil {
+		panic("NewCountTime(): app==nil")
+	}
+	sf := &CountTime{
+		app:        app,
+		val:        safeint.NewSafeInt(),
+		chTick:     make(chan int, 3),
+		chCall:     make(chan int, 2),
+		isWork:     safebool.NewSafeBool(),
+		parser:     parsetime.NewParseTime(),
+		timeTarget: safeint.NewSafeInt(),
+	}
+	sf.isWork.Set()
+	go sf.makeTick()
+	go sf.run()
+	return sf
+}
+
+// Запускает тикер для секундных интервалов
+func (sf *CountTime) makeTick() {
+	defer func() {
+		if !sf.isWork.Get() {
+			return
+		}
+		sf.isWork.Reset()
+		close(sf.chTick)
+		// log._rintf("CountTime.makeTick(): работа завершена")
+	}()
+	countSleep := time.Duration(0)
+	for {
+		select {
+		case <-sf.app.Done(): // Отмена контекста приложения
+			// log._rintf("CountTime.makeTick(): глобальная отмена контекста\n")
+			return
+		default:
+			if !sf.isWork.Get() {
+				return
+			}
+
+			if countSleep >= time.Second {
+				sf.chTick <- 1
+				countSleep = 0
+			}
+			countSleep += iSleep
+			time.Sleep(iSleep)
+		}
+	}
+}
+
+// Главный цикл обратного отсчёта
+func (sf *CountTime) run() {
+	for range sf.chTick {
+		timeNow := time.Now().UTC().Unix()
+		if sf.timeTarget.Get() > int(timeNow) {
+			val := sf.timeTarget.Get() - int(timeNow)
+			val -= 1
+			sf.Set(val)
+			continue
+		}
+		sf.chCall <- 1
+		continue
+	}
+	close(sf.chCall)
+}
+
+// Stop -- останавливает работу чяётчика
+func (sf *CountTime) Stop() {
+	sf.isWork.Reset()
+}
+
+// Get -- возвращает число оставшихся сек
+func (sf *CountTime) Get() int {
+	return sf.val.Get()
+}
+
+// Parse -- устанавливает число оставшихся сек
+func (sf *CountTime) Parse(val string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if val == "" {
+		return fmt.Errorf("CountTime.Set(): val is empty")
+	}
+	sf.parser.Parse(val)
+	_val := sf.parser.Hour().Get()*3600 + sf.parser.Min().Get()*60 + sf.parser.Min().Get()
+	sf.val.Set(_val)
+	_val = int(time.Now().UTC().Unix()) + sf.val.Get()
+	sf.timeTarget.Set(_val)
+	return nil
+}
+
+// Set -- устанавливает число оставшихся сек
+func (sf *CountTime) Set(val int) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if val < 0 {
+		return fmt.Errorf("CountTime.Set(): val(%v)<0", val)
+	}
+	sf.val.Set(val)
+	{ // Обновить локальные счётчики
+		if val < 60 {
+			sf.parser.Hour().Reset()
+			sf.parser.Min().Reset()
+			sf.parser.Sec().Set(val)
+			return nil
+		}
+		if 60 < val && val < 3600 {
+			sf.parser.Hour().Reset()
+			iMin := val / 60
+			sf.parser.Min().Set(iMin)
+			val -= iMin * 60
+			sf.parser.Sec().Set(val)
+			return nil
+		}
+		sf.parser.Hour().Set(val / 3600)
+		val -= sf.parser.Hour().Get() * 3600
+		sf.parser.Min().Set(val / 60)
+		val -= sf.parser.Min().Get() * 60
+		sf.parser.Sec().Set(val)
+		val = int(time.Now().UTC().Unix()) + sf.val.Get()
+		sf.timeTarget.Set(val)
+	}
+	return nil
+}
+
+// String -- возвращает строковое представление оставшихся сек
+func (sf *CountTime) String() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.parser.String()
+}
+
+// ChanSig -- возвращает канал чтения тиков
+func (sf *CountTime) ChanSig() <-chan int {
+	return sf.chCall
+}

+ 256 - 0
pkg/components/counttime/counttime_test.go

@@ -0,0 +1,256 @@
+package counttime
+
+import (
+	"testing"
+	"time"
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/mock/mockapp"
+)
+
+/*
+	Тест для счётчика времени
+*/
+
+// Тестер для счётчика времени
+type tester struct {
+	t      *testing.T
+	app    *mockapp.MockApp
+	ct     *CountTime
+	err    error
+	isCall *safebool.SafeBool // Признак обратного вызова
+}
+
+// Обратный вызов для счётчика времени
+func (sf *tester) call() {
+	<-sf.ct.chCall
+}
+
+func TestCountTime(t *testing.T) {
+	test := &tester{
+		t:      t,
+		isCall: safebool.NewSafeBool(),
+		app:    mockapp.NewMockApp(),
+	}
+	time.Sleep(time.Millisecond * 100)
+	test.create()
+	test.setInt()
+	test.setStr()
+	test.checkTick()
+	test.cancel()
+}
+
+// Оменяет работу таймера
+func (sf *tester) cancel() {
+	sf.t.Logf("=cancel=\n")
+	ct := NewCountTime(sf.app)
+	for len(ct.chTick) > 0 {
+		<-ct.chTick
+	}
+	sf.app.CancelApp()
+	time.Sleep(time.Millisecond * 150)
+}
+
+// Проверяет обработчик тика
+func (sf *tester) checkTick() {
+	ct := NewCountTime(sf.app)
+	{ // Секундный тик
+		ct.Parse("00:00:08")
+		time.Sleep(time.Second * 1)
+		ct.chTick <- 1
+		time.Sleep(time.Millisecond * 20)
+		if val := ct.String(); val != "00:00:08" {
+			sf.t.Errorf("checkTick(): счётчик(%v)!='00:00:08'", val)
+		}
+	}
+	{ // Проверка времени прямо сейчас
+		ct.chTick <- 1
+		time.Sleep(time.Millisecond * 20)
+		if val := ct.String(); val != "00:00:08" {
+			sf.t.Errorf("checkTick(): счётчик(%v)!='00:00:08'", val)
+		}
+	}
+	{ // Проверка обратного вызова прямо сейчас
+		strTime := time.Now().UTC().Format("15:04:05")
+		ct.Parse(strTime)
+		if val := ct.String(); val != strTime {
+			sf.t.Errorf("checkTick(): счётчик(%v)!=%s", val, strTime)
+		}
+		ct.chTick <- 1
+		// Выход из функции -- и есть факт обратного вызова
+		sf.call()
+		{ // Проверка отсутствия обратного вызова прямо сейчас
+			ct.Parse("00:00:00")
+			ct.chTick <- 1
+			// Выход из функции -- и есть факт обратного вызова
+			sf.call()
+			if val := ct.Get(); val != 0 {
+				sf.t.Errorf("checkTick(): счётчик(%v)!=0", val)
+			}
+			ct.Stop()
+			sf.app.CancelApp()
+			time.Sleep(time.Millisecond * 50)
+		}
+	}
+}
+
+func (sf *tester) setStrBad1(strBad string) {
+	defer func() {
+		if _panic := recover(); _panic == nil {
+			sf.t.Errorf("setStrBad1(): panic==nil\n")
+		}
+	}()
+	if sf.err = sf.ct.Parse(strBad); sf.err == nil {
+		sf.t.Errorf("setStrBad1(): BAD-2 err==nil")
+	}
+}
+
+// Устанавливает строковое значение времени
+func (sf *tester) setStr() {
+	go sf.call()
+	ct := NewCountTime(sf.app)
+	{ // BAD-1 пустая строка
+		if sf.err = ct.Parse(""); sf.err == nil {
+			sf.t.Errorf("setStr(): BAD-1 err==nil")
+		}
+	}
+	// BAD-2 неформатная строка
+	sf.setStrBad1(":::")
+	// BAD-3 кривые часы
+	sf.setStrBad1("a1:02:03")
+	// BAD-4 кривые минуты
+	sf.setStrBad1("01:a2:03")
+	// BAD-5 кривые секунды
+	sf.setStrBad1("01:02:a3")
+	// BAD-6 кривые только секунды
+	sf.setStrBad1("a3")
+	// BAD-7 кривые минуты +секунды
+	sf.setStrBad1("a2:03")
+	// BAD-8 кривые часы +минуты +секунды
+	sf.setStrBad1("a1:02:03")
+	// BAD-9 минуты +кривые секунды
+	sf.setStrBad1("02:a3")
+	// BAD-10 кривые минуты +секунды
+	sf.setStrBad1("60:03")
+	// BAD-11 кривые минуты +секунды
+	sf.setStrBad1("-1:03")
+	// BAD-12 минуты +кривые секунды
+	sf.setStrBad1("01:60")
+	// BAD-13 минуты +кривые секунды
+	sf.setStrBad1("01:-1")
+	// BAD-14 кривые часы +минуты + секунды
+	sf.setStrBad1("-1:02:03")
+	// BAD-15 кривые часы +минуты + секунды
+	//sf.setStrBad1("24:02:03")
+	{ // GOOD-1 секунды
+		if sf.err = ct.Parse("03"); sf.err != nil {
+			sf.t.Errorf("setStr(): GOOD-1 err=%v", sf.err)
+		}
+	}
+	{ // GOOD-2 минуты секунды
+		if sf.err = ct.Parse("02:03"); sf.err != nil {
+			sf.t.Errorf("setStr(): GOOD-2 err=%v", sf.err)
+		}
+	}
+	{ // GOOD-3 часы минуты секунды
+		if sf.err = ct.Parse("01:02:03"); sf.err != nil {
+			sf.t.Errorf("setStr(): GOOD-3 err=%v", sf.err)
+		}
+	}
+}
+
+// Устанавливает число секунд для отсчёта
+func (sf *tester) setInt() {
+	go sf.call()
+	ct := NewCountTime(sf.app)
+	{ // Bad-1 Отрицательное число
+		if sf.err = ct.Set(-1); sf.err == nil {
+			sf.t.Errorf("setInt(): BAD-1 err==nil")
+		}
+	}
+	{ // GOOD-1
+		if sf.err = ct.Set(8); sf.err != nil {
+			sf.t.Errorf("setInt(): GOOD-1 err=%v", sf.err)
+		}
+		if ct.parser.Hour().Get() != 0 {
+			sf.t.Errorf("setInt(): GOOD-1 hour(%v)!=0", sf.ct.parser.Hour().Get())
+		}
+		if ct.parser.Min().Get() != 0 {
+			sf.t.Errorf("setInt(): GOOD-1 min(%v)!=0", sf.ct.parser.Min().Get())
+		}
+		if ct.parser.Sec().Get() != 8 {
+			sf.t.Errorf("setInt(): GOOD-1 sec(%v)!=8", sf.ct.parser.Sec().Get())
+		}
+		if strVal := ct.String(); strVal != "00:00:08" {
+			sf.t.Errorf("setInt(): GOOD-1 strVal(%v)!='00:00:08'", strVal)
+		}
+	}
+	{ // GOOD-2
+		if sf.err = ct.Set(121); sf.err != nil {
+			sf.t.Errorf("setInt(): GOOD-2 err=%v", sf.err)
+		}
+		if ct.parser.Hour().Get() != 0 {
+			sf.t.Errorf("setInt(): GOOD-2 hour(%v)!=0", sf.ct.parser.Hour().Get())
+		}
+		if ct.parser.Min().Get() != 2 {
+			sf.t.Errorf("setInt(): GOOD-2 min(%v)!=2", sf.ct.parser.Min().Get())
+		}
+		if ct.parser.Sec().Get() != 1 {
+			sf.t.Errorf("setInt(): GOOD-2 sec(%v)!=1", sf.ct.parser.Sec().Get())
+		}
+		if strVal := ct.String(); strVal != "00:02:01" {
+			sf.t.Errorf("setInt(): GOOD-2 strVal(%v)!='00:02:01'", strVal)
+		}
+	}
+	{ // GOOD-3
+		if sf.err = ct.Set(7203); sf.err != nil {
+			sf.t.Errorf("setInt(): GOOD-3 err=%v", sf.err)
+		}
+		if ct.parser.Hour().Get() != 2 {
+			sf.t.Errorf("setInt(): GOOD-3 hour(%v)!=2", sf.ct.parser.Hour().Get())
+		}
+		if ct.parser.Min().Get() != 0 {
+			sf.t.Errorf("setInt(): GOOD-3 min(%v)!=0", sf.ct.parser.Min().Get())
+		}
+		if ct.parser.Sec().Get() != 3 {
+			sf.t.Errorf("setInt(): GOOD-3 sec(%v)!=3", sf.ct.parser.Sec().Get())
+		}
+		if strVal := ct.String(); strVal != "02:00:03" {
+			sf.t.Errorf("setInt(): GOOD-3 strVal(%v)!='02:00:03'", strVal)
+		}
+	}
+}
+
+// Нет обратного канала
+func (sf *tester) createBad1() {
+	defer func() {
+		if _panic := recover(); _panic == nil {
+			sf.t.Errorf("createBad1(): panic==nil")
+		}
+	}()
+	sf.ct = NewCountTime(nil)
+	if sf.ct != nil {
+		sf.t.Errorf("createBad1(): countTime!=nil")
+	}
+}
+
+// Правильное создание
+func (sf *tester) createGood1() {
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			sf.t.Errorf("createGood1(): panic=%v", _panic)
+		}
+	}()
+	ct := NewCountTime(sf.app)
+	if ct == nil {
+		sf.t.Errorf("createGood1(): countTime==nil")
+	}
+	if val := ct.Get(); val != 0 {
+		sf.t.Errorf("createGood1(): val(%v)!=0", val)
+	}
+}
+
+// Создание счётчика обратного времени
+func (sf *tester) create() {
+	sf.createBad1()
+	sf.createGood1()
+}

+ 56 - 0
pkg/components/kernel/keeper/keeper.go

@@ -0,0 +1,56 @@
+package keeper
+
+/*
+	Сторож системных сигналов.
+	Мониторит ОС и части сервиса на завершение работы.
+	В системе он должен быть только один.
+*/
+
+import (
+	"os"
+	"os/signal"
+	"syscall"
+
+	"wartank/pkg/types"
+)
+
+// Keeper -- сторож системных и внутренних сигналов
+type Keeper struct {
+	kernel types.IKernel
+	slog   types.ISlog
+	chSys  chan os.Signal
+}
+
+// NewKeeper -- возвращает глобальный объект сторожа *Keeper
+func NewKeeper(kernel types.IKernel) *Keeper {
+	if kernel == nil {
+		panic("GetKeeper(): IKernel is nil")
+	}
+
+	sf := &Keeper{
+		kernel: kernel,
+		slog:   kernel.Slog(),
+		chSys:  make(chan os.Signal, 3),
+	}
+	sf.kernel.Wg().Add("keeper")
+	signal.Notify(sf.chSys, os.Interrupt, syscall.SIGTERM)
+	go sf.run()
+	sf.slog.Debugf("NewKeeper()\n")
+	return sf
+}
+
+// Слушает сигналы завершения изнутри и снаружи
+func (sf *Keeper) run() {
+	sf.slog.Infof("Keeper.run()\n")
+	defer func() {
+		sf.slog.Infof("Keeper.run(): end\n")
+		sf.kernel.Wg().Done("keeper")
+	}()
+	select {
+	case sig := <-sf.kernel.Done(): // глобальная отмена контекста
+		sf.slog.Infof("Keeper.run(): intern_sig=%q\n", sig)
+	case sig := <-sf.chSys: // Системный сигнал закрытия
+		sf.kernel.CancelApp()
+		sf.slog.Infof("Keeper.run(): sys_sig=%v\n", sig)
+	}
+}

+ 85 - 0
pkg/components/kernel/keeper/keeper_test.go

@@ -0,0 +1,85 @@
+package keeper
+
+import (
+	"os"
+	"syscall"
+	"testing"
+	"time"
+
+	"wartank/pkg/components/kernel/slog"
+	"wartank/pkg/mock/mockenv"
+	"wartank/pkg/mock/mockkernel"
+)
+
+/*
+	Тест для сторожа
+*/
+
+// Тестер для сторожа
+type tester struct {
+	t    *testing.T
+	err  error
+	me   *mockenv.MockEnv
+	kp   *Keeper
+	kern *mockkernel.MockKernel
+}
+
+func TestKeeper(t *testing.T) {
+	test := &tester{
+		t:  t,
+		me: mockenv.NewMockEnv(),
+	}
+	test.create()
+	test.cancel()
+	_ = os.RemoveAll("./log")
+}
+
+// Отмена глобального контекста приложения
+func (sf *tester) cancel() {
+	sf.kp.chSys <- syscall.SIGTERM
+	time.Sleep(time.Microsecond * 150)
+	sf.createGood1()
+	sf.kern.CancelApp()
+	time.Sleep(time.Microsecond * 150)
+}
+
+// Создание сторожа
+func (sf *tester) create() {
+	sf.createBad1()
+	sf.createGood1()
+}
+
+func (sf *tester) createGood1() {
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			sf.t.Errorf("createGood1(: panic=\n\t%v\n", _panic)
+		}
+	}()
+	sf.me.Unset()
+	if err := sf.me.InitLocal(); err != nil {
+		sf.t.Errorf("createGood1(): не локальное окружение, err=\n\t%v\n", err)
+		return
+	}
+	sf.kern = mockkernel.NewMockKernel()
+	sf.kern.Slog_, sf.err = slog.NewSlog(sf.kern)
+	if sf.err != nil {
+		sf.t.Errorf("createGood1(): in create ISlog, err=\n\t%v", sf.err)
+	}
+	sf.kp = NewKeeper(sf.kern)
+	if sf.kp == nil {
+		sf.t.Errorf("createGood1(): keep==nil")
+	}
+}
+
+// Нет объекта приложения
+func (sf *tester) createBad1() {
+	defer func() {
+		if _panic := recover(); _panic == nil {
+			sf.t.Errorf("createBad1(: panic==nil\n")
+		}
+		if sf.kp != nil {
+			sf.t.Errorf("createBad1(): keep!=nil")
+		}
+	}()
+	sf.kp = NewKeeper(nil)
+}

+ 79 - 0
pkg/components/kernel/kernel.go

@@ -0,0 +1,79 @@
+// Package kernel -- объект ядра прилоэжения
+//
+//	Содержит базовый функционал сервиса
+package kernel
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	// "time"
+
+	"wartank/pkg/components/kernel/keeper"
+	"wartank/pkg/components/kernel/slog"
+	"wartank/pkg/components/kernel/wgname"
+	"wartank/pkg/types"
+)
+
+/*
+	Базовый объект приложения.
+*/
+
+// Kernel -- объект ядра приложения
+type Kernel struct {
+	ctxBg    context.Context // Неотменяемый контекст приложения
+	ctxApp   context.Context // Отменяемый контекст приложения
+	fnCancel func()          // Функция отмены приложения
+	keeper   *keeper.Keeper  // Сторож системных сигналов
+	slog     *slog.Slog      // Логгер в два вывода
+	wg       *wgname.WgName  // Групповое ожидание частей приложения
+	block    sync.Mutex
+}
+
+// NewKernel -- возвращает новый *Kernel
+func NewKernel() (sf *Kernel, err error) {
+	// timeStart := time.Now().UTC().Format("2006-10-02 15:04:05.000")
+	// fmt._rintf("%v\t%v\tCI/CD test\n", cons.SelfName, timeStart)
+	sf = &Kernel{
+		ctxBg: context.Background(),
+		wg:    wgname.NewWgName(),
+	}
+	sf.ctxApp, sf.fnCancel = context.WithCancel(sf.ctxBg)
+	sf.block.Lock()
+	sf.slog, err = slog.NewSlog(sf)
+	sf.block.Unlock()
+	if err != nil {
+		return nil, fmt.Errorf("NewKernel(): in creste ISlog, err=%w", err)
+	}
+	sf.slog.Debugf("NewKernel()")
+	sf.keeper = keeper.NewKeeper(sf)
+	return sf, nil
+}
+
+// Wg -- возвращает объект групповой сихнронизации
+func (sf *Kernel) Wg() types.IWgName {
+	return sf.wg
+}
+
+// CtxApp -- возвращает глобальный контекст приложения
+func (sf *Kernel) CtxApp() context.Context {
+	return sf.ctxApp
+}
+
+// Done -- возвращает канал отмены контекста приложения
+func (sf *Kernel) Done() <-chan struct{} {
+	return sf.ctxApp.Done()
+}
+
+// CancelApp -- отменяет глобальный контекст приложения
+func (sf *Kernel) CancelApp() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.slog.Infof("Kernel.CancelApp()\n")
+	sf.fnCancel()
+}
+
+// Slog -- возвращает глобальный лог приложения
+func (sf *Kernel) Slog() types.ISlog {
+	return sf.slog
+}

+ 43 - 0
pkg/components/kernel/kernel_test.go

@@ -0,0 +1,43 @@
+package kernel
+
+import (
+	"os"
+	"testing"
+)
+
+// Тестер для базового объекта приложения
+type tester struct {
+	t    *testing.T
+	err  error
+	kern *Kernel
+}
+
+func TestKernel(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	_ = os.RemoveAll("./log")
+}
+
+// Создание базового объекта
+func (sf *tester) create() {
+	sf.t.Logf("create\n")
+	sf.kern, sf.err = NewKernel()
+	if sf.err != nil {
+		sf.t.Errorf("create(): err=\n\t%v", sf.err)
+	}
+	if sf.kern == nil {
+		sf.t.Errorf("create(): kernel==nil\n")
+	}
+	if wg := sf.kern.Wg(); wg == nil {
+		sf.t.Errorf("create(): wg==nil\n")
+	}
+	if ctx := sf.kern.CtxApp(); ctx == nil {
+		sf.t.Errorf("create(): ctx==nil\n")
+	}
+	if slog := sf.kern.Slog(); slog == nil {
+		sf.t.Errorf("create(): slog==nil\n")
+	}
+	sf.kern.CancelApp()
+}

+ 144 - 0
pkg/components/kernel/slog/slog.go

@@ -0,0 +1,144 @@
+package slog
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"sync"
+	"time"
+
+	"wartank/pkg/components/kernel/slog/slog_file"
+	"wartank/pkg/components/kernel/slog/slog_term"
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/cons"
+	"wartank/pkg/types"
+)
+
+/*
+	Попытка сделать логирование в файл и консоль
+*/
+
+const (
+	strSlog    = "ISlog"
+	LevelDebug = iota
+	LevelInfo
+	LevelWarn
+	LevelError
+)
+
+// Slog -- лог событий для записи
+type Slog struct {
+	kern   types.IKernel
+	buf    strings.Builder
+	block  sync.RWMutex
+	lf     *slog_file.SlogFile
+	lt     *slog_term.SlogTerm
+	isWork *safebool.SafeBool
+	level  int // Уровень логирования
+}
+
+// NewSlog -- возвращает объект логирования
+func NewSlog(kern types.IKernel) (*Slog, error) {
+	if kern == nil {
+		return nil, fmt.Errorf("NewSlog(): IKernel is nil")
+	}
+	os.Setenv("BUILD", "test_BUILD")
+	build := os.Getenv("BUILD")
+	if build == "" {
+		return nil, fmt.Errorf("NewSlog(): strBuild is empty")
+	}
+	sf := &Slog{
+		kern:   kern,
+		buf:    strings.Builder{},
+		lt:     slog_term.NewSlogTerm(),
+		isWork: safebool.NewSafeBool(),
+	}
+	lf, err := slog_file.NewSlogFile(kern, build)
+	if err != nil {
+		return nil, fmt.Errorf("NewSlog(): in create SlogFile, err=\n\t%w", err)
+	}
+	sf.lf = lf
+
+	go sf.close()
+	sf.isWork.Set()
+	sf.kern.Wg().Add(strSlog)
+	sf.Infof("NewSlog()\n")
+	return sf, nil
+}
+
+// Debugf -- выводит в лог отладочную инфу
+func (sf *Slog) Debugf(str string, lstVal ...interface{}) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.printf("DEBUG", str, lstVal...)
+}
+
+// Infof -- выводит в лог дежурную инфу
+func (sf *Slog) Infof(str string, lstVal ...interface{}) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.printf("INFO", str, lstVal...)
+}
+
+// Warnf -- выводит в лог предупредительную инфу
+func (sf *Slog) Warnf(str string, lstVal ...interface{}) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.printf("WARN", str, lstVal...)
+}
+
+// Errorf -- выводит в лог инфу об ошибках
+func (sf *Slog) Errorf(str string, lstVal ...interface{}) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.printf("ERROR", str, lstVal...)
+}
+
+// Внутренний вызов без блокировки
+func (sf *Slog) printf(pref, str string, lstVal ...interface{}) {
+	style := ""
+	switch pref {
+	case "DEBUG": // Режим отладки
+		style = "\033[2;37;40m"
+	// case "PRINT": // Режим печати
+	// 	style = "\033[0;34;40m"
+	case "INFO": // Режим информирования
+		style = "\033[0;37;40m"
+	case "WARN": // Режим предупреждения
+		style = "\033[1;31;40m"
+	case "ERROR": // Режим ошибки
+		style = "\033[1;37;41m"
+	}
+	strTime := time.Now().Local().Format("2006-01-02 15:04:05.000\t")
+	sf.buf.WriteString(strTime + "\t")
+	sf.buf.WriteString(pref + "\t")
+	sf.buf.WriteString(cons.SelfName + "\n\t")
+	sf.buf.WriteString(fmt.Sprintf(str, lstVal...))
+	sf.lf.Write(sf.buf.String())
+	sf.lt.Write(style + sf.buf.String() + "\033[00m")
+	sf.buf.Reset()
+}
+
+// Ожидает закрытия приложения
+func (sf *Slog) close() {
+	<-sf.kern.Done()
+	sf.block.Lock()
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.isWork.Reset()
+	sf.kern.Wg().Done(strSlog)
+	sf.block.Unlock()
+}

+ 110 - 0
pkg/components/kernel/slog/slog_file/slog_file.go

@@ -0,0 +1,110 @@
+// package slog_file -- бекенд для вывода в файл
+package slog_file
+
+import (
+	"fmt"
+	// "log"
+	"os"
+	"sync"
+	"time"
+
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/cons"
+	"wartank/pkg/types"
+)
+
+const (
+	threadName = "slog_file"
+)
+
+// SlogFile -- бэкенд для вывода в файл
+type SlogFile struct {
+	kern     types.IKernel
+	file     *os.File
+	fileName string
+	isWork   *safebool.SafeBool
+	chMsg    chan []byte // канал входящих сообщений
+	block    sync.Mutex
+}
+
+// NewSlogFile -- возвращает новый бэкенд логгера для вывода в файл
+func NewSlogFile(kern types.IKernel, build string) (*SlogFile, error) {
+	{ // Предусловия
+		if kern == nil {
+			return nil, fmt.Errorf("NewSlogFile(): IKernel is nil")
+		}
+		if build == "" {
+			return nil, fmt.Errorf("NewSlogFile(): strBuild is empty")
+		}
+	}
+	sf := &SlogFile{
+		kern:     kern,
+		fileName: cons.SelfName + "_" + build + time.Now().UTC().Format("_2006-01-02 15:04:05.000") + ".log",
+		isWork:   safebool.NewSafeBool(),
+		chMsg:    make(chan []byte, 10),
+	}
+	sf.isWork.Set()
+	_ = os.MkdirAll("./log", 0700)
+	sf.fileName = "./log/" + sf.fileName
+	if err := sf.open(); err != nil {
+		return nil, fmt.Errorf("NewSlogFile(): in open log-file, err=%w", err)
+	}
+	sf.kern.Wg().Add(threadName)
+	go sf.run()
+	return sf, nil
+}
+
+// Открывает файл на запись
+func (sf *SlogFile) open() error {
+	file, err := os.OpenFile(sf.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
+	if err != nil {
+		sf.kern.CancelApp()
+		return fmt.Errorf("NewSlogFile.open(): in open %q, err=%w", sf.fileName, err)
+	}
+	sf.file = file
+	return nil
+}
+
+// Запускает работу в отдельном потоке
+func (sf *SlogFile) run() {
+	defer func() {
+		sf.isWork.Reset()
+		sf.block.Lock()
+		defer sf.block.Unlock()
+		_ = sf.file.Close()
+		close(sf.chMsg)
+		sf.kern.Wg().Done(threadName)
+		// log._rintf("SlogFile.run(): end work\n")
+		sf.kern.CancelApp()
+	}()
+	for {
+		select {
+		case <-sf.kern.Done(): // Ожидание отмены контекста приложения
+			return
+		case msg := <-sf.chMsg:
+			if err := sf.write(msg); err != nil {
+				sf.kern.Slog().Errorf("SlogFile.run(): in write msg, err=\n\t%v", err)
+				return
+			}
+		}
+	}
+}
+
+// Внцтренни вызов записи
+func (sf *SlogFile) write(msg []byte) error {
+	_, err := sf.file.Write(msg)
+	if err != nil {
+		return fmt.Errorf("SlogFile.run(): in write file %q, err=\n\t%w", sf.fileName, err)
+	}
+	return nil
+}
+
+// Write -- записывает данные в файл
+func (sf *SlogFile) Write(msg string) {
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.chMsg <- []byte(msg)
+}

+ 110 - 0
pkg/components/kernel/slog/slog_file/slog_file_test.go

@@ -0,0 +1,110 @@
+package slog_file
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"wartank/pkg/mock/mockenv"
+	"wartank/pkg/mock/mockkernel"
+)
+
+/*
+	Тест для интерфейса логгера в файл
+*/
+
+type tester struct {
+	t    *testing.T
+	err  error
+	me   *mockenv.MockEnv
+	kern *mockkernel.MockKernel
+	sf   *SlogFile
+}
+
+func TestSlogFile(t *testing.T) {
+	_ = os.RemoveAll("./log")
+	test := &tester{
+		t:  t,
+		me: mockenv.NewMockEnv(),
+	}
+	test.create()
+	test.write()
+	test.close()
+	test.writeBad1()
+	test.cancel()
+	_ = os.RemoveAll("./log")
+}
+
+// файл уже закрыт
+func (sf *tester) writeBad1() {
+	if err := sf.sf.write([]byte{}); err == nil {
+		sf.t.Errorf("write(): err==nil")
+	}
+}
+
+// закрытие логгера
+func (sf *tester) close() {
+	sf.t.Logf("=close=")
+	sf.kern.CancelApp()
+	sf.kern.Wg().Wait()
+	sf.sf.Write("")
+}
+
+// отмена работы
+func (sf *tester) cancel() {
+	sf.t.Logf("=cancal=")
+	sf.createGood1()
+	sf.kern.CancelApp()
+	sf.kern.Wg().Wait()
+}
+
+// запись сообщения
+func (sf *tester) write() {
+	sf.t.Logf("=write=")
+	sf.sf.Write("test msg")
+	time.Sleep(time.Millisecond * 150)
+}
+
+// Создание файлового логгера
+func (sf *tester) create() {
+	sf.t.Logf("=create=")
+	sf.createBad1()
+	sf.createBad2()
+	sf.createGood1()
+}
+
+func (sf *tester) createGood1() {
+	sf.t.Logf("=createGood1=")
+	sf.sf, sf.err = NewSlogFile(sf.kern, "test")
+	if sf.err != nil {
+		sf.t.Errorf("createGood1(): err=%v", sf.err)
+	}
+	if sf.sf == nil {
+		sf.t.Errorf("createGood1(): SlogFile == nil")
+	}
+}
+
+// Нет верси сборки
+func (sf *tester) createBad2() {
+	sf.t.Logf("=createBad2=")
+	sf.kern = mockkernel.NewMockKernel()
+	sf.sf, sf.err = NewSlogFile(sf.kern, "")
+	if sf.err == nil {
+		sf.t.Errorf("createBad2(): err == nil")
+	}
+	if sf.sf != nil {
+		sf.t.Errorf("createBad2(): SlogFile != nil")
+	}
+}
+
+// Нет объекта ядра
+func (sf *tester) createBad1() {
+	sf.t.Logf("=createBad1=")
+	sf.sf, sf.err = NewSlogFile(nil, "test")
+	if sf.err == nil {
+		sf.t.Errorf("createBad1(): err == nil")
+	}
+	if sf.sf != nil {
+		sf.t.Errorf("createBad1(): SlogFile != nil")
+	}
+}

+ 28 - 0
pkg/components/kernel/slog/slog_term/slog_term.go

@@ -0,0 +1,28 @@
+// package slog_term -- логгера для терминала
+package slog_term
+
+// SlogTerm -- терминальный логгер
+type SlogTerm struct {
+	chMsg chan string
+}
+
+// NewSlogTerm -- возвращает новый терминальный логгер
+func NewSlogTerm() *SlogTerm {
+	sf := &SlogTerm{
+		chMsg: make(chan string, 10),
+	}
+	go sf.run()
+	return sf
+}
+
+// Write -- пишет запись в консоль
+func (sf *SlogTerm) Write(msg string) {
+	sf.chMsg <- msg
+}
+
+// Главный цикл, работает в отдельном потоке
+func (sf *SlogTerm) run() {
+	// for msg := range sf.chMsg {
+	// 	// fmt._rintln(msg)
+	// }
+}

+ 12 - 0
pkg/components/kernel/slog/slog_term/slog_term_test.go

@@ -0,0 +1,12 @@
+package slog_term
+
+import (
+	"testing"
+	"time"
+)
+
+func TestSlogTerm(t *testing.T) {
+	st := NewSlogTerm()
+	st.Write("123")
+	time.Sleep(time.Millisecond * 100)
+}

+ 84 - 0
pkg/components/kernel/slog/slog_test.go

@@ -0,0 +1,84 @@
+package slog
+
+import (
+	"fmt"
+	"os"
+	"testing"
+	"time"
+
+	"wartank/pkg/mock/mockkernel"
+)
+
+/*
+	Тестер для логера
+*/
+
+// Создание тестера
+type tester struct {
+	t    *testing.T
+	err  error
+	kern *mockkernel.MockKernel
+	slog *Slog
+}
+
+func TestSlog(t *testing.T) {
+	test := &tester{
+		t:    t,
+		kern: mockkernel.NewMockKernel(),
+	}
+	test.create()
+	_ = os.RemoveAll("./log")
+}
+
+// Создание логера
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.createBad1()
+	sf.createBad2()
+	sf.createGood1()
+}
+
+func (sf *tester) createGood1() {
+	sf.t.Logf("=createGood1=\n")
+	sf.slog, sf.err = NewSlog(sf.kern)
+	if sf.err != nil {
+		sf.t.Errorf("createGood1(): in create ISlog, err=\n\t%v", sf.err)
+	}
+	sf.kern.Slog_ = sf.slog
+	sf.slog.Debugf("Проверка good1: %v\n", "test_123")
+	sf.slog.Infof("Проверка good1: %v\n", "test_123")
+	sf.slog.Warnf("Проверка good1: %v\n", "test_123")
+	sf.slog.Errorf("Проверка good1: %v\n", fmt.Errorf("test_123").Error())
+	sf.kern.CancelApp()
+	time.Sleep(time.Millisecond * 150)
+	sf.slog.Debugf("Проверка good1: %v\n", "test_123 bad")
+	sf.slog.Infof("")
+	sf.slog.Warnf("")
+	sf.slog.Errorf("")
+	sf.slog.close()
+}
+
+// Нет объекта ядра
+func (sf *tester) createBad2() {
+	sf.t.Logf("=createBad2=\n")
+	slog, err := NewSlog(nil)
+	if err == nil {
+		sf.t.Errorf("createBad2(): in create ISlog, err==nil")
+	}
+	if slog != nil {
+		sf.t.Errorf("createBad2): slog!=nil")
+	}
+}
+
+// Нет строки сборки
+func (sf *tester) createBad1() {
+	sf.t.Logf("=createBad1=\n")
+	slog, err := NewSlog(sf.kern)
+	if err == nil {
+		sf.t.Errorf("createBad1(): in create ISlog, err==nil")
+	}
+	if slog != nil {
+		sf.t.Errorf("createBad1(): slog!=nil")
+	}
+	// os.RemoveAll(slog.)
+}

+ 64 - 0
pkg/components/kernel/wgname/wgname.go

@@ -0,0 +1,64 @@
+// package wgname -- ожидатель группы по имени для завершения
+package wgname
+
+import (
+	"fmt"
+	"sync"
+
+	"wartank/pkg/components/safebool"
+)
+
+// WgName -- ожидатель группы завершения
+type WgName struct {
+	dictName map[string]bool    // Словарь имён потоков
+	chEnd    chan int           // Канал сигнала закрытия ожидания
+	isWork   *safebool.SafeBool // Признак работы ожидания
+	block    sync.RWMutex
+}
+
+// NewWgName -- возвращает новый ожидатель группы
+func NewWgName() *WgName {
+	sf := &WgName{
+		dictName: make(map[string]bool),
+		chEnd:    make(chan int, 1),
+		isWork:   safebool.NewSafeBool(),
+	}
+	sf.isWork.Set()
+	return sf
+}
+
+// Add -- добавляет имя в группу ожидания
+func (sf *WgName) Add(name string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	_, isOk := sf.dictName[name]
+	if isOk {
+		return fmt.Errorf("WgName.Add(): name %q already exists", name)
+	}
+	sf.dictName[name] = true
+	return nil
+}
+
+// Done -- отмечает словарь ожидания готовности потока
+func (sf *WgName) Done(name string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	_, isOk := sf.dictName[name]
+	if !isOk {
+		return fmt.Errorf("WgName.Done(): name %q not exists", name)
+	}
+	delete(sf.dictName, name)
+	if !sf.isWork.Get() {
+		return nil
+	}
+	if len(sf.dictName) == 0 {
+		sf.isWork.Reset()
+		close(sf.chEnd)
+	}
+	return nil
+}
+
+// Wait -- ожидание закрытия группы
+func (sf *WgName) Wait() {
+	<-sf.chEnd
+}

+ 88 - 0
pkg/components/kernel/wgname/wgname_test.go

@@ -0,0 +1,88 @@
+package wgname
+
+import "testing"
+
+/*
+	Тест для ожидания группы по имени
+*/
+
+type tester struct {
+	t  *testing.T
+	wn *WgName
+}
+
+func TestWgName(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.add()
+	test.done()
+	test.wn.Wait()
+}
+
+// Отпскание имени
+func (sf *tester) done() {
+	sf.t.Logf("=done=")
+	sf.doneGood1()
+	sf.doneBad1()
+	sf.doneBad1()
+}
+
+// Повторное удаление
+func (sf *tester) doneBad1() {
+	sf.t.Logf("=doneBad1=")
+	if err := sf.wn.Done("test"); err == nil {
+		sf.t.Errorf("doneBad1(): err==nil")
+	}
+}
+
+func (sf *tester) doneGood1() {
+	sf.t.Logf("=doneGood1=")
+	if err := sf.wn.Done("test"); err != nil {
+		sf.t.Errorf("doneGood1(): err=%v", err)
+	}
+}
+
+// Добавление имени в группу
+func (sf *tester) add() {
+	sf.t.Logf("=add=")
+	sf.addGood1()
+	sf.addBad1()
+}
+
+// Уже есть такое имя
+func (sf *tester) addBad1() {
+	sf.t.Logf("=addBad1=")
+	if err := sf.wn.Add("test"); err == nil {
+		sf.t.Errorf("addBad1(): err==nil")
+	}
+	if len(sf.wn.dictName) != 1 {
+		sf.t.Errorf("addBad1(): len dictName != 1")
+	}
+}
+
+func (sf *tester) addGood1() {
+	sf.t.Logf("=addGood1=")
+	if err := sf.wn.Add("test"); err != nil {
+		sf.t.Errorf("addGood1(): err=%v", err)
+	}
+	if len(sf.wn.dictName) != 1 {
+		sf.t.Errorf("addGood1(): len dictName != 1")
+	}
+}
+
+// Создание группы ожидания
+func (sf *tester) create() {
+	sf.t.Logf("=create=")
+	sf.wn = NewWgName()
+	if sf.wn.dictName == nil {
+		sf.t.Errorf("create(): dictName == nil")
+	}
+	if len(sf.wn.dictName) != 0 {
+		sf.t.Errorf("create(): dictName not empty")
+	}
+	if len(sf.wn.chEnd) != 0 {
+		sf.t.Errorf("create(): chEnd not empty")
+	}
+}

+ 66 - 0
pkg/components/lststring/lststring.go

@@ -0,0 +1,66 @@
+package lststring
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+)
+
+/*
+	Потокобезопасный компонент списка строк для анализа объектов
+*/
+
+// LstString -- потокобезопасный список строк объекта
+type LstString struct {
+	val        []string
+	strControl string
+	block      sync.RWMutex
+}
+
+// NewLstString -- возвращает новый *LstString
+func NewLstString(strControl string) (*LstString, error) {
+	if strControl == "" {
+		return nil, fmt.Errorf("NewLstString(): strControl is empty")
+	}
+	sf := &LstString{
+		val:        make([]string, 0),
+		strControl: strControl,
+	}
+	return sf, nil
+}
+
+// Get -- возвращает список строк
+func (sf *LstString) Get() []string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает список строк
+func (sf *LstString) Set(lstString []string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if lstString == nil {
+		return fmt.Errorf("LstString.Set(): lstString is nil")
+	}
+	isOk := false
+	for _, strControl := range lstString {
+		if strings.Contains(strControl, sf.strControl) {
+			isOk = true
+			break
+		}
+	}
+	if isOk {
+		sf.val = lstString
+		return nil
+	}
+	// Найти заголовок
+	var strOut string
+	for _, strOut = range lstString {
+		if strings.Contains(strOut, "<title>") {
+			break
+		}
+	}
+	return fmt.Errorf("LstString.Set(): lstString не имеет правильный title(%q), фактически(%q)", sf.strControl, strOut)
+
+}

+ 77 - 0
pkg/components/parsetime/parsehour/parsehour.go

@@ -0,0 +1,77 @@
+package parsehour
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+)
+
+/*
+	Потокобезопасно парсит из строки значение часа
+*/
+
+// ParseHour -- потокобезопасный парсер часа из строки
+type ParseHour struct {
+	val   int // Значение часа
+	block sync.RWMutex
+}
+
+// NewParseHour -- возвращает новый *ParseHour
+func NewParseHour() *ParseHour {
+	return &ParseHour{}
+}
+
+// Get -- возвращает хранимое значение
+func (sf *ParseHour) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// String -- возвращает строковое значение часов
+func (sf *ParseHour) String() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	res := fmt.Sprintf("%02d", sf.val)
+	return res
+}
+
+// Reset -- сбрасывает значение часов
+func (sf *ParseHour) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = 0
+}
+
+// Parse -- устанавливает значение часов
+func (sf *ParseHour) Parse(strHour string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	iHour, err := strconv.Atoi(strHour)
+	if err != nil {
+		return fmt.Errorf("ParseHour.Parse(): часы(%q) не число, err=\n\t%w", strHour, err)
+	}
+	if err := sf.set(iHour); err != nil {
+		return fmt.Errorf("ParseHour.Parse(): in internal set hour(%q), err=\n\t%w", strHour, err)
+	}
+	return nil
+}
+
+// Set - -устанавливает числовое значение часов
+func (sf *ParseHour) Set(iHour int) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if err := sf.set(iHour); err != nil {
+		return fmt.Errorf("ParseHour.Set(): in internal set hour(%v), err=\n\t%w", iHour, err)
+	}
+	return nil
+}
+
+// Внутренняя процедура для числовой установки часов без блокировки
+func (sf *ParseHour) set(iHour int) error {
+	if iHour < 0 {
+		return fmt.Errorf("ParseHour.set(): часы(%v) < 0", iHour)
+	}
+	sf.val = iHour
+	return nil
+}

+ 117 - 0
pkg/components/parsetime/parsehour/parsehour_test.go

@@ -0,0 +1,117 @@
+package parsehour
+
+import (
+	"testing"
+)
+
+/*
+	Тест для парсера времени часов
+*/
+
+// Тестер для проверки парсера времени
+type tester struct {
+	t  *testing.T
+	ph *ParseHour
+}
+
+func TestParseHour(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.parse()
+	test.set()
+	test.reset()
+}
+
+// Сброс часов в ноль
+func (sf *tester) reset() {
+	sf.t.Logf("=reset=\n")
+	sf.ph.Reset()
+	if strHour := sf.ph.String(); strHour != "00" {
+		sf.t.Errorf("setGood2(): strHour(%q)!='00'\n", strHour)
+	}
+}
+
+// Устанавливает целочисленное значение часов
+func (sf *tester) set() {
+	sf.t.Logf("=set=\n")
+	sf.setBad1()
+	sf.setGood1()
+}
+func (sf *tester) setGood1() {
+	sf.t.Logf("=setGood1=\n")
+	if err := sf.ph.Set(8); err != nil {
+		sf.t.Errorf("setGood1(): err=\n\t%v\n", err)
+	}
+}
+
+// Отрицательное значение часа
+func (sf *tester) setBad1() {
+	sf.t.Logf("=setBad1=\n")
+	if err := sf.ph.Set(-1); err == nil {
+		sf.t.Errorf("setBad1(): err==nil\n")
+	}
+}
+
+// Устанавливает значение часов
+func (sf *tester) parse() {
+	sf.t.Logf("=parse=\n")
+	sf.parseBad1()
+	sf.parseBad2()
+	sf.parseGood1()
+	sf.parseGood2()
+}
+
+// Установка правильных большихчасов
+func (sf *tester) parseGood2() {
+	sf.t.Logf("=parseGood2=\n")
+	if err := sf.ph.Parse("867"); err != nil {
+		sf.t.Errorf("parseGood2(): err=\n\t%v\n", err)
+	}
+	if strHour := sf.ph.String(); strHour != "867" {
+		sf.t.Errorf("parseGood2(): strHour(%q)!='867'\n", strHour)
+	}
+}
+
+// Установка правильных часов
+func (sf *tester) parseGood1() {
+	sf.t.Logf("=parseGood1=\n")
+	if err := sf.ph.Parse("8"); err != nil {
+		sf.t.Errorf("parseGood1(): err=\n\t%v\n", err)
+	}
+	if strHour := sf.ph.String(); strHour != "08" {
+		sf.t.Errorf("parseGood1(): strHour(%q)!='08'\n", strHour)
+	}
+}
+
+// Установка отрицательных часов
+func (sf *tester) parseBad2() {
+	sf.t.Logf("=parseBad2=\n")
+	if err := sf.ph.Parse("-1"); err == nil {
+		sf.t.Errorf("parseBad2(): err==nil\n")
+	}
+}
+
+// Установка не часов
+func (sf *tester) parseBad1() {
+	sf.t.Logf("=parseBad1=\n")
+	if err := sf.ph.Parse("abc"); err == nil {
+		sf.t.Errorf("parseBad1(): err==nil\n")
+	}
+}
+
+// Создание парсера часов
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.ph = NewParseHour()
+	if sf.ph == nil {
+		sf.t.Errorf("create(): parseHour==nil\n")
+	}
+	if hour := sf.ph.Get(); hour != 0 {
+		sf.t.Errorf("create(): hour(%v)!=0\n", hour)
+	}
+	if strHour := sf.ph.String(); strHour != "00" {
+		sf.t.Errorf("create(): strHour(%q)!='00'\n", strHour)
+	}
+}

+ 77 - 0
pkg/components/parsetime/parsemin/parsemin.go

@@ -0,0 +1,77 @@
+package parsemin
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+)
+
+/*
+	Потокобезопасно парсит из строки значение vbyen
+*/
+
+// ParseMin -- потокобезопасный парсер vbyen из строки
+type ParseMin struct {
+	val   int // Значение vbyen
+	block sync.RWMutex
+}
+
+// NewParseMin -- возвращает новый *ParseMin
+func NewParseMin() *ParseMin {
+	return &ParseMin{}
+}
+
+// Get -- возвращает хранимое значение
+func (sf *ParseMin) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// String -- возвращает строковое значение минут
+func (sf *ParseMin) String() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	res := fmt.Sprintf("%02d", sf.val)
+	return res
+}
+
+// Reset -- сбрасывает значение минут
+func (sf *ParseMin) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = 0
+}
+
+// Parse -- устанавливает значение минут
+func (sf *ParseMin) Parse(strMin string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	iMin, err := strconv.Atoi(strMin)
+	if err != nil {
+		return fmt.Errorf("ParseMin.Parse(): минуты(%v) не число, err=%w", strMin, err)
+	}
+	if err := sf.set(iMin); err != nil {
+		return fmt.Errorf("ParseMin.Parse(): in internal set, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Set -- устанавливает целочисленное значение минут
+func (sf *ParseMin) Set(iMin int) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if err := sf.set(iMin); err != nil {
+		return fmt.Errorf("ParseMin.Set(): in internal set, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Внтренняя установка минут
+func (sf *ParseMin) set(iMin int) error {
+	if !(0 <= iMin && iMin < 60) {
+		return fmt.Errorf("ParseMin.set(): минуты не в диапазоне(%v) 0..60", iMin)
+	}
+	sf.val = iMin
+	return nil
+}

+ 112 - 0
pkg/components/parsetime/parsemin/parsemin_test.go

@@ -0,0 +1,112 @@
+package parsemin
+
+import (
+	"testing"
+)
+
+/*
+	Тест для парсера времени часов
+*/
+
+// Тестер для проверки парсера времени
+type tester struct {
+	t  *testing.T
+	ph *ParseMin
+}
+
+func TestParseMin(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.parse()
+	test.set()
+	test.reset()
+}
+
+// Устанавливает целочисленное значение
+func (sf *tester) set() {
+	sf.t.Logf("=set=\n")
+	sf.setGood1()
+	sf.setBad1()
+}
+
+// Кривое число минут
+func (sf *tester) setBad1() {
+	sf.t.Logf("=setBad1=\n")
+	if err := sf.ph.Set(60); err == nil {
+		sf.t.Errorf("setBad1(): err==nil\n")
+	}
+	if strHour := sf.ph.String(); strHour != "08" {
+		sf.t.Errorf("setBad1(): strHour(%q)!='08'\n", strHour)
+	}
+}
+
+func (sf *tester) setGood1() {
+	sf.t.Logf("=setGood1=\n")
+	if err := sf.ph.Set(8); err != nil {
+		sf.t.Errorf("setGood1(): err=\n\t%v\n", err)
+	}
+	if strHour := sf.ph.String(); strHour != "08" {
+		sf.t.Errorf("setGood1(): strHour(%q)!='08'\n", strHour)
+	}
+}
+
+// Сброс часов в ноль
+func (sf *tester) reset() {
+	sf.t.Logf("=reset=\n")
+	sf.ph.Reset()
+	if strHour := sf.ph.String(); strHour != "00" {
+		sf.t.Errorf("reset(): strHour(%q)!='00'\n", strHour)
+	}
+}
+
+// Устанавливает значение минут
+func (sf *tester) parse() {
+	sf.t.Logf("=parse=\n")
+	sf.parseBad1()
+	sf.parseBad2()
+	sf.parseGood1()
+}
+
+// Установка правильных минут
+func (sf *tester) parseGood1() {
+	sf.t.Logf("=parseGood1=\n")
+	if err := sf.ph.Parse("8"); err != nil {
+		sf.t.Errorf("parseGood1(): err=\n\t%v\n", err)
+	}
+	if strHour := sf.ph.String(); strHour != "08" {
+		sf.t.Errorf("parseGood1(): strHour(%q)!='08'\n", strHour)
+	}
+}
+
+// Установка отрицательных минут
+func (sf *tester) parseBad2() {
+	sf.t.Logf("=parseBad2=\n")
+	if err := sf.ph.Parse("-1"); err == nil {
+		sf.t.Errorf("parseBad2(): err==nil\n")
+	}
+}
+
+// Установка не минут
+func (sf *tester) parseBad1() {
+	sf.t.Logf("=parseBad1=\n")
+	if err := sf.ph.Parse("abc"); err == nil {
+		sf.t.Errorf("parseBad1(): err==nil\n")
+	}
+}
+
+// Создание парсера минут
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.ph = NewParseMin()
+	if sf.ph == nil {
+		sf.t.Errorf("create(): parseHour==nil\n")
+	}
+	if hour := sf.ph.Get(); hour != 0 {
+		sf.t.Errorf("create(): hour(%v)!=0\n", hour)
+	}
+	if strHour := sf.ph.String(); strHour != "00" {
+		sf.t.Errorf("create(): strHour(%q)!='00'\n", strHour)
+	}
+}

+ 77 - 0
pkg/components/parsetime/parsesec/parsesec.go

@@ -0,0 +1,77 @@
+package parsesec
+
+import (
+	"fmt"
+	"strconv"
+	"sync"
+)
+
+/*
+	Парсер секунд
+*/
+
+// ParseSec -- парсер секунд
+type ParseSec struct {
+	val   int
+	block sync.RWMutex
+}
+
+// NewParseSec -- возвращает новый *ParseSec
+func NewParseSec() *ParseSec {
+	return &ParseSec{}
+}
+
+// Get -- возвращает хранимое значение
+func (sf *ParseSec) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// String -- возвращает строковое значение секунд
+func (sf *ParseSec) String() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	res := fmt.Sprintf("%02d", sf.val)
+	return res
+}
+
+// Reset -- сбрасывает значение секунд
+func (sf *ParseSec) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = 0
+}
+
+// Parse -- устанавливает значение секунд
+func (sf *ParseSec) Parse(strSec string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	iSec, err := strconv.Atoi(strSec)
+	if err != nil {
+		return fmt.Errorf("ParseSec.Parse(): секунды(%v) не число, err=%w", strSec, err)
+	}
+	if err := sf.set(iSec); err != nil {
+		return fmt.Errorf("ParseSec.Parse(): in internal setting, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Set -- устанавливает целочисленное значение
+func (sf *ParseSec) Set(iSec int) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if err := sf.set(iSec); err != nil {
+		return fmt.Errorf("ParseSec.Set(): in internal setting int, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Внутренняя установка значения секунд
+func (sf *ParseSec) set(iSec int) error {
+	if !(0 <= iSec && iSec < 60) {
+		return fmt.Errorf("ParseSec.set(): секунды(%v) не в диапазоне 0..60", iSec)
+	}
+	sf.val = iSec
+	return nil
+}

+ 139 - 0
pkg/components/parsetime/parsesec/parsesec_test.go

@@ -0,0 +1,139 @@
+package parsesec
+
+import (
+	"testing"
+)
+
+/*
+	Тест для парсера времени секунд
+*/
+
+// Тестер для проверки парсера времени
+type tester struct {
+	t  *testing.T
+	ph *ParseSec
+}
+
+func TestParsesec(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.parse()
+	test.set()
+	test.reset()
+}
+
+// Целочисленная установка секунд
+func (sf *tester) set() {
+	sf.t.Logf("=set=\n")
+	sf.setBad1()
+	sf.setGood1()
+}
+
+func (sf *tester) setGood1() {
+	sf.t.Logf("=setGood1=\n")
+	if err := sf.ph.Set(26); err != nil {
+		sf.t.Errorf("setGood1(): err=\n\t%v\n", err)
+	}
+	if strHour := sf.ph.String(); strHour != "26" {
+		sf.t.Errorf("setGood1(): strHour(%q)!='26'\n", strHour)
+	}
+}
+
+// Отрицательные секунды
+func (sf *tester) setBad1() {
+	sf.t.Logf("=setBad1=\n")
+	if err := sf.ph.Set(-1); err == nil {
+		sf.t.Errorf("setBad1(): err==nil\n")
+	}
+	if strHour := sf.ph.String(); strHour != "59" {
+		sf.t.Errorf("setBad1(): strHour(%q)!='59'\n", strHour)
+	}
+}
+
+// Сброс секунд в ноль
+func (sf *tester) reset() {
+	sf.t.Logf("=reset=\n")
+	sf.ph.Reset()
+	if strSec := sf.ph.String(); strSec != "00" {
+		sf.t.Errorf("setGood2(): strSec(%q)!='00'\n", strSec)
+	}
+}
+
+// Устанавливает значение секунд
+func (sf *tester) parse() {
+	sf.t.Logf("=parse=\n")
+	sf.parseBad1()
+	sf.parseBad2()
+	sf.parseBad3()
+	sf.parseGood1()
+	sf.parseGood2()
+}
+
+// Установка правильных секунд
+func (sf *tester) parseGood2() {
+	sf.t.Logf("=parseGood2=\n")
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			sf.t.Errorf("parseGood2(): panic=\n\t%v\n", _panic)
+		}
+	}()
+	sf.ph.Parse("59")
+	if strHour := sf.ph.String(); strHour != "59" {
+		sf.t.Errorf("parseGood2(): strHour(%q)!='867'\n", strHour)
+	}
+}
+
+// Установка правильных часов
+func (sf *tester) parseGood1() {
+	sf.t.Logf("=parseGood1=\n")
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			sf.t.Errorf("parseGood1(): panic=\n\t%v\n", _panic)
+		}
+	}()
+	sf.ph.Parse("8")
+	if strHour := sf.ph.String(); strHour != "08" {
+		sf.t.Errorf("parseGood1(): strHour(%q)!='08'\n", strHour)
+	}
+}
+
+// Установка больших часов
+func (sf *tester) parseBad3() {
+	sf.t.Logf("=parseBad3=\n")
+	if err := sf.ph.Parse("61"); err == nil {
+		sf.t.Errorf("parseBad3(): err==nil\n")
+	}
+}
+
+// Установка отрицательных часов
+func (sf *tester) parseBad2() {
+	sf.t.Logf("=parseBad2=\n")
+	if err := sf.ph.Parse("-1"); err == nil {
+		sf.t.Errorf("parseBad2(): err==nil\n")
+	}
+}
+
+// Установка не часов
+func (sf *tester) parseBad1() {
+	sf.t.Logf("=parseBad1=\n")
+	if err := sf.ph.Parse("abc"); err == nil {
+		sf.t.Errorf("parseBad1(): err==nil\n")
+	}
+}
+
+// Создание парсера часов
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.ph = NewParseSec()
+	if sf.ph == nil {
+		sf.t.Errorf("create(): parseHour==nil\n")
+	}
+	if hour := sf.ph.Get(); hour != 0 {
+		sf.t.Errorf("create(): hour(%v)!=0\n", hour)
+	}
+	if strHour := sf.ph.String(); strHour != "00" {
+		sf.t.Errorf("create(): strHour(%q)!='00'\n", strHour)
+	}
+}

+ 105 - 0
pkg/components/parsetime/parsetime.go

@@ -0,0 +1,105 @@
+package parsetime
+
+import (
+	"fmt"
+	"strings"
+	"sync"
+
+	"wartank/pkg/components/parsetime/parsehour"
+	"wartank/pkg/components/parsetime/parsemin"
+	"wartank/pkg/components/parsetime/parsesec"
+)
+
+/*
+	Выковыривает из строки время и потокобезопасно хранит его
+*/
+
+// ParseTime -- потокобезопасный ковырятор строки времени
+type ParseTime struct {
+	intVal int                  // Числовое значение хранимого времени
+	hour   *parsehour.ParseHour // Часы метки времени
+	min    *parsemin.ParseMin   // Минуты метки времени
+	sec    *parsesec.ParseSec   // Секунды метки времени
+
+	block sync.RWMutex
+}
+
+// NewParseTime -- возвращает новый *ParseTime
+func NewParseTime() *ParseTime {
+	return &ParseTime{
+		hour: parsehour.NewParseHour(),
+		min:  parsemin.NewParseMin(),
+		sec:  parsesec.NewParseSec(),
+	}
+}
+
+// GetInt -- возвращает хранимоесчётчика времени
+func (sf *ParseTime) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.intVal
+}
+
+// Parse -- разбирает строковое представление на части
+func (sf *ParseTime) Parse(strTime string) error {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if strTime == "" {
+		return fmt.Errorf("CountTime.Set(): val is empty")
+	}
+	// Разбить время, перевести в секунды
+	lstTime := strings.Split(strTime, ":")
+	if len(lstTime) == 1 { // Только секунды
+		sf.hour.Reset()
+		sf.min.Reset()
+		if err := sf.sec.Parse(lstTime[0]); err != nil {
+			return fmt.Errorf("ParseTime(): in parse second only, err=\n\t%w", err)
+		}
+	}
+	if len(lstTime) == 2 { // Минуты, секунды
+		sf.hour.Reset()
+		if err := sf.min.Parse(lstTime[0]); err != nil {
+			return fmt.Errorf("ParseTime(): in parse [min]/sec, err=\n\t%w", err)
+		}
+		if err := sf.sec.Parse(lstTime[1]); err != nil {
+			return fmt.Errorf("ParseTime(): in parse min/[sec], err=\n\t%w", err)
+		}
+	}
+	if len(lstTime) >= 3 { // Есть всё, возможно с левыми полями в конце
+		strHour := lstTime[0]
+		strMin := lstTime[1]
+		strSec := lstTime[2]
+		if err := sf.sec.Parse(strSec); err != nil {
+			return fmt.Errorf("ParseTime(): in parse hour/min/[sec], err=\n\t%w", err)
+		}
+		if err := sf.min.Parse(strMin); err != nil {
+			return fmt.Errorf("ParseTime(): in parse hour/[min]/sec, err=\n\t%w", err)
+		}
+		if err := sf.hour.Parse(strHour); err != nil {
+			return fmt.Errorf("ParseTime(): in parse [hour]/min/sec, err=\n\t%w", err)
+		}
+	}
+	sf.intVal = sf.hour.Get()*3600 + sf.min.Get()*60 + sf.sec.Get()
+	return nil
+}
+
+// Hour -- возвращает хранимые часы
+func (sf *ParseTime) Hour() *parsehour.ParseHour {
+	return sf.hour
+}
+
+// Min -- возвращает хранимые минуты
+func (sf *ParseTime) Min() *parsemin.ParseMin {
+	return sf.min
+}
+
+// Sec -- возвращает хранимые секунды
+func (sf *ParseTime) Sec() *parsesec.ParseSec {
+	return sf.sec
+}
+
+// String -- возвращает хранимое время
+func (sf *ParseTime) String() string {
+	res := sf.hour.String() + ":" + sf.min.String() + ":" + sf.sec.String()
+	return res
+}

+ 164 - 0
pkg/components/parsetime/parsetime_test.go

@@ -0,0 +1,164 @@
+package parsetime
+
+import (
+	"testing"
+)
+
+/*
+	Тест для парсинга времени
+*/
+
+// Тестер для парсера времени
+type tester struct {
+	t    *testing.T
+	pars *ParseTime
+}
+
+func TestParseTime(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.parse()
+}
+
+// Создание парсера
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.pars = NewParseTime()
+	if sf.pars.hour == nil {
+		sf.t.Errorf("create(): hour==nil\n")
+	}
+	if sf.pars.min == nil {
+		sf.t.Errorf("create(): min==nil\n")
+	}
+	if sf.pars.sec == nil {
+		sf.t.Errorf("create(): sec==nil\n")
+	}
+	if val := sf.pars.Get(); val != 0 {
+		sf.t.Errorf("create(): valInt(%d)!=0\n", val)
+	}
+}
+
+// Парсинг строки времени
+func (sf *tester) parse() {
+	sf.t.Logf("=parse=\n")
+	sf.parseBad1()
+	sf.parseSec()
+	sf.parseSecBad1()
+	sf.parseMin()
+	sf.parseMinSecBad1()
+	sf.parseMinMinBad1()
+	sf.parseHour()
+	sf.parseHourSecBad1()
+	sf.parseHourMinBad1()
+	sf.parseHourHourBad1()
+}
+
+func (sf *tester) parseHourHourBad1() {
+	sf.t.Logf("=parseHourHourBad1=\n")
+	if err := sf.pars.Parse("-11:14:54"); err == nil {
+		sf.t.Errorf("parseHourHourBad1(): err==nil\n")
+	}
+	if val := sf.pars.Get(); val != 5820 {
+		sf.t.Errorf("parseHourHourBad1(): valInt(%d)!=5820\n", val)
+	}
+	if hour := sf.pars.Hour().Get(); hour != 1 {
+		sf.t.Errorf("parseHourHourBad1(): hour(%d)!=1\n", hour)
+	}
+	if min := sf.pars.Min().Get(); min != 14 {
+		sf.t.Errorf("parseHourHourBad1(): min(%d)!=14\n", min)
+	}
+	if sec := sf.pars.Sec().Get(); sec != 54 {
+		sf.t.Errorf("parseHourHourBad1(): sec(%d)!=54\n", sec)
+	}
+}
+
+func (sf *tester) parseHourMinBad1() {
+	sf.t.Logf("=parseHourMinBad1=\n")
+	if err := sf.pars.Parse("01:-4:01"); err == nil {
+		sf.t.Errorf("parseHourMinBad1(): err==nil\n")
+	}
+	if val := sf.pars.Get(); val != 5820 {
+		sf.t.Errorf("parseHourMinBad1(): valInt(%d)!=5820\n", val)
+	}
+}
+
+func (sf *tester) parseHourSecBad1() {
+	sf.t.Logf("=parseHourSecBad1=\n")
+	if err := sf.pars.Parse("01:37:a"); err == nil {
+		sf.t.Errorf("parseHourSecBad1(): err==nil\n")
+	}
+	if val := sf.pars.Get(); val != 5820 {
+		sf.t.Errorf("parseHourSecBad1(): valInt(%d)!=5820\n", val)
+	}
+}
+
+func (sf *tester) parseHour() {
+	sf.t.Logf("=parseHour=\n")
+	if err := sf.pars.Parse("01:37:00"); err != nil {
+		sf.t.Errorf("parseHour(): err=\n\t%v\n", err)
+	}
+	if val := sf.pars.Get(); val != 5820 {
+		sf.t.Errorf("parseHour(): valInt(%d)!=5820\n", val)
+	}
+}
+
+func (sf *tester) parseMinMinBad1() {
+	sf.t.Logf("=parseMinMinBad1=\n")
+	if err := sf.pars.Parse("60:25"); err == nil {
+		sf.t.Errorf("parseMinMinBad1(): err==nil\n")
+	}
+	if val := sf.pars.Get(); val != 444 {
+		sf.t.Errorf("parseMinMinBad1(): valInt(%d)!=444\n", val)
+	}
+}
+
+func (sf *tester) parseMinSecBad1() {
+	sf.t.Logf("=parseMinSecBad1=\n")
+	if err := sf.pars.Parse("07:-1"); err == nil {
+		sf.t.Errorf("parseMinSecBad1(): err==nil\n")
+	}
+	if val := sf.pars.Get(); val != 7*60+24 {
+		sf.t.Errorf("parseMinSecBad1(): valInt(%d)!=7*60+24\n", val)
+	}
+}
+
+func (sf *tester) parseMin() {
+	sf.t.Logf("=parseMin=\n")
+	if err := sf.pars.Parse("07:24"); err != nil {
+		sf.t.Errorf("parseMin(): err=\n\t%v\n", err)
+	}
+	if val := sf.pars.Get(); val != 7*60+24 {
+		sf.t.Errorf("parseMin(): valInt(%d)!=7*60+24\n", val)
+	}
+}
+
+// Слишком большие секунды
+func (sf *tester) parseSecBad1() {
+	sf.t.Logf("=parseSecBad1=\n")
+	if err := sf.pars.Parse("60"); err == nil {
+		sf.t.Errorf("parseSecBad1(): err==nil\n")
+	}
+	if val := sf.pars.Get(); val != 28 {
+		sf.t.Errorf("parseSecBad1(): valInt(%d)!=28\n", val)
+	}
+}
+
+func (sf *tester) parseSec() {
+	sf.t.Logf("=parseSec=\n")
+	if err := sf.pars.Parse("28"); err != nil {
+		sf.t.Errorf("parseSec(): err=\n\t%v\n", err)
+	}
+	if val := sf.pars.Get(); val != 28 {
+		sf.t.Errorf("parseSec(): valInt(%d)!=28\n", val)
+	}
+}
+
+// Нет строки для парсинга
+func (sf *tester) parseBad1() {
+	sf.t.Logf("=parseBad1=\n")
+	if err := sf.pars.Parse(""); err == nil {
+		sf.t.Errorf("parseBad1(): err==nil\n")
+	}
+}

+ 39 - 0
pkg/components/safebool/safebool.go

@@ -0,0 +1,39 @@
+package safebool
+
+import "sync"
+
+/*
+	Потокобезопасный булевый признак.
+*/
+
+// SafeBool -- потокобезопасный булевый признак
+type SafeBool struct {
+	val   bool
+	block sync.RWMutex
+}
+
+// NewSafeBool -- возвращает новый *SafeBool
+func NewSafeBool() *SafeBool {
+	return &SafeBool{}
+}
+
+// Get -- возвращает хранимое значение булевого признака
+func (sf *SafeBool) Get() bool {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое значение булевого признака
+func (sf *SafeBool) Set() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = true
+}
+
+// Reset -- сбарсывает хранимое значение булевого признака
+func (sf *SafeBool) Reset() {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = false
+}

+ 46 - 0
pkg/components/safebool/safebool_test.go

@@ -0,0 +1,46 @@
+package safebool
+
+import "testing"
+
+/*
+	Тест для потокобезопасного булевого признака
+*/
+
+// Тестер для булевого признака
+type tester struct {
+	t  *testing.T
+	sb *SafeBool
+}
+
+func TestSafeBool(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.set()
+}
+
+// Присовение и сброс признака
+func (sf *tester) set() {
+	sf.t.Logf("=set=\n")
+	sf.sb.Set()
+	if isReset := sf.sb.Get(); !isReset {
+		sf.t.Errorf("create(): isReset==true\n")
+	}
+	sf.sb.Reset()
+	if isSet := sf.sb.Get(); isSet {
+		sf.t.Errorf("create(): isSet==true\n")
+	}
+}
+
+// Создание признака
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.sb = NewSafeBool()
+	if sf.sb == nil {
+		sf.t.Errorf("create(): safeBool==nil\n")
+	}
+	if isSet := sf.sb.Get(); isSet {
+		sf.t.Errorf("create(): isSet==true\n")
+	}
+}

+ 32 - 0
pkg/components/safeint/safeint.go

@@ -0,0 +1,32 @@
+package safeint
+
+import "sync"
+
+/*
+	Потокобезопасное целое
+*/
+
+// SafeInt -- потокобезопасное целое
+type SafeInt struct {
+	val   int
+	block sync.RWMutex
+}
+
+// NewSafeInt -- возвращает новый *SafeInt
+func NewSafeInt() *SafeInt {
+	return &SafeInt{}
+}
+
+// Get -- возвращает хранимое значение
+func (sf *SafeInt) Get() int {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое значение
+func (sf *SafeInt) Set(val int) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = val
+}

+ 44 - 0
pkg/components/safeint/safeint_test.go

@@ -0,0 +1,44 @@
+package safeint
+
+import (
+	"testing"
+)
+
+/*
+	Тест для безопасного целого
+*/
+
+// Тестер для безопасного целого
+type tester struct {
+	t  *testing.T
+	si *SafeInt
+}
+
+func TestSafeInt(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.set()
+}
+
+// Установка значения
+func (sf *tester) set() {
+	sf.t.Logf("=set=\n")
+	sf.si.Set(-8)
+	if val := sf.si.Get(); val != -8 {
+		sf.t.Errorf("create(): val(%d)!=-8\n", val)
+	}
+}
+
+// Создание целого
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.si = NewSafeInt()
+	if sf.si == nil {
+		sf.t.Errorf("create(): safeInt==nil\n")
+	}
+	if val := sf.si.Get(); val != 0 {
+		sf.t.Errorf("create(): val(%d)!=0\n", val)
+	}
+}

+ 32 - 0
pkg/components/safestring/safestring.go

@@ -0,0 +1,32 @@
+package safestring
+
+import "sync"
+
+/*
+	Потокобезопасная строка
+*/
+
+// SafeString -- потокобезопасная строка
+type SafeString struct {
+	val   string
+	block sync.RWMutex
+}
+
+// NewSafeString -- возвращает новый *SafeString
+func NewSafeString() *SafeString {
+	return &SafeString{}
+}
+
+// Get -- возвращает хранимое значение
+func (sf *SafeString) Get() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Set -- устанавливает хранимое значение
+func (sf *SafeString) Set(strVal string) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = strVal
+}

+ 44 - 0
pkg/components/safestring/safestring_test.go

@@ -0,0 +1,44 @@
+package safestring
+
+import (
+	"testing"
+)
+
+/*
+	Тест для безопасной строки
+*/
+
+// Тестер для безопасной строки
+type tester struct {
+	t  *testing.T
+	si *SafeString
+}
+
+func TestSafeString(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.set()
+}
+
+// Установка значения
+func (sf *tester) set() {
+	sf.t.Logf("=set=\n")
+	sf.si.Set("test_str")
+	if val := sf.si.Get(); val != "test_str" {
+		sf.t.Errorf("create(): val(%s)!='test_str'\n", val)
+	}
+}
+
+// Создание целого
+func (sf *tester) create() {
+	sf.t.Logf("=create=\n")
+	sf.si = NewSafeString()
+	if sf.si == nil {
+		sf.t.Errorf("create(): safeString==nil\n")
+	}
+	if val := sf.si.Get(); val != "" {
+		sf.t.Errorf("create(): val(%s)!=``\n", val)
+	}
+}

+ 61 - 0
pkg/components/section/section.go

@@ -0,0 +1,61 @@
+package section
+
+import (
+	"fmt"
+	"log"
+
+	"wartank/pkg/components/counttime"
+	"wartank/pkg/components/lststring"
+	"wartank/pkg/components/section/sectionmode"
+	"wartank/pkg/types"
+)
+
+/*
+	Базовый объект игры -- секция.
+	Основа множества зданий игры.
+*/
+
+// Section -- секция игры
+type Section struct {
+	countDown types.ICountTime
+	lstString *lststring.LstString
+	mode      types.IMode
+}
+
+// NewSection -- возвращает новый *Section
+func NewSection(app types.IServer, strControl string) (*Section, error) {
+	log.Printf("NewSection(): strControl=%q\n", strControl)
+	sf := &Section{
+		countDown: counttime.NewCountTime(app),
+		mode:      sectionmode.NewSectionMode(),
+	}
+	var err error
+	sf.lstString, err = lststring.NewLstString(strControl)
+	if err != nil {
+		return nil, fmt.Errorf("NewSection(): in create *LstString, err=\n\t%w", err)
+	}
+	return sf, nil
+}
+
+// Update -- обновляет список строк секции
+func (sf *Section) Update(lstString []string) error {
+	if err := sf.lstString.Set(lstString); err != nil {
+		return fmt.Errorf("Section.Update(): in set lstString, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// GetLst -- возвращает список строк арсенала
+func (sf *Section) GetLst() []string {
+	return sf.lstString.Get()
+}
+
+// CountDown -- возвращает объект оставшегося времени
+func (sf *Section) CountDown() types.ICountTime {
+	return sf.countDown
+}
+
+// ModeCurrent -- текущий режим работы
+func (sf *Section) ModeCurrent() types.IMode {
+	return sf.mode
+}

+ 51 - 0
pkg/components/section/sectionmode/sectionmode.go

@@ -0,0 +1,51 @@
+package sectionmode
+
+import (
+	"sync"
+)
+
+/*
+	Режим работы секции
+*/
+
+// SectionMode -- режим работы секции
+type SectionMode struct {
+	val   string // Имя режима
+	work  string // Имя работы
+	block sync.RWMutex
+}
+
+// NewSectionMode -- возвращает новый *SectionMode
+func NewSectionMode() *SectionMode {
+	return &SectionMode{
+		val: "start",
+	}
+}
+
+// Set -- устанавливает режим работы
+func (sf *SectionMode) Set(val string) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.val = val
+}
+
+// Get -- возвращает хранимый режим работы
+func (sf *SectionMode) Get() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.val
+}
+
+// Work -- возвращает хранимое имя работы
+func (sf *SectionMode) Work() string {
+	sf.block.RLock()
+	defer sf.block.RUnlock()
+	return sf.work
+}
+
+// WorkSet -- устанавливает хранимое имя работы
+func (sf *SectionMode) WorkSet(work string) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	sf.work = work
+}

+ 145 - 0
pkg/components/sectionnet/netclient/netclient.go

@@ -0,0 +1,145 @@
+package netclient
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"log"
+
+	// "log"
+	"net/http"
+	"strings"
+	"sync"
+	"time"
+
+	"wartank/pkg/components/sectionnet/netstat"
+	"wartank/pkg/types"
+)
+
+/*
+	Объект сетевого соединения
+*/
+
+// Ответ после запроса
+type response struct {
+	lstString []string
+	err       error
+}
+
+// NetClient -- объект сетевого соединения
+type NetClient struct {
+	server   types.IServer
+	conn     *http.Client
+	stat     *netstat.NetStat
+	ctx      context.Context
+	chRes    chan *response // Канал ответа от http-клиента
+	fnCancel func()         // Отмена контекста приложения
+	block    sync.Mutex
+}
+
+// NewNetClient -- возвращает сетевого клиента
+func NewNetClient(server types.IServer, bot types.IServBot) *NetClient {
+	sf := &NetClient{
+		server:   server,
+		conn:     bot.BotNet().Conn(),
+		stat:     netstat.NewNetStat(server),
+		chRes:    make(chan *response, 2),
+		ctx:      context.Background(),
+		fnCancel: server.CancelApp,
+	}
+	return sf
+}
+
+// Теневая функция на блокировку
+func (sf *NetClient) Get(strLink string) (lstString []string, err error) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	ctxCancel, fnCancel := context.WithTimeout(sf.ctx, time.Second*7)
+	defer func() {
+		fnCancel()
+		if _panic := recover(); _panic != nil {
+			err = fmt.Errorf("NetClient.get(): перехвачена паника для URL(%v), panic=%v", strLink, _panic)
+			lstString = nil
+			time.Sleep(time.Millisecond * 250) // Чтобы не насиловать не работающую сеть
+		}
+	}()
+	go sf.get(strLink)
+	select {
+	case <-ctxCancel.Done(): // Таймаут по ожиданию
+		err = fmt.Errorf("NetClient.get(): таймаут ожидания ответа")
+		sf.fnCancel()
+		return nil, err
+	case resp := <-sf.chRes: // Получен ответ
+		if resp.err != nil {
+			return nil, resp.err
+		}
+		return resp.lstString, nil
+	}
+}
+
+// Внутренний вызов для сокрытия под общей блокировкой
+func (sf *NetClient) get(strLink string) {
+	req, err := http.NewRequest("GET", strLink, nil)
+	if err != nil {
+		log.Fatalln(err)
+	}
+	req.Header.Set("User-Agent", "Mozilla Firefox 86.0")
+	httpResp, err := sf.conn.Do(req)
+	resp := &response{}
+	if err != nil {
+		resp.err = fmt.Errorf("NetClient.get().fnGet(): при выполнении GET-запроса, err=\n\t%w", err)
+		sf.stat.AddByte(-1) // Фиксируем ошибку
+		return
+	}
+	defer sf.endGet(strLink, httpResp, resp)
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.err = fmt.Errorf("NetClient.get().fnGet(): code=%v, status=%v", httpResp.StatusCode, httpResp.Status)
+		sf.stat.AddByte(-1) // Фиксируем ошибку
+		return
+	}
+	binData, err := io.ReadAll(httpResp.Body)
+	if err != nil {
+		resp.err = fmt.Errorf("NetClient.get().fnGet(): при чтении тела ответа, err=\n\t%w", err)
+		sf.stat.AddByte(-1) // Фиксируем ошибку
+		return
+	}
+	if len(binData) == 0 {
+		resp.err = fmt.Errorf("NetClient.get().fnGet(): пустое тело ответа, err=\n\t%w", err)
+		sf.stat.AddByte(-1) // Фиксируем ошибку
+		return
+	}
+	lenData := len(binData) + len(strLink)
+	sf.stat.AddByte(lenData)
+
+	lstString := strings.Split(string(binData), "\n")
+	if len(lstString) == 0 {
+		resp.err = fmt.Errorf("NetClient.get().fnGet(): lstString is empty")
+		sf.stat.AddByte(-1) // Фиксируем ошибку
+		return
+	}
+	resp.lstString = lstString
+}
+
+// Вызывается по завершению вызова
+func (sf *NetClient) endGet(strLink string, httpResp *http.Response, resp *response) {
+	defer func() {
+		if _panic := recover(); _panic != nil {
+			// log._rintf("NetClient.sndGet(): strLink='%v', panic=%v\n", strLink, _panic)
+			sf.fnCancel()
+		}
+	}()
+	err := httpResp.Body.Close()
+	if err != nil {
+		switch resp.err == nil {
+		case true:
+			resp.err = fmt.Errorf("NetClient.get().fnGet(): ошибка при закрытии запроса URL(%v), err=\n\t%w", strLink, err)
+		case false:
+			resp.err = fmt.Errorf("NetClient.get().fnGet(): ошибка при закрытии запроса URL(%v), err=\n\t%v\n\toldErr=\n\t%w", strLink, err, resp.err)
+		}
+		resp.lstString = nil
+
+		sf.stat.AddByte(-1) // Отметим ошибку закрытия клиента
+	}
+	sf.chRes <- resp
+}

+ 84 - 0
pkg/components/sectionnet/netstat/netstat.go

@@ -0,0 +1,84 @@
+package netstat
+
+import (
+	// "log"
+	// "log"
+	"sync"
+	"time"
+
+	"wartank/pkg/types"
+)
+
+/*
+	Сетевая статистика для сетевого соединения
+*/
+
+// NetStat -- статистика сетевого соединения
+type NetStat struct {
+	server       types.IServer
+	countByte    int      // Счётчик переданных/полученных байтов
+	totalByte    int      // Сколько всего байт передано вообще
+	totalMinut   int      // Сколько всего минут работала передача
+	countRequest int      // Число запросов
+	countErr     int      // Число зафиксированных ошибок
+	chTick       chan int // Сигналы времени
+	block        sync.Mutex
+}
+
+// NewNetStat -- возвращает новый *NetStat
+func NewNetStat(server types.IServer) *NetStat {
+	sf := &NetStat{
+		server: server,
+		chTick: make(chan int, 2),
+	}
+	go sf.run()
+	go sf.makeTick()
+	return sf
+}
+
+// Тикер меток времени
+func (sf *NetStat) makeTick() {
+	defer close(sf.chTick)
+	for {
+		select {
+		case <-sf.server.Done():
+			return
+		default:
+			time.Sleep(time.Second * 5 * 60)
+			sf.totalMinut += 5
+			sf.chTick <- 1
+		}
+	}
+}
+
+// Главный цикл работы статистики
+func (sf *NetStat) run() {
+	for range sf.chTick {
+		select {
+		case <-sf.server.Done():
+			return
+		case <-sf.chTick:
+			sf.block.Lock()
+			// mbyte := float32(sf.totalByte) / float32(sf.totalMinut*60) * (3600 * 24 * 30.5) / (1024 * 1024)
+			// log._rintf("INFO NetStat.run(): запросы=%0.2f/сек\tтраф0=%0.2f бит/сек\tтраф1=%0.2f МБ/мес\tошибки=%v\n",
+			// float32(sf.countRequest)/300, float32(sf.countByte*8)/300, mbyte, sf.countErr)
+			sf.countByte = 0
+			sf.countRequest = 0
+			sf.block.Unlock()
+		}
+	}
+}
+
+// AddByte -- увеличивает счётчик запросов и байтов на передачу/приём
+func (sf *NetStat) AddByte(val int) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	if val < 0 {
+		// log._rintf("ERRO NatStat.AddByte(): val(%v)<0\n", val)
+		sf.countErr++
+		return
+	}
+	sf.countByte += val
+	sf.totalByte += val
+	sf.countRequest++
+}

+ 88 - 0
pkg/components/sectionnet/sectionnet.go

@@ -0,0 +1,88 @@
+package sectionnet
+
+import (
+	"fmt"
+	"log"
+	"strings"
+	"sync"
+
+	// "time"
+
+	"wartank/pkg/types"
+)
+
+/*
+	Базовый тип для сетевых секций
+*/
+
+// SectionNet -- базовый тип для сетевых секций
+type SectionNet struct {
+	client  types.INetClient
+	section types.ISection
+	strUrl  string
+	block   sync.Mutex
+}
+
+// NewSectionNet -- возвращает новый *SectionNet
+func NewSectionNet(server types.IServer, bot types.IServBot, section types.ISection, strUrl string) *SectionNet {
+	log.Printf("NewSectionNet(): url=%q\n", strUrl)
+	{ // Предусловия
+		if server == nil {
+			panic("NewSectionNet(): IServer == nil")
+		}
+		if bot == nil {
+			panic("NewSectionNet(): IServBot == nil")
+		}
+		if section == nil {
+			panic("NewSectionNet(): ISection == nil")
+		}
+		if strUrl == "" {
+			panic("NewSectionNet(): strUrl is empty")
+		}
+	}
+	sf := &SectionNet{
+		section: section,
+		strUrl:  strUrl,
+		client:  bot.BotNet().Net(),
+	}
+	return sf
+}
+
+func (sf *SectionNet) Run() {
+
+}
+
+// Обновляет список строк
+func (sf *SectionNet) UpdateLst(name string) (err error) {
+	if sf == nil {
+		return
+	}
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	// FIXME: попытка разобраться, что за фигня творится
+	// time.Sleep(time.Millisecond * 500)
+	// log._rintf("INFO SectionNet.UpdateLst(): name=%s\tlink=%v\n", name, sf.strUrl)
+	lstString, err := sf.client.Get(sf.strUrl)
+	if err != nil {
+		return fmt.Errorf("SectionNet.UpdateLst(): in make request, err=\n\t%w", err)
+	}
+	if err := sf.section.Update(lstString); err != nil {
+		return fmt.Errorf("SectionNet.UpdateLst(): in update ISection, err=\n\t%w", err)
+	}
+	return nil
+}
+
+// Get -- выполняет GET-запрос по указанному URL
+func (sf *SectionNet) Get(strLink string) (lstString []string, err error) {
+	sf.block.Lock()
+	defer sf.block.Unlock()
+	// log._rintf("INFO SectionNet.Get(): link=%v\n", sf.strUrl)
+	if !strings.Contains(strLink, sf.strUrl) {
+		return nil, fmt.Errorf("SectionNet.Get(): strLink(%v) не содержит strUrl(%v)", strLink, sf.strUrl)
+	}
+	lstString, err = sf.client.Get(strLink)
+	if err != nil {
+		return nil, fmt.Errorf("SectionNet.Get(): err=\n\t%v", err)
+	}
+	return lstString, nil
+}

+ 130 - 0
pkg/components/sound/sound.go

@@ -0,0 +1,130 @@
+package sound
+
+import (
+	"fmt"
+	"os/exec"
+)
+
+/*
+	Играет простые мелодии в кончоле при наступлении каких-либо событий
+*/
+
+const ( // Ноты для проигрывания, Гц
+	do  = 261.63
+	re  = 293.66
+	mi  = 329.63
+	fa  = 349.23
+	sol = 392.00
+	la  = 440.00
+	si  = 493.88
+)
+
+// Battle -- играет звук начала битвы
+func Battle() {
+	play(do)
+	play(re)
+	play(re)
+}
+
+// DivWar -- играет звук начала сражения дивизий
+func DivWar() {
+	play(mi)
+	play(fa)
+	play(fa)
+}
+
+// MineForce -- играет звук ускорения апгрейда шахты
+func MineForce() {
+	play(sol)
+	play(sol)
+	play(sol)
+}
+
+// ArsenalForce -- играет звук ускорения апгрейда арсенала
+func ArsenalForce() {
+	play(re)
+	play(re)
+	play(re)
+}
+
+// BankTake -- играет звук забрать серебро
+func BankTake() {
+	play(si)
+	play(la)
+	play(sol)
+}
+
+// BankForce -- играет звук ускорения апгрейда банка
+func BankForce() {
+	play(fa)
+	play(fa)
+	play(fa)
+}
+
+// Polygon -- играет звук работы полигона
+func Polygon() {
+	play(la)
+	play(re)
+	play(mi)
+}
+
+// Shot -- звук выстрела
+func Shot() {
+	cmd := exec.Command("./beep", "-f=1200.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=1300.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=1400.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+}
+
+// Repair -- звук восстановления здоровья
+func Repair() {
+	cmd := exec.Command("./beep", "-f=1500.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=1750.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=2000.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+}
+
+// HelthDown -- звук потери здоровья
+func HelthDown() {
+	cmd := exec.Command("./beep", "-f=800.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=700.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=6000.0", "-t=50", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+}
+
+// Manver -- звук выполнения манёвра
+func Manevr() {
+	cmd := exec.Command("./beep", "-f=150.0", "-t=25", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=150.0", "-t=25", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=150.0", "-t=25", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+}
+
+func play(note float32) {
+	cmd := exec.Command("./beep", "-f="+fmt.Sprintf("%0.2f", note), "-t=100", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+	cmd = exec.Command("./beep", "-f=0", "-t=20", "-v=25")
+	_ = cmd.Start()
+	_ = cmd.Wait()
+}

+ 67 - 0
pkg/components/wrag/wrag.go

@@ -0,0 +1,67 @@
+package wrag
+
+import (
+	"strconv"
+	"strings"
+	"wartank/pkg/types"
+
+	"github.com/sirupsen/logrus"
+)
+
+/*
+	Танк враг на битве, сражении, дуэли, войне
+*/
+
+// Wrag -- объект врага
+type Wrag struct {
+	app    types.IServer
+	health int // здоровье вражины
+}
+
+// NewWrag -- возвращает новый объект врага
+func NewWrag(app types.IServer, lstBattle []string) *Wrag {
+	sf := &Wrag{
+		app: app,
+	}
+	sf.update(lstBattle)
+	return sf
+}
+
+// Обновляет вражину
+func (sf *Wrag) update(lstBattleOn []string) {
+	// <img class="tank-img" src="/tankimg?c=2&amp;k=1&amp;m=0-2,1-2,2-0,3-2,5-2,6-0&amp;t=png" alt="Тень Брата">
+	var (
+		ind    int
+		strOut string
+		isFind bool
+	)
+	for ind, strOut = range lstBattleOn {
+		if strings.Contains(strOut, `<img class="tank-img" src="/`) {
+			// Убедиться, что это не свой танк
+			if strings.Contains(strOut, "prospero tank") {
+				continue
+			}
+			isFind = true
+			break
+		}
+	}
+	if !isFind { // Не нашёл метку врага
+		logrus.Errorf("Wrag.update(): не нашёл имя врага")
+		sf.health = 800
+		return
+	}
+	// Вражина найдена, ищем настоящее здоровье
+	ind += 13
+	strOut = lstBattleOn[ind]
+	lstHealth := strings.Split(strOut, `<span>`)
+	strHealth := lstHealth[1]
+	lstHealth = strings.Split(strHealth, `</span>`)
+	strHealth = lstHealth[0]
+	iHealth, err := strconv.Atoi(strHealth)
+	if err != nil {
+		logrus.WithError(err).Errorf("Wrag.update(): здоровье(%v) не число", strHealth)
+		sf.health = 800
+		return
+	}
+	sf.health = iHealth
+}

+ 16 - 0
pkg/cons/cons.go

@@ -0,0 +1,16 @@
+package cons
+
+/*
+	Содержит константы для работы
+*/
+
+const (
+	Delay100 = 100 // Задержка в миллисекундах для битвы, сражения, войны
+	Delay500 = 500 // Задержка в миллисекундах для сражения (100 мсек -- слишком часто)
+	SelfName = "WarTank"
+)
+
+const (
+	TypeMsgResource = 1 // Тип сообщения -- ресурсы
+	TypeMsgArsenal  = 2 // Тип сообщения ресурсы
+)

+ 77 - 0
pkg/mock/mockapp/mockapp.go

@@ -0,0 +1,77 @@
+package mockapp
+
+import (
+	"context"
+	"sync"
+	"wartank/pkg/mock/mockkernel"
+	"wartank/pkg/types"
+)
+
+type MockApp struct {
+	*mockkernel.MockKernel
+	fnCancel func()
+	ctx      context.Context
+	bot      types.IWarBot
+	store    types.IStore
+	block    *sync.RWMutex
+	ServWeb_ types.IServWeb
+}
+
+func NewMockApp() *MockApp {
+	ctxBg := context.Background()
+	ctx, fnCancel := context.WithCancel(ctxBg)
+	sf := &MockApp{
+		MockKernel: mockkernel.NewMockKernel(),
+		ctx:        ctx,
+		fnCancel:   fnCancel,
+		block:      &sync.RWMutex{},
+	}
+	return sf
+}
+
+func (sf *MockApp) Store() types.IStore {
+	return sf.store
+}
+
+func (sf *MockApp) Bot() types.IWarBot {
+	return sf.bot
+}
+
+func (sf *MockApp) Angar() types.IAngar {
+	return nil
+}
+
+func (sf *MockApp) CtxApp() context.Context {
+	return sf.ctx
+}
+
+func (sf *MockApp) GuiWeb() types.IGuiWeb {
+	return nil
+}
+func (sf *MockApp) Tank() types.ITank {
+	return nil
+}
+
+func (sf *MockApp) Run() error {
+	return nil
+}
+
+func (sf *MockApp) CancelApp() {
+	go sf.fnCancel()
+}
+
+func (sf *MockApp) Block() *sync.RWMutex {
+	return sf.block
+}
+
+func (sf *MockApp) NetClient() types.IBotNet {
+	return nil
+}
+
+func (sf *MockApp) ServWeb() types.IServWeb {
+	return sf.ServWeb_
+}
+
+func (sf *MockApp) ServBots() types.IServBots {
+	return nil
+}

+ 146 - 0
pkg/mock/mockenv/mockenv.go

@@ -0,0 +1,146 @@
+package mockenv
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+)
+
+/*
+	Мок-объект для инициализации переменных окружения
+*/
+
+var (
+	vars = []string{
+		"STAGE",
+		"SERVICE_NUM",
+		"BUS_LOCAL_URL",
+		"KAFKA_URL",
+		"COMUNDA_URL",
+	}
+	path, _  = os.Getwd()
+	selfPath = `/notify_service/` // Путь к своему проекту
+	fileEnv  = ".env"
+)
+
+// MockEnv -- мок-объект для переменных окружения
+type MockEnv struct {
+	dict map[string]string
+}
+
+// NewMockEnv -- возвращает новый *MockEnv
+func NewMockEnv() *MockEnv {
+	sf := &MockEnv{
+		dict: make(map[string]string),
+	}
+	return sf
+}
+
+// InitFake -- инициализация фейковыми данными
+func (sf *MockEnv) InitFake() {
+	sf.dict = make(map[string]string)
+	i := 0
+	for _, var_ := range vars {
+		os.Unsetenv(var_)
+		strVal := fmt.Sprint(i)
+		os.Setenv(var_, strVal)
+		if var_ == "STAGE" {
+			os.Unsetenv(var_)
+			os.Setenv(var_, "local")
+		}
+		sf.dict[var_] = strVal
+		i++
+	}
+}
+
+// InitLocal -- читает из локального окружения файл, заполняет переменные
+func (sf *MockEnv) InitLocal() error {
+	strData, err := sf.readFile()
+	if err != nil {
+		return fmt.Errorf("MockEnv.InitLocal(): при чтении файла, err=\n\t%w", err)
+	}
+	dict, err := sf.getPairs(strData)
+	if err != nil {
+		return fmt.Errorf("MockEnv.InitLocal(): при получении пар ключ:значение, err=\n\t%w", err)
+	}
+	sf.initEnv(dict)
+	sf.dict = dict
+	sf.checkStage()
+	return nil
+}
+
+// Проверяет наличие поля STAGE
+func (sf *MockEnv) checkStage() {
+	stage, ok := sf.dict["STAGE"]
+	if !ok {
+		panic(fmt.Errorf("MockEnv.checkStage(): не установлена переменная STAGE"))
+	}
+	if stage != "local" {
+		panic(fmt.Errorf("MockEnv.checkStage(): не локальное окружение, STAGE=%q", stage))
+	}
+}
+
+// Инициализирует все заданные переменные окружения
+func (sf *MockEnv) initEnv(mapEnv map[string]string) {
+	// Здесь не должен быть nil НИКОГДА
+	for key, value := range mapEnv {
+		os.Unsetenv(key)
+		os.Setenv(key, value)
+	}
+}
+
+// Готовит словарь значений
+func (sf *MockEnv) getPairs(strData string) (map[string]string, error) {
+	lstData := strings.Split(strData, "\n")
+	mapEnv := make(map[string]string)
+	for _, line := range lstData {
+		if line == "" {
+			continue
+		}
+		lstPart := strings.Split(line, `="`)
+		if len(lstPart) != 2 {
+			return nil, fmt.Errorf("MockEnv.getPairs(): в параметре %q неправильный формат", line)
+		}
+		key := lstPart[0]
+		if key == "" {
+			return nil, fmt.Errorf("MockEnv.getPairs(): пустой ключ в строке %q", line)
+		}
+		value := lstPart[1]
+		value = value[:len(value)-1]
+		if value == "" {
+			return nil, fmt.Errorf("MockEnv.getPairs(): пустое значение в строке %q", line)
+		}
+		mapEnv[key] = value
+	}
+	if len(mapEnv) == 0 {
+		return nil, fmt.Errorf("MockEnv.getPairs(): len dict == 0")
+	}
+	return mapEnv, nil
+}
+
+// Читает файл окружения
+func (sf *MockEnv) readFile() (string, error) {
+	if !strings.Contains(path, selfPath) {
+		return "", fmt.Errorf("MockEnv.readFile(): неправильный путь проекта")
+	}
+	pt := path
+	lstPath := strings.Split(pt, selfPath)
+	if len(lstPath) != 2 {
+		return "", fmt.Errorf("MockEnv.readFile(): плохой путь к файлу окружения %q", path)
+	}
+	pt = lstPath[0]
+	fileName := pt + selfPath + fileEnv
+	binData, err := ioutil.ReadFile(fileName)
+	if err != nil {
+		return "", fmt.Errorf("MockEnv.readFile(): при чтении файла %q .env, err=%w", fileName, err)
+	}
+	return string(binData), nil
+}
+
+// Unset -- удаляет все переменные окружения сервиса
+func (sf *MockEnv) Unset() {
+	for key := range sf.dict {
+		os.Unsetenv(key)
+	}
+}

+ 256 - 0
pkg/mock/mockenv/mockenv_test.go

@@ -0,0 +1,256 @@
+package mockenv
+
+import (
+	"testing"
+)
+
+/*
+	Тест для отладочных переменных окружения
+*/
+
+// Тестер для переменных окружения
+type tester struct {
+	t  *testing.T
+	me *MockEnv
+}
+
+func TestMockEnv(t *testing.T) {
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	test.initFake()
+	test.readFile()
+	test.getPairs()
+	test.initLocal()
+	test.stage()
+}
+
+// Плохой STAGE
+func (sf *tester) stage() {
+	sf.t.Logf("stage()\n")
+	sf.badStage1()
+	sf.badStage2()
+}
+
+// STAGE имеет неизвестное значение
+func (sf *tester) badStage2() {
+	sf.t.Logf("badStage2()\n")
+	defer func() {
+		if _panic := recover(); _panic == nil {
+			sf.t.Errorf("badStage2(): panic==nil\n")
+		}
+	}()
+	sf.me.dict["STAGE"] = "bad_local"
+	sf.me.checkStage()
+}
+
+// Нет STAGE
+func (sf *tester) badStage1() {
+	sf.t.Logf("badStage1()\n")
+	defer func() {
+		if _panic := recover(); _panic == nil {
+			sf.t.Errorf("badStage1(): panic==nil\n")
+		}
+	}()
+	delete(sf.me.dict, "STAGE")
+	sf.me.checkStage()
+}
+
+// Локальная инициализация
+func (sf *tester) initLocal() {
+	sf.initLocalBad1()
+	sf.initLocalBad2()
+	sf.initLocalBad3()
+	sf.initLocalBad4()
+	sf.initLocalGood1()
+	sf.me.Unset()
+}
+
+func (sf *tester) initLocalGood1() {
+	if err := sf.me.InitLocal(); err != nil {
+		sf.t.Errorf("initLocalGood1(): err=\n\t%v\n", err)
+	}
+}
+
+// Вообще нет STAGE
+func (sf *tester) initLocalBad4() {
+	_fileEnv := fileEnv
+	fileEnv = "./test_data/env_bad2"
+	if err := sf.me.InitLocal(); err == nil {
+		sf.t.Errorf("initLocalBad4(): err==nil\n")
+	}
+	fileEnv = _fileEnv
+}
+
+// Кривое значение STAGE
+func (sf *tester) initLocalBad3() {
+	_fileEnv := fileEnv
+	fileEnv = "./test_data/env_bad1"
+	if err := sf.me.InitLocal(); err == nil {
+		sf.t.Errorf("initLocalBad3(): err==nil\n")
+	}
+	fileEnv = _fileEnv
+}
+
+// Кривое содержимое файла
+func (sf *tester) initLocalBad2() {
+	_fileEnv := fileEnv
+	fileEnv = "./test_data/env_bad"
+	if err := sf.me.InitLocal(); err == nil {
+		sf.t.Errorf("initLocalBad2(): err==nil\n")
+	}
+	fileEnv = _fileEnv
+}
+
+// Кривое имя файла
+func (sf *tester) initLocalBad1() {
+	_fileEnv := fileEnv
+	fileEnv = "4"
+	if err := sf.me.InitLocal(); err == nil {
+		sf.t.Errorf("initLocalBad1(): err==nil\n")
+	}
+	fileEnv = _fileEnv
+}
+
+// Получение пар значений
+func (sf *tester) getPairs() {
+	sf.getPairsBad1()
+	sf.getPairsBad2()
+	sf.getPairsBad3()
+	sf.getPairsBad4()
+	sf.getPairsGood1()
+}
+
+func (sf *tester) getPairsGood1() {
+	dict, err := sf.me.getPairs("\nPARAM=\"123\"\nPARAM2=\"param2\"\n")
+	if err != nil {
+		sf.t.Errorf("getPairsGood1(): err=\n\t%v\n", err)
+	}
+	if dict == nil {
+		sf.t.Errorf("getPairsGood1(): dict == nil\n")
+	}
+}
+
+// Кривая пара
+func (sf *tester) getPairsBad4() {
+	dict, err := sf.me.getPairs("\nPARAM=\"=\"=\"\"\nPARAM2=\"param2\"\n")
+	if err == nil {
+		sf.t.Errorf("getPairsBad4(): err==nil\n")
+	}
+	if dict != nil {
+		sf.t.Errorf("getPairsBad4(): dict != nil\n")
+	}
+}
+
+// Кривое значение
+func (sf *tester) getPairsBad3() {
+	dict, err := sf.me.getPairs("\nPARAM=\"\"\nPARAM2=\"param2\"\n")
+	if err == nil {
+		sf.t.Errorf("getPairsBad3(): err==nil\n")
+	}
+	if dict != nil {
+		sf.t.Errorf("getPairsBad3(): dict != nil\n")
+	}
+}
+
+// Кривой ключ
+func (sf *tester) getPairsBad2() {
+	dict, err := sf.me.getPairs("\n=\"param\"\nPARAM2=\"param2\"\n")
+	if err == nil {
+		sf.t.Errorf("getPairsBad2(): err==nil\n")
+	}
+	if dict != nil {
+		sf.t.Errorf("getPairsBad2(): dict != nil\n")
+	}
+}
+
+// Пустой текст
+func (sf *tester) getPairsBad1() {
+	dict, err := sf.me.getPairs("\n\n\n")
+	if err == nil {
+		sf.t.Errorf("getPairsBad1(): err==nil\n")
+	}
+	if dict != nil {
+		sf.t.Errorf("getPairsBad1(): dict != nil\n")
+	}
+}
+
+// Чтение файла переменных окружения
+func (sf *tester) readFile() {
+	sf.readFileBad1()
+	sf.readFileBad2()
+	sf.readFileBad3()
+	sf.readFileGood1()
+}
+
+func (sf *tester) readFileGood1() {
+	strData, err := sf.me.readFile()
+	if err != nil {
+		sf.t.Errorf("readFileGood1(): err=\n\t%v\n", err)
+	}
+	if strData == "" {
+		sf.t.Errorf("readFileGood1(): strData is empty\n")
+	}
+}
+
+// Кривой путь-2
+func (sf *tester) readFileBad3() {
+	_selfPath := selfPath
+	selfPath = "me/"
+	strData, err := sf.me.readFile()
+	if err == nil {
+		sf.t.Errorf("readFileBad3(): err==nil\n")
+	}
+	if strData != "" {
+		sf.t.Errorf("readFileBad3(): strData not empty\n")
+	}
+	selfPath = _selfPath
+}
+
+// Неправильное имя файла
+func (sf *tester) readFileBad2() {
+	_fileEnv := fileEnv
+	fileEnv = ""
+	strData, err := sf.me.readFile()
+	if err == nil {
+		sf.t.Errorf("readFileBad2(): err==nil\n")
+	}
+	if strData != "" {
+		sf.t.Errorf("readFileBad2(): strData not empty\n")
+	}
+	fileEnv = _fileEnv
+}
+
+// Кривой путь
+func (sf *tester) readFileBad1() {
+	_selfPath := selfPath
+	selfPath = "#"
+	strData, err := sf.me.readFile()
+	if err == nil {
+		sf.t.Errorf("readFileBad1(): err==nil\n")
+	}
+	if strData != "" {
+		sf.t.Errorf("readFileBad1(): strData not empty\n")
+	}
+	selfPath = _selfPath
+}
+
+// Иницилизация фейковыми данными
+func (sf *tester) initFake() {
+	sf.me.InitFake()
+}
+
+// Создание объекта переменных окружения
+func (sf *tester) create() {
+	sf.me = NewMockEnv()
+	if sf.me == nil {
+		sf.t.Errorf("create(): mockEnv is nil\n")
+	}
+	if sf.me.dict == nil {
+		sf.t.Errorf("create(): dict env is nil")
+	}
+	if len_ := len(sf.me.dict); len_ != 0 {
+		sf.t.Errorf("create(): len dict(%v)!=0\n", len_)
+	}
+}

+ 62 - 0
pkg/mock/mockkernel/mockkernel.go

@@ -0,0 +1,62 @@
+package mockkernel
+
+import (
+	"context"
+
+	"wartank/pkg/components/kernel/wgname"
+	"wartank/pkg/types"
+)
+
+/*
+	Мок-объект для имитации объекта ядра приложения.
+	Нужен из-за возникающей циклической ссылки.
+
+	ВНИМАНИЕ!!!!!
+
+	Slog_ присваивать руками !!!!
+*/
+
+// MockKernel -- мок-объект ядра приложения
+type MockKernel struct {
+	ctxBg    context.Context // Глобальный неотменяемый контекст приложения
+	ctxApp   context.Context // глобальный отменяемый контекст приложения
+	fnCancel func()          // Функия глобальной отмены контекста приложения
+	wg       *wgname.WgName  // Объект группового ожидания
+	Slog_    types.ISlog     // Двойной логер
+}
+
+// NewMockKernel -- возвращает новый *MockKernel
+func NewMockKernel() *MockKernel {
+	sf := &MockKernel{
+		ctxBg: context.Background(),
+		wg:    wgname.NewWgName(),
+	}
+	sf.ctxApp, sf.fnCancel = context.WithCancel(sf.ctxBg)
+	return sf
+}
+
+// Wg -- возвращает объект групповой сихнронизации
+func (sf *MockKernel) Wg() types.IWgName {
+	return sf.wg
+}
+
+// CtxApp -- возвращает глобальный контекст приложения
+func (sf *MockKernel) CtxApp() context.Context {
+	return sf.ctxApp
+}
+
+// Done -- возвращает канал отмены контекста приложения
+func (sf *MockKernel) Done() <-chan struct{} {
+	return sf.ctxApp.Done()
+}
+
+// CancelApp -- отменяет глобальный контекст приложения
+func (sf *MockKernel) CancelApp() {
+	// fmt._rintf("MockKernel.CancelApp()\n")
+	sf.fnCancel()
+}
+
+// Slog -- возвращает хранимый логгер
+func (sf *MockKernel) Slog() types.ISlog {
+	return sf.Slog_
+}

+ 51 - 0
pkg/mock/mockkernel/mockkernel_test.go

@@ -0,0 +1,51 @@
+package mockkernel
+
+import (
+	"os"
+	"testing"
+
+	"wartank/pkg/components/kernel/slog"
+)
+
+const (
+	path = "./log"
+)
+
+// Тестер для мок-объекта ядра
+type tester struct {
+	t    *testing.T
+	err  error
+	kern *MockKernel
+}
+
+func TestMockKernel(t *testing.T) {
+	_ = os.RemoveAll(path)
+	test := &tester{
+		t: t,
+	}
+	test.create()
+	_ = os.RemoveAll(path)
+}
+
+// Создание мок-объекта ядра приложения
+func (sf *tester) create() {
+	sf.t.Logf("create()\n")
+	sf.kern = NewMockKernel()
+	if sf.kern == nil {
+		sf.t.Errorf("create(): app==nil\n")
+	}
+	if wg := sf.kern.Wg(); wg == nil {
+		sf.t.Errorf("create(): wg==nil\n")
+	}
+	if ctx := sf.kern.CtxApp(); ctx == nil {
+		sf.t.Errorf("create(): ctx==nil\n")
+	}
+	sf.kern.Slog_, sf.err = slog.NewSlog(sf.kern)
+	if sf.err != nil {
+		sf.t.Errorf("create(): in create ISlog, err=\n\t%v", sf.err)
+	}
+	if slog := sf.kern.Slog(); slog == nil {
+		sf.t.Errorf("create(): slog==nil")
+	}
+	sf.kern.CancelApp()
+}

+ 10 - 0
pkg/net_struct/net_arsenal.go

@@ -0,0 +1,10 @@
+package net_struct
+
+// Arsenal -- стрктура для хранения данных арсенала
+type Arsenal struct {
+	Time  string `json:"t,omitempty"` // Время до окончания производства арсенала
+	Remka int    `json:"r,omitempty"` // Количество ремок
+	Armor int    `json:"a,omitempty"` // Бронебойные снаярды
+	Kumul int    `json:"k,omitempty"` // Кумулятивные снаряды
+	Fugas int    `json:"f,omitempty"` // Фугасные снаряды
+}

+ 11 - 0
pkg/net_struct/net_mine.go

@@ -0,0 +1,11 @@
+package net_struct
+
+// Mine -- стрктура для хранения данных шахты
+type Mine struct {
+	Ruda    int    `json:"r,omitempty"` // Руда шахты
+	Ferrum  int    `json:"f,omitempty"` // Железо шахты
+	Steel   int    `json:"s,omitempty"` // Сталь шахты
+	Plumbum int    `json:"p,omitempty"` // Свинец шахты
+	Time    string `json:"t,omitempty"` // Время производства
+	Mode    string `json:"m,omitempty"` // Режим производства
+}

+ 10 - 0
pkg/net_struct/net_resource.go

@@ -0,0 +1,10 @@
+// package net_struct -- сетевые структуры обмена
+package net_struct
+
+// Resource -- стрктура для хранения ресурсов
+type Resource struct {
+	Glory  int `json:"a,omitempty"` // Слава танка
+	Silver int `json:"s,omitempty"` // Всё серебро танка
+	Gold   int `json:"g,omitempty"` // Всё золото танка
+	Fuel   int `json:"f,omitempty"` // Всё топливо танка
+}

+ 101 - 0
pkg/store/store.go

@@ -0,0 +1,101 @@
+// package store -- хранилище данных бота
+package store
+
+import (
+	"fmt"
+
+	"github.com/syndtr/goleveldb/leveldb"
+	"github.com/syndtr/goleveldb/leveldb/util"
+
+	"wartank/pkg/components/safebool"
+	"wartank/pkg/types"
+)
+
+const (
+	strStore = "store"
+)
+
+// Store -- хранилище данных
+type Store struct {
+	app    types.IServer
+	db     *leveldb.DB
+	isWork *safebool.SafeBool
+}
+
+// NewStore - -возвращает новый объект хранилища
+func NewStore(app types.IServer) (*Store, error) {
+	if app == nil {
+		return nil, fmt.Errorf("NewStore(): IApp is nil")
+	}
+	sf := &Store{
+		app:    app,
+		isWork: safebool.NewSafeBool(),
+	}
+	if err := sf.open(); err != nil {
+		return nil, fmt.Errorf("NewStore(): in open store, err=%w", err)
+	}
+	sf.isWork.Set()
+	go sf.close()
+	sf.app.Wg().Add(strStore)
+	sf.app.Slog().Infof("NewStore(): run")
+	return sf, nil
+}
+
+// Открытие хранилища
+func (sf *Store) open() error {
+	db, err := leveldb.OpenFile("store", nil)
+	if err != nil {
+		return fmt.Errorf("open(): in open store, err=%w", err)
+	}
+	sf.db = db
+	return nil
+}
+
+// Get -- возвращает запись по ключу
+func (sf *Store) Get(key string) (string, error) {
+	if !sf.isWork.Get() {
+		return "", fmt.Errorf("Store.Get():  store is close")
+	}
+	binData, err := sf.db.Get([]byte(key), nil)
+	if err != nil {
+		return "", fmt.Errorf("Store.Get(): in get by key(%q), err=%w", key, err)
+	}
+	return string(binData), nil
+}
+
+// Put -- помещает запись по ключу
+func (sf *Store) Put(key string, val string) error {
+	if !sf.isWork.Get() {
+		return fmt.Errorf("Store.Put():  store is close")
+	}
+	if err := sf.db.Put([]byte(key), []byte(val), nil); err != nil {
+		return fmt.Errorf("Store.Put():  in put key(%q), err=\n\t%w", key, err)
+	}
+	return nil
+}
+
+func (sf *Store) Find(prefix string) (map[string]string, error) {
+	dictOut := make(map[string]string)
+	iter := sf.db.NewIterator(&util.Range{Start: []byte(prefix)}, nil)
+	for iter.Next() {
+		key := iter.Key()
+		val := iter.Value()
+		dictOut[string(key)] = string(val)
+	}
+	iter.Release()
+	err := iter.Error()
+	if err != nil {
+		return nil, fmt.Errorf("Store.Find(): in find by prefix(%q), err=\n\t%w", prefix, err)
+	}
+	return dictOut, nil
+}
+
+func (sf *Store) close() {
+	<-sf.app.Done()
+	if !sf.isWork.Get() {
+		return
+	}
+	sf.app.Wg().Done(strStore)
+	sf.isWork.Reset()
+	sf.app.Slog().Infof("NewStore(): close")
+}

+ 36 - 0
pkg/types/iangar.go

@@ -0,0 +1,36 @@
+package types
+
+/*
+	Интерфейс к ангару
+*/
+
+// IAngar -- интерфейс ангара
+type IAngar interface {
+	ISection
+	// Run -- запускает ангар в работу
+	Run() error
+	// Convoy -- возвращает объект конвоя
+	Convoy() IConvoy
+	// Gold -- возвращает объект золота
+	Gold() IStatParam
+	// Fuel -- возвращает объект топлива
+	Fuel() IStatParam
+	// Level -- возвращает объект уровня игрока
+	Level() IStatParam
+	// Progress -- возвращает прогресс уровня игрока
+	Progress() IStatParam
+	// Online -- возвращает число игроков онлайн
+	Online() IStatParam
+	// SilverOnline -- возвращает заработанное серебро с момента запуска бота
+	SilverOnline() IStatParam
+	// SilverAll -- возвращает всё серебро бота
+	SilverAll() IStatParam
+	// SilverUpdate -- на основе фактического серебра -- обновляет вырабатанное серебро
+	SilverUpdate(silverFact int)
+	// Battle -- возвращает объект сражения
+	Battle() IBattle
+	// Base -- возвращает объект базы
+	Base() IBase
+	// Missions -- возвращает объект миссий
+	Missions() IMissions
+}

+ 18 - 0
pkg/types/iarsenal.go

@@ -0,0 +1,18 @@
+package types
+
+/*
+	Интерфейс к объекту арсенала
+*/
+
+// IArsenal -- интерфейс к объекту арсенала
+type IArsenal interface {
+	ISection
+	// Fugas -- возвращает объект фугасов
+	Fugas() IStatParam
+	// Armor -- возвращает объект бронебойных снарядов
+	Armor() IStatParam
+	// Kumul -- возвращает объект кумулятивных снарядов
+	Kumul() IStatParam
+	// Remka -- возвращает объект ремкомплектов
+	Remka() IStatParam
+}

+ 16 - 0
pkg/types/ibank.go

@@ -0,0 +1,16 @@
+package types
+
+/*
+	Предоставляет интерфейс банка на базе
+*/
+
+// IBank -- интерфейс банка на базе
+type IBank interface {
+	ISection
+	// SilverBot -- сколько серебра заработал бот
+	SilverBot() IStatParam
+	// Mode1 -- объект первого режима производства
+	Mode1() IBankMode
+	// Mode2 -- объект второго режима производства
+	Mode2() IBankMode
+}

+ 15 - 0
pkg/types/ibankmode.go

@@ -0,0 +1,15 @@
+package types
+
+/*
+	Интерфейс к объекту режима производства
+*/
+
+// IBankMode -- режим производства банка
+type IBankMode interface {
+	// Silver -- количество серебра
+	Silver() IStatParam
+	// Time -- время производства
+	Time() string
+	// TimeSet -- устанавливает время производства
+	TimeSet(string)
+}

+ 22 - 0
pkg/types/ibase.go

@@ -0,0 +1,22 @@
+package types
+
+/*
+	Интерфейс к базы игры
+*/
+
+// IBase -- интерфейс к базе игры
+type IBase interface {
+	ISection
+	// Arsenal -- возвращает объект арсенала
+	Arsenal() IArsenal
+	// Bank -- возвращает объект банка
+	Bank() IBank
+	// Polygon -- возвращает объект полигона
+	Polygon() IPolygon
+	// Mine -- возвращает объект шахты
+	Mine() IMine
+	// Battle -- сражение
+	Battle() IBattle
+	// Market -- возвращает рынок
+	Market() IMarket
+}

+ 12 - 0
pkg/types/ibattle.go

@@ -0,0 +1,12 @@
+package types
+
+/*
+	Интерфейс к сражению
+*/
+
+// IBattle -- интерфейс к сражению
+type IBattle interface {
+	ISection
+	// Alarm -- возвращает признак начала битвы
+	Alarm() IStatParam
+}

+ 26 - 0
pkg/types/ibattleon.go

@@ -0,0 +1,26 @@
+package types
+
+import (
+	"context"
+
+	"wartank/server/serv_bots/warbot/angar/battle/battle_worker/battleon/shot/isshot"
+)
+
+/*
+	Интерфейс к сражению
+*/
+
+// IBattleOn -- интерфейс к непосредственному сражению
+type IBattleOn interface {
+	ISection
+	// Net -- возвращает сетевой компонент сражения
+	Net() ISectionNet
+	// SetNeedManevr -- устанавливает признак необходимости манёвра
+	SetNeedManevr()
+	// Masking -- признак запрета на стрельбу
+	Masking() *isshot.IsShot
+	// Ctx -- возвращает контекст битвы
+	Ctx() context.Context
+	// CancelBattle -- вызывает контекст отмены битвы
+	CancelBattle()
+}

+ 12 - 0
pkg/types/ibot.go

@@ -0,0 +1,12 @@
+package types
+
+// IWarBot -- бот игры
+type IWarBot interface {
+	// Angar -- возвращает ангар
+	Angar() IAngar
+	// Tank -- возвращает объект танка
+	Tank() ITank
+}
+
+// IBot -- бот с данными для десктопа
+type IBot interface{}

+ 13 - 0
pkg/types/ibot_cookie.go

@@ -0,0 +1,13 @@
+package types
+
+import (
+	"net/http"
+)
+
+// IBotCookie -- куки серверного бота
+type IBotCookie interface {
+	// Set -- устанавливает куки бота
+	Set(cook []*http.Cookie) error
+	// Get -- возвращает куки бота
+	Get() []*http.Cookie
+}

+ 22 - 0
pkg/types/ibot_net.go

@@ -0,0 +1,22 @@
+package types
+
+import (
+	"net/http"
+
+	"wartank/pkg/components/safebool"
+)
+
+/*
+	Интерфейс к сетевому клиенту
+*/
+
+// IBotNet -- интерфейс к сетевому клиенту
+type IBotNet interface {
+	// IsOnline -- возвращает признак подключенности к интернету
+	IsOnline() *safebool.SafeBool
+	Conn() *http.Client
+	Cookie() IBotCookie
+	Net() INetClient
+	// Login -- пытается зайти по сети
+	Login() error
+}

+ 12 - 0
pkg/types/iconvoy.go

@@ -0,0 +1,12 @@
+package types
+
+/*
+	Объект конвоя
+*/
+
+// IConvoy -- интерфейс к объекту конвоя
+type IConvoy interface {
+	ISection
+	// Glory -- возвращает объект славы конвоя
+	Glory() IStatParam
+}

+ 25 - 0
pkg/types/icounttime.go

@@ -0,0 +1,25 @@
+package types
+
+/*
+	Интерфейс к счётчику оставшегося времени
+*/
+
+// ICountTime -- нтерфейс к счётчику оставшегося времени
+type ICountTime interface {
+	// Parse -- устанавливает интервал времени
+	Parse(string) error
+	// Set -- устанавливает интервал времени из числа секунд
+	Set(int) error
+	// Get -- возвращает оставшееся время
+	Get() int
+	// String -- возвращает стороковое представление оставшегося времени
+	String() string
+	// Stop -- останавливает работу отсчёта
+	Stop()
+	// ChanSig -- возвращает канал тиков
+	ChanSig() <-chan int
+}
+
+func FakeTest() {
+	// fmt._rintf("fake test\n")
+}

+ 14 - 0
pkg/types/idesktop.go

@@ -0,0 +1,14 @@
+package types
+
+// IDesktop -- интерфейс к десктоп-приложению
+type IDesktop interface {
+	IKernel
+	// Store -- возвращает хранилище клиента
+	Store() IStore
+	// Ws -- возвращает веб-сокет до сервера
+	Ws() IWebSocket
+	// Root -- возвращает объект рута
+	Root() IRoot
+	// DictBot -- возвращает словарь ботов игрока
+	DictBot() IDictBot
+}

+ 7 - 0
pkg/types/idict_bot.go

@@ -0,0 +1,7 @@
+package types
+
+// IDictBot -- интрефейс к словарю ботов
+type IDictBot interface {
+	// Show -- показывает словарь ботов
+	Show()
+}

+ 12 - 0
pkg/types/idivwar.go

@@ -0,0 +1,12 @@
+package types
+
+/*
+	Интерфейс к битве дивизиий
+*/
+
+// IDivWar -- интерфейс к битве дивизий
+type IDivWar interface {
+	ISection
+	// Alarm -- возвращает признак начала битвы дивизий
+	Alarm() IStatParam
+}

+ 31 - 0
pkg/types/idivwaron.go

@@ -0,0 +1,31 @@
+package types
+
+import (
+	"context"
+	"wartank/pkg/components/safebool"
+)
+
+/*
+	Интерфейс к сражению
+*/
+
+// IDivWarOn -- интерфейс к непосредственному сражению
+type IDivWarOn interface {
+	ISection
+	// Net -- возвращает сетевой компонент сражения
+	Net() ISectionNet
+	// IsEnd -- признак окончания сражения
+	// IsEnd() *isdivwar.IsDivWar
+	// Manevr -- выполняет манёвр по требованию
+	Manevr()
+	// Masking -- признак запрета на стрельбу
+	// Masking() *isrepair.IsRepair
+	// Ctx -- возвращает контекст битвы
+	Ctx() context.Context
+	// CancelBattle -- вызывает контекст отмены битвы
+	CancelBattle()
+	// IsEnd -- признак окончания битвы дивизий
+	IsEnd() *safebool.SafeBool
+	// Masking -- объект маскировки
+	Masking() *safebool.SafeBool
+}

+ 15 - 0
pkg/types/iguiweb.go

@@ -0,0 +1,15 @@
+package types
+
+import (
+	"github.com/gofiber/fiber/v2"
+)
+
+/*
+	Интерфейс к веб-морде
+*/
+
+// IGuiWeb -- интерфейс к веб-морде
+type IGuiWeb interface {
+	// Router -- возвращает роутер
+	Router() *fiber.App
+}

+ 31 - 0
pkg/types/ikernel.go

@@ -0,0 +1,31 @@
+package types
+
+import "context"
+
+/*
+	Базовый интерфейс ядра
+*/
+
+// IWgName -- интерфейс к группе ожидания по именам
+type IWgName interface {
+	// Add -- добавляет имя для ожидания
+	Add(name string) error
+	// Done -- отпускает имя для ожидания
+	Done(name string) error
+	// Wait -- ожидает отпускание группы
+	Wait()
+}
+
+// IKernel -- базовый интерфейс ядра
+type IKernel interface {
+	// Done -- возвращает канал отмены контекста приложения
+	Done() <-chan struct{}
+	// CancelApp -- функция тмены глобального контекста ядра
+	CancelApp()
+	// Wg -- возвращает групповой объект ожидания
+	Wg() IWgName
+	// Slog -- возвращает логгер
+	Slog() ISlog
+	// CtxApp -- возвращает контекст приложения
+	CtxApp() context.Context
+}

+ 10 - 0
pkg/types/imarket.go

@@ -0,0 +1,10 @@
+package types
+
+/*
+	Интерфейс к рынку
+*/
+
+// IMarket -- инетерфейс к рынку
+type IMarket interface {
+	ISection
+}

+ 20 - 0
pkg/types/imine.go

@@ -0,0 +1,20 @@
+package types
+
+/*
+	Интерфейс к объекту шахты
+*/
+
+// IMine -- интерфейс к объекту шахты
+type IMine interface {
+	ISection
+	// Ruda -- возвращает объект руды
+	Ruda() IStatParam
+	// Ferrum -- возвращает объект железа
+	Ferrum() IStatParam
+	// Steel -- возвращает объект стали
+	Steel() IStatParam
+	// Plumbum -- возвращает объект свинца
+	Plumbum() IStatParam
+	// NumProduct -- количество производимого продукта
+	NumProduct() IStatParam
+}

+ 10 - 0
pkg/types/imissions.go

@@ -0,0 +1,10 @@
+package types
+
+/*
+	Объект миссий(сбор золы)
+*/
+
+// IMissions -- интерфейс к объекту миссий
+type IMissions interface {
+	ISection
+}

+ 17 - 0
pkg/types/imode.go

@@ -0,0 +1,17 @@
+package types
+
+/*
+	Режим работы секции.
+*/
+
+// IMode -- интерфейс к режиму работы секции
+type IMode interface {
+	// Set -- устанавливает режим работы
+	Set(string)
+	// Get -- возвращает режим работы
+	Get() string
+	// Work -- возвращает имя текущей работы
+	Work() string
+	// WorkSet -- устанавилвае тимя текущей работы
+	WorkSet(name string)
+}

+ 7 - 0
pkg/types/inet_client.go

@@ -0,0 +1,7 @@
+package types
+
+// INetClient -- интерфейс к GET-запросу
+type INetClient interface {
+	// Get -- теневая функция на блокировку
+	Get(strLink string) (lstString []string, err error)
+}

+ 10 - 0
pkg/types/inetangar.go

@@ -0,0 +1,10 @@
+package types
+
+/*
+	Интерфейс к сетевому ангару
+*/
+
+// INetAngar -- интерфейс к сетевому ангару
+type INetAngar interface {
+	ISectionNet
+}

+ 10 - 0
pkg/types/inetbase.go

@@ -0,0 +1,10 @@
+package types
+
+/*
+	Интерфейс к сетевой базе.
+*/
+
+// INetBase -- интерфейс к сетевой базе
+type INetBase interface {
+	ISectionNet
+}

+ 8 - 0
pkg/types/inetmarket.go

@@ -0,0 +1,8 @@
+package types
+
+/*
+	Интерфейс к сетевому рынку.
+*/
+
+// INetMarket -- интрфейс к сетевому рынку
+type INetMarket interface{}

+ 9 - 0
pkg/types/ipassword.go

@@ -0,0 +1,9 @@
+package types
+
+// IPassword -- возвращает объект пароля
+type IPassword interface {
+	// Get -- возвращает хранимый пароль
+	Get() string
+	// Set -- устанавливает хранимый пароль
+	Set(val string)
+}

+ 10 - 0
pkg/types/ipolygon.go

@@ -0,0 +1,10 @@
+package types
+
+/*
+	Интерфейс к полигону.
+*/
+
+// IPolygon -- интерфейс к полигону
+type IPolygon interface {
+	ISection
+}

+ 4 - 0
pkg/types/iroot.go

@@ -0,0 +1,4 @@
+package types
+
+// IRoot -- интерфейс к объекту рута
+type IRoot interface{}

+ 17 - 0
pkg/types/isection.go

@@ -0,0 +1,17 @@
+package types
+
+/*
+	Базовый тип для любой секции игры
+*/
+
+// ISection -- интерфейс базового типа любой секции игры
+type ISection interface {
+	// Update -- обновляет строки секции
+	Update(lstString []string) error
+	// GetLst -- возвращает список строк секции
+	GetLst() []string
+	// CountDown -- объект оставшегося времени до опроса секции
+	CountDown() ICountTime
+	// ModeCurrent -- текущий режим работы
+	ModeCurrent() IMode
+}

+ 13 - 0
pkg/types/isectionnet.go

@@ -0,0 +1,13 @@
+package types
+
+/*
+	Базовый тип для сетевой секции
+*/
+
+// ISectionNet --	базовый тип для сетевой секции
+type ISectionNet interface {
+	// UpdateLst -- обновляет список строк секции
+	UpdateLst(name string) error
+	// Get -- выполняет GET-запрос по указанному URL
+	Get(strLink string) ([]string, error)
+}

+ 15 - 0
pkg/types/iserv_bot.go

@@ -0,0 +1,15 @@
+package types
+
+// IServBot -- серверный бот среальным состоянием
+type IServBot interface {
+	// Name -- возвращает им бота
+	Name() string
+	// Pass -- возвращае тпароль бота
+	Pass() string
+	// Angar -- возвращает ангар бота
+	Angar() IAngar
+	// Tank -- возврщает параметры танка
+	Tank() ITank
+	// BotNet -- возвращает объект сети
+	BotNet() IBotNet
+}

+ 7 - 0
pkg/types/iserv_bots.go

@@ -0,0 +1,7 @@
+package types
+
+// IServBots -- словарьсерверных ботов
+type IServBots interface {
+	// Get -- возвращает бота по его имени
+	Get(name string) IServBot
+}

+ 11 - 0
pkg/types/iserv_web.go

@@ -0,0 +1,11 @@
+package types
+
+import "github.com/gofiber/fiber/v2"
+
+// IServWeb -- интерфейс к веб-серверу
+type IServWeb interface {
+	// Router -- возвращае тссылку на роутер веб-сервера
+	Router() *fiber.App
+	// Run -- запускает сервер в работу
+	Run() error
+}

+ 18 - 0
pkg/types/iserver.go

@@ -0,0 +1,18 @@
+package types
+
+/*
+	Интерфейс для приложения
+*/
+
+// IServer -- интерфейс для приложения
+type IServer interface {
+	IKernel
+	// Store -- хранилище приложения
+	Store() IStore
+	// Run -- запускает приложение в работу
+	Run() error
+	// ServWeb -- возвращает ссылку на веб-сервер
+	ServWeb() IServWeb
+	// ServBots -- словарь имеющихся ботов
+	ServBots() IServBots
+}

+ 17 - 0
pkg/types/islog.go

@@ -0,0 +1,17 @@
+package types
+
+/*
+	Интерфейс логирования
+*/
+
+// ISlog -- интерфейс к логирования
+type ISlog interface {
+	// Debugf -- отладочный вывод
+	Debugf(str string, lst ...interface{})
+	// Infof -- информационный дежурный вывод
+	Infof(str string, lst ...interface{})
+	// Warnf -- вывод предупреждения
+	Warnf(str string, lst ...interface{})
+	// Errorf -- вывод ошибки
+	Errorf(str string, lst ...interface{})
+}

+ 17 - 0
pkg/types/istatparam.go

@@ -0,0 +1,17 @@
+package types
+
+/*
+	Интерфейс к параметру
+*/
+
+// IStatParam -- интерфейс к праметру
+type IStatParam interface {
+	// Get -- возвращает хранимый параметр
+	Get() int
+	// Set -- устанавливает хранимый параметр
+	Set(val int)
+	// Name -- возвращает имя параметра
+	Name() string
+	// SetName -- устанавливает имя параметра
+	SetName(name string)
+}

+ 12 - 0
pkg/types/istore.go

@@ -0,0 +1,12 @@
+package types
+
+// IStore -- хранилище данных бот
+type IStore interface {
+	// Get -- читает запись по ключу
+	Get(key string) (string, error)
+	// Put -- пишет запись в хранилище
+	Put(key string, val string) error
+	// Find -- ищет записи по префиксу
+	Find(prefix string) (map[string]string, error)
+	// Del -- удаляет запись по ключу
+}

+ 11 - 0
pkg/types/itank.go

@@ -0,0 +1,11 @@
+package types
+
+/*
+	Интерфейс к состоянию танка
+*/
+
+// ITank -- интерфейс к состоянию танка
+type ITank interface {
+	// TankStat -- возвращает объект статистики танка
+	TankStat() ITankStat
+}

+ 21 - 0
pkg/types/itankstat.go

@@ -0,0 +1,21 @@
+package types
+
+/*
+	Интерфейс к параметрам танка
+*/
+
+// ITankStat -- интерфейс к статистике танка
+type ITankStat interface {
+	// Attack -- возвращает силу атаки
+	Attack() IStatParam
+	// Armor -- возвращает броню танка
+	Armor() IStatParam
+	// Fyne -- возвращает точность танка
+	Fyne() IStatParam
+	// Hard -- возвращает прочность танка
+	Hard() IStatParam
+	// Power -- возвращает мощность танка
+	Power() IStatParam
+	// Force -- устанавливает форсирование параметра
+	Force() IStatParam
+}

+ 13 - 0
pkg/types/iweb_socket.go

@@ -0,0 +1,13 @@
+package types
+
+// IWebSocket -- интерфейс к постоянному веб-сокету сервера
+type IWebSocket interface {
+	// Write -- записывает топик на сервер
+	Write(topic string, dictReq map[string]string) error
+	// Read -- читает топик с сервера
+	Read(topic string) (mapResp map[string]string, err error)
+	// Call -- вызывает худалённую процедуру
+	Call(topic string, dictReq map[string]string) (mapResp map[string]string, err error)
+	// IsConnect -- возвращает признак подключенности к серверу
+	IsConnect() bool
+}

+ 16 - 0
pkg/types/iwin.go

@@ -0,0 +1,16 @@
+package types
+
+/*
+	Содержит интерфейсы окон
+*/
+
+// IWin -- базовый интерфейс окна
+type IWin interface {
+	// Show -- показать окно
+	Show()
+}
+
+// IWinAngar -- окно ангара
+type IWinAngar interface {
+	IWin
+}

+ 13 - 0
pkg/types/types_test.go

@@ -0,0 +1,13 @@
+package types
+
+import (
+	"testing"
+)
+
+/*
+	Фейковый тест для покрытия кода тестами
+*/
+
+func TestTypes(t *testing.T) {
+	FakeTest()
+}