package snakesimulator import ( context "context" fmt "fmt" math "math" "math/rand" "net" "sort" "time" "gonum.org/v1/gonum/mat" genetic "../genetic" neuralnetwork "../neuralnetwork" 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) (fitnesses []*genetic.IndividalFitness) { s.stats.Generation++ s.statsUpdateQueue <- true s.field.GenerateNextFood() s.fieldUpdateQueue <- true fitnesses = make([]*genetic.IndividalFitness, len(population.Networks)) for index, inidividual := range population.Networks { s.stats.Individual = uint32(index) s.statsUpdateQueue <- true s.runSnake(inidividual, false) fitnesses[index] = &genetic.IndividalFitness{ Fitness: float64(len(s.snake.Points)-2) * float64(s.stats.Move), // Fitness: float64(s.stats.Move), Index: index, } } //This is duplication of crossbreedPopulation functionality. sort.Slice(fitnesses, func(i, j int) bool { return fitnesses[i].Fitness > fitnesses[j].Fitness //Descent order best will be on top, worst in the bottom }) //Best snake showtime! prevSpeed := s.speed s.speed = 2 s.runSnake(population.Networks[fitnesses[0].Index], false) s.speed = prevSpeed return } func (s *SnakeSimulator) runSnake(inidividual *neuralnetwork.NeuralNetwork, randomStart bool) { if randomStart { 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}, }, } } } else { s.snake = &Snake{ Points: []*Point{ &Point{X: 19, Y: 20}, &Point{X: 20, Y: 20}, &Point{X: 21, Y: 20}, }, } } i := 0 s.stats.Move = 0 for i < 300 { if s.speed > 0 { 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 selfCollisionIndex := s.snake.SelfCollision(newHead); selfCollisionIndex > 0 { if selfCollisionIndex == 1 { switch Direction(direction + 1) { case Direction_Up: newHead = s.snake.NewHead(Direction_Down) case Direction_Down: newHead = s.snake.NewHead(Direction_Up) case Direction_Left: newHead = s.snake.NewHead(Direction_Right) default: newHead = s.snake.NewHead(Direction_Left) } // continue } else { fmt.Printf("Game over self collision\n") break } } if newHead.X == s.field.Food.X && newHead.Y == s.field.Food.Y { i = 0 s.snake.Feed(newHead) s.field.GenerateNextFood() s.fieldUpdateQueue <- true } else if newHead.X >= s.field.Width || newHead.Y >= s.field.Height || newHead.X < 0 || newHead.Y < 0 { // fmt.Printf("Game over\n") // time.Sleep(1000 * time.Millisecond) break } else { s.snake.Move(newHead) } if s.speed > 0 { time.Sleep(100 / time.Duration(s.speed) * time.Millisecond) } s.stats.Move++ } } 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 lWall := headX rWall := (width - headX) tWall := headY bWall := (height - headY) lFood := float64(width) rFood := float64(width) tFood := float64(height) bFood := float64(height) if headX == foodX { tFood = headY - foodY if tFood < 0 { tFood = height } else { tFood = 0 } bFood = foodY - headY if bFood < 0 { bFood = height } else { bFood = 0 } } if headY == foodY { rFood = foodX - headX if rFood < 0 { rFood = width } else { rFood = 0 } lFood = headX - foodX if lFood < 0 { lFood = width } else { lFood = 0 } } tlFood := float64(diag) trFood := float64(diag) blFood := float64(diag) brFood := float64(diag) if math.Abs(foodY-headY) == math.Abs(foodX-headX) { if foodX > headX { if foodY > headY { trFood = 0 //math.Abs(foodX-headX) * math.Sqrt2 } else { brFood = 0 //math.Abs(foodX-headX) * math.Sqrt2 } } else { if foodY > headY { tlFood = 0 //math.Abs(foodX-headX) * math.Sqrt2 } else { blFood = 0 //math.Abs(foodX-headX) * math.Sqrt2 } } } tlWall := float64(0) trWall := float64(0) blWall := float64(0) brWall := float64(0) 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 } 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 := float64(0) bTail := float64(0) if headX == tailX { tTail = (headY - tailY) if tTail < 0 { tTail = height } else { tTail = 0 } bTail = (tailY - headY) if bTail < 0 { bTail = height } else { bTail = 0 } } lTail := float64(0) rTail := float64(0) if headY == tailY { lTail = (headX - tailX) if lTail < 0 { tTail = width } else { tTail = 0 } rTail = (tailX - headX) if lTail < 0 { tTail = width } else { tTail = 0 } } tlTail := float64(0) trTail := float64(0) blTail := float64(0) brTail := float64(0) if math.Abs(headY-tailY) == math.Abs(headX-tailX) { if tailY > headY { if tailX > headX { trTail = diag //math.Abs(tailX-headX) * math.Sqrt2 } else { tlTail = diag //math.Abs(tailX-headX) * math.Sqrt2 } } else { if tailX > headX { brTail = diag //math.Abs(tailX-headX) * math.Sqrt2 } else { blTail = diag //math.Abs(tailX-headX) * math.Sqrt2 } } } return []float64{ lWall / width, rWall / width, tWall / height, bWall / height, (1.0 - lFood/width), (1.0 - rFood/width), (1.0 - tFood/height), (1.0 - bFood/height), tlWall / diag, trWall / diag, blWall / diag, brWall / diag, (1.0 - tlFood/diag), (1.0 - trFood/diag), (1.0 - blFood/diag), (1.0 - brFood/diag), tTail / height, bTail / height, lTail / width, rTail / width, tlTail / diag, trTail / diag, blTail / diag, brTail / 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() // } 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 }