- package main
-
- import (
- v "bitbucket.org/hackerbots/vector"
- "log"
- "math"
- "math/rand"
- )
-
- type Robot struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Message string `json:"-"`
- Stats Stats `json:"-"`
- TargetSpeed float32 `json:"-"`
- Speed float32 `json:"speed"`
- Health int `json:"health"`
- RepairCounter float32 `json:"repair"`
- ScanCounter float32 `json:"scan_bonus"`
- ActiveScan bool `json:"-"`
- Position v.Point2d `json:"position"`
- Heading v.Vector2d `json:"heading"`
- DesiredHeading *v.Vector2d `json:"-"`
- MoveTo *v.Point2d `json:"-"`
- FireAt *v.Point2d `json:"-"`
- Scanners []Scanner `json:"scanners"`
- LastFired int `json:"-"`
- Collision bool `json:"collision"`
- Hit bool `json:"hit"`
- Probe *v.Point2d `json:"probe"`
- ProbeResult *ProbeResult `json:"probe_result"`
- gameStats *BotStats `json:-`
- }
-
- type ProbeResult struct {
- v.Point2d
- Type string `json:"type"`
- }
-
- // This is the subset of data we send to players about robots
- // that are not theirs.
- type OtherRobot struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Position v.Point2d `json:"position"`
- Heading v.Vector2d `json:"heading"`
- Health int `json:"health"`
- }
-
- func (r Robot) GetTruncatedDetails() OtherRobot {
- return OtherRobot{
- Id: r.Id,
- Name: r.Name,
- Position: r.Position,
- Heading: r.Heading,
- Health: r.Health,
- }
- }
-
- type RobotSorter struct {
- Robots []OtherRobot
- }
-
- func (s RobotSorter) Len() int {
- return len(s.Robots)
- }
-
- func (s RobotSorter) Swap(i, j int) {
- s.Robots[i], s.Robots[j] = s.Robots[j], s.Robots[i]
- }
-
- func (s RobotSorter) Less(i, j int) bool {
- return s.Robots[i].Id < s.Robots[j].Id
- }
-
- // TODO - how do I not duplicate this code???
- type AllRobotSorter struct {
- Robots []BotHealth
- }
-
- func (s AllRobotSorter) Len() int {
- return len(s.Robots)
- }
-
- func (s AllRobotSorter) Swap(i, j int) {
- s.Robots[i], s.Robots[j] = s.Robots[j], s.Robots[i]
- }
-
- func (s AllRobotSorter) Less(i, j int) bool {
- return s.Robots[i].RobotId < s.Robots[j].RobotId
- }
-
- type Stats struct {
- Hp int `json:"-"`
- Speed float32 `json:"-"`
- Acceleration float32 `json:"-"`
- WeaponRadius int `json:"-"`
- ScannerRadius int `json:"-"`
- TurnSpeed int `json:"-"`
- FireRate int `json:"-"`
- WeaponDamage int `json:"-"`
- WeaponSpeed float32 `json:"-"`
- }
-
- // We request stats using an integer between 1 and 100, the
- // integer values map to sensible min-max ranges
- type StatsRequest struct {
- Hp int `json:"hp"`
- Speed int `json:"speed"`
- Acceleration int `json:"acceleration"`
- WeaponRadius int `json:"weapon_radius"`
- ScannerRadius int `json:"scanner_radius"`
- TurnSpeed int `json:"turn_speed"`
- FireRate int `json:"fire_rate"`
- WeaponDamage int `json:"weapon_damage"`
- WeaponSpeed int `json:"weapon_speed"`
- }
-
- func DeriveStats(request StatsRequest) Stats {
- s := Stats{}
-
- // Conversion Tables
- var hp_min float32 = 20.0
- var hp_max float32 = 200.0
- s.Hp = int(((float32(request.Hp) / 100.0) * (hp_max - hp_min)) + hp_min)
-
- var speed_min float32 = 40.0
- var speed_max float32 = 200.0
- s.Speed = ((float32(request.Speed) / 100.0) * (speed_max - speed_min)) + speed_min
-
- var accel_min float32 = 20.0
- var accel_max float32 = 200.0
- s.Acceleration = ((float32(request.Acceleration) / 100.0) * (accel_max - accel_min)) + accel_min
-
- var wep_rad_min float32 = 5.0
- var wep_rad_max float32 = 60.0
- s.WeaponRadius = int(((float32(request.WeaponRadius) / 100.0) * (wep_rad_max - wep_rad_min)) + wep_rad_min)
-
- var scan_rad_min float32 = 100.0
- var scan_rad_max float32 = 400.0
- s.ScannerRadius = int(((float32(request.ScannerRadius) / 100.0) * (scan_rad_max - scan_rad_min)) + scan_rad_min)
-
- var turn_spd_min float32 = 30.0
- var turn_spd_max float32 = 300.0
- s.TurnSpeed = int(((float32(request.TurnSpeed) / 100.0) * (turn_spd_max - turn_spd_min)) + turn_spd_min)
-
- var fire_rate_min float32 = 10.0
- var fire_rate_max float32 = 2000.0
- s.FireRate = int(fire_rate_max+300.0) - int(((float32(request.FireRate)/100.0)*(fire_rate_max-fire_rate_min))+fire_rate_min)
-
- var weapon_damage_min float32 = 0.0
- var weapon_damage_max float32 = 20.0
- s.WeaponDamage = int(((float32(request.WeaponDamage) / 100.0) * (weapon_damage_max - weapon_damage_min)) + weapon_damage_min)
-
- var weapon_speed_min float32 = 80.0
- var weapon_speed_max float32 = 600.0
- s.WeaponSpeed = float32(((float32(request.WeaponSpeed) / 100.0) * (weapon_speed_max - weapon_speed_min)) + weapon_speed_min)
-
- return s
- }
-
- type Instruction struct {
- Message *string `json:"message,omitempty"`
- MoveTo *v.Point2d `json:"move_to,omitempty"`
- Heading *v.Vector2d `json:"heading,omitempty"`
- FireAt *v.Point2d `json:"fire_at,omitempty"`
- Probe *v.Point2d `json:"probe,omitempty"`
- TargetSpeed *float32 `json:"target_speed,omitempty"`
- Repair *bool `json:"repair,omitempty"`
- Scan *bool `json:"scan,omitempty"`
- }
-
- // returns collision, the intersection point, and the robot with whom r has
- // collided, if this happened.
- func (r *Robot) checkCollisions(g *game, probe v.Vector2d) (bool, *v.Point2d, *Robot) {
- finalCollision := false
- collision := false
- closest := float32(math.Inf(1))
- var intersection *v.Point2d
- var finalRobot *Robot
-
- // TODO: this needs moved to the conf?
- botSize := float32(5.0)
- botPolygon := v.OrientedSquare(r.Position, r.Heading, botSize)
-
- // Check Walls
- r_walls := v.AABB2d{A: v.Point2d{X: botSize, Y: botSize}, B: v.Point2d{X: g.width - botSize, Y: g.height - botSize}}
- collision, _, wallIntersect := v.RectIntersection(r_walls, r.Position, probe)
- if collision && wallIntersect != nil {
- finalCollision = collision
- if dist := r.Position.Sub(*wallIntersect).Mag(); dist < closest {
- intersection = wallIntersect
- closest = dist
- }
- }
-
- // Check Other Bots
- for player := range g.players {
- for _, bot := range player.Robots {
- if bot.Id == r.Id {
- continue
- }
- player_rect := v.OrientedSquare(bot.Position, bot.Heading, botSize)
- collision, move_collision, translation := v.PolyPolyIntersection(
- botPolygon, probe, player_rect)
- if collision || move_collision {
- finalCollision = collision
- p := r.Position.Add(probe).Add(translation.Scale(1.2))
- if dist := r.Position.Sub(p).Mag(); dist < closest {
- intersection = &p
- closest = dist
- finalRobot = bot
- }
- }
- }
- }
- // Check Obstacles
- for _, obj := range g.obstacles {
- // collision due to motion:
- collision, move_collision, translation := v.PolyPolyIntersection(
- botPolygon, probe, obj.Bounds.ToPolygon())
-
- if collision || move_collision {
- finalCollision = collision
- p := r.Position.Add(probe).Add(translation.Scale(1.1))
- if dist := r.Position.Sub(p).Mag(); dist < closest {
- intersection = &p
- closest = dist
- }
- }
-
- // collision due to probe
- collision, _, wallIntersect := v.RectIntersection(obj.Bounds, r.Position, probe)
- if collision && wallIntersect != nil {
- finalCollision = collision
- if dist := r.Position.Sub(*wallIntersect).Mag(); dist < closest {
- intersection = wallIntersect
- closest = dist
- }
- }
- }
-
- return finalCollision, intersection, finalRobot
- }
-
- func (r *Robot) Tick(g *game) {
- r.Collision = false
- r.Hit = false
- r.scan(g)
-
- // Adjust Speed
- if r.Speed < r.TargetSpeed {
- r.Speed += (r.Stats.Acceleration * delta)
- if r.Speed > r.TargetSpeed {
- r.Speed = r.TargetSpeed
- }
- } else if float32(math.Abs(float64(r.Speed-r.TargetSpeed))) > v.Epsilon {
- r.Speed -= (r.Stats.Acceleration * delta)
- // Cap reverse to 1/2 speed
- if r.Speed < (-0.5 * r.TargetSpeed) {
- r.Speed = (-0.5 * r.TargetSpeed)
- }
- } else {
- r.Speed = r.TargetSpeed
- }
-
- // Adjust Heading
- current_heading := r.Heading
- if current_heading.Mag() == 0 && r.MoveTo != nil {
- // We may have been stopped before this and had no heading
- current_heading = r.MoveTo.Sub(r.Position).Normalize()
- }
-
- new_heading := current_heading
- if r.MoveTo != nil {
- // Where do we WANT to be heading?
- new_heading = r.MoveTo.Sub(r.Position).Normalize()
- }
-
- if r.DesiredHeading != nil {
- // Where do we WANT to be heading?
- new_heading = r.DesiredHeading.Normalize()
- }
-
- if new_heading.Mag() > 0 {
- // Is our direction change too much? Hard coding to 5 degrees/s for now
- angle := v.Angle(current_heading, new_heading) * v.Rad2deg
-
- dir := 1.0
- if angle < 0 {
- dir = -1.0
- }
-
- // Max turn radius in this case is in degrees per second
- if float32(math.Abs(float64(angle))) > (float32(r.Stats.TurnSpeed) * delta) {
- // New heading should be a little less, take current heading and
- // rotate by the max turn radius per frame.
- rot := (float32(r.Stats.TurnSpeed) * delta) * v.Deg2rad
-
- new_heading = current_heading.Rotate(rot * float32(dir))
- }
-
- move_vector := new_heading.Scale(r.Speed * delta)
- collision, intersection_point, hit_robot := r.checkCollisions(g, move_vector)
- if collision {
- dmg := int(math.Abs(float64(r.Speed)) / 10.0)
- if dmg <= 0 {
- // All collisions need to do at least a little damage,
- // otherwise robots could get stuck and never die
- dmg = 1
- }
-
- r.Collision = true
- if hit_robot != nil {
- hit_robot.Health -= dmg
- hit_robot.Speed = (hit_robot.Speed * 0.5)
- // hit_robot.Heading = r.Heading
- if hit_robot.Health <= 0 {
- hit_robot.gameStats.Deaths++
- r.gameStats.Kills++
- }
- }
-
- if r.Position != *intersection_point {
- r.Position = *intersection_point
- }
-
- r.Health -= dmg
- r.MoveTo = &r.Position
- r.Speed = (r.Speed * -0.5)
- // r.Heading = r.Heading.Scale(-1.0)
-
- if r.Health <= 0 {
- r.gameStats.Deaths++
- r.gameStats.Suicides++
- }
- } else {
- r.Position = r.Position.Add(move_vector)
- if new_heading.Mag() > 0 {
- r.Heading = new_heading
- } else {
- log.Printf("Zero Heading %v", new_heading)
- }
- }
- }
-
- // We only self repair when we're stopped
- if math.Abs(float64(r.Speed)) < v.Epsilon && r.RepairCounter > 0 {
- r.RepairCounter -= delta
- if r.RepairCounter < 0 {
- r.Health += g.repair_hp
- if r.Health > r.Stats.Hp {
- r.Health = r.Stats.Hp
- }
- r.RepairCounter = g.repair_rate
- }
- }
-
- // We are only allowed to scan when we're stopped
- if math.Abs(float64(r.Speed)) < v.Epsilon && r.ActiveScan {
- r.ScanCounter += delta * float32(r.Stats.ScannerRadius) * 0.1
- } else if r.ScanCounter > 0 {
- r.ScanCounter -= delta * float32(r.Stats.ScannerRadius) * 0.05
- if r.ScanCounter <= 0 {
- r.ScanCounter = 0
- }
- }
-
- if r.FireAt != nil {
- proj := r.fire(g)
- if proj != nil {
- g.projectiles[proj] = true
- }
- }
-
- if r.Probe != nil && r.ProbeResult == nil {
- probe_vector := r.Probe.Sub(r.Position)
- coll, pos, robo := r.checkCollisions(g, probe_vector)
- if coll {
- r.ProbeResult = &ProbeResult{
- Point2d: *pos,
- Type: "obstacle",
- }
- if robo != nil {
- r.ProbeResult.Type = "robot"
- }
- }
- }
- }
-
- func (r *Robot) scan(g *game) {
- r.Scanners = r.Scanners[:0]
- for player := range g.players {
- for _, bot := range player.Robots {
- if bot.Id == r.Id || bot.Health <= 0 {
- continue
- }
- dist := v.Distance(bot.Position, r.Position)
- if dist < float32(r.Stats.ScannerRadius+int(r.ScanCounter)) {
- s := Scanner{
- Id: bot.Id,
- Type: "robot",
- }
- r.Scanners = append(r.Scanners, s)
- }
- }
- }
-
- for proj := range g.projectiles {
- if proj.Owner == r {
- continue
- }
-
- dist := v.Distance(proj.Position, r.Position)
- if dist < float32(r.Stats.ScannerRadius+int(r.ScanCounter)) {
- s := Scanner{
- Id: proj.Id,
- Type: "projectile",
- }
- r.Scanners = append(r.Scanners, s)
- }
- }
-
- for splo := range g.splosions {
- dist := v.Distance(splo.Position, r.Position)
- if dist < float32(r.Stats.ScannerRadius+int(r.ScanCounter)) {
- s := Scanner{
- Id: splo.Id,
- Type: "explosion",
- }
- r.Scanners = append(r.Scanners, s)
- }
- }
-
- }
-
- func (r *Robot) fire(g *game) *Projectile {
- // Throttle the fire rate
- time_since_fired := (float32(g.turn) * (delta * 1000)) - (float32(r.LastFired) * (delta * 1000))
- if time_since_fired < float32(r.Stats.FireRate) {
- return nil
- }
-
- r.LastFired = g.turn
- r.gameStats.Shots++
-
- return &Projectile{
- Id: idg.Hash(),
- Position: r.Position,
- MoveTo: *r.FireAt,
- Damage: r.Stats.WeaponDamage,
- Radius: r.Stats.WeaponRadius,
- Speed: r.Stats.WeaponSpeed,
- Owner: r,
- }
- }
-
- func (r *Robot) reset(g *game) {
- for {
- start_pos := v.Point2d{
- X: rand.Float32() * float32(g.width),
- Y: rand.Float32() * float32(g.height),
- }
- r.MoveTo = &start_pos
- r.Position = start_pos
- r.Health = r.Stats.Hp
-
- // Check Obstacles
- retry := false
- for _, obj := range g.obstacles {
- _, inside, _ := v.RectIntersection(obj.Bounds, r.Position, v.Vector2d{0, 0})
- if inside {
- retry = true
- }
- }
- if !retry {
- break
- }
- }
- }
|