snakesimulator.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
  5. *
  6. * This file is part of NeuralNetwork project https://git.semlanik.org/semlanik/NeuralNetwork
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy of this
  9. * software and associated documentation files (the "Software"), to deal in the Software
  10. * without restriction, including without limitation the rights to use, copy, modify,
  11. * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
  12. * to permit persons to whom the Software is furnished to do so, subject to the following
  13. * conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in all copies
  16. * or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  19. * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  20. * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  21. * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  22. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. * DEALINGS IN THE SOFTWARE.
  24. */
  25. package snakesimulator
  26. import (
  27. context "context"
  28. fmt "fmt"
  29. math "math"
  30. "math/rand"
  31. "net"
  32. "sort"
  33. "sync"
  34. "time"
  35. "gonum.org/v1/gonum/mat"
  36. genetic "git.semlanik.org/semlanik/NeuralNetwork/genetic"
  37. neuralnetwork "git.semlanik.org/semlanik/NeuralNetwork/neuralnetwork"
  38. grpc "google.golang.org/grpc"
  39. )
  40. type SnakeSimulator struct {
  41. field *Field
  42. snake *Snake
  43. maxVerificationSteps int
  44. stats *Stats
  45. //GUI interface part
  46. speed uint32
  47. fieldUpdateQueue chan bool
  48. snakeUpdateQueue chan bool
  49. statsUpdateQueue chan bool
  50. isPlayingUpdateQueue chan bool
  51. speedQueue chan uint32
  52. isPlaying bool
  53. repeatInLoop bool
  54. snakeReadMutex sync.Mutex
  55. fieldReadMutex sync.Mutex
  56. }
  57. // Initializes new snake population with maximum number of verification steps
  58. func NewSnakeSimulator(maxVerificationSteps int) (s *SnakeSimulator) {
  59. s = &SnakeSimulator{
  60. field: &Field{
  61. Food: &Point{},
  62. Width: 40,
  63. Height: 40,
  64. },
  65. snake: &Snake{
  66. Points: []*Point{
  67. &Point{X: 20, Y: 20},
  68. &Point{X: 21, Y: 20},
  69. &Point{X: 22, Y: 20},
  70. },
  71. },
  72. stats: &Stats{},
  73. maxVerificationSteps: maxVerificationSteps,
  74. fieldUpdateQueue: make(chan bool, 2),
  75. snakeUpdateQueue: make(chan bool, 2),
  76. statsUpdateQueue: make(chan bool, 2),
  77. isPlayingUpdateQueue: make(chan bool, 1),
  78. speedQueue: make(chan uint32, 1),
  79. speed: 10,
  80. }
  81. return
  82. }
  83. // Population test method
  84. // Verifies population and returns unsorted finteses for each individual
  85. func (s *SnakeSimulator) Verify(population *genetic.Population) (fitnesses []*genetic.IndividalFitness) {
  86. s.stats.Generation++
  87. s.statsUpdateQueue <- true
  88. s.field.GenerateNextFood()
  89. if s.speed > 0 {
  90. s.fieldUpdateQueue <- true
  91. }
  92. fitnesses = make([]*genetic.IndividalFitness, len(population.Networks))
  93. for index, inidividual := range population.Networks {
  94. s.stats.Individual = uint32(index)
  95. s.statsUpdateQueue <- true
  96. s.runSnake(inidividual, false)
  97. fitnesses[index] = &genetic.IndividalFitness{
  98. // Fitness: float64(s.stats.Move), //Uncomment this to decrese food impact to individual selection
  99. Fitness: float64(s.stats.Move) * float64(len(s.snake.Points)-2),
  100. Index: index,
  101. }
  102. }
  103. //This is duplication of crossbreedPopulation functionality to display best snake
  104. sort.Slice(fitnesses, func(i, j int) bool {
  105. return fitnesses[i].Fitness > fitnesses[j].Fitness //Descent order best will be on top, worst in the bottom
  106. })
  107. //Best snake showtime!
  108. s.fieldReadMutex.Lock()
  109. s.field.GenerateNextFood()
  110. s.fieldReadMutex.Unlock()
  111. s.fieldUpdateQueue <- true
  112. prevSpeed := s.speed
  113. s.speed = 5
  114. if s.isPlaying == true {
  115. // Play best of the best
  116. s.isPlaying = false
  117. s.isPlayingUpdateQueue <- s.isPlaying
  118. s.runSnake(population.GetBestNetwork(), false)
  119. population.GetBestNetwork().SetStateWatcher(nil)
  120. } else {
  121. // Play best from generation
  122. s.runSnake(population.Networks[fitnesses[0].Index], false)
  123. population.Networks[fitnesses[0].Index].SetStateWatcher(nil)
  124. }
  125. s.speed = prevSpeed
  126. return
  127. }
  128. func (s *SnakeSimulator) PlayBestNetwork(network *neuralnetwork.NeuralNetwork) {
  129. for s.repeatInLoop {
  130. s.stats.Generation++
  131. s.statsUpdateQueue <- true
  132. s.field.GenerateNextFood()
  133. if s.speed > 0 {
  134. s.fieldUpdateQueue <- true
  135. }
  136. //Best snake showtime!
  137. s.fieldReadMutex.Lock()
  138. s.field.GenerateNextFood()
  139. s.fieldReadMutex.Unlock()
  140. s.fieldUpdateQueue <- true
  141. s.isPlaying = false
  142. s.isPlayingUpdateQueue <- s.isPlaying
  143. prevSpeed := s.speed
  144. s.speed = 5
  145. s.runSnake(network, false)
  146. network.SetStateWatcher(nil)
  147. s.speed = prevSpeed
  148. }
  149. }
  150. func (s *SnakeSimulator) runSnake(inidividual *neuralnetwork.NeuralNetwork, randomStart bool) {
  151. s.snakeReadMutex.Lock()
  152. if randomStart {
  153. rand.Seed(time.Now().UnixNano())
  154. s.snake = NewSnake(Direction(rand.Uint32()%4), *s.field)
  155. } else {
  156. s.snake = NewSnake(Direction_Left, *s.field)
  157. }
  158. s.snakeReadMutex.Unlock()
  159. s.stats.Move = 0
  160. for i := 0; i < s.maxVerificationSteps; i++ {
  161. //Read speed from client and sleep in case if user selected slow preview
  162. select {
  163. case newSpeed := <-s.speedQueue:
  164. fmt.Printf("Apply new speed: %v\n", newSpeed)
  165. if newSpeed <= 10 && newSpeed >= 0 {
  166. s.speed = newSpeed
  167. } else if newSpeed < 0 {
  168. s.speed = 0
  169. }
  170. default:
  171. }
  172. if s.speed > 0 {
  173. time.Sleep(100 / time.Duration(s.speed) * time.Millisecond)
  174. s.statsUpdateQueue <- true
  175. s.snakeUpdateQueue <- true
  176. }
  177. predictIndex, _ := inidividual.Predict(mat.NewDense(inidividual.Sizes[0], 1, s.getHeadState()))
  178. direction := Direction(predictIndex + 1)
  179. newHead := s.snake.NewHead(direction)
  180. if s.snake.selfCollision(newHead, direction) {
  181. fmt.Printf("Game over self collision\n")
  182. break
  183. } else if wallCollision(newHead, *s.field) {
  184. break
  185. } else if foodCollision(newHead, s.field.Food) {
  186. i = 0
  187. s.snakeReadMutex.Lock()
  188. s.snake.Feed(newHead)
  189. s.snakeReadMutex.Unlock()
  190. s.fieldReadMutex.Lock()
  191. s.field.GenerateNextFood()
  192. s.fieldReadMutex.Unlock()
  193. if s.speed > 0 {
  194. s.fieldUpdateQueue <- true
  195. }
  196. } else {
  197. s.snakeReadMutex.Lock()
  198. s.snake.Move(newHead)
  199. s.snakeReadMutex.Unlock()
  200. }
  201. s.stats.Move++
  202. }
  203. }
  204. // Produces input activations for neural network
  205. func (s *SnakeSimulator) getHeadState() []float64 {
  206. // Snake state
  207. headX := float64(s.snake.Points[0].X)
  208. headY := float64(s.snake.Points[0].Y)
  209. tailX := float64(s.snake.Points[len(s.snake.Points)-1].X)
  210. tailY := float64(s.snake.Points[len(s.snake.Points)-1].Y)
  211. // Field state
  212. foodX := float64(s.field.Food.X)
  213. foodY := float64(s.field.Food.Y)
  214. width := float64(s.field.Width)
  215. height := float64(s.field.Height)
  216. diag := float64(width) * math.Sqrt2 //We assume that field is always square
  217. // Output activations
  218. // Distance to walls in 4 directions
  219. lWall := headX
  220. rWall := (width - headX)
  221. tWall := headY
  222. bWall := (height - headY)
  223. // Distance to walls in 4 diagonal directions, by default is completely inactive
  224. tlWall := float64(0)
  225. trWall := float64(0)
  226. blWall := float64(0)
  227. brWall := float64(0)
  228. // Distance to food in 4 directions
  229. // By default is size of field that means that there is no activation at all
  230. lFood := float64(width)
  231. rFood := float64(width)
  232. tFood := float64(height)
  233. bFood := float64(height)
  234. // Distance to food in 4 diagonal directions
  235. // By default is size of field diagonal that means that there is no activation
  236. // at all
  237. tlFood := float64(diag)
  238. trFood := float64(diag)
  239. blFood := float64(diag)
  240. brFood := float64(diag)
  241. // Distance to tail in 4 directions
  242. tTail := float64(0)
  243. bTail := float64(0)
  244. lTail := float64(0)
  245. rTail := float64(0)
  246. // Distance to tail in 4 diagonal directions
  247. tlTail := float64(0)
  248. trTail := float64(0)
  249. blTail := float64(0)
  250. brTail := float64(0)
  251. // Diagonal distance to each wall
  252. if lWall > tWall {
  253. tlWall = float64(tWall) * math.Sqrt2
  254. } else {
  255. tlWall = float64(lWall) * math.Sqrt2
  256. }
  257. if rWall > tWall {
  258. trWall = float64(tWall) * math.Sqrt2
  259. } else {
  260. trWall = float64(rWall) * math.Sqrt2
  261. }
  262. if lWall > bWall {
  263. blWall = float64(bWall) * math.Sqrt2
  264. } else {
  265. blWall = float64(lWall) * math.Sqrt2
  266. }
  267. if rWall > bWall {
  268. blWall = float64(bWall) * math.Sqrt2
  269. } else {
  270. brWall = float64(rWall) * math.Sqrt2
  271. }
  272. // Check if food is on same vertical line with head and
  273. // choose vertical direction for activation
  274. if headX == foodX {
  275. if headY-foodY > 0 {
  276. tFood = 0
  277. } else {
  278. bFood = 0
  279. }
  280. }
  281. // Check if food is on same horizontal line with head and
  282. // choose horizontal direction for activation
  283. if headY == foodY {
  284. if headX-foodX > 0 {
  285. lFood = 0
  286. } else {
  287. rFood = 0
  288. }
  289. }
  290. //Check if food is on diagonal any of 4 ways
  291. if math.Abs(foodY-headY) == math.Abs(foodX-headX) {
  292. //Choose diagonal direction to food
  293. if foodX > headX {
  294. if foodY > headY {
  295. trFood = 0
  296. } else {
  297. brFood = 0
  298. }
  299. } else {
  300. if foodY > headY {
  301. tlFood = 0
  302. } else {
  303. blFood = 0
  304. }
  305. }
  306. }
  307. // Check if tail is on same vertical line with head and
  308. // choose vertical direction for activation
  309. if headX == tailX {
  310. if headY-tailY > 0 {
  311. tTail = headY - tailY
  312. } else {
  313. bTail = headY - tailY
  314. }
  315. }
  316. // Check if tail is on same horizontal line with head and
  317. // choose horizontal direction for activation
  318. if headY == tailY {
  319. if headX-tailX > 0 {
  320. rTail = headX - tailX
  321. } else {
  322. lTail = headX - tailX
  323. }
  324. }
  325. //Check if tail is on diagonal any of 4 ways
  326. if math.Abs(headY-tailY) == math.Abs(headX-tailX) {
  327. //Choose diagonal direction to tail
  328. if tailY > headY {
  329. if tailX > headX {
  330. trTail = diag
  331. } else {
  332. tlTail = diag
  333. }
  334. } else {
  335. if tailX > headX {
  336. brTail = diag
  337. } else {
  338. blTail = diag
  339. }
  340. }
  341. }
  342. return []float64{
  343. lWall / width,
  344. rWall / width,
  345. tWall / height,
  346. bWall / height,
  347. tlWall / diag,
  348. trWall / diag,
  349. blWall / diag,
  350. brWall / diag,
  351. (1.0 - lFood/width),
  352. (1.0 - rFood/width),
  353. (1.0 - tFood/height),
  354. (1.0 - bFood/height),
  355. (1.0 - tlFood/diag),
  356. (1.0 - trFood/diag),
  357. (1.0 - blFood/diag),
  358. (1.0 - brFood/diag),
  359. tTail / height,
  360. bTail / height,
  361. lTail / width,
  362. rTail / width,
  363. tlTail / diag,
  364. trTail / diag,
  365. blTail / diag,
  366. brTail / diag,
  367. }
  368. }
  369. // Server part
  370. // Runs gRPC server for GUI
  371. func (s *SnakeSimulator) StartServer() {
  372. go func() {
  373. grpcServer := grpc.NewServer()
  374. RegisterSnakeSimulatorServer(grpcServer, s)
  375. lis, err := net.Listen("tcp", "localhost:65002")
  376. if err != nil {
  377. fmt.Printf("Failed to listen: %v\n", err)
  378. }
  379. fmt.Printf("Listen SnakeSimulator localhost:65002\n")
  380. if err := grpcServer.Serve(lis); err != nil {
  381. fmt.Printf("Failed to serve: %v\n", err)
  382. }
  383. }()
  384. }
  385. // Steaming of Field updates
  386. func (s *SnakeSimulator) Field(_ *None, srv SnakeSimulator_FieldServer) error {
  387. ctx := srv.Context()
  388. for {
  389. select {
  390. case <-ctx.Done():
  391. return ctx.Err()
  392. default:
  393. }
  394. s.snakeReadMutex.Lock()
  395. srv.Send(s.field)
  396. s.snakeReadMutex.Unlock()
  397. <-s.fieldUpdateQueue
  398. }
  399. }
  400. // Steaming of Snake position and length updates
  401. func (s *SnakeSimulator) Snake(_ *None, srv SnakeSimulator_SnakeServer) error {
  402. ctx := srv.Context()
  403. for {
  404. select {
  405. case <-ctx.Done():
  406. return ctx.Err()
  407. default:
  408. }
  409. srv.Send(s.snake)
  410. <-s.snakeUpdateQueue
  411. }
  412. }
  413. // Steaming of snake simulator statistic
  414. func (s *SnakeSimulator) Stats(_ *None, srv SnakeSimulator_StatsServer) error {
  415. ctx := srv.Context()
  416. for {
  417. select {
  418. case <-ctx.Done():
  419. return ctx.Err()
  420. default:
  421. }
  422. s.fieldReadMutex.Lock()
  423. srv.Send(s.stats)
  424. s.fieldReadMutex.Unlock()
  425. <-s.statsUpdateQueue
  426. }
  427. }
  428. // Setup new speed requested from gRPC GUI client
  429. func (s *SnakeSimulator) SetSpeed(ctx context.Context, speed *Speed) (*None, error) {
  430. s.speedQueue <- speed.Speed
  431. return &None{}, nil
  432. }
  433. // Ask to play requested from gRPC GUI client
  434. func (s *SnakeSimulator) PlayBest(ctx context.Context, _ *None) (*None, error) {
  435. s.isPlaying = true
  436. s.isPlayingUpdateQueue <- s.isPlaying
  437. return &None{}, nil
  438. }
  439. // Play in loop
  440. func (s *SnakeSimulator) PlayBestInLoop(_ context.Context, playBest *PlayingBestState) (*None, error) {
  441. s.repeatInLoop = playBest.State
  442. return &None{}, nil
  443. }
  444. // State of playing
  445. func (s *SnakeSimulator) IsPlaying(_ *None, srv SnakeSimulator_IsPlayingServer) error {
  446. ctx := srv.Context()
  447. for {
  448. select {
  449. case <-ctx.Done():
  450. return ctx.Err()
  451. default:
  452. }
  453. srv.Send(&PlayingBestState{
  454. State: s.isPlaying,
  455. })
  456. <-s.isPlayingUpdateQueue
  457. }
  458. }