program XWord;
{$I graph.p}

(*
   The program displays a grid of 15x15 squares.  The cursor starts
   in the upper left.
     When the user hits an arrow key, the cursor moves to the next
   square in that direction.  When the user then starts typing, the
   letters appear in the grid going in the direction (horizontal or
   vertical) she last moved in.
     The '-' and '|' commands change the current direction to
   horizontal and vertical, respectively.
     When the user hits the '#' or ' ' keys, the current box is
   changed to black or white, respectively, and so is the diametrically
   opposite box, according to the symmetry settings.  The user can
   toggle between 0, 90, and 180-degree rotational symmetries with the
   '/' and '\' keys.
      The '#' command will not have any effect if any of its target
   grid boxes are already filled with text.  The ',' command can be
   used to clear any single grid box back to its initial "gray" state;
   the ',' command does not respect symmetries.
     The '.' command takes the current direction and looks forward
   and backward from the current box, looking for the boxes on either
   side of the current "word" (text-filled boxes).  Once it finds the
   two ends of the current word, it executes the '#' command on them.

     The '%' command updates the clue count by counting the number of
   boxes with black neighbors above and/or to their left.
     The '@' or 'Esc' command quits the program and writes the grid
   to a disk file called "xword.sav".
     The '~' command resets the entire grid to the initial state.
*)

var
  Grid: array[0..16] of array [0..16] of char;
  Command: char;
  CurX, CurY: integer;
  Horiz: boolean;
  SymRot: integer;
  HorizCount, VertCount: integer;
  invcmd: boolean;
  NotOkay: boolean;


procedure InitializeGrid;
var i,j: integer;
  begin
    for i:=1 to 15 do
      for j:=1 to 15 do
        Grid[i][j] := '.';
    for i:=0 to 16 do
      begin
        Grid[ i][ 0] := '#';
        Grid[ i][16] := '#';
        Grid[ 0][ i] := '#';
        Grid[16][ i] := '#';
      end;
    CurX := 1;
    CurY := 1;
    Horiz := true;
    SymRot := 1;
    HorizCount := 0; VertCount := 0;
    invcmd := false;
  end;

procedure BlackSquare(y,x:integer; s:integer);
var i: integer;
  begin
    for i:=0 to s-1 do
      Draw(y+i, x, y+i, x+s-1, 1);
  end;

procedure ShowGrid;
var i,j: integer;
  begin
    ClearScreen;
    for i:=1 to 15 do
      for j:=1 to 15 do
        begin
          case Grid[i][j] of
            '#': BlackSquare(8*i+8, 8*j+8, 7);
            '.': BlackSquare(8*i+9, 8*j+9, 5);
            else begin
              GotoXY(2+i,2+j);
              Write(Grid[i][j]);
            end;
          end;
        end;
    for i:=0 to 15 do begin
      Draw(8*i+15, 15, 8*i+15, 135, 1);
      Draw(15, 8*i+15, 135, 8*i+15, 1);
    end;
    Draw(8*CurX+7, 8*CurY+7, 8*CurX+15, 8*CurY+7, 3);
    Draw(8*CurX+15, 8*CurY+7, 8*CurX+15, 8*CurY+15, 3);
    Draw(8*CurX+15, 8*CurY+15, 8*CurX+7, 8*CurY+15, 3);
    Draw(8*CurX+7, 8*CurY+15, 8*CurX+7, 8*CurY+7, 3);
    GotoXY(31,23); Write('CC: '); Write(HorizCount:2);
    Write('/'); Write(VertCount:2);
    if (invcmd) then begin
      GotoXY(25, 24); Write('Invalid Command');
      invcmd := false;
    end;
    if (NotOkay) then begin
      GotoXY(23, 24); Write('Conflicting Marks');
      NotOkay := false;
    end;
  end;

procedure InvalidCommand;
  begin
    invcmd := true;
  end;

procedure SetGrid(x,y:integer; ch:char);
var okay: boolean;

  procedure SetClear(x,y:integer);
    begin
      if Grid[x][y] in ['#','.',' '] then Grid[x][y] := ' ';
    end;

  begin
    if ((x < 1) OR (x > 15) OR (y < 1) OR (y > 15)) then
    else begin
      if (ch = '#') then begin
        okay := true;
        if (SymRot>0) AND not (Grid[16-x][16-y] in ['#','.',' ']) then
          okay := false;
        if (SymRot>1) AND not (Grid[16-y][x] in ['#','.',' ']) then
          okay := false;
        if (SymRot>1) AND not (Grid[y][16-x] in ['#','.',' ']) then
          okay := false;
        if (okay) then begin
          Grid[x][y] := ch;
          if (SymRot>0) then Grid[16-x][16-y] := ch;
          if (SymRot>1) then Grid[16-y][x] := ch;
          if (SymRot>1) then Grid[y][16-x] := ch;
        end else NotOkay := true;
      end
      else if (ch = ' ') then begin
        okay := true;
(*        if (Grid[x][y] = '#') then okay := false;
        if (SymRot>0) AND (Grid[16-x][16-y] = '#') then okay := false;
        if (SymRot>1) AND (Grid[16-y][x] = '#') then okay := false;
        if (SymRot>1) AND (Grid[y][16-x] = '#') then okay := false;*)
        if (okay) then begin
          Grid[x][y] := ch;
          if (SymRot>0) then SetClear(16-x, 16-y);
          if (SymRot>1) then SetClear(16-y, x);
          if (SymRot>1) then SetClear(y, 16-x);
        end else NotOkay := true;
      end
      else if (ch = '.') then begin
        Grid[x][y] := '.';
      end
      else begin
        Grid[x][y] := ch;
        if (SymRot > 0) then begin (* 180-degree symmetry *)
          SetClear(16-x, 16-y);
          if (SymRot > 1) then begin (* 90-degree symmetry *)
            SetClear(y, 16-x);
            SetClear(16-y, x);
          end;
        end;
      end;
    end;
  end;

procedure BracketCurrentWord;
var cx, cy: integer;
  begin
    cx := CurX; cy := CurY;
    while (cx<16) AND (cy<16) AND not (Grid[cx][cy] in ['#','.',' ']) do
      if Horiz then cx := cx + 1 else cy := cy + 1;
    SetGrid(cx,cy,'#');
    cx := CurX; cy := CurY;
    while (cx>0) AND (cy>0) AND not (Grid[cx][cy] in ['#','.',' ']) do
      if Horiz then cx := cx - 1 else cy := cy - 1;
    SetGrid(cx,cy,'#');
  end;

procedure advance;
  begin
    if Horiz AND (CurX < 15) then CurX := CurX + 1;
    if (not Horiz) AND (CurY < 15) then CurY := CurY + 1;
  end;

procedure CalculateClueCount;
var i,j:integer;
  begin
    HorizCount := 0; VertCount := 0;
    for i:=1 to 15 do
      for j:=1 to 15 do
        begin
          if Grid[i-1][j] = '#' then HorizCount := HorizCount+1;
          if Grid[i][j-1] = '#' then VertCount := VertCount+1;
        end;
  end;

procedure LoadWork; forward;

procedure ActOnCommand;
var EscC: integer;
  begin
    EscC := 0;
    while not KeyPressed do;
    Read(KBD, Command);
    if KeyPressed then
      begin
        Read(KBD, Command);
        EscC := 1000;
      end;
    case Ord(Command)+EscC of
      65..90, 97..122: Command:=UpCase(Command);
      1075: Command:='<';
      1077: Command:='>';
      1072: Command:='^';
      1080: Command:='_';
      27, 64: Command:='@';
    end;

    (*
       Now we actually deal with the command we have been given.
       Letters go in grid slots; arrows move the active grid slot
       up or down; the space, hash, and dot characters change the
       category of the active grid slot; and the comma "fixes" the
       current word in the grid by sandwiching it with black squares.
    *)
    case Command of
      '<': begin if CurX>1 then CurX:=CurX-1; Horiz := true; end;
      '>': begin if CurX<15 then CurX:=CurX+1; Horiz := true; end;
      '^': begin if CurY>1 then CurY:=CurY-1; Horiz := false; end;
      '_': begin if CurY<15 then CurY:=CurY+1; Horiz := false; end;
      '#': begin SetGrid(CurX, CurY, '#'); end;
      ' ': begin SetGrid(CurX, CurY, ' '); advance; end;
      '.': begin SetGrid(CurX, CurY, '.'); advance; end;
      ',': BracketCurrentWord;
      '-': Horiz := true;
      '|': Horiz := false;
      '\', '/': SymRot := (SymRot+1) mod 3;
      'A'..'Z', 'a'..'z': begin SetGrid(CurX, CurY, Command); advance; end;
      '%': CalculateClueCount;
      '~': InitializeGrid;
      '!': LoadWork;
      '@': ;
      else InvalidCommand;
    end;
  end;

procedure SaveWork;
var i,j: integer;
    fp: TEXT;
  begin
    Assign(fp, 'xword.sav');
    Rewrite(fp);
    for j:=1 to 15 do begin
      for i:=1 to 15 do begin
        case Grid[i][j] of
          ' ': Write(fp, '.');
          '.': Write(fp, '+');
          else Write(fp, Grid[i][j]);
        end;
      end;
      Writeln(fp);
    end;
    Close(fp);
  end;

procedure LoadWork;
var i,j: integer;
    fp: TEXT;
  begin
    Assign(fp, 'xword.sav');
    Reset(fp);
    for j:=1 to 15 do begin
      for i:=1 to 15 do begin
        Read(fp, Grid[i][j]);
        case Grid[i][j] of
          '.': Grid[i][j] := ' ';
          '+': Grid[i][j] := '.';
        end;
      end;
      Readln(fp);
    end;
    Close(fp);
  end;

begin {main}
  InitializeGrid;
  GraphColorMode;
  REPEAT
    ShowGrid;
    ActOnCommand;
  UNTIL (Command = '@');
  TextMode;
  SaveWork;
end.

