add doors

This commit is contained in:
Владимир Фёдоров 2026-03-07 19:15:24 +07:00
parent 83868cc778
commit 6ad47cbc38
11 changed files with 362 additions and 300 deletions

27
.vscode/settings.json vendored
View File

@ -1,16 +1,41 @@
{ {
"cSpell.words": [ "cSpell.words": [
"ввода",
"внимательно",
"Детективы",
"диалогом",
"корректный",
"Можно",
"Название",
"найдена",
"найдено",
"Нельзя",
"Открываем",
"открытую",
"открыть",
"получение",
"правила",
"Приложение", "Приложение",
"приложением",
"проходами",
"скрытой",
"скрытую",
"существует",
"сходить",
"Такой",
"Текст", "Текст",
"Точка", "Точка",
"точки", "точки",
"точку",
"читайте",
"AUTOINCREMENT", "AUTOINCREMENT",
"gopdf", "gopdf",
"gwmux", "gwmux",
"localtime", "localtime",
"palces", "palces",
"qrcode", "qrcode",
"signintech" "signintech",
"stretchr"
], ],
"makefile.configureOnOpen": false, "makefile.configureOnOpen": false,
"go.testFlags": [ "go.testFlags": [

View File

@ -25,7 +25,7 @@ clear:
rm ./internal/tests/store.db rm ./internal/tests/store.db
test: test:
DB_FILENAME=store.db go test -count=1 ./... go test -count=1 ./...
text_to_program: text_to_story text_to_program: text_to_story
cp ./cmd/text_to_story/story.json ./data/story/story.json cp ./cmd/text_to_story/story.json ./data/story/story.json

View File

@ -133,9 +133,13 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G
if err != nil { if err != nil {
return nil, status.Errorf(codes.Internal, err.Error()) 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)) res := make([]*proto.Action, 0, len(actions))
for _, place := range s.getPlaces(actions) { for _, place := range s.storyService.GetPlaces(actionsCodes) {
newAction := mapPlaceToProtoAction(place) newAction := mapPlaceToProtoAction(place)
newAction.Text = place.Text newAction.Text = place.Text
newAction.Name = place.Name newAction.Name = place.Name
@ -151,27 +155,6 @@ func (s *Services) GetTeam(ctx context.Context, req *proto.GetTeamReq) (*proto.G
}, err }, 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) { func (s *Services) GetTeamsCSV(ctx context.Context, req *proto.GetTeamsCSVReq) (*proto.GetTeamsCSVRsp, error) {
panic("unimplemented") panic("unimplemented")
} }

View File

@ -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)
})
}
}

View File

@ -0,0 +1,11 @@
package story_service
type Application struct {
Name string `json:"name"`
}
func NewApplication(name string) *Application {
return &Application{
Name: name,
}
}

View File

@ -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"`
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -47,11 +47,7 @@ func (s *StoryService) Update(ctx context.Context) error {
func (s *StoryService) GetPlace(code string) *Place { func (s *StoryService) GetPlace(code string) *Place {
if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") { if strings.HasPrefix(code, "[") || strings.HasSuffix(code, "]") {
return &Place{ return NewClientErrorPlace(code)
Code: code,
Name: "Не найдено",
Text: "Уважаемые детективы внимательно прочитайте правила.",
}
} }
clearCode := s.cleaner.ClearCode(code) clearCode := s.cleaner.ClearCode(code)
for _, place := range s.story.Places { for _, place := range s.story.Places {
@ -66,21 +62,34 @@ func (s *StoryService) GetPlace(code string) *Place {
}, },
) )
} }
return &Place{ return NewPlace(
Code: place.Code, place.Code,
Name: place.Name, place.Name,
Text: s.cleaner.ClearText(place.Text), s.cleaner.ClearText(place.Text),
Applications: applications, WithPlaceApplication(applications...),
Hidden: place.Hidden, WithPlaceHidden(place.Hidden),
Doors: place.Doors, WithPlaceDoors(place.Doors...),
)
} }
} }
return NewNotFoundPlace(code)
} }
return &Place{
Code: code, func (s *StoryService) GetPlaces(codes []string) []*Place {
Name: "Не найдено", places := make([]*Place, 0, 100)
Text: "Такой точки не существует.", 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 { 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 { func (s *StoryService) addPlace(ctx context.Context, node *GraphNode) error {
s.story.Places = append( s.story.Places = append(
s.story.Places, s.story.Places,
&Place{ NewPlace(
Code: node.Code, node.Code,
Name: node.Name, node.Name,
Text: s.formatter.FormatText(node.Text), s.formatter.FormatText(node.Text),
Applications: s.getApplications(node), WithPlaceApplication(s.getApplications(node)...),
}, ),
) )
return s.Update(ctx) 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 { func (s *StoryService) updatePlace(ctx context.Context, code string, node *GraphNode) error {
for i := range s.story.Places { for i := range s.story.Places {
if s.story.Places[i].Code == code { if s.story.Places[i].Code == code {
s.story.Places[i] = &Place{ s.story.Places[i] = NewPlace(
Code: node.Code, node.Code,
Name: node.Name, node.Name,
Text: s.formatter.FormatText(node.Text), s.formatter.FormatText(node.Text),
Applications: s.getApplications(node), WithPlaceApplication(s.getApplications(node)...),
} )
return s.Update(ctx) return s.Update(ctx)
} }
} }
for i := range s.story.Places { for i := range s.story.Places {
if s.story.Places[i].Code == node.Code { if s.story.Places[i].Code == node.Code {
s.story.Places[i] = &Place{ s.story.Places[i] = NewPlace(
Code: code, code,
Name: node.Name, node.Name,
Text: s.formatter.FormatText(node.Text), s.formatter.FormatText(node.Text),
Applications: s.getApplications(node), WithPlaceApplication(s.getApplications(node)...),
} )
break break
} }
} }

View File

@ -21,99 +21,123 @@ func TestStoryService_GetPlace(t *testing.T) {
name: "не корректный ввода", name: "не корректный ввода",
story: &story_service.Story{}, story: &story_service.Story{},
code: "[Ы]", code: "[Ы]",
want: &story_service.Place{ want: story_service.NewClientErrorPlace("[Ы]"),
Code: "[Ы]",
Name: "Не найдено",
Text: "Уважаемые детективы внимательно прочитайте правила.",
},
}, },
{ {
name: "точка не найдена", name: "точка не найдена",
story: &story_service.Story{}, story: &story_service.Story{},
code: "Ы", code: "Ы",
want: &story_service.Place{ want: story_service.NewNotFoundPlace("Ы"),
Code: "Ы",
Name: "Не найдено",
Text: "Такой точки не существует.",
},
}, },
{ {
name: "получение точки", name: "получение точки",
story: &story_service.Story{ story: &story_service.Story{
Places: []*story_service.Place{ Places: []*story_service.Place{
{ story_service.NewPlace("Ы", "Название", "Текст"),
Code: "Ы",
Name: "Название",
Text: "Текст",
},
}, },
}, },
code: "Ы", code: "Ы",
want: &story_service.Place{ want: story_service.NewPlace("Ы", "Название", "Текст"),
Code: "Ы",
Name: "Название",
Text: "Текст",
Applications: []*story_service.Application{},
}, },
{
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: "получение точки с приложением", name: "получение точки с приложением",
story: &story_service.Story{ story: &story_service.Story{
Places: []*story_service.Place{ Places: []*story_service.Place{
{ story_service.NewPlace(
Code: "Ы", "Ы",
Name: "Название", "Название",
Text: "Текст", "Текст",
Applications: []*story_service.Application{ story_service.WithPlaceApplication(
{ story_service.NewApplication("Приложение"),
Name: "Приложение", ),
}, ),
},
},
}, },
}, },
code: "Ы", code: "Ы",
want: &story_service.Place{ want: story_service.NewPlace(
Code: "Ы", "Ы",
Name: "Название", "Название",
Text: "Текст", "Текст",
Applications: []*story_service.Application{ story_service.WithPlaceApplication(
{ story_service.NewApplication("Приложение"),
Name: "Приложение", ),
}, ),
},
},
}, },
{ {
name: "получение точки с проходами", name: "получение точки с проходом",
story: &story_service.Story{ story: &story_service.Story{
Places: []*story_service.Place{ Places: []*story_service.Place{
story_service.NewPlace(
"Ы",
"Название",
"Текст",
story_service.WithPlaceDoors(
story_service.NewDoor("Й", "Приложение"),
),
),
},
},
code: "Ы",
want: story_service.NewPlace(
"Ы",
"Название",
"Текст",
story_service.WithPlaceDoors(
story_service.NewDoor("Й", "Приложение"),
),
),
},
{ {
Code: "Ы-1", name: "получение точки с диалогом",
Name: "Название", story: &story_service.Story{
Text: "Текст", Places: []*story_service.Place{
Doors: []*story_service.Door{ story_service.NewPlace(
{ "Ы",
Code: "Ы-2", "Название",
Name: "Приложение", "Текст",
}, story_service.WithPlaceDoors(
}, story_service.NewDoor(
}, "Й",
}, "Приложение",
}, story_service.WithDoorShow(true),
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(
"Й",
"Приложение",
story_service.WithDoorShow(true),
),
),
),
}, },
} }
for _, tt := range tests { 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)
})
}
}

View File

@ -0,0 +1,5 @@
package story_service
type Story struct {
Places []*Place `json:"places"`
}