package snakesimulator import ( fmt "fmt" math "math" "math/rand" "net" "time" "gonum.org/v1/gonum/mat" genetic "../genetic" grpc "google.golang.org/grpc" ) type SnakeSimulator struct { field *Field snake *Snake stats *Stats fieldUpdateQueue chan bool snakeUpdateQueue chan bool statsUpdateQueue chan bool } func NewSnakeSimulator() (s *SnakeSimulator) { s = &SnakeSimulator{ field: &Field{ Food: &Point{}, Width: 40, Height: 40, }, snake: &Snake{ Points: []*Point{ &Point{X: 20, Y: 20}, &Point{X: 21, Y: 20}, &Point{X: 22, Y: 20}, }, }, stats: &Stats{}, fieldUpdateQueue: make(chan bool, 2), snakeUpdateQueue: make(chan bool, 2), statsUpdateQueue: make(chan bool, 2), } return } func (s *SnakeSimulator) Verify(population *genetic.Population) (results []*genetic.IndividalResult) { s.stats.Generation++ s.statsUpdateQueue <- true results = make([]*genetic.IndividalResult, len(population.Networks)) for index, inidividual := range population.Networks { s.stats.Individual = uint32(index) s.statsUpdateQueue <- true s.field.GenerateNextFood() s.snake = &Snake{ Points: []*Point{ &Point{X: 20, Y: 20}, &Point{X: 21, Y: 20}, &Point{X: 22, Y: 20}, }, } i := 0 for i < 300 { s.stats.Move = uint32(i) s.statsUpdateQueue <- true i++ s.snakeUpdateQueue <- true direction, _ := inidividual.Predict(mat.NewDense(inidividual.Sizes[0], 1, s.GetHeadState())) newHead := s.snake.NewHead(Direction(direction + 1)) if newHead.X == s.field.Food.X && newHead.Y == s.field.Food.Y { s.snake.Feed(newHead) s.field.GenerateNextFood() s.fieldUpdateQueue <- true } else if newHead.X >= s.field.Width || newHead.Y >= s.field.Height { fmt.Printf("Game over\n") // time.Sleep(1000 * time.Millisecond) break } else if selfCollisionIndex := s.snake.SelfCollision(newHead); selfCollisionIndex > 0 { if selfCollisionIndex == 1 { fmt.Printf("Step backward, skip\n") continue } fmt.Printf("Game over self collision\n") break } else { s.snake.Move(newHead) } time.Sleep(10 * time.Millisecond) } results[index] = &genetic.IndividalResult{ Result: float64(len(s.snake.Points)) * float64(i), Index: index, } } return } func (s *SnakeSimulator) GetHeadState() []float64 { headX := int32(s.snake.Points[0].X) headY := int32(s.snake.Points[0].Y) foodX := int32(s.field.Food.X) foodY := int32(s.field.Food.Y) width := int32(s.field.Width) height := int32(s.field.Height) diag := float64(width) * math.Sqrt2 lWall := headX rWall := width - headX tWall := headY bWall := height - headY lFood := int32(0) rFood := int32(0) tFood := int32(0) bFood := int32(0) tlFood := float64(0) trFood := float64(0) blFood := float64(0) brFood := float64(0) tlWall := float64(0) trWall := float64(0) blWall := float64(0) brWall := float64(0) if foodX == headX { if foodY > headY { bFood = foodY - headY } else { tFood = headY - foodY } } if foodY == headY { if foodX > headX { rFood = foodX - headX } else { lFood = headX - foodX } } if lWall > tWall { tlWall = float64(tWall) * math.Sqrt2 } else { tlWall = float64(lWall) * math.Sqrt2 } if rWall > tWall { trWall = float64(tWall) * math.Sqrt2 } else { trWall = float64(rWall) * math.Sqrt2 } if lWall > bWall { blWall = float64(bWall) * math.Sqrt2 } else { blWall = float64(lWall) * math.Sqrt2 } if rWall > bWall { blWall = float64(bWall) * math.Sqrt2 } else { brWall = float64(rWall) * math.Sqrt2 } foodDiagXDiff := math.Abs(float64(foodX - headX)) foodDiagYDiff := math.Abs(float64(foodY - headY)) if foodDiagXDiff == foodDiagYDiff { if math.Signbit(float64(foodX - headX)) { if math.Signbit(float64(foodY - headY)) { trFood = foodDiagXDiff * math.Sqrt2 } else { brFood = foodDiagXDiff * math.Sqrt2 } } else { if math.Signbit(float64(foodY - headY)) { tlFood = foodDiagXDiff * math.Sqrt2 } else { blFood = foodDiagXDiff * math.Sqrt2 } } } return []float64{ float64(lWall) / float64(width), float64(rWall) / float64(width), float64(tWall) / float64(height), float64(bWall) / float64(height), float64(lFood) / float64(width), float64(rFood) / float64(width), float64(tFood) / float64(height), float64(bFood) / float64(height), float64(tlFood) / diag, float64(trFood) / diag, float64(blFood) / diag, float64(brFood) / diag, float64(tlWall) / diag, float64(trWall) / diag, float64(blWall) / diag, float64(brWall) / diag, } } func (s *SnakeSimulator) StartServer() { go func() { grpcServer := grpc.NewServer() RegisterSnakeSimulatorServer(grpcServer, s) lis, err := net.Listen("tcp", "localhost:65002") if err != nil { fmt.Printf("Failed to listen: %v\n", err) } fmt.Printf("Listen SnakeSimulator localhost:65002\n") if err := grpcServer.Serve(lis); err != nil { fmt.Printf("Failed to serve: %v\n", err) } }() } func (s *SnakeSimulator) Run() { s.field.GenerateNextFood() for true { direction := rand.Int31()%4 + 1 newHead := s.snake.NewHead(Direction(direction)) if newHead.X == s.field.Food.X && newHead.Y == s.field.Food.Y { s.snake.Feed(newHead) s.field.GenerateNextFood() s.fieldUpdateQueue <- true } else if newHead.X >= s.field.Width || newHead.Y >= s.field.Height { fmt.Printf("Game over\n") break } else if selfCollisionIndex := s.snake.SelfCollision(newHead); selfCollisionIndex > 0 { if selfCollisionIndex == 1 { fmt.Printf("Step backward, skip\n") continue } fmt.Printf("Game over self collision\n") break } else { s.snake.Move(newHead) } s.snakeUpdateQueue <- true time.Sleep(50 * time.Millisecond) } } func (s *SnakeSimulator) Field(_ *None, srv SnakeSimulator_FieldServer) error { ctx := srv.Context() for { select { case <-ctx.Done(): return ctx.Err() default: } srv.Send(s.field) <-s.fieldUpdateQueue } } func (s *SnakeSimulator) Snake(_ *None, srv SnakeSimulator_SnakeServer) error { ctx := srv.Context() for { select { case <-ctx.Done(): return ctx.Err() default: } srv.Send(s.snake) <-s.snakeUpdateQueue } } func (s *SnakeSimulator) Stats(_ *None, srv SnakeSimulator_StatsServer) error { ctx := srv.Context() for { select { case <-ctx.Done(): return ctx.Err() default: } srv.Send(s.stats) <-s.statsUpdateQueue } }