Rebol3 Code Examplex


Box the compass

Generate compass point names from degree values.

Rebol [
    title: "Rosetta code: Box the compass"
    file:  %Box_the_compass.r3
    url:   https://rosettacode.org/wiki/Box_the_compass
    needs: 3.10.0 ;; or something like that
]

form-angle: func[
    "Format the angle as a string with at least one decimal and pad to 2 decimals"
    angle [decimal!]
][
    angle: form round/to angle 0.01
    pad/with find/last angle #"." 3 #"0"  ;; ensure two digits after the decimal point
    append angle #"°"                     ;; add the degree symbol
]

compass: context [
    points: none

    make-points: function [/verbose][
        out: copy []
        ;; Define a character set for the four main directions
        main-dir: charset "NESW"
        ;; 32-wind compass notation (with quarter winds and by-points)
        words: [
            N NbE NNE NEbN NE NEbE ENE EbN E EbS ESE SEbE SE SEbS SSE
            SbE S SbW SSW SWbS SW SWbW WSW WbS W WbN WNW NWbW NW NWbN NNW NbW N
        ]
        ;; Replacement map from shorthand characters to full words (and spacing)
        replacement: #[
            #"N" "north"
            #"b" " by "
            #"S" "south"
            #"E" "east"
            #"W" "west"
        ]
        i: 0  ;; index of the current compass point (0..32)
        foreach word words [
            ;; Compute heading angle:
            ;; Each step is 11.25°, with offsets of +5.62, 0.0, or -5.62 for the 1/3 sub-steps.
            heading: i * 11.25 + (pickz [0.0 5.62 -5.62] i % 3)
            ;; Start with the symbolic label (e.g., "NEbE") as a mutable string
            text: to string! word
            ;; Insert a hyphen after the first main direction if followed by two more main directions
            ;; (e.g., "NEbE" -> "N-EbE")
            parse text [1 main-dir ahead 2 main-dir insert "-"]
            ;; Expand shorthand letters to full words using the replacement table:
            ;; - Replace N/S/E/W with north/south/east/west
            ;; - Replace 'b' with " by "
            parse text [any [p: change skip (any [replacement/(p/1) p/1])]]
            ;; Capitalize first letter of the expanded label (e.g., "north by east" -> "North by east")
            uppercase/part text 1
            if verbose [
                ;; Print three columns:
                ;; - Ordinal index within 32-wind cycle (1..32)
                ;; - Formatted angle
                ;; - Expanded textual compass point
                print [
                    pad (i % 32 + 1) -3
                    pad form-angle heading -7
                    pad form word 5
                    text
                ]
            ]
            repend out [heading word text]
            ++ i
        ]
        new-line/skip out true 3
        out
    ]

    nearest-text: function [
        "Returns the nearest compass point text for a given angle."
        angle     [number! integer! decimal!]
    ][
        ;; :lazily build the points table if not already built
        unless points [points: make-points]
        ;; Normalize query angle into [0,360)
        angle: angle % 360.0
        if angle < 0 [angle: angle + 360.0]

        best-diff: 1e9
        ;; Walk triples: angle word text
        foreach [deg wrd txt] points [
            ;; Compute minimal circular difference
            diff: abs deg - angle
            if diff < best-diff [
                best-diff: diff
                best: txt ;; remember the text for the closest heading
            ]
        ]
        best
    ]
]

;; Print all angle texts
compass/make-points/verbose

print "^/Resolve texts for random angles:"
random/seed 1
loop 5 [
    angle: random 360.0
    print [
        pad form-angle angle -7
        compass/nearest-text angle
    ]
]

Output:

  1   0.00° N     North
  2  16.87° NbE   North by east
  3  16.88° NNE   North-northeast
  4  33.75° NEbN  Northeast by north
  5  50.62° NE    Northeast
  6  50.63° NEbE  Northeast by east
  7  67.50° ENE   East-northeast
  8  84.37° EbN   East by north
  9  84.38° E     East
 10 101.25° EbS   East by south
 11 118.12° ESE   East-southeast
 12 118.13° SEbE  Southeast by east
 13 135.00° SE    Southeast
 14 151.87° SEbS  Southeast by south
 15 151.88° SSE   South-southeast
 16 168.75° SbE   South by east
 17 185.62° S     South
 18 185.63° SbW   South by west
 19 202.50° SSW   South-southwest
 20 219.37° SWbS  Southwest by south
 21 219.38° SW    Southwest
 22 236.25° SWbW  Southwest by west
 23 253.12° WSW   West-southwest
 24 253.13° WbS   West by south
 25 270.00° W     West
 26 286.87° WbN   West by north
 27 286.88° WNW   West-northwest
 28 303.75° NWbW  Northwest by west
 29 320.62° NW    Northwest
 30 320.63° NWbN  Northwest by north
 31 337.50° NNW   North-northwest
 32 354.37° NbW   North by west
  1 354.38° N     North

Resolve texts for random angles:
 24.64° North-northeast
233.98° Southwest by west
119.07° Southeast by east
 97.76° East by south
357.45° North