#include <iostream>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
#include <sstream>
#include <string>
#include <functional>
#include <queue>
#include <deque>
#include <stack>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#include <fstream>
#include <iterator>
#include <random>
#include <chrono>

 
#define forr(i,start,count) for (int i = (start); i < (start)+(count); ++i)
#define set_map_includes(set, elt) (set.find((elt)) != set.end())
#define readint(i) int i; cin >> i
#define readll(i) ll i; cin >> i
#define readdouble(i) double i; cin >> i
#define readstring(s) string s; cin >> s
 
typedef long long ll;
 
using namespace std;


template<class T>
class depth_first_search_iterator {
  public:
    stack<pair<T,T>> todo;
    function<bool(T)> notvisited;
    function<void(T)> mark_visited;
    function<set<T>(T)> neighbors;

    depth_first_search_iterator() {}

    depth_first_search_iterator(T start, function<bool(T)> notv, function<void(T)> mark_v, function<set<T>(T)> neigh) :
      notvisited(notv), mark_visited(mark_v), neighbors(neigh), end_(false), current(start), prev(start) {
          todo.push(make_pair(start, start)); operator++();
    }

    void operator++() {
        end_ = true;
        while (!todo.empty()) {
            current = todo.top().first; prev = todo.top().second; todo.pop();
            if (notvisited(current)) {
                mark_visited(current);
                for (const T x : neighbors(current)) {  todo.push(make_pair(x, current));   }
                end_ = false;
                break;
            }
        }
    }

    T operator*() {     return current;    }

    T previous() {  return prev;  }

    bool end() {  return end_;  }

  private:
    T current; // contains current node
    T prev;    // contains previous node
    bool end_;
};

template<class T>
class breadth_first_search_iterator {
  public:
    queue<pair<T,T>> todo;
    function<bool(T)> notvisited;
    function<void(T)> mark_visited;
    function<set<T>(T)> neighbors;

    breadth_first_search_iterator() {}

    breadth_first_search_iterator(T start, function<bool(T)> notv, function<void(T)> mark_v, function<set<T>(T)> neigh) :
      notvisited(notv), mark_visited(mark_v), neighbors(neigh), end_(false), current(start), prev(start) {
          todo.push(make_pair(start, start)); operator++();
    }

    void operator++() {
        end_ = true;
        while (!todo.empty()) {
            current = todo.front().first; prev = todo.front().second; todo.pop();
            if (notvisited(current)) {
                mark_visited(current);
                for (const T x : neighbors(current)) {  todo.push(make_pair(x, current));   }
                end_ = false;
                break;
            }
        }
    }

    T operator*() {     return current;    }

    T previous() {  return prev;  }

    bool end() {  return end_;  }

  private:
    T current;   // current node
    T prev;      // previous node
    bool end_;
};

template<class T>
class Graph {
    // allows only single connection between two vertices
    public:
      vector<T> vertices;
      vector<set<int>> neighbors;
      depth_first_search_iterator<int> dfs_iterator;
      breadth_first_search_iterator<int> bfs_iterator;
      vector<bool> visited;

      Graph() {}

      Graph(int n, T default_val = 0) {  for (int i = 0; i < n; ++i) {  AddVertex(default_val);  }   }

      void AddVertex(T val) {
          vertices.push_back(val);
          neighbors.push_back(set<int>());      }

      bool EdgeExists(int i, int j) {
          return (neighbors[i].find(j) != neighbors[i].end());
     }

      void AddEdge(int i, int j) {
          if (i==j) {return;}
          if (!EdgeExists(i,j)) {
            neighbors[i].insert(j);
            if (i != j) {         neighbors[j].insert(i);         } 
          }
      }

      T& operator[](int i) {      return vertices[i];      }

      void RemoveEdge(int i, int j) {
          if (EdgeExists(i,j)) {
            neighbors[i].erase(find(neighbors[i].begin(), neighbors[i].end(), j));
            neighbors[j].erase(find(neighbors[j].begin(), neighbors[j].end(), i));
          }
      }

      int vertices_count() {   return vertices.size();  }

      void dfs_init(int start) {
          visited = vector<bool>(vertices.size(), false);
          dfs_iterator = depth_first_search_iterator<int>(start, [this](int x){ return !visited[x]; }, [this](int x) { visited[x] = true;  },
            [this](int x) {  return neighbors[x];  } );
      }

      void bfs_init(int start) {
          visited = vector<bool>(vertices.size(), false);
          bfs_iterator = breadth_first_search_iterator<int>(start, [this](int x){ return !visited[x]; }, [this](int x) { visited[x] = true;  },
            [this](int x) {  return neighbors[x];  } );
      }

      void print_() {
          forr(i, 0, vertices.size()) {
              for (auto x : neighbors[i]) {
                  if (i <= x) {  cout << i << "-" << x << endl;  }                  
              }
          }
      }
};



        class UnionFind {
    public:
      vector<int> parent;
      vector<int> size;
//      size[i] = number of nodes in tree rooted at i
// Note: not necessarily correct if i is not a root node
      set<int> roots;

    UnionFind(int n) : parent(n), size(n) {
        forr(i,0,n) { roots.insert(i); }
        forr(i,0,n) {
            parent[i] = i; size[i] = 1;
        }
    } 

    int Find(int p) {
        int root = p;
        while (root != parent[root]) {  root = parent[root]; }
        while (p != root) {
            int newp = parent[p];
            parent[p] = root;
            p = newp;
        }
        return root;
    }

    void Union(int p, int q) {
        int rootP = Find(p);
        int rootQ = Find(q);
        if (rootP == rootQ) return;

        // make smaller root point to larger one
        if (size[rootP] < size[rootQ]) {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
            roots.erase(rootP);
        }
        else {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
            roots.erase(rootQ);
        }
    }

};

template<class T>
pair<vector<T>, vector<int>> DijkstraUndirected(int N_vertices, map<pair<int,int>, T>& edges, int s, T infinity) {
    // input edges with weight of type T, return two arrays: first contains shortest distance from s, second (prev)
    // contains info on constructing path: start at vertex t, go to prev[t], then prev[prev[t]], until s, you get (shortest) path from t to s
    // infinity should be higher than all distances, and too high to prevent weird wraparound  effects in addition dist[u]+edges[]... below

    vector<T> dist(N_vertices, infinity);
    dist[s] = 0;
    vector<int> prev(N_vertices);
    set<pair<T,int>> Q;
    for (int i = 0; i < N_vertices; ++i) {  Q.insert(make_pair(dist[i], i));   }
    Graph<int> g(N_vertices);
    for (auto x : edges) {  g.AddEdge(x.first.first, x.first.second);  }

    while (!Q.empty()) {
        int u = Q.begin()->second;
        Q.erase(Q.begin());

        for (int v : g.neighbors[u]) {
                T one_way = 0, other_way = 0;
                if (edges.find(make_pair(u,v)) != edges.end() ) {   one_way = edges[make_pair(u,v)];   }
                if (edges.find(make_pair(v,u)) != edges.end() ) {   other_way = edges[make_pair(v,u)];   }
                T alt = dist[u] + one_way + other_way;   // since each edge is present in only one of those edges[u,v] & edges[v,u]
                   // if you need modification for directed graph, only first edges should be used
                   // CAREFUL!!!! edges should contain each edge only once!!! not twice for both directions
                if (alt < dist[v]) {
                    prev[v] = u;
                    Q.erase(make_pair(dist[v], v));
                    dist[v] = alt;
                    Q.insert(make_pair(dist[v], v));
                }
        }
    }

    return make_pair(dist, prev);
};

template<class T>
pair<vector<int>, vector<int>> DijkstraUndirectedGraph(Graph<T>& g, int s, int infinity = -1) {   // does dijsktra undirected on a graph, returns the same as above
   map<pair<int,int>, int> edges;
   int count = 0;
   for (int i = 0; i < g.vertices.size(); ++i) {
       for (auto w : g.neighbors[i]) {
           if ((edges.find(make_pair(i, w)) == edges.end()) && (edges.find(make_pair(w, i)) == edges.end())) {
               edges[make_pair(i, w)] = 1;
           }
           ++count;
       }
   }
   if (infinity == -1) {
     return DijkstraUndirected<int>(g.vertices.size(), edges, s, count+1);
   } else {
     return DijkstraUndirected<int>(g.vertices.size(), edges, s, infinity);
   }
};


ll modd = 1000*1000*1000+7;
int h, w;

int ind(int hh, int ww)  {
  return (hh*w + ww);
}

int main()   {


    ios_base::sync_with_stdio(false);

    cout.precision(17);
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
    uniform_int_distribution<int> rand_gen(0, modd);   // rand_gen(rng) gets the rand no

//    auto start = chrono::steady_clock::now()

//    readint(test_cases);
    int test_cases = 1;
    forr(t, 1, test_cases) {
        readint(hh); readint(ww);
        h = hh; w = ww;
        readint(s1); readint(s2); s1--; s2--;
        readint(g1); readint(g2); g1--; g2--;
        vector<string> grid;
        forr(i,0,h) {
            readstring(s); grid.push_back(s);
        }
        UnionFind uf(h*w);
        forr(i,0,h) {
            forr(j,0,w) {
                if ((i-1>=0) && (grid[i-1][j]=='.') && (grid[i][j]=='.')) { uf.Union(ind(i,j), ind(i-1,j)); }
                if ((j-1>=0) && (grid[i][j-1]=='.') && (grid[i][j]=='.')){ uf.Union(ind(i,j), ind(i,j-1)); }
                if ((i+1<h) && (grid[i+1][j]=='.') && (grid[i][j]=='.')) { uf.Union(ind(i,j), ind(i+1,j)); }
                if ((j+1<w) && (grid[i][j+1]=='.') && (grid[i][j]=='.')) { uf.Union(ind(i,j), ind(i,j+1)); }
            }
        }

        map<int,int> root_dict;
        for(int x : uf.roots) {
            int ind_ = root_dict.size();
            root_dict[x] = ind_;
        }
        Graph<int> g(uf.roots.size());
        forr(i,0,h) {
            forr(j,0,w) {
                if (grid[i][j]!='.') {continue;}
                for(int ii = -2; ii <= 2; ii++) {
                    for(int jj = -2; jj <= 2; jj++) {
                        int x1 = i + ii, y1 = j + jj;
                        if ((x1>=0) && (y1>=0) && (x1<h) && (y1<w) && (grid[x1][y1]=='.')) {
                            g.AddEdge(root_dict[uf.Find(ind(i,j))], root_dict[uf.Find(ind(x1,y1))]);
                        }
                    }
                }
            }
        }

        ll ret = -1;
        if (root_dict[uf.Find(ind(g1,g2))] == root_dict[uf.Find(ind(s1,s2))]) { ret = 0; } else {
          g.bfs_init(root_dict[uf.Find(ind(s1,s2))]); ++g.bfs_iterator;
          while (!g.bfs_iterator.end()) {
            g[*g.bfs_iterator] = 1+g[g.bfs_iterator.previous()];
            if (*g.bfs_iterator==root_dict[uf.Find(ind(g1,g2))]) {  ret = g[*g.bfs_iterator]; break;  }
            ++g.bfs_iterator;
          }
        }
        cout << ret << endl;
        
/*        auto q = DijkstraUndirectedGraph(g, root_dict[uf.Find(ind(s1,s2))], modd).first;
        if (q[root_dict[uf.Find(ind(g1,g2))]]>h*w) {
            cout << -1 << endl;
        } else {
            cout << q[root_dict[uf.Find(ind(g1,g2))]] << endl;
        } */


    }

//    auto end = chrono::steady_clock::now();
//    cerr << chrono::duration_cast<chrono::milliseconds>(end - start).count() << endl;

    return 0;
}

Measured memory_bytes: 172220