package client import ( "fmt" "math" "github.com/nsf/termbox-go" "hackerbots.us/server" ) var botDown rune = 'v' var botUp rune = '^' var botRight rune = '>' var botLeft rune = '<' type size struct { width, height int } // Recv is our implementation of receiving a server.Boardstate from the server func (s *Client) Recv(bs *server.Boardstate) map[string]server.Instruction { s.StateStream <- bs return nil } func (s *Client) Visualize() { err := termbox.Init() if err != nil { panic(err) } s.viewX, s.viewY = termbox.Size() events := make(chan termbox.Event, 1024) go func() { for { events <- termbox.PollEvent() } }() termbox.HideCursor() termbox.Clear(termbox.ColorBlack, termbox.ColorBlack) func() { for { select { case event := <-events: switch event.Type { case termbox.EventKey: switch event.Key { case termbox.KeyCtrlZ, termbox.KeyCtrlC: return } switch event.Ch { case 'q': return case 'c': termbox.Clear(termbox.ColorBlack, termbox.ColorBlack) } case termbox.EventResize: s.viewX, s.viewY = event.Width, event.Height case termbox.EventError: panic(fmt.Sprintf("Quitting because of termbox error:\n%v\n", event.Err)) } case u := <-s.StateStream: termbox.Clear(termbox.ColorBlack, termbox.ColorBlack) for _, obstacle := range u.Obstacles { startX := int((obstacle.Bounds.A.X / s.width) * float64(s.viewX)) stopX := int((obstacle.Bounds.B.X / s.width) * float64(s.viewX)) startY := int((obstacle.Bounds.A.Y / s.height) * float64(s.viewY)) stopY := int((obstacle.Bounds.B.Y / s.height) * float64(s.viewY)) for x := startX; x < stopX; x++ { for y := startY; y < stopY; y++ { termbox.SetCell( x, s.viewY-y, ' ', termbox.ColorBlack, termbox.ColorBlue, ) } } } for _, bot := range u.OtherRobots { x := int((bot.Position.X / s.width) * float64(s.viewX)) y := int((bot.Position.Y / s.height) * float64(s.viewY)) var b rune if math.Abs(bot.Heading.X) > math.Abs(bot.Heading.Y) { if bot.Heading.X > 0 { b = botRight } else { b = botLeft } } else { if bot.Heading.Y > 0 { b = botUp } else { b = botDown } } c := termbox.ColorRed if bot.Health <= 0 { c = termbox.ColorBlack } termbox.SetCell( x, s.viewY-y, b, c, termbox.ColorBlack, ) } for _, p := range u.Projectiles { x := int((p.Position.X / s.width) * float64(s.viewX)) y := int((p.Position.Y / s.height) * float64(s.viewY)) termbox.SetCell( x, s.viewY-y, '·', termbox.ColorWhite|termbox.AttrBold, termbox.ColorBlack, ) } for _, splosion := range u.Splosions { startX := int(((splosion.Position.X - float64(splosion.Radius)) / s.width) * float64(s.viewX)) startY := int(((splosion.Position.Y - float64(splosion.Radius)) / s.height) * float64(s.viewY)) stopX := int(((splosion.Position.X+float64(splosion.Radius))/s.width)*float64(s.viewX)) + 1 stopY := int(((splosion.Position.Y+float64(splosion.Radius))/s.height)*float64(s.viewY)) + 1 for x := startX; x < stopX; x++ { for y := startY; y < stopY; y++ { realX := float64(x) * s.width / float64(s.viewX) realY := float64(y) * s.height / float64(s.viewY) dX := realX - splosion.Position.X dY := realY - splosion.Position.Y curRad := math.Sqrt(dX*dX + dY*dY) if curRad < float64(splosion.Radius) { termbox.SetCell( x, s.viewY-y, '·', termbox.ColorYellow|termbox.AttrBold, termbox.ColorRed, ) } } } } for _, bot := range u.MyRobots { x := int((bot.Position.X / s.width) * float64(s.viewX)) y := int((bot.Position.Y / s.height) * float64(s.viewY)) var b rune if math.Abs(bot.Heading.X) > math.Abs(bot.Heading.Y) { if bot.Heading.X > 0 { b = botRight } else { b = botLeft } } else { if bot.Heading.Y > 0 { b = botUp } else { b = botDown } } c := termbox.ColorWhite if bot.Health <= 0 { c = termbox.ColorBlack } termbox.SetCell( x, s.viewY-y, b, c|termbox.AttrBold, termbox.ColorBlack, ) } err := termbox.Flush() if err != nil { panic(err) } case <-s.Die: return } } }() termbox.Close() }