g | x | w | all
Bytes Lang Time Link
487Python3250312T030435ZAjax1234
212Python 2191031T034950ZChas Bro
089Jelly191031T083221ZNick Ken
208Javascript ES6191031T121349Zedc65
199Python 2191031T075516ZBubbler

Python3, 487 bytes

import math
E=enumerate
M=[(0,1),(1,0),(0,-1),(-1,0)]
def f(b):
 d,D={},{}
 for x,r in E(b):
  for y,v in E(r):d[(x,y)]=v!='#';D[v]=D.get(v,[])+[(x,y)]
 O=D['o'][0]
 q=[(*O,[O],D)]
 for x,y,p,D in q:
  if x in[0,len(b)]or y in[0,len(b[0])]:return 1
  if len(G:=[o for J,K in D['g']if(o:=min([P for X,Y in M if(P:=(J+X,K+Y))in d],key=lambda X:math.dist((x,y),X)))!=(x,y)])==len(D['g']):
   for X,Y in M:
    if(P:=(x+X,y+Y))not in p and P not in G and d.get(P):q+=[(*P,p+[P],{**D,'g':G})]

Try it online!

Python 2, 236 235 227 212 bytes

def f((x,y),G,M,U=[]):
 R=1-(len(M[0])-1>x>0<y<len(M)-1);H=[(u+cmp(x,u),v+cmp(y,v)*(u==x))for u,v in G];d,e=0,1
 for _ in' '*4*(R<1>((x,y)in H)):J=z,w=x+d,y+e;d,e=-e,d;R|=M[w][z]>(J in U)<f(J,H,M,U+[J])
 return R

Try it online!

-15 bytes thx to Bubbler

As input, takes a tuple (x,y) as jimmy's initial position, a list G == [(g1x,g1y), ...] of initial ghost positions, and a list of lists M which is the 2-dimensional binary matrix where cells are 0 if they contain "an object" (i.e., #) and 1 otherwise.

Returns 1 for truthy, 0 for falsey.

Loosely speaking, the idea here is that we're going to depth-first recurse over down, left, up, right to seek a border cell. To start,

R=1-(len(M[0])-1>x>0<y<len(M)-1)

R is truthy if jimmy starts on a border cell. We could just return R if he does, but that would add an additional return. So in any case, we calculate where the ghosts would move to next.

H=[(u+cmp(x,u),v+cmp(y,v)*(u==x))for u,v in G]

cmp(a,b) returns the sign of a-b as -1,0,1; so this updates the ghosts positions with preference for horizontal movement if possible. Next,

for _ in' '*4*(R<1>((x,y)in H)):

executes a loop 4 times (' '*4 is shorter than range(4)). Note that if jimmy is on the boundary (not R<1) or a ghost has landed on jimmy (not 1>((x,y)in H), then we skip the for loop and so we return 1 if jimmy has escaped, or 0 if a ghost got him.

    J=z,w=x+d,y+e;d,e=-e,d

d,e is the offset to jimmy's next position, starting as 0,1; and each time through the loop, d,e=-e,d rotates us through the 4 possible orthogonal positions.

    R|=M[w][z]>(J in U)<f(J,H,M,U+[J])

R gets set to truthy if jimmy's next position J is not an on "object" (M[w][z]>0), AND we haven't visited this position before (1>(J in U)), AND via recursion, jimmy can escape starting at position J with ghosts at H (0<f(J,H,M,U+[J])).

Jelly, 94 89 bytes

Ø.,U$;N$+®ŒṬ€×8+®ŒṬ¤+&7$Ç€Ẹ
&Ɱ8,2ŒṪ€Ḣ©_¥/Ṡ01¦Ạ¡€+ƲṪŒṬḤ+&13$ÑÇFṀ>8Ʋ?
|Ø.¦4Z$⁺FṀ=12ƲÇFṀ>8Ʋ?

Try it online!

A full program that takes an integer matrix as its argument, with 0 as space, 1 as wall, 2 as ghost and 8 as Jimmy. Returns 1 for escape and 0 for no escape.

Javascript (ES6), 208 bytes

Recursive, depth-first search. A breadth-first approach would be probably faster, but less golfy. Input: a multi line string, using 1 for ghosts, 4 for solid objects, 6 for Jimmy and 2 for empty space.

f=(s,l=-~s.search`
`,j=s.search(6),g=[...s],x=j%l)=>j<l|!g[j+l]|!x|x>l-3||(g[j]=6,![...g].some((a,i)=>a&1&&(g[--g[i],t=i%l,i+=t<x?1:t>x?-1:i<j?l:i>j?-l:0]|=1,i==j))&&[l,-l,1,-1].some(d=>2==g[d+=j]&&f(g,l,d)))

Less golfed

// using parameters with default values instead of declared variables

f = (s,  // input board (as a string at first call, then as an array)
     l = -~g.search('\n'), // line size +1, that is distance to next line
     // at first call it is calculated, for recursive calls is passed
     j = g.search(6), // position of jimmy in grid
     // at first call it is calculated, for recursive calls is passed
     grid = [...s], // modifiable copy of board (converted to char array)
     x = j % l, // x position of jimmy, always obtained from j
) =>
{
  // first, check if Jimmy is out
  if (j<l | !grid[j+l] | !x | x>l-3) // top,bottom,left,right border
  {
    return true
  }
  // move all ghosts and check if some ghost can take Jimmy
  // the loop is on a copy of grid, so I can safely modify grid during the loop
  var taken = [...grid].some( // check all ghosts
        (a,i) => a & 1 && ( // is it a ghost?
         --grid[i], // remove ghost from grid, as it will move
         t = i % l, // x position of ghost
         // move ghost towards Jimmy
         i += t < x 
           ? 1 
           : t > x 
             ? -1 
             : i < j 
               ? l 
               : i > j 
                 ? -l 
                 : 0,
         grid[i] |= 1, // put ghost on grid in its new position
         i == j // return true if it reached Jimmy
  )
  return !taken && // if taken return false immediately
  // else recurse
  [l,-l,1,-1].some( // try to move Jimmy in all directions
    d => (
      d+=j, 2==g[d] && f(grid, l, d))
    )
  )
}

Test

cases=['##########\n......g...\n#.o.......\n#.....g...\n##########\n'
,'#.\nog\n'
,'#########\n.........\n....g....\n...g.g...\n....o....\n...g.g...\n....g....\n.........\n'
,'##########\n#g.o.....#\n########.#\n#........#\n#.########\n#........#\n########.#\n#........#\n#.########\n'
,'.....\n..g..\n.go..\n.....\n.....'
,'###.......\n\#o#......g\n###.......\n'
,'#####.....\n#o..#....g\n#####.....\n'
,'g#.#\n.#.#\n\.#.#\n.#o#\n.#.#\n.#.#\ng#.#\n'
,'##########\n#...o....g\n##########\n'
,'#.########\n#g.......#\n#....g...#\n#......g.#\n#..g.....#\n#....g...#\n#........#\n#....g...#\n#..g....o#\n##########\n'
]


f=(s,l=-~s.search`
`,j=s.search(6),g=[...s],x=j%l)=>j<l|!g[j+l]|!x|x>l-3||(g[j]=6,![...g].some((a,i)=>a&1&&(g[--g[i],t=i%l,i+=t<x?1:t>x?-1:i<j?l:i>j?-l:0]|=1,i==j))&&[l,-l,1,-1].some(d=>2==g[d+=j]&&f(g,l,d)))


cases.forEach(x=> (
  console.log(x,
    f(x.replace(/#/g,4).replace(/\./g,2).replace(/o/,6).replace(/g/g,2|1)))
))

Python 2, 199 bytes

def g(I,G,W,w,h):
 u,v=I.real,I.imag;R=1-(w-1>u>0<v<h-1);H=[z+cmp(u,z.real)+cmp(v,z.imag)*(u==z.real)*1jfor z in G]
 for d in(R<1>(I in H+W))*range(4):J=I+1j**d;R|=(J in W)<g(J,H,W+[I],w,h)
 return R

Try it online!

Rewrite of Chas Brown's solution using complex numbers.

All the coordinates are represented as a complex number x+y*1j. I is Jimmy's position, G is the list of ghosts and W is the list of walls. w and h are the width and height of the board, respectively.

The output values and the algorithm are the same as the linked solution.