3
\$\begingroup\$

I'm learning Go and I wrote this for a programming challenge. It is working (building and running) but I feel the code is not what Go code should be:

  • Am I using the struct element correctly? especially struct methods?
  • In the playTurn() method, I reassign results (else not taken into account) because scope. How to write it better? Pointer?
  • I am struggling with int, float64 and casting all the time. What's the good practice in this context were I need float for precision in-between but inputs and outputs are int?

What does this code do? It plays a 2D racing game between 4 pod-racers. In an infinite loop controlled by the programming challenge server, this code reads inputs about pods states (cf. comments) and outputs the next action my pods should take. The part I want to discuss is the simulation part: the code also forecast what my pod state should be after taking the next action. I don't really care if there are bugs right now (since I am writing unit tests to catch them), still I am open to any criticism.

However, I am worried I didn't write in a Go-like manner. See my 3 questions. As a consequence of a better Go code, I expect better performances.

Full script:

package main

import (
    "fmt"
    "math"
    "os"
    "strconv"
)

// Pod is life
type Pod struct {
    position                Dot
    vx, vy, angle, nextCpID int
    hasShieldOn             bool
}

func squaredDist(a, b Dot) int {
    return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y)
}
func distance(a, b Dot) float64 {
    return math.Sqrt(float64(squaredDist(a, b)))
}

func (pod Pod) getAngle(p Dot) float64 {
    d := distance(p, pod.position)
    dx := float64(p.x-pod.position.x) / d
    dy := float64(p.y-pod.position.y) / d
    a := math.Acos(dx) * 180.0 / math.Pi

    // If the point I want is below me, I have to shift the angle for it to be correct
    if dy < 0 {
        a = 360.0 - a
    }
    return a
}

func (pod Pod) diffAngle(p Dot) float64 {
    a := pod.getAngle(p)
    pangle := float64(pod.angle)

    right := 0.0
    if pangle <= a {
        right = a - pangle
    } else {
        right = 360.0 - pangle + a
    }

    left := 0.0
    if pangle >= a {
        left = pangle - a
    } else {
        left = pangle + 360.0 - a
    }

    if right < left {
        return right
    }
    return -left
}

func (pod Pod) rotate(p Dot) int {
    a := pod.diffAngle(p)

    // Can't turn more than 18° in one turn !
    if a > 18.0 {
        a = 18.0
    } else if a < -18.0 {
        a = -18.0
    }

    pod.angle += int(math.Round(a))

    if pod.angle >= 360.0 {
        pod.angle = pod.angle - 360.0
    } else if pod.angle < 0.0 {
        pod.angle += 360.0
    }
    return pod.angle
}

func (pod Pod) boost(t int) (int, int) {
    if pod.hasShieldOn {
        return pod.vx, pod.vy
    }
    pangle := float64(pod.angle)
    pod.vx += int(math.Round(math.Cos(pangle) * float64(t)))
    pod.vy += int(math.Round(math.Sin(pangle) * float64(t)))
    return pod.vx, pod.vy
}

// t shoud become a float later on
func (pod Pod) move(t int) (int, int) {
    pod.position.x += pod.vx * t
    pod.position.y += pod.vy * t
    return pod.position.x, pod.position.y
}

func (pod Pod) endTurn() (int, int) {
    // todo rounding position if needed
    pod.vx = int(float64(pod.vx) * 0.85)
    pod.vy = int(float64(pod.vy) * 0.85)
    return pod.vx, pod.vy
}

func (pod Pod) playTurn(p Dot, t int) {
    pod.angle = pod.rotate(p)
    pod.vx, pod.vy = pod.boost(t)
    pod.position.x, pod.position.y = pod.move(1)
    pod.vx, pod.vy = pod.endTurn()
    fmt.Fprintf(os.Stderr, "\nPredicted Pod position : ")
    fmt.Fprintf(os.Stderr, "\n(%d, %d) speed (%d,%d)", pod.position.x, pod.position.y, pod.vx, pod.vy)
}

// Dot is king
type Dot struct {
    x, y int
}

func main() {
    var laps int
    fmt.Scan(&laps)

    var checkpointCount int
    fmt.Scan(&checkpointCount)
    var checkPoints []Dot
    for i := 0; i < checkpointCount; i++ {
        var checkpointX, checkpointY int
        fmt.Scan(&checkpointX, &checkpointY)
        checkPoints = append(checkPoints, Dot{checkpointX, checkpointY})
    }
    var myPods [2]Pod
    var itsPods [2]Pod
    for {
        for i := 0; i < 2; i++ {
            // x: x position of your pod
            // y: y position of your pod
            // vx: x speed of your pod
            // vy: y speed of your pod
            // angle: angle of your pod
            // nextCheckPointId: next check point id of your pod
            var x, y, vx, vy, angle, nextCheckPointID int
            fmt.Scan(&x, &y, &vx, &vy, &angle, &nextCheckPointID)
            myPods[i] = Pod{Dot{x, y}, vx, vy, angle, nextCheckPointID, false}
            fmt.Fprintf(os.Stderr, "\nActual Pod position : ")
            fmt.Fprintf(os.Stderr, "\n(%d, %d) speed (%d,%d)", myPods[i].position.x, myPods[i].position.y, myPods[i].vx, myPods[i].vy)
        }
        for i := 0; i < 2; i++ {
            // x2: x position of the opponent's pod
            // y2: y position of the opponent's pod
            // vx2: x speed of the opponent's pod
            // vy2: y speed of the opponent's pod
            // angle2: angle of the opponent's pod
            // nextCheckPointId2: next check point id of the opponent's pod
            var x2, y2, vx2, vy2, angle2, nextCheckPointID2 int
            fmt.Scan(&x2, &y2, &vx2, &vy2, &angle2, &nextCheckPointID2)
            itsPods[i] = Pod{Dot{x2, y2}, vx2, vy2, angle2, nextCheckPointID2, false}
        }

        // fmt.Fprintln(os.Stderr, "Debug messages...")
        for _, pod := range myPods {
            nx := checkPoints[pod.nextCpID].x - 3*pod.vx
            ny := checkPoints[pod.nextCpID].y - 3*pod.vy
            // Predicting where my pods will be next turn:
            pod.playTurn(Dot{nx, ny}, 100)
            //
            fmt.Println(strconv.Itoa(nx) + " " + strconv.Itoa(ny) + " 100")
        }
    }
}
\$\endgroup\$
  • 2
    \$\begingroup\$ What does this code do? \$\endgroup\$ – 200_success Mar 24 at 1:11
  • 1
    \$\begingroup\$ The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. \$\endgroup\$ – Toby Speight Mar 25 at 9:30
  • \$\begingroup\$ thanks for the feedback & the edit. Should I split it into 3 posts : OOP in Go, is it a pointers use case, how to manage int and float64 ? \$\endgroup\$ – Poutrathor Mar 25 at 14:29
  • \$\begingroup\$ @Poutrathor I've not taken the time to read through your code yet, but I did notice the use of value receivers. It doesn't make sense to have functions like move on a type, and in those funcs statements like pod.position.x += pod.vx * t (assigning values), unless you're dealing with pointer receivers. Otherwise, you'd just be reassigning values on a copy, but the object itself is never changed. \$\endgroup\$ – Elias Van Ootegem Apr 19 at 14:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.