{Логическая игра ним. Описание игры и программы см. п.2.7.3 книги 1}
Uses CRT;               {подключение библиотеки дополнительных
                         процедур и функций для управления экраном}
const
  MAXROW = 14;          {максимальное количество рядов}
  MAXCOL = 20;          {максимальное количество фишек в ряду}
type
  ColType   = array [1..MAXROW] of integer;
var
  exit   : Boolean;     {признак окончания работы}
  change : Boolean;     {признак изменения условий игры}
  nrow   : integer;     {количество рядов}
  ncol   : ColType;     {максимальное количество фишек по рядам}
  col    : ColType;     {текущее количество фишек по рядам}
{------------------------------------------------------------}
Procedure ShowField;
{ Отображает на экране текущее состояние игрового поля }
const
  FISH = #220;      { символ-указатель фишки }
  X0   = 4;         { левая колонка номеров рядов }
  X1   = 72;        { правая колонка количества фишек }
  X    = 20;        { левый край игрового поля }
var
  i,j : integer;
begin {ShowField}
  for i := 1 to nrow do
    begin
      GotoXY(X0,i+4);
      write(i);                { номер ряда }
      GotoXY(X1,i+4);
      write(col[i]:2);         { количество фишек в ряду }
      for j := 1 to ncol[i] do { вывод ряда фишек:}
        begin
          GotoXY(X+2*j,i+4);
          if j<=col[i] then write(FISH) else write('.')
        end
    end
end;  {ShowField}
{------------------------------------------------------------}
Procedure Prepare;
{ Подготовка данных и формирование экрана }
const
  Header0 = 'И Г Р А   Н И М';
  Header1 = 'Вы можете взять любое число фишек из любого ряда.';
  Header2 = 'Выигрывает тот, кто возьмет последнюю фишку.';
  Header3 = 'Номер ряда';
  Header4 = 'Кол-во фишек';
var
  i : integer;
begin {Prepare}
  clrscr;               { очистить экран }
  { Вывести заголовок: }
  GotoXY((80-Length(Header0)) div 2,1);
  write(Header0);
  GotoXY((80-Length(Header1)) div 2,2);
  write(Header1);
  GotoXY((80-Length(Header2)) div 2,3);
  writeln(Header2);
  write(Header3);
  GotoXY(80-Length(Header4),4);
  write(Header4);
  { Подготовить начальную раскладку: }
  for i := 1 to nrow do col[i] := ncol[i]
end;  {Prepare}
{------------------------------------------------------------}
Procedure GetPlayerMove;
{ Получить, проконтролировать и отобразить ход игрока }
const
  TEXT1 = 'Введите Ваш ход в формате РЯД КОЛИЧ (например, 2 3 - взять из 2 ряда 3 фишки)';
  TEXT2 = 'или введите 0 0 для выхода из игры; -1 0 для настройки игры';
  TEXT3 = 'Ваш ход:                 ';
  Y     = 20;            {номер строки для вывода сообщений}
var
  correctly : Boolean;   {признак правильности сделанного хода}
  x1,x2     : integer;   {вводимый ход}
{-------------------------------------}
Procedure GetChange;
{ Ввести новую настройку игры (количество рядов и
  количество фишек в каждом ряду }
const
  t1 = 'Н А С Т Р О Й К А    И Г Р Ы';
  t2 = '(ввод количества рядов и количества фишек в каждом ряду)';
var
  correctly : Boolean;
  i         : integer;
begin {GetChange}
  clrscr;
  GotoXY((80-Length(t1)) div 2,1);
  write(t1);
  GotoXY((80-Length(t2)) div 2,2);
  write(t2);
  repeat
    GotoXY(1,3);
    write('Введите количество рядов (максимум ',MAXROW,'):      ');
    GotoXY(WhereX-6,WhereY);
    readln(nrow);
    correctly := (nrow<=MAXROW) and (nrow>1);
    if not correctly then
      write(#7)
  until correctly;
  for i := 1 to nrow do
    repeat
      GotoXY(1,i+3);
      write(' ряд ',i,', количество фишек (максимум ',MAXCOL,'):       ');
      GotoXY(WhereX-6,WhereY);
      readln(ncol[i]);
      correctly := (ncol[i]<=MAXCOL) and (ncol[i]>0);
      if not correctly then
        write(#7)
    until correctly
end;  {GetChange}
{-------------------------------------}
begin {GetPlayerMove}
  ShowField;            { показать начальное состояние поля }
  { Сообщить игроку правила ввода хода: }
  GotoXY((80-Length(TEXT1)) div 2,Y);
  write(TEXT1);
  GotoXY((80-Length(TEXT2)) div 2,Y+1);
  write(TEXT2);
  repeat
    { Пригласить игрока ввести ход: }
    GotoXY(1,Y+2);
    write(TEXT3);        {вывести приглашение и стереть предыдущий ход}
    GotoXY(WhereX-16,Y+2); {курсор влево на 16 позиций}
    readln(x1,x2);       {ввести очередной ход}
    exit   := x1=0;      {контроль команды выхода}
    change := x1=-1;     {контроль команды изменения}
    if not (exit or change) then
      begin
        correctly := (x1>0) and (x1<=nrow) and
                     (x2<=col[x1]) and (x2>0);
        if correctly then
          begin          {ход правильный:}
            col[x1] := col[x1]-x2; {изменить раскладку фишек}
            ShowField              {показать поле}
          end
        else
          write(#7)      {ход неправильный: дать звуковой сигнал }
      end
    else
      correctly := true  {случай EXIT или CHANGE}
  until correctly;
  if change then
    GetChange
end;  {GetPlayerMove}
{------------------------------------------------------------}
Procedure SetOwnerMove;
{ Найти и отобразить очередной ход программы }
{-------------------------------------}
FUNCTION CheckField : integer;
{ Проверка состояния игры. Возвращает 0, если нет ни одной
  фишки (победа игрока), 1 - есть один ряд (победа машины)
  и количество непустых рядов в остальных случаях }
var
  i,j : integer;
begin {CheckField}
  j := 0;
  for i := 1 to nrow do if col[i]>0 then inc(j);
  CheckField := j
end;  {CheckField}
{-------------------------------------}
Procedure CheckPlay;
{ Контроль окончания игры }
var
  i : integer;
begin {CheckPlay}
  GotoXY(1,25);
  write('Введите 1, если хотите сыграть еще раз, 0 - выход: ');
  readln(i);
  if i=1 then change := true else exit := true
end;  {CheckPlay}
{-------------------------------------}
Procedure PlayerVictory;
{ Поздравить игрока с победой и усложнить игру }
const
  t1 = 'ПОЗДРАВЛЯЮ С ОТЛИЧНОЙ ПОБЕДОЙ!';
var i : integer;
begin
  GotoXY((80-Length(t1)) div 2,24);
  writeln(t1,#7);
  for i := 1 to nrow do
    if ncol[i]<MAXROW then inc(ncol[i]);
  CheckPlay
end;  {PlayerVictory}
{-------------------------------------}
Procedure OwnVictory;
{ Победа машины }
const
  t1 = 'ВЫ ПРОИГРАЛИ: СЛЕДУЮЩИМ ХОДОМ Я БЕРУ ВЕСЬ РЯД ';
var
  i : integer;
begin {OwnVictory}
  i := 1;
  while col[i]=0 do inc(i);
  GotoXY((80-Length(t1)) div 2,24);
  write(t1,i,#7);
  delay(2000);     {задержка на 2 секунды}
  col[i] := 0;
  ShowField;
  CheckPlay
end;  {OwnVictory}
{-------------------------------------}
Procedure ChooseMove;
{ Выбор очередного хода }
const
  BIT = 6;         {количество двоичных разрядов}
type
  BitType = array [1..BIT] of integer;
var
  ncbit : array [1..MAXROW] of BitType;
  i,j,k : integer;
  nbit  : BitType;
{-------------------------------------}
Procedure BitForm(n : integer; var b : BitType);
{ Формирует двоичное представление b целого числа n }
var
  i : integer;
begin {BitForm}
  for i := BIT downto 1 do
    begin
      if odd(n) then b[i] := 1 else b[i] := 0;
      n := n shr 1
    end
end;  {BitForm}
{-------------------------------------}
begin {ChooseMove}
  {Найти двоичное представление количества фишек во всех рядах:}
  for i := 1 to nrow do BitForm(col[i],ncbit[i]);
  {Найти сумму разрядов по модулю 2:}
  for i := 1 to BIT do
    begin
      nbit[i] := 0;
      for j := 1 to nrow do nbit[i] := nbit[i] xor ncbit[j,i]
    end;
  {Найти i = старший ненулевой разряд суммы}
  i := 1;
  while nbit[i]=0 do inc(i);
  if i>BIT then
    {Опасный вариант}
    begin
      j := 1;
      while col[j]=0 do inc(j);   {найти ненулевой ряд}
      k := 1                      {взять из него 1 фишку}
    end
  else
    {Безопасный вариант}
    begin
      j := 1;
      while ncbit[j,i]=0 do inc(j); {найти нужный ряд}
      for i := i to BIT do
        if nbit[i]=1 then
          ncbit[j,i] := ord(ncbit[j,i]=0); {инверсия разрядов}
      k := 0;
      for i := 1 to BIT do
        begin
          if ncbit[j,i]=1 then inc(k);
          if i<BIT then k := k shl 1
        end;
      k := col[j] - k
    end;
    GotoXY(1,23);
    write('Мой ход:         ');
    GotoXY(WhereX-8,WhereY);
    delay(1000);
    write(j,' ',k);
    col[j] := col[j]-k
end;  {ChooseMove}
{-------------------------------------}
begin {SetOwnerMove}
  case CheckField of      {проверить количество непустых рядов}
    0 : PlayerVictory;    {все ряды пусты - победа игрока}
    1 : OwnVictory;       {один непустой ряд - победа машины}
    else
      ChooseMove;         {выбрать очередной ход}
  end; {case}
end;  {SetOwnerMove}
{------------------------------------------------------------}
begin {Главная программа}
  nrow := 3;            { Подготовить игру }
  ncol[1] := 3;         { на поле из трех  }
  ncol[2] := 4;         { рядов фишек      }
  ncol[3] := 5;
  repeat                { Цикл изменения условий игры }
    Prepare;            { Подготовить экран }
    repeat              { Игровой цикл }
      GetPlayerMove;    { Получить ход пользователя }
      if not (exit or change) then
        SetOwnerMove    { Определить собственный ход }
    until exit or change
  until exit
end.
