11
\$\begingroup\$

Description

The task of this challenge is to devise a program or function that tracks a given object in an \$n×n\$ space.

I/O

Your program will be given 3 inputs, which may be taken in any sensible way:

n will be the size of the plane's side. (so, for \$n=5\$, your plane will be \$5×5\$). You may assume n will always be an odd integer.

s will be the starting position of the object, given as a pair of \$(x, y)\$ coordinates.

D will be a vector of ordered pairs. D will follow the format \$D = [(d_0,t_0),(d_1,t_1),...,(d_n,t_n)]\$, where \$d_k\$ will always be one of 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', for the cardinal and primary intercardinal directions, and \$t_k\$ will be an integer for the number of 'ticks'.

Given these inputs, your program must output a tracking of the object in the plane.

Rules

The output must contain the plane's boundaries. E.g.:

- 21012 +
+┌─────┐
2│     │
1│     │
0│     │
1│     │
2│     │
-└─────┘

would be an example of an empty \$5×5\$ plane. The numbers above and to the side are for reference only and don't need to be printed.

You may use whatever character(s) for the boundaries, as long as it's not whitespace (or renders as whitespace). The characters you choose must delineate the full plane, meaning that there can be no gaps between them.

Some acceptable planes include:

┌──┐  ....  ----  +--+
│  │  .  .  |  |  |  |
│  │  .  .  |  |  |  |
└──┘; ....; ----; +--+

Nonacceptable planes include:

      ....  ....  ++++  .  .
            .  .  +  +  .  .
            .     +  +  .  .
    ; ....; ....; +  +; .  .

The object to be tracked may be whatever character you choose, as long as it only occupies 1 space on the plane and is different from the boundary characters.

The trace of the tracked object may also be whatever characters you choose, as long as they only occupy 1 space on the plane and are different from the object.

For each element \$(d_k,t_k)\$ in \$D\$, the object must move \$t\$ spaces towards \$d\$, and leave a trace behind.

If the object would hit a boundary, it'll be reflected. If the object still has any moves left, it'll keep moving in the direction it was reflected to.

For reference, these directions reflect to each other:

\$N\rightleftharpoons S\$ → when the top or bottom boundary is met;

\$E\rightleftharpoons W\$ → when a lateral boundary is met;

The final output will contain the newest possible traces, that is, if the object would leave a trace in a space where there's already a trace, the newer trace character will overwrite the older.

As usual, standard loopholes are forbidden by default.

Scoring:

This is a challenge.

Examples:

Input: \$n=5\$, \$s=(0,0)\$, \$D=[('NW',2),('S',2),('E',1)]\$

Working it out:

\$t=0\$

    0
 ┌─────┐
 │     │
 │     │
0│  ○  │
 │     │
 │     │
 └─────┘

\$t=2\$

    0
 ┌─────┐
 │○    │
 │ \   │
0│  \  │
 │     │
 │     │
 └─────┘

\$t=4\$

    0
 ┌─────┐
 │∧    │
 │|\   │
0│○ \  │
 │     │
 │     │
 └─────┘

\$t=5\$, which will be the output.

    0
 ┌─────┐
 │∧    │
 │|\   │
0│└○\  │
 │     │
 │     │
 └─────┘

(The 0s are just for reference, and they don't need to be in the final output.)


Input: \$n=9\$, \$s=(3,-1)\$, \$D=[('N',2),('SW',8),('SE',3),('NE',8)]\$

Notice that, when \$t=10\$:

      0     
 ┌─────────┐
 │         │
 │         │
 │         │
 │       ∧ │
0│      /| │
 │ ○   / | │
 │⟨   /    │
 │ \ /     │
 │  ∨      │
 └─────────┘

The object has been reflected twice: once when reaching the bottom of the plane while going towards the \$SW\$, where it reflects to the \$NW\$; then once again when reaching the left side of the plane, where \$NW\$ reflects to \$NE\$.

The final output comes at \$t=21\$:

      0     
 ┌─────────┐
 │    ○    │
 │     \   │
 │      \  │
 │       \ │
0│      /|⟩│
 │ ∧   / / │
 │⟨ \ / /  │
 │ \ \ /   │
 │  ∨ ∨    │
 └─────────┘

Test cases:

Input: \$n=5\$, \$s=(0,0)\$, \$D=[('NW',2),('S',2),('E',1)]\$

Output:

    0
 ┌─────┐
 │∧    │
 │|\   │
0│└○\  │
 │     │
 │     │
 └─────┘


Input: \$n=9\$, \$s=(3,-1)\$, \$D=[('N',2),('SW',8),('SE',3),('NE',8)]\$

Output:

      0     
 ┌─────────┐
 │    ○    │
 │     \   │
 │      \  │
 │       \ │
0│      /|⟩│
 │ ∧   / / │
 │⟨ \ / /  │
 │ \ \ /   │
 │  ∨ ∨    │
 └─────────┘


Input: \$n=3\$, \$s=(1,1)\$, \$D=[('N',5),('W',5)]\$

Output:

   0
 ┌───┐
 │  |│
0│-○┐│
 │  |│
 └───┘


Input: \$n=11\$, \$s=(3,-5)\$, \$D=[('NW',8),('E',5),('SE',3),('SW',5),('N',6),('NE',10)]\$

Output:

       0
 ┌───────────┐
 │     ∧     │
 │    / \    │
 │┌--/-\ \   │
 │ \ |/ \ \  │
 │  \|   \ \ │
0│   |   /  ⟩│
 │   |\ /  / │
 │   | /  ○  │
 │   |/ \    │
 │   ∨   \   │
 │        \  │
 └───────────┘
\$\endgroup\$
  • \$\begingroup\$ Forgot to mention that this challenge was sandboxed. \$\endgroup\$ – J. Sallé Sep 26 '18 at 14:20
  • \$\begingroup\$ Can we take 'N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW' as a 0-indexed (or 1-indexed) integer instead? So [('NW',2),('S',2),('E',1)] becomes [[7,2],[4,2],[2,1]] for example. \$\endgroup\$ – Kevin Cruijssen Sep 26 '18 at 14:33
  • \$\begingroup\$ @KevinCruijssen sure, no problem. Just make sure to point that out in the answer. \$\endgroup\$ – J. Sallé Sep 26 '18 at 14:34
  • 1
    \$\begingroup\$ @Arnauld yes, you're allowed to use a single trace character. I used more than one so it'd be easier to visualize the test cases, but it's not required. Just make sure the trace character is different from the character of the object being traced. \$\endgroup\$ – J. Sallé Sep 26 '18 at 14:46
  • 1
    \$\begingroup\$ @Arnauld "The object to be tracked may be whatever character you choose, as long as it only occupies 1 space on the plane and is different from the boundary characters. The trace of the tracked object may also be whatever characters you choose, as long as they only occupy 1 space on the plane and are different from the object." \$\endgroup\$ – Kevin Cruijssen Sep 26 '18 at 14:49
9
\$\begingroup\$

JavaScript (ES6), 228 bytes

Takes input as (n,x,y,[[dir,len],[dir,len],...]) where directions are encoded counterclockwise from \$0\$ for South to \$7\$ for South-West.

Outputs a string with 0 for a boundary, 1 for a trace and 3 for the final position.

(n,x,y,a)=>(g=X=>Y>n?'':(Y%n&&X%n&&a.map(([d,l],i)=>(M=H=>(h-X|v-Y||(k|=a[i+!l]?1:3),l--&&M(H=(h+H)%n?H:-H,h+=H,v+=V=(v+V)%n?V:-V)))(~-(D='12221')[d],V=~-D[d+2&7]),h=x+n/2,v=n/2-y,k=' ')&&k)+(X<n?'':`
`)+g(X++<n?X:!++Y))(Y=!++n)

Try it online!

How?

Initializing and drawing into a 'canvas' (i.e. a matrix of characters) is a bit tedious and lengthy in JavaScript.

This code is using a different strategy: instead of storing the output in a 2D array, it builds a string character by character, from left to right and from top to bottom. At each iteration:

  • We output a 0 if we're over a boundary.
  • Otherwise, we simulate the full path and see if it's crossing our current position. We output either 1 or 3 if it does, or a space otherwise.
  • We append a linefeed if we've reached the right boundary.

All in all, this may not be the shortest approach, but I thought it was worth trying.

\$\endgroup\$
9
\$\begingroup\$

Java 10, 350 343 340 336 bytes

(n,s,S,D)->{int N=n+2,x=N/2+s,y=N/2-S,i=N*N;var r=new char[N][N];for(;i-->0;)r[i/N][i%N]=i/N*(i%N)<1|i/N>n|i%N>n?'#':32;r[y][x]=42;for(var d:D)for(i=d[0];d[1]-->0;r[y+=i%7<2?1/y*2-1:i>2&i<6?y<n?1:-1:0][x+=i>0&i<4?x<n?1:-1:i>4?1/x*2-1:0]=42)i=y<2&i<2|y>=n&i>2&i<5?4-i:x<2&i>4|x>=n&i>0&i<4?8-i:y<2&i>6?5:y<n|i!=5?i:7;r[y][x]=79;return r;}

D is a 2D integer-array where the directions are 0-indexed integers: N=0, NE=1, E=2, SE=3, S=4, SW=5, W=6, NW=7. The starting x,y-coordinates will be two separate parameters s and S. The output is a character-matrix.
It uses # as border, * as trail, and O for the ending position (but can be all three be any ASCII characters in the unicode range [33,99] for the same byte-count if you'd want).

Try it online.

-4 bytes thanks to @ceilingcat.
Can definitely be golfed some more by simplifying the movements and in which direction we're traveling some more.

Explanation:

(n,s,S,D)->{           // Method with `n`,`s,S`,`D` parameters & char-matrix return-type
  int N=n+2,           //  Set `N` to `n+2`, since we use it multiple times
      x=N/2+s,         //  Calculate the starting `x` coordinate
      y=N/2-S,         //  Calculate the starting `y` coordinate
      i=N*N;           //  Index integer
  var r=new char[N][N];//  Result char-matrix of size `N` by `N`
  for(;i-->0;)         //  Loop `i` in the range (`N**2`, 0]
    r[i/N][i%N]=       //    Set the cell at position `i` divmod-`N` to:
      i/N*(i%N)<1|i/N>n|i%N>n?
                       //     If we're at a border:
       '#'             //      Set the current cell to '#'
      :                //     Else:
       32;             //      Set the current cell to ' ' (a space) instead
  r[y][x]=42;          //  Then set the starting position `x,y` to a '*'
  for(var d:D)         //  Loop over the `D` input:
    for(i=d[0];        //   Set `i` to the current direction
        d[1]-->0       //   Inner loop the current `d` amount of times
        ;              //     After every iteration:
         r[y+=         //      Change `y` based on the current direction
            i%7<2?     //       If the current direction is N, NE, or NW
             1/y*2-1   //        If we're at the top border:
                       //         Go one row down
                       //        Else
                       //         Go one row up
            :i>2&i<6?  //       Else-if the current direction is S, SE, or SW
             y<n?      //        If we're at the bottom border
              1        //         Go one row up
             :         //        Else
              -1       //         Go one row down
            :          //       Else (directions E or W)
             0]        //        Stay in the same row
          [x+=         //      Change `x` based on the current direction
            i>0&i<4?   //       If the current direction is E, NE, or SE
             x<n?      //        If we're NOT at the right border
              1        //         Go one column to the right
             :         //        Else:
              -1       //         Go one column to the left
            :i>4?      //       Else-if the current direction is W, NW, or SW
             1/x*2-1   //        If we're NOT at the left border:
                       //         Go one column to the left
                       //        Else:
                       //         Go one column to the right
            :          //       Else (directions N or S)
             0]        //        Stay in the same column
               =42)    //      And fill this new `x,y` cell with a '*'
      i=               //    Determine the new direction
        y<2&i<2|y>=n&i>2&i<5?4-i:x<2&i>4|x>=n&i>0&i<4?8-i:y<2&i>6?5:y<n|i!=5?i:7;
                       //     (See loose explanation below)
  r[y][x]=79;          //  And finally set the last `x,y` cell to 'O'
  return r;}           //  And return the result-matrix

y<2&i<2|y>=n&i>2&i<5?4-i:x<2&i>4|x>=n&i>0&i<4?8-i:y<2&i>6?5:y<n|i!=5?i:7 is a golfed version of this below by using 4-i and 8-i for most direction changes:

y<2?     // If we're at the top border
 i==0?   //  If the current direction is N
  4      //   Change it to direction S
 :i==1?  //  Else-if the current direction is NE
  3      //   Change it to SE
 :i==7?  //  Else-if the current direction is NW
  5      //   Change it to SW
 :       //  Else
  i      //   Leave the direction the same
:x<2?    // Else-if we're at the left border
 i==7?   //  If the current direction is NW
  1      //   Change it to NE
 :i==6?  //  Else-if the current direction is W
  2      //   Change it to E
 :i==5?  //  Else-if the current direction is SW
  3      //   Change it to SE
 :       //  Else
  i      //   Leave the direction the same
:y>=n?   // Else-if we're at the bottom border
 i==3?   //  If the current direction is SE
  1      //   Change it to NE
 :i==4?  //  Else-if the current direction is S
  0      //   Change it to N
 :i==5?  //  Else-if the current direction is SW
  7      //   Change it to NW
 :       //  Else
  i      //   Leave the direction the same
:x>=n?   // Else-if we're at the right border
 i==1?   //  If the current direction is NE
  7      //   Change it to NW
 :i==2?  //  Else-if the current direction is E
  6      //   Change it to W
 :i==3?  //  Else-if the current direction is SE
  5      //   Change it to SW
 :       //  Else
  i      //   Leave the direction the same
:        // Else
 i       //  Leave the direction the same
\$\endgroup\$
3
\$\begingroup\$

Charcoal, 74 bytes

NθJ⊘⊕θ⊘⊕θUR±⊕⊕θJN±NFA«≔⊟ιζF⊟ι«≔ζδ↷δ¶F›⊗↔ⅈθ≦±ζF›⊗↔ⅉθ≦⁻⁴ζ≧﹪⁸ζ↷⁴¶↶⁴↶δ↷ζ*¶↶ζPo

Try it online! Link is to verbose version of code. Takes input in the format n, x, y, d where d is an array of arrays of [distance, direction] pairs where the direction is a numerical encoding 0 = south clockwise to 7 = south east. Explanation:

NθJ⊘⊕θ⊘⊕θUR±⊕⊕θ

Input n and draw a box whose interior is that size centred on the origin.

JN±N

Input and jump to x and y (but negate y because Charcoal's y-axis is increasing down).

FA«

Loop over the entries in d.

≔⊟ιζ

Extract the initial direction.

F⊟ι«

Repeat for the desired distance.

≔ζδ

Save the direction.

↷δ¶

Make an experimental move in that direction.

F›⊗↔ⅈθ≦±ζ

If this goes off the sides then flip the direction horizontally.

F›⊗↔ⅉθ≦⁻⁴ζ

If this goes off the top or bottom then flip the direction vertically.

≧﹪⁸ζ

Reduce the direction modulo 8 (the Pivot commands only accept values from 0 to 7).

↷⁴¶↶⁴

Undo the experimental move.

↶δ↷ζ*¶

Face the correct direction, and then print a trace and move.

↶ζPo

Face back to the default direction, and print the object in the current position.

\$\endgroup\$
2
\$\begingroup\$

JavaScript, 206 bytes

Takes input as (n,x,y,[[dir,len],[dir,len],...]) where directions are encoded using bitmasks :

S : 1  
N : 2   
E : 4  
W : 8  
SE: 5 (1|4)  
SW: 9 (1|8)
NE: 6 (2|4)
NW:10 (2|8)

Outputs a string with

- 1 for top and bottom boundary
- 4 for left and right boundary 
- 5 for corners 
- 0 for trace
- 8 for the final position.

The different values for boundaries are used to evaluate the next direction

(n,x,y,d)=>(Q=[e=`
5`+'1'[R='repeat'](n)+5,o=n+3,-o,c=[...e+(`
4`+' '[R](n)+4)[R](n)+e],1,1+o,1-o,,-1,o-1,~o],p=1-o*(~n/2+y)-~n/2+x,c[d.map(([q,s])=>{for(a=q;s--;p+=Q[a^=c[p+Q[a]]*3])c[p]=0}),p]=8,c.join``)

Less golfed

F=(n,x,y,d) => (
  o = n+3, // vertical offset, accounting for boundaries and newline
  // Q = offsets for different directions, bitmask indexed 
  Q = [,  // 0000 no direction
     o,   // 0001 S
     -o,  // 0010 N
     ,    // 0011 NS - invalid
     1 ,  // 0100 E
     1+o, // 0101 SE
     1-o, // 0110 NE
     ,    // 0111 NSE - invalid
     -1,  // 1000 W
     o-1, // 1001 SW
    -o-1],// 1010 NW

  e = `\n5`+'1'.repeat(n)+5, // top and bottom boundary
  c = [...e + (`\n4` + ' '.repeat(n) + 4).repeat(n) + e], // canvas
  p = 1 - o*(~n/2+y) - ~n/2 + x, // start position
  d.map( ([q,s]) => { // repeat for each element in 'd'
    a = q; // starting offset pointer - will change when bounce
    while( s-- )
    {
      c[p] = 0; // trace
      b = c[p + Q[a]] // boundary value or 0 (space count 0)
      a ^= b * 3 // xor with 0 if no bounce, else 3 or 12 or 15
      p += Q[q]  // advance position
    }
  })
  c[p] = 8, // set end position
  c.join``
)

TEST

var F=
(n,x,y,d)=>(Q=[e=`
5`+'1'[R='repeat'](n)+5,o=n+3,-o,c=[...e+(`
4`+' '[R](n)+4)[R](n)+e],1,1+o,1-o,,-1,o-1,~o],p=1-o*(~n/2+y)-~n/2+x,c[d.map(([q,s])=>{for(a=q;s--;p+=Q[a^=c[p+Q[a]]*3])c[p]=0}),p]=8,c.join``)

var out=x=>O.textContent+=x

var test=(n,x,y,d)=>{
  var dd = d.map(([d,s])=>[,'S','N',,'E','SE','NE',,'W','SW','NW'][d]+' '+s)
  out([n,x,y]+' ['+dd+']')
  out(F(n,x,y,d))
  out('\n\n')
}

test(5,0,0,[[10,2],[1,2],[4,1]])
test(9,3,-1,[[2,2],[9,8],[5,3],[6,8]])
test(11,3,-5,[[10,8],[4,5],[5,2],[9,5],[2,6],[6,10]])
<pre id=O></pre>

\$\endgroup\$
2
\$\begingroup\$

C (gcc), 352 323 bytes

Golfed down 29 bytes thanks to ceilingcat.

#define G(x,a)x+=a=x<2|x>m-3?-a:a
#define A(p)atoi(v[p])
m,r,c,x,y,s,a,b;main(q,v)int**v;{m=A(1)+2;int f[r=m*m];for(x=A(2)+m/2;r--;f[r]=32);for(y=A(s=3)+m/2;++s<q;)for(a=cos(A(s)*.8)*2,b=sin(A(s)*.8)*2,c=A(++s);c--;G(y,b),f[y*m+x]=42)G(x,a);for(f[y*m+x]=64;++r<m;puts(""))for(c=0;c<m;c++)putchar(c%~-m&&r%~-m?f[r*m+c]:35);}

Try it online!

The program takes input as command line arguments (like a.out 10 1 1 3 5 0 4 7 2):

  • the first argument is the field size,
  • the next two are initial coordinates \$(x,y)\$ of the actor,
  • all pairs of arguments starting from the fourth are \$(d, t)\$ pairs where \$d\$ is direction (represented as a number 0..7, starting from 0=E and turning clockwise) and \$t\$ is number of steps.

Explanation

// Update the coordinate AND simultaneously modify the direction (if needed)
#define G (x, a) x += a = x < 2 || x >= m - 2 ? -a : a

// Get the numeric value of an argument
#define A (p) atoi (v[p])

// variables
m, // width and height of the array with field data
r, c, // helpers
x, y, // current coordinates of the actor
s, // helper
a, b; // current direction of the movement

main (q, v) char **v;
{
    // array size is field size + 2 (for borders)
    m = A (1) + 2;

    // allocate the array
    int f[r = m * m];

    // fill the array with spaces,
    for
    (
        // but first get x of the actor
        x = A (2) + m / 2;

        r--;

        f[r] = 32
    );

    // trace: iterate over remaining commandline argument pairs
    for
    (
        // but first get y of the actor
        y = A (s = 3) + m / 2;

        ++s < q; // loop until no args left
    )
        // for each such pair
        for
        (
            a = cos (A (s) * .8) * 2,  // get the x-increment
            b = sin (A (s) * .8) * 2, // get the y-increment
            c = A (++s);  // then get the number of steps

            c--;

            // after each step:
            G (y, b), // update y and maybe the y-direction
            f[y * m + x] = 42 // draw the trail
        )
            G (x, a); // update x and maybe the x-direction

   // output
   for
   (
       f[x * m + y] = 64; // put a @ to the current position of the actor
       ++r < m; // r == -1 at the beginning of the loop so preincrement

       puts("") // terminate each row with newline
   )
       // iterate over columns in the row
       for (c = 0; c < m; c++)
           putchar
           (
               c % ~ -m && r % ~ -m ? // if it is not a border cell,
               f[r * m + c] // output the character from the array
               : 35 // otherwise output the #
           );
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I believe your code is missing the object output at the end position, since The trace of the tracked object may also be whatever characters you choose, as long as they only occupy 1 space on the plane and are different from the object. Other than that, it looks good to me. \$\endgroup\$ – J. Sallé Sep 27 '18 at 14:35
  • \$\begingroup\$ Oops, I totally missed that, thanks for noticing J.Sallé . Luckily the correction did not make the program longer. \$\endgroup\$ – Max Yekhlakov Sep 28 '18 at 5:04

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.