evg

Программма - решатель пятнашек.
Реализация на pascal-е.
Проверенно в PascalABC.NET.

Работает дико медленно :) Но работает.

Имеем на воходе разложение пятнашек в виде массива 4x4, с цифрами от 0 до 15. Где "0" пустая клетка. Разложение может быть случайным. Решением является перестановка фишек в строй по порядку от 1 до 15.

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0

Ровно в половине случаев задачу невозможно разрешить.
Решение необходимо выполнить за минимально возможное количество ходов.

Для решения задачи используется алгоритм A star(A*). Он заключается в обходе графа всех возможных состояний поля с учетом предыдущих.

В качестве эвристической функции используется манхеттеновское расстояние. Причем если убрать из программы учет номера шага, то решение будет находиться очень быстро, но это будет не оптимальное решение. В данном примере решение находится за 90 ходов, а оптимальное 44.


program fifth;
{// A* algorithm}
const
  move_desc: array of char = ('D', 'L', 'U', 'R');// Возможные ходы
  opposite_move: array of integer = (2, 3, 0, 1);// Противоположные ходы (2, 3, 0, 1)

type
  pA = ^tA;
  tA = Array[0..3, 0..3] of integer;

var
  board: tA;
  id: double;
  x0, y0: integer;

var
  boardGoal: tA;// доска целевого состояния  


var
  resultString: string;//результат

var
  ols, cls: integer;
  

type
  pState = ^TState;
  TState = record
    //    board: pA; // доска
    x, y: smallint;
    id: int64;
  end;

type
  pNode = ^tNode;
  tNode = record
    state: pState;
    f, g, h: smallint;
    move: char;
    parent: pNode;      
  end;

type
  pList = ^tList;
  tList = record
    val: pNode;
    next, prev: pList;
  end;

var
  openList, closedList: pList;
  initNode: pNode;
  minNode:pNode;


procedure writelist(s: string; list: pList);
var
  l: pList;
begin
  l := list;
  writeln(s);
  while l  <> nil do 
  begin
    if l^.val  <> nil then writeln(integer(l^.val));
    l := l^.next;
  end;
  
end;

procedure AddE(var list: pList; node: pNode);
var
  l: pList;
begin
  if list = nil then 
  begin
    new(list);
    l := list;
  end 
  else begin
    l := list;
    new(list);
    list^.next := l;
  end;
  list^.val := node;
end;


procedure DelE(var list: pList; var node: pNode);
var
  l, t: pList;
begin
  if list = nil then exit;
  l := list;
  if l^.val = node then begin
    list := list^.next;
    dispose(l);
    exit;
  end;
  while l^.next <> nil do 
  begin
    if l^.next^.val = node then begin
      t := l^.next;
      l^.next := t^.next; 
      dispose(t);
      exit;
    end;
    l := l^.next;
  end;
  
end;  
  
procedure DelEL(var list,prev: pList);
var
  l, t: pList;
begin
  if list = nil then exit;
  if prev <> nil then begin
    prev^.next:=list^.next;
  end;
  dispose(list);
  list:=nil;
end;

procedure DelI(var list: pList; var item: pList);
var
  l: pList;
begin
  if list = nil then exit;
  l := list;
  if l = item then begin
    list := list^.next;
    dispose(l);
    exit;
  end;
  while l^.next <> nil do 
  begin
    if l^.next = item then begin
      l^.next := item^.next;
      dispose(l);
      exit;
    end;
    l := l^.next;
  end;
end;

procedure DelA(var list: pList);
var
  l, t: pList;
begin
  l := list;
  while list <> nil do 
  begin
    t := list;
    list := list^.next;
    if t^.val <> nil then begin
      if t^.val^.state <> nil then dispose(t^.val^.state);
      dispose(t^.val);
      t^.val := nil;
    end;
    dispose(t);
    t := nil;
  end;  
end; 

procedure wl(s: string; list: pList);
var
  l: pList;
begin
  l := list;
  writeln(s);
  while l <> nil do 
  begin
    wp('', l^.val);
    l := l^.next;
  end;
end;

//--------------------------------
//инициализирует целевое состояние
procedure initGoalArrays;
begin
  var i: integer;
  for i := 0 to 14 do
  begin
    goalX[i + 1] := i mod 4;
    goalY[i + 1] := i div 4;
  end;
  goalX[0] := 3;
  goalY[0] := 3;
end;

//определяет "решаемость" пятнашек
function isSolvable: Boolean;
begin
  var count, transpos, value, i, j: integer;
  count := 0;
  transpos := 0;
  value := 0;  
  var a: Array[0..16] of integer;  

  for i := 0 to 3 do 
  begin
    if (i mod 2 = 0) then begin
      for j := 0 to 3 do 
      begin
        value := board[i][j];
        if (value > 0) then begin
          a[count] := value;
          inc(count);
        end;
      end;
    end
    else 
    begin
      for j := 3 downto 0 do 
      begin
        value := board[i][j];
        if (value > 0) then begin
          a[count] := value;
          inc(count);
        end;
      end;
    end;
  end;
  
  for i := 0 to count - 2 do 
  begin
    for j := i + 1 to count - 1 do 
    begin
      if (a[i] > a[j]) then begin
        inc(transpos);
      end;
    end;
  end;
  isSolvable := ((transpos mod 2) = 1);
end;

//эвристическая оценочная функция Манхеттеновское расстояние
function estimate(board: pA): integer;
begin
  var manhattan, i, j, value, m: integer;
  manhattan := 0;
  for i := 0 to 3 do
    for j := 0 to 3 do 
    begin
      value := board^[i][j];
      if (value > 0) then begin
        m := abs(i - goalY[value]) + abs(j - goalX[value]);
        manhattan := manhattan + m;
      end;
    end;
  estimate := manhattan;
end;
//
procedure writed(h, n: integer);
begin
  var i, j: integer;
  for i := 0 to 3 do 
  begin
    for j := 0 to 3 do
      write(board[i][j] + ' ');
    writeln;
  end;  
  write(h);
  writeln(' ' + move_desc[n]);
  writeln;
end;



procedure find0cell;
begin
  var i, j: integer;
    // поиск пустой клетки
  for i := 0 to  3 do 
    for j := 0 to 3 do 
    begin
      if (board[i][j] = 0) then begin
        x0 := j;
        y0 := i;
      end;
      
    end;
end;


  //строит уникальный идентификатор по доске
function getUniqueID(p: pA): int64;
var
  d, t, k: int64;
  i, r, j: integer;
begin
  d := 0;
  r := 0;
  for j := 0 to 3 do 
    for i := 0 to 3 do
    begin
      k := p^[j][i];
      t := k shl r;
      d := d or t;
      r := r + 4;
    end;
  getUniqueID := d;
end;

procedure unpackID(p: pA; id: int64);
var
  i, j: integer;
  d, t, n: int64;
begin
  t := 0;
  n := 15;
  for j := 0 to 3 do 
    for i := 0 to 3 do
    begin
      d := n shl t;
      d := (id and d);
      d := (d shr t) and n;
      p^[j][i] := d;
      t := t + 4;
    end;
end;


procedure writen(n: pNode);
var
  i, j: integer;
  b: tA;
begin
  unpackID(@b, n^.state^.id);
  for i := 0 to 3 do 
  begin
    for j := 0 to 3 do
      write(b[i][j] + ' ');
    writeln;
  end;    
  writeln;
end;


procedure SetState(id: int64; s: pState; xo, yo: integer);
begin
  var i, j: integer;  
  s^.x := xo;
  s^.y := yo;
  s^.id := id;
end;


function isInList(list: pList; n: pNode): boolean;
var
  l: pList;
begin
  l := list;  
  while l <> nil do 
  begin
    if l^.val <> nil then begin
      if n^.state^.id = l^.val^.state^.id then 
      begin
        isInList := true;
        exit;
      end;
    end;
    l := l^.next;
  end;  
  isInList := false;
end;

function isInListID(list: pList; id: int64; var tl: pList): boolean;
var
  l: pList;
  i: integer;
begin
  l := list;  
  while l <> nil do 
  begin
    if l^.val <> nil then begin
      if id = l^.val^.state^.id then 
      begin
        isInListID := true;
        tl := l;
        exit;
      end;
    end;
    l := l^.next;
  end;  
  tl := nil;
  isInListID := false;
end;
  //строит всех наследников этого состояния: то есть
  //список всех состояний, в которые можно перейти из текущего
procedure successors(n: pNode);
var
  i, j, newx, newy, t: integer;
  ns, s: pState;
  list: pList;
  node: pNode;
  previousMove: char;
  id, uid: int64;
  iO, iC: boolean;
  cost: integer;
  ol, cl: pList;
begin
  s := n^.state;
  previousMove := n^.move;
  unpackID(@board, s^.id);
  for i := 0 to 3 do 
  begin//перебираем всевозможные ходы пустой клетки
    if previousMove <> move_desc[opposite_move[i]] then 
    begin
      newx := s^.x + dx[i];
      newy := s^.y + dy[i]; //новые координаты пустой клетки
      if ((newy >= 0) and (newy <= 3) and (newx >= 0) and (newx <= 3)) then begin
        t := board[newy][newx]; //двигаем пустую клетку на новое место
        board[newy][newx] := 0; 
        board[s^.y][s^.x] := t;
        id := getUniqueID(@board);
        iO := isInListID(openList, id, ol);
        iC := isInListID(closedList, id, cl);

        new(ns);
        SetState(id, ns, newx, newy);
        //добавляем новоиспеченный узел в список
        new(node);
        node^.state := ns;
        node^.g := n^.g + 1; // 1 - is cost
        node^.h := estimate(@board);
        node^.f := node^.g + node^.h;
        node^.move := move_desc[i];//запоминаем ход, с помощью которого получили этого наследника
        node^.parent := n;
        if(not iO) and (not iC) then begin
          AddE(openList, node);
          inc(ols);
        end else
        if iO then begin
          if ol^.val^.f > node^.f then begin
            AddE(openList, node);
            inc(ols);
          end
          else begin dispose(ns);dispose(node);end;
        end
        else
        if iC then begin
          if cl^.val^.f > node^.f then begin
            DelE(closedList, cl^.val);
            AddE(openList, node);
          end
          else begin dispose(ns);dispose(node); end;
        end;
        board[newy][newx] := t; //пустую клетку - на место
        board[s^.y][s^.x] := 0;
      end;
    end;
  end;
end;

var
  rGoal: int64;
 //проверяет, "а не цель ли это?"
function isGoal(s: pState): boolean;
begin
  var i, j, v: integer;
  if s^.id = rGoal then isGoal := true else isGoal := false;
end;


function genstep(n: pNode; c: integer): string;
var
  i, j: integer;
  b: tA;
  s: string;
begin
  unpackID(@b, n^.state^.id);
  str(c, s);
  result := 'step ' + s + #13;
  
  for i := 0 to 3 do 
  begin
    for j := 0 to 3 do 
    begin
      str(b[i][j], s);
      result := result + s + ' ';
    end;
    result := result + #13;
  end;    
  result := result + #13;
end;


  //строит строку-результат, пробегая узлы от конца к началу
procedure buildResult(lastNode: pNode);
var
  currentNode: pNode;
  n: integer;
  step: string;
begin
  resultString := '';
  step := '';
  currentNode := lastNode;
  while (currentNode^.parent <> nil) do 
  begin
    resultString := currentNode^.move + resultString;
    step :=  genstep(currentNode, n) + step;
    inc(n);
    currentNode := currentNode^.parent;
  end;
  writeln(step);
  write('moves:');
  writeln(n);
end;

var
  minh: integer;

function solve: boolean ;
var
  mini, i: integer;  
  node: pNode;
  list,prev,mprev,mlist: pList;


begin
  AddE(openList, initNode);
  inc(ols);
  //основной A* цикл
  while openList <> nil do 
  begin
      //ищем минимальный узел в OPEN-списке (по критерию min(f))    
    minNode := openList^.val;
    list := openList;
    prev := nil;
    mlist:=openList;
    mprev:=nil;
    while list <> nil do 
    begin
      node := list^.val;
      if (node^.f < minNode^.f) or ((node^.f = minNode^.f) and (node^.h < minNode^.h)) then begin
        minNode := node; 
        mprev:=prev;
        mlist:=list;
      end;
      prev := list;
      list := list^.next;
    end;

    //удаляем minNode из OPEN-списка
    if(mprev <> nil) then begin
    if(mlist <> nil) then begin
      mprev^.next:=mlist^.next;
      dispose(mlist);mlist:=nil;
    end
    else
    	mprev^.next:=nil;
    end
    else if mlist <> nil then begin
      openList:=mlist^.next;
      dispose(mlist);mlist:=nil;
    end;
    dec(ols);
    if minNode^.h < minh then
      minh := minNode^.h;
    if isGoal(minNode^.state) then begin
      //если это цель
      buildResult(minNode); //строим строку-результат
      solve := true; //ура!
      exit;
    end;
      //создаем потомков minNode и
      //добавляем потомков minNode в OPENLIST
    successors(minNode);

      //добавляем минимальный узел в CLOSEDLIST
    AddE(closedList, minNode);
    inc(cls);
    if(cls mod 50000) = 0 then begin write('|'); end else
    if(cls mod 1000) = 0 then begin {writeln(cls);writeln(ols);}write('.'); end;
  end;
  writeln;
     //если OPEN-список исчерпан 
  solve := false; //к цели не пришли :(
end;



// ----------------------------



begin
  id := 0;
  initGoalArrays();
  board[0][0] := 2;
  board[0][1] := 5;
  board[0][2] := 4;
  board[0][3] := 6;
  
  board[1][0] := 1;
  board[1][1] := 13;
  board[1][2] := 11;
  board[1][3] := 3;
  
  board[2][0] := 14;
  board[2][1] := 12;
  board[2][2] := 7;
  board[2][3] := 10;
  
  board[3][0] := 8;
  board[3][1] := 15;
  board[3][2] := 9;
  board[3][3] := 0;
  // Исходная доска
  {board[0][0] := 5;
  board[0][1] := 1;
  board[0][2] := 2;
  board[0][3] := 3;
  
  board[1][0] := 9;
  board[1][1] := 7;
  board[1][2] := 11;
  board[1][3] := 4;
  
  board[2][0] := 13;
  board[2][1] := 6;
  board[2][2] := 15;
  board[2][3] := 8;
  
  board[3][0] := 14;
  board[3][1] := 10;
  board[3][2] := 12;
  board[3][3] := 0;
  }
  {
  board[0][0] := 1;
  board[0][1] := 2;
  board[0][2] := 3;
  board[0][3] := 13;
  
  board[1][0] := 6;
  board[1][1] := 4;
  board[1][2] := 12;
  board[1][3] := 0;
  
  board[2][0] := 15;
  board[2][1] := 5;
  board[2][2] := 8;
  board[2][3] := 7;
  
  board[3][0] := 9;
  board[3][1] := 10;
  board[3][2] := 14;
  board[3][3] := 11;
  }
  
  // Целевая доска
  boardGoal[0][0] := 1;
  boardGoal[0][1] := 2;
  boardGoal[0][2] := 3;
  boardGoal[0][3] := 4;  
  
  boardGoal[1][0] := 5;
  boardGoal[1][1] := 6;
  boardGoal[1][2] := 7;
  boardGoal[1][3] := 8;  
  
  boardGoal[2][0] := 9;
  boardGoal[2][1] := 10;
  boardGoal[2][2] := 11;
  boardGoal[2][3] := 12;  
  
  boardGoal[3][0] := 13;
  boardGoal[3][1] := 14;
  boardGoal[3][2] := 15;
  boardGoal[3][3] := 0;  

  new(initNode);
  new(initNode^.state);  
  find0cell;
  SetState(getUniqueID(@board), initNode^.state, x0, y0);

  initNode^.h := estimate(@board);
  initNode^.g := 0;
  initNode^.move := '0';
  initNode^.parent := nil;
  initNode^.f := initNode^.g + initNode^.h;       
  minh := initNode^.h;
  writed(0, 0);
  rGoal := getUniqueID(@boardGoal);

  if not isSolvable then begin// если задача нерешаема
    writeln('Задача неразрешима');
  end
  else if (estimate(@board) = 0) then begin//если это уже цель
    writeln('Это уже цель');
  end
  else if (solve) then begin//делаем IDA* поиск
    writeln('Путь:' + resultString); //выводим результат
  end
  else 
    writeln('A* failed');
  DelA(openList);
  DelA(closedList);
  
end.

Коментарии