package snakesimulator import ( context "context" 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 speed uint32 fieldUpdateQueue chan bool snakeUpdateQueue chan bool statsUpdateQueue chan bool speedQueue chan uint32 } 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), speedQueue: make(chan uint32, 1), speed: 10, } 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() rand.Seed(time.Now().UnixNano()) switch rand.Uint32() % 4 { case 1: s.snake = &Snake{ Points: []*Point{ &Point{X: 20, Y: 20}, &Point{X: 21, Y: 20}, &Point{X: 22, Y: 20}, }, } case 2: s.snake = &Snake{ Points: []*Point{ &Point{X: 22, Y: 20}, &Point{X: 21, Y: 20}, &Point{X: 20, Y: 20}, }, } case 3: s.snake = &Snake{ Points: []*Point{ &Point{X: 20, Y: 20}, &Point{X: 20, Y: 21}, &Point{X: 20, Y: 22}, }, } default: s.snake = &Snake{ Points: []*Point{ &Point{X: 20, Y: 22}, &Point{X: 20, Y: 21}, &Point{X: 20, Y: 20}, }, } } i := 0 s.stats.Move = 0 for i < 300 { s.statsUpdateQueue <- true //Read speed from client select { case newSpeed := <-s.speedQueue: fmt.Printf("Apply new speed: %v\n", newSpeed) if newSpeed < 10 { if newSpeed > 0 { s.speed = newSpeed } else { s.speed = 0 } } default: } i++ if s.speed > 0 { 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) } s.stats.Move++ if s.speed > 0 { time.Sleep(100 / time.Duration(s.speed) * time.Millisecond) } } results[index] = &genetic.IndividalResult{ Result: float64(len(s.snake.Points)-2) * float64(s.stats.Move), Index: index, } } return } func (s *SnakeSimulator) GetHeadState() []float64 { headX := float64(s.snake.Points[0].X) headY := float64(s.snake.Points[0].Y) tailX := float64(s.snake.Points[len(s.snake.Points)-1].X) tailY := float64(s.snake.Points[len(s.snake.Points)-1].Y) // prevX := float64(s.snake.Points[1].X) // prevY := float64(s.snake.Points[1].Y) foodX := float64(s.field.Food.X) foodY := float64(s.field.Food.Y) width := float64(s.field.Width) height := float64(s.field.Height) diag := float64(width) * math.Sqrt2 tBack := float64(1.0) bBack := float64(1.0) lBack := float64(1.0) rBack := float64(1.0) // if prevX == headX { // if prevY > headY { // tBack = 0.0 // } else { // bBack = 0.0 // } // } // if prevY == headY { // if prevX > headX { // rBack = 0.0 // } else { // lBack = 0.0 // } // } lWall := headX * lBack rWall := (width - headX) * rBack tWall := headY * tBack bWall := (height - headY) * bBack lFood := float64(0) rFood := float64(0) tFood := float64(0) bFood := float64(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) tFood = (1.0 - (headY-foodY)/height) * tBack bFood = (1.0 - (foodY-headY)/height) * bBack rFood = (1.0 - (foodX-headX)/width) * rBack lFood = (1.0 - (headX-foodX)/width) * lBack 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 } tTail := (headY - tailY) bTail := (tailY - headY) lTail := (headX - tailX) rTail := (tailX - headX) return []float64{ lWall / width, lFood, rWall / width, rFood, tWall / height, tFood, bWall / height, bFood, tlWall / diag, tlFood, trWall / diag, trFood, blWall / diag, blFood, brWall / diag, brFood, tTail / height, bTail / height, lTail / width, rTail / width, } } 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 } } func (s *SnakeSimulator) SetSpeed(ctx context.Context, speed *Speed) (*None, error) { s.speedQueue <- speed.Speed return &None{}, nil }