From 6ad47cbc388bb0995e4f62f72ba97940e9a9926b Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Sat, 7 Mar 2026 19:15:24 +0700 Subject: [PATCH] add doors --- .vscode/settings.json | 27 +- Makefile | 2 +- internal/services/services.go | 27 +- internal/services/services_test.go | 155 ----------- .../services/story_service/application_dto.go | 11 + .../services/story_service/data_models.go | 23 -- internal/services/story_service/door_dto.go | 31 +++ internal/services/story_service/place_dto.go | 62 +++++ internal/services/story_service/service.go | 79 +++--- .../services/story_service/service_test.go | 240 +++++++++++++----- internal/services/story_service/story_dto.go | 5 + 11 files changed, 362 insertions(+), 300 deletions(-) delete mode 100644 internal/services/services_test.go create mode 100644 internal/services/story_service/application_dto.go delete mode 100644 internal/services/story_service/data_models.go create mode 100644 internal/services/story_service/door_dto.go create mode 100644 internal/services/story_service/place_dto.go create mode 100644 internal/services/story_service/story_dto.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 2053561..35ad1b0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,16 +1,41 @@ { "cSpell.words": [ + "ввода", + "внимательно", + "Детективы", + "диалогом", + "корректный", + "Можно", + "Название", + "найдена", + "найдено", + "Нельзя", + "Открываем", + "открытую", + "открыть", + "получение", + "правила", "Приложение", + "приложением", + "проходами", + "скрытой", + "скрытую", + "существует", + "сходить", + "Такой", "Текст", "Точка", "точки", + "точку", + "читайте", "AUTOINCREMENT", "gopdf", "gwmux", "localtime", "palces", "qrcode", - "signintech" + "signintech", + "stretchr" ], "makefile.configureOnOpen": false, "go.testFlags": [ diff --git a/Makefile b/Makefile index f594f00..c0e9034 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ clear: rm ./internal/tests/store.db test: - DB_FILENAME=store.db go test -count=1 ./... + go test -count=1 ./... text_to_program: text_to_story cp ./cmd/text_to_story/story.json ./data/story/story.json diff --git a/internal/services/services.go b/internal/services/services.go index fda6088..5f1642d 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -133,9 +133,13 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G if err != nil { return nil, status.Errorf(codes.Internal, err.Error()) } + actionsCodes := make([]string, 0, len(actions)) + for _, action := range actions { + actionsCodes = append(actionsCodes, action.Place) + } res := make([]*proto.Action, 0, len(actions)) - for _, place := range s.getPlaces(actions) { + for _, place := range s.storyService.GetPlaces(actionsCodes) { newAction := mapPlaceToProtoAction(place) newAction.Text = place.Text newAction.Name = place.Name @@ -151,27 +155,6 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G }, err } -func (s *Services) getPlaces(actions []*models.Action) []*story_service.Place { - places := []*story_service.Place{} - m := map[string]any{} - for _, action := range actions { - place := s.storyService.GetPlace(action.Place) - _, ok := m[place.Code] - if place.Hidden && !ok { - place = &story_service.Place{ - Code: place.Code, - Name: "Не найдено", - Text: "Такой точки не существует.", - } - } - places = append(places, place) - for _, door := range place.Doors { - m[door.Code] = struct{}{} - } - } - return places -} - func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) { panic("unimplemented") } diff --git a/internal/services/services_test.go b/internal/services/services_test.go deleted file mode 100644 index b824dd9..0000000 --- a/internal/services/services_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package services - -import ( - "evening_detective/internal/models" - "evening_detective/internal/modules/cleaner" - "evening_detective/internal/modules/formatter" - "evening_detective/internal/services/story_service" - "evening_detective/internal/services/story_storage" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestServices_getPlaces(t *testing.T) { - tests := []struct { - name string - story *story_service.Story - actions []*models.Action - want []*story_service.Place - }{ - // { - // name: "Нельзя открыть скрытую точку", - // story: &story_service.Story{ - // Places: []*story_service.Place{ - // { - // Code: "Ы", - // Name: "Название", - // Text: "Текст", - // Hidden: true, - // }, - // }, - // }, - // actions: []*models.Action{ - // { - // Place: "Ы", - // }, - // }, - // want: []*story_service.Place{ - // { - // Code: "Ы", - // Name: "Не найдено", - // Text: "Такой точки не существует.", - // }, - // }, - // }, - // { - // name: "Нельзя открыть скрытую точку", - // story: &story_service.Story{ - // Places: []*story_service.Place{ - // { - // Code: "Ы-1", - // Name: "Название", - // Text: "Текст", - // }, - // { - // Code: "Ы-2", - // Name: "Название", - // Text: "Текст", - // Hidden: true, - // }, - // }, - // }, - // actions: []*models.Action{ - // { - // Place: "Ы-1", - // }, - // { - // Place: "Ы-2", - // }, - // }, - // want: []*story_service.Place{ - // { - // Code: "Ы-1", - // Name: "Название", - // Text: "Текст", - // Applications: []*story_service.Application{}, - // }, - // { - // Code: "Ы-2", - // Name: "Не найдено", - // Text: "Такой точки не существует.", - // }, - // }, - // }, - { - name: "Нельзя открыть скрытую точку", - story: &story_service.Story{ - Places: []*story_service.Place{ - { - Code: "Ы-1", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{}, - Doors: []*story_service.Door{ - { - Code: "Ы-2", - Name: "Название", - }, - }, - }, - { - Code: "Ы-2", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{}, - Hidden: true, - }, - }, - }, - actions: []*models.Action{ - { - Place: "Ы-1", - }, - { - Place: "Ы-2", - }, - }, - want: []*story_service.Place{ - { - Code: "Ы-1", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{}, - Doors: []*story_service.Door{ - { - Code: "Ы-2", - Name: "Название", - }, - }, - }, - { - Code: "Ы-2", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{}, - Hidden: true, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - storyService, err := story_service.NewStoryService( - cleaner.NewCleaner(), - formatter.NewFormatter(), - story_storage.NewVarStoryStorage(tt.story), - ) - assert.Nil(t, err) - - s := NewServices(nil, storyService, nil, nil, nil, "") - got := s.getPlaces(tt.actions) - assert.Equal(t, got, tt.want) - }) - } -} diff --git a/internal/services/story_service/application_dto.go b/internal/services/story_service/application_dto.go new file mode 100644 index 0000000..a79fada --- /dev/null +++ b/internal/services/story_service/application_dto.go @@ -0,0 +1,11 @@ +package story_service + +type Application struct { + Name string `json:"name"` +} + +func NewApplication(name string) *Application { + return &Application{ + Name: name, + } +} diff --git a/internal/services/story_service/data_models.go b/internal/services/story_service/data_models.go deleted file mode 100644 index d50bff1..0000000 --- a/internal/services/story_service/data_models.go +++ /dev/null @@ -1,23 +0,0 @@ -package story_service - -type Story struct { - Places []*Place `json:"places"` -} - -type Place struct { - Code string `json:"code"` - Name string `json:"name"` - Text string `json:"text"` - Applications []*Application `json:"applications,omitempty"` - Hidden bool `json:"hidden"` - Doors []*Door `json:"doors"` -} - -type Application struct { - Name string `json:"name"` -} - -type Door struct { - Code string `json:"code"` - Name string `json:"name"` -} diff --git a/internal/services/story_service/door_dto.go b/internal/services/story_service/door_dto.go new file mode 100644 index 0000000..8670610 --- /dev/null +++ b/internal/services/story_service/door_dto.go @@ -0,0 +1,31 @@ +package story_service + +type Door struct { + Code string `json:"code"` + Name string `json:"name"` + Show bool `json:"show"` +} + +func NewDoor( + code string, + name string, + opts ...DoorOpt, +) *Door { + door := &Door{ + Code: code, + Name: name, + } + for _, opt := range opts { + opt(door) + } + return door +} + +type DoorOpt func(door *Door) error + +func WithDoorShow(show bool) DoorOpt { + return func(door *Door) error { + door.Show = show + return nil + } +} diff --git a/internal/services/story_service/place_dto.go b/internal/services/story_service/place_dto.go new file mode 100644 index 0000000..3e83c2d --- /dev/null +++ b/internal/services/story_service/place_dto.go @@ -0,0 +1,62 @@ +package story_service + +type Place struct { + Code string `json:"code"` + Name string `json:"name"` + Text string `json:"text"` + Applications []*Application `json:"applications,omitempty"` + Hidden bool `json:"hidden"` + Doors []*Door `json:"doors"` +} + +func NewPlace( + code string, + name string, + text string, + opts ...PlaceOpt, +) *Place { + p := &Place{ + Code: code, + Name: name, + Text: text, + } + for _, opt := range opts { + opt(p) + } + return p +} + +func NewNotFoundPlace(code string) *Place { + return NewPlace(code, "Не найдено", "Такой точки не существует.") +} + +func NewClientErrorPlace(code string) *Place { + return NewPlace(code, "Не найдено", "Детективы, внимательно читайте правила.") +} + +type PlaceOpt func(place *Place) error + +func WithPlaceApplication(applications ...*Application) PlaceOpt { + return func(place *Place) error { + for _, application := range applications { + place.Applications = append(place.Applications, application) + } + return nil + } +} + +func WithPlaceHidden(hidden bool) PlaceOpt { + return func(place *Place) error { + place.Hidden = hidden + return nil + } +} + +func WithPlaceDoors(doors ...*Door) PlaceOpt { + return func(place *Place) error { + for _, door := range doors { + place.Doors = append(place.Doors, door) + } + return nil + } +} diff --git a/internal/services/story_service/service.go b/internal/services/story_service/service.go index 450ce07..dc08300 100644 --- a/internal/services/story_service/service.go +++ b/internal/services/story_service/service.go @@ -47,11 +47,7 @@ func (s *StoryService) Update(ctx context.Context) error { func (s *StoryService) GetPlace(code string) *Place { if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") { - return &Place{ - Code: code, - Name: "Не найдено", - Text: "Уважаемые детективы внимательно прочитайте правила.", - } + return NewClientErrorPlace(code) } clearCode := s.cleaner.ClearCode(code) for _, place := range s.story.Places { @@ -66,21 +62,34 @@ func (s *StoryService) GetPlace(code string) *Place { }, ) } - return &Place{ - Code: place.Code, - Name: place.Name, - Text: s.cleaner.ClearText(place.Text), - Applications: applications, - Hidden: place.Hidden, - Doors: place.Doors, - } + return NewPlace( + place.Code, + place.Name, + s.cleaner.ClearText(place.Text), + WithPlaceApplication(applications...), + WithPlaceHidden(place.Hidden), + WithPlaceDoors(place.Doors...), + ) } } - return &Place{ - Code: code, - Name: "Не найдено", - Text: "Такой точки не существует.", + return NewNotFoundPlace(code) +} + +func (s *StoryService) GetPlaces(codes []string) []*Place { + places := make([]*Place, 0, 100) + m := map[string]any{} + for _, code := range codes { + place := s.GetPlace(code) + _, ok := m[place.Code] + if place.Hidden && !ok { + place = NewNotFoundPlace(place.Code) + } + places = append(places, place) + for _, door := range place.Doors { + m[door.Code] = struct{}{} + } } + return places } func (s *StoryService) UpdatePlace(ctx context.Context, code string, node *GraphNode) error { @@ -109,12 +118,12 @@ func (s *StoryService) deletePlace(ctx context.Context, code string) error { func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error { s.story.Places = append( s.story.Places, - &Place{ - Code: node.Code, - Name: node.Name, - Text: s.formatter.FormatText(node.Text), - Applications: s.getApplications(node), - }, + NewPlace( + node.Code, + node.Name, + s.formatter.FormatText(node.Text), + WithPlaceApplication(s.getApplications(node)...), + ), ) return s.Update(ctx) } @@ -122,23 +131,23 @@ func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error { func (s *StoryService) updatePlace(ctx context.Context, code string, node *GraphNode) error { for i := range s.story.Places { if s.story.Places[i].Code == code { - s.story.Places[i] = &Place{ - Code: node.Code, - Name: node.Name, - Text: s.formatter.FormatText(node.Text), - Applications: s.getApplications(node), - } + s.story.Places[i] = NewPlace( + node.Code, + node.Name, + s.formatter.FormatText(node.Text), + WithPlaceApplication(s.getApplications(node)...), + ) return s.Update(ctx) } } for i := range s.story.Places { if s.story.Places[i].Code == node.Code { - s.story.Places[i] = &Place{ - Code: code, - Name: node.Name, - Text: s.formatter.FormatText(node.Text), - Applications: s.getApplications(node), - } + s.story.Places[i] = NewPlace( + code, + node.Name, + s.formatter.FormatText(node.Text), + WithPlaceApplication(s.getApplications(node)...), + ) break } } diff --git a/internal/services/story_service/service_test.go b/internal/services/story_service/service_test.go index eedef81..70f99aa 100644 --- a/internal/services/story_service/service_test.go +++ b/internal/services/story_service/service_test.go @@ -21,99 +21,123 @@ func TestStoryService_GetPlace(t *testing.T) { name: "не корректный ввода", story: &story_service.Story{}, code: "[Ы]", - want: &story_service.Place{ - Code: "[Ы]", - Name: "Не найдено", - Text: "Уважаемые детективы внимательно прочитайте правила.", - }, + want: story_service.NewClientErrorPlace("[Ы]"), }, { name: "точка не найдена", story: &story_service.Story{}, code: "Ы", - want: &story_service.Place{ - Code: "Ы", - Name: "Не найдено", - Text: "Такой точки не существует.", - }, + want: story_service.NewNotFoundPlace("Ы"), }, { name: "получение точки", story: &story_service.Story{ Places: []*story_service.Place{ - { - Code: "Ы", - Name: "Название", - Text: "Текст", - }, + story_service.NewPlace("Ы", "Название", "Текст"), }, }, code: "Ы", - want: &story_service.Place{ - Code: "Ы", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{}, + want: story_service.NewPlace("Ы", "Название", "Текст"), + }, + { + name: "получение скрытой точки", + story: &story_service.Story{ + Places: []*story_service.Place{ + story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceHidden(true), + ), + }, }, + code: "Ы", + want: story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceHidden(true), + ), }, { name: "получение точки с приложением", story: &story_service.Story{ Places: []*story_service.Place{ - { - Code: "Ы", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{ - { - Name: "Приложение", - }, - }, - }, + story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceApplication( + story_service.NewApplication("Приложение"), + ), + ), }, }, code: "Ы", - want: &story_service.Place{ - Code: "Ы", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{ - { - Name: "Приложение", - }, - }, - }, + want: story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceApplication( + story_service.NewApplication("Приложение"), + ), + ), }, { - name: "получение точки с проходами", + name: "получение точки с проходом", story: &story_service.Story{ Places: []*story_service.Place{ - { - Code: "Ы-1", - Name: "Название", - Text: "Текст", - Doors: []*story_service.Door{ - { - Code: "Ы-2", - Name: "Приложение", - }, - }, - }, + story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceDoors( + story_service.NewDoor("Й", "Приложение"), + ), + ), }, }, - code: "Ы-1", - want: &story_service.Place{ - Code: "Ы-1", - Name: "Название", - Text: "Текст", - Applications: []*story_service.Application{}, - Doors: []*story_service.Door{ - { - Code: "Ы-2", - Name: "Приложение", - }, + code: "Ы", + want: story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceDoors( + story_service.NewDoor("Й", "Приложение"), + ), + ), + }, + { + name: "получение точки с диалогом", + story: &story_service.Story{ + Places: []*story_service.Place{ + story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceDoors( + story_service.NewDoor( + "Й", + "Приложение", + story_service.WithDoorShow(true), + ), + ), + ), }, }, + code: "Ы", + want: story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceDoors( + story_service.NewDoor( + "Й", + "Приложение", + story_service.WithDoorShow(true), + ), + ), + ), }, } for _, tt := range tests { @@ -131,3 +155,93 @@ func TestStoryService_GetPlace(t *testing.T) { }) } } + +func TestStoryService_GetPlaces(t *testing.T) { + tests := []struct { + name string + story *story_service.Story + codes []string + want []*story_service.Place + }{ + { + name: "Можно сходить в открытую точку", + story: &story_service.Story{ + Places: []*story_service.Place{ + story_service.NewPlace("Ы", "Название", "Текст"), + }, + }, + codes: []string{"Ы"}, + want: []*story_service.Place{ + story_service.NewPlace("Ы", "Название", "Текст"), + }, + }, + { + name: "Нельзя открыть скрытую точку", + story: &story_service.Story{ + Places: []*story_service.Place{ + story_service.NewPlace( + "Ы", + "Название", + "Текст", + story_service.WithPlaceHidden(true), + ), + }, + }, + codes: []string{"Ы"}, + want: []*story_service.Place{ + story_service.NewNotFoundPlace("Ы"), + }, + }, + { + name: "Открываем скрытую точку", + story: &story_service.Story{ + Places: []*story_service.Place{ + story_service.NewPlace( + "Ы-1", + "Название", + "Текст", + story_service.WithPlaceDoors( + story_service.NewDoor("Ы-2", "Название"), + ), + ), + story_service.NewPlace( + "Ы-2", + "Название", + "Текст", + story_service.WithPlaceHidden(true), + ), + }, + }, + codes: []string{"Ы-1", "Ы-2"}, + want: []*story_service.Place{ + story_service.NewPlace( + "Ы-1", + "Название", + "Текст", + story_service.WithPlaceDoors( + story_service.NewDoor("Ы-2", "Название"), + ), + ), + story_service.NewPlace( + "Ы-2", + "Название", + "Текст", + story_service.WithPlaceHidden(true), + ), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := story_service.NewStoryService( + cleaner.NewCleaner(), + formatter.NewFormatter(), + story_storage.NewVarStoryStorage(tt.story), + ) + assert.Nil(t, err) + + got := s.GetPlaces(tt.codes) + assert.Equal(t, got, tt.want) + }) + } +} diff --git a/internal/services/story_service/story_dto.go b/internal/services/story_service/story_dto.go new file mode 100644 index 0000000..40e8789 --- /dev/null +++ b/internal/services/story_service/story_dto.go @@ -0,0 +1,5 @@ +package story_service + +type Story struct { + Places []*Place `json:"places"` +}