Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование с нуля - это совсем просто! 52) Таймер-лайф


Информационный Канал Subscribe.Ru

 
Школа программирования

Программирование с нуля - это совсем просто!

52) Таймер-лайф

Таймер - замечательное понятие программирования. С его помощью можно легко оживлять программы, а также создавать полноценные приложения. Но сначала мы коснемся другого аспекта, связанного с перерисовкой графики.

Как вы помните, в 43 выпуске мы рассмотрели способ вывода на экран (на канву) графических элементов, рисование фактически. Только тот метод был незакончен в том смысле, что изображение, созданное программой, стиралось и пропадало, стоило нам переключиться на другую программу или закрыть область, на которой произошло рисование, другими окнами. Почему?

Потому, что вывод графики произошел только один раз, в момент нажатия на кнопку. Когда изображение на канве PaintBox стерлось, заново ведь его никто уже не рисовал, вот оно и не появилось снова.

Поэтому процесс рисования на канве правильно делать (вызывать) автоматически каждый раз, когда в этом возникает необходимость. Windows автоматически определяет такие ситуации для каждого окна, посылая тому сообщение Paint. Нам достаточно запрограммировать обработчик этого сообщения, после чего перерисовка изображения в окне будет уже происходить автоматом.

Возьмем например алгоритм построения фрактала, который отыскала Екатерина на сайте http://nsft.narod.ru/ .

  var d,rt:integer;
  i:longint;
  wx,wy,cx,cy,x,y,m,n,r,theta:real
  begin
  x:=GetMaxX;{ширина экрана}
  y:=GetMaxY;{высота экрана}
  for i:=1 to 100000 do {количество итераций}
  begin
    cx:=-1;cy:=0; {Константы вида}
    wx:=x-cx;
    wy:=y-cy;
    if wx>0 then theta:=arctan(wy/wx);
    if wx<0 then theta:=3.14159+arctan(wy/wx);
    if wx=0 then theta:=1.57079; {pi/2}
    theta:=theta/2;
    r:=sqrt(wx*wx+wy*wy);
    if Random<0.5 then
       r:=sqrt(r)
    else r:=-sqrt(r);
    x:=r*cos(theta);
    y:=r*sin(theta);
    m:=-5+(x)*100+GetMaxX/2;
    n:=(y)*100+GetMaxY/2;
  PutPixel(trunc(m),trunc(n),White);
  end;

Он для ДОСа (точнее, для Turbo Pascal), а мы его немного переделаем для Дельфи.

  x:=GetMaxX;{ширина экрана}
  y:=GetMaxY;{высота экрана}

мы заменим на размеры поля PaintBox1 (640 на 480 например), и PutPixel сменим на уже знакомый нам метод вывода цветной точки:

  var i:longint;
      wx,wy,cx,cy,x,y,m,n,r,theta:real;
  begin
  x:=PaintBox1.Width;{ширина экрана}
  y:=PaintBox1.Height;{высота экрана}
  theta:=1.57079; {pi/2}
  for i:=1 to 100000 do {количество итераций}
  begin
    cx:=-1;cy:=0; {Константы вида}
    wx:=x-cx;
    wy:=y-cy;
    if wx>0 then theta:=arctan(wy/wx);
    if wx<0 then theta:=3.14159+arctan(wy/wx);
    if wx=0 then theta:=1.57079; {pi/2}
    theta:=theta/2;
    r:=sqrt(wx*wx+wy*wy);
    if Random<0.5 then
       r:=sqrt(r)
    else r:=-sqrt(r);
    x:=r*cos(theta);
    y:=r*sin(theta);
    m:=-5+(x)*100+PaintBox1.Width/2;
    n:=(y)*100+PaintBox1.Height/2;
  PaintBox1.Canvas.Pixels[trunc(m),trunc(n)] := clBlack;
  end;

clBlack - это стандартная константа, задающая черный цвет.

Вопрос - в какой обработчик нам этот код записать? В нажатие на кнопку неправильно, он же не будет тогда автоматом вызываться. Нам нужно создать обработчик события перерисовки формы. Для этого выбираем PaintBox1 на форме и в инспекторе находим для него на вкладке Events событие OnPaint. Дважды щелкаем на нем, появляется обработчик:

  procedure TForm1.PaintBox1Paint(Sender: TObject);
  begin

  end;

Вот внутрь него и записываем наш код, после чего запускаем программу - аллес! Теперь красивый фрактал будет рисоваться автоматически и показываться всегда, независимо от того, переключались мы на другие программы или нет.

А вот код Екатерины на Си++,

  void __fastcall TForm1::Button1Click(TObject *Sender)
  {
       int rt;
       unsigned int i;
       double wx,wy,cx,cy,x,y,m,n,r,theta,pi;
       pi=3,14159265;
       x=640; //Разрешение картинки
       y=480;
       PaintBox1->Canvas->Brush->Color = RGB(255,255,255);
       PaintBox1->Canvas->FillRect(Rect(0,0,640,480));
       for (i=1;i<=100000;i++){ //Количество иттераций(Если плохо видно,
  можно увеличить)
       cx=StrToFloat(Edit1->Text);
       cy=StrToFloat(Edit2->Text);
       wx=x-cx;
       wy=y-cy;
       if (wx>0) theta=atan(wx/wy);
       if (wx<0) theta=pi+atan(wy/wx);
       if (wx==0) theta=pi/2;
       theta=theta/2;
       r=sqrt(wx*wx+wy*wy); //Это функции для разных картинок
       //r = sqrt(pow((wx + wy),2) + pow((wy + wx),2));
       //r=sqrt(pow((wx + y),2) + pow((wy - x),2));
       //r=sqrt(pow((wx-wy),2) + pow((wy - wx),2));
       //r=sqrt(pow((wx-wy)*x,2) + pow((wy - wx)*y,2));
       //r = sqrt(pow((wx - wy),2) + pow((wy - wx),2));
       //r = sqrt(pow((wx - y),2) + pow((wy - x),2));
       //r = sqrt(pow((wx - y),2) + pow((wy + x),2));
       //r=sqrt(pow((wx-wy),2));
      // r=sqrt(pow((wx-wy),2)+pow((x-y),2));
       //r=sqrt(fabs(wx*wy));
       //r=sqrt(fabs(wx*wy-y*x));
       //r=sqrt(fabs(wx*wy+y*x));
       //r=sqrt(fabs(wx*y+wy*x));
       //r=sqrt(fabs(wx*x+wy*y));
       //r=sqrt(fabs(wx*x-wy*y));
       if (random(2)==1){
            r=sqrt(r);
       }
       else r=-sqrt(r);
       x=r*cos(theta);
       y=r*sin(theta);
       m=-5+x*100+640/2;
       n=y*100+480/2;
       PaintBox1->Canvas->Pixels[m][n]=0;
  }

который переносится в полноценную программу точно так же. Просто перемещаем его в обработчик события Paint, и все.

Теперь вернемся к таймеру. Этот невизуальный компонент, совсем простой, расположен на панели System. Ставим его на форму, свойство Enabled пока переводим в false.

Что он делает? Он просто вызывает собственный обработчик через заданный промежуток времени. Этот промежуток, в миллисекундах (тысячных секунды) задается в свойстве Interval.

Вот простейший пример учета числа секунд, прошедших с момента запуска программы. Подготовим форму, на ней разместим одно поле-надпись, в ее описании добавим поле-внутреннюю переменную seks: Integer; :) в событии создания формы (OnCreate - в нем потому, что оно вызывается всего один раз и до показа формы) занесем в нее 0 и запустим таймер переводом свойства Enabled в true:

  procedure TForm1.FormCreate(Sender: TObject);
  begin
  seks := 0;
  Timer1.Enabled := true;
  end;

Теперь создадим обработчик таймера - на его вкладке Events дважды щелкнем на строке OnTimer. Пусть в его поле Interval пока сохраняется значение 1000 по умолчанию (одна секунда).

В обработчике нам достаточно увеличить значение seks на 1 и показать в надписи общее число секунд:

  procedure TForm1.Timer1Timer(Sender: TObject);
  begin
  seks := seks + 1;
  Label1.Caption := ' С момента запуска программы прошло ' +
  IntToStr(seks) + ' секунд ' ;
  end;

При желании вы можете улучшить форму вывода, аккуратно переводя секунды в минуты и часы.

Таймеры применяются очень активно, когда надо запускать в программе всевозможные фоновые процессы, отслеживая динамически состояние различных элементов на форме. Только - важно! - не нагружать обработчик таймера какими-то объемными и интенсивными вычислениями. Он для этого не предназначен.

В качестве примера создадим программу, моделирующую общеизвестную игру "Жизнь" Конвея. По ней сделано огромное число программ, для практики сделаем это и мы. Шаблон конечно будет учебный, особого качества ожидать не стоит, но общие принципы будут очень наглядно продемонстированы.

Эта игра заключается в следующем. На игровой клетчатой доске расставлены неким образом фишки, имитирующие колонию например бактерий. Очередной такт ее развития состоит в том, что состояние каждой клетки меняется по простым правилам:

* если у некоторой фишки число соседей-фишек (8 клеток со всех сторон; задание такое было, помните? :) меньше двух или больше трех, она погибает от голода или перенаселения (фишка снимается);
* если число фишек у пустой клетки равно 3, то на ней появляется - рождается новая фишка;
* во всех остальных случаях состояние клетки не меняется.

Правила крайне просты, правда? ОДнако они приводят к развитию совершенно поразительных и красивых колоний.

Вот как мы эту игру реализуем. Разместим на форме компонент PaintBox размером 100 на 100 точек. Опишем два массива:

L1, L2: array[0..101, 0..101] of Integer;

Значение 1 в массиве будет означать наличие фишки, значение 0 - пустую клетку. Подготовим процедуру, которая будет пересчитывать конфигурации - делать один такт виртуального развития. Опишем ее в классе формы:

    ...
    public
      { public declarations }

      L1, L2: array[0..101, 0..101] of Integer;

      procedure Turn;
    end;

Можно быстро создать пустую реализацию метода формы, поставив на его строчку курсор, и нажать Shift + Ctrl + C . В редакторе автоматом появится готовый код:

  procedure TForm1.Turn;
  begin

  end;

Кто не понял, как это сделать, придется набирать ручками :)

Как будет работать наш алгоритм? Примем, что исходная конфигурация всегда будет в массиве L1, а конечная - в L2. Поэтому будем анализировать L1, а финальную версию записывать в L2:

  procedure TForm1.Turn;
  var x,y,i,j,sum: Integer;
  begin

  for x := 0 to 101 do
  for y := 0 to 101 do
    if (x = 0) or (x = 101) or (y = 0) or (y = 101) then
       L2[x,y] := 0

    else
      begin
      // сколько вокруг?
      sum := 0;
      for i := x-1 to x+1 do
      for j := y-1 to y+1 do
        sum := sum + L1[i,j];

      sum := sum - L1[x,y]; // вычитаем саму клетку

      L2[x,y] := L1[x,y]; // пока без изменений...

      if L1[x,y]=1 then
        begin
        if (sum < 2) or (sum > 3)
           then L2[x,y] := 0 // сдохла!
        end else

      if sum = 3 then
        L2[x,y] := 1; // родилась!
      end;

  for x := 0 to 101 do
  for y := 0 to 101 do
    begin
    L1[x,y] := L2[x,y];
    if (x = 0) or (x = 101) or (y = 0) or (y = 101) then
       // ничего не делаем

    else
      if L1[x,y] = 1
         then PaintBox1.Canvas.Pixels[x,y] := clWhite
         else PaintBox1.Canvas.Pixels[x,y] := clBlack;
    end
  end;

Кто делал соответствующее упражнение, разберется без проблем. Здесь мы приняли для удобства, что границы массива всегда заполнены нулями, чтобы не мучаться с проверкой индексов на выход за границы.

В конце мы копируем L2 обратно в L1, чтобы синхронизировать их содержимое. Делать это можно только после того, как L2 построен польностью, понятно, почему? В процессе этой синхронизации также выведем точки на экран - пустую черным, фишку - белым.

Куда вставим вызов Turn? Конечно, в таймер!

  procedure TForm1.Timer1Timer(Sender: TObject);
  begin
  Turn;
  end;

Таймер, допустим, будет активизироваться по кнопке. Пусть в исходном состоянии он будет отключен, а при нажатии на кнопку расставим произвольную базовую конфигурацию и включим таймер:

  procedure TForm1.SpeedButton1Click(Sender: TObject);
  var x,y: Integer;
  begin

  Timer1.Enabled := true;

  for x := 0 to 101 do
  for y := 0 to 101 do
      L1[x,y] := 0;

  L1[50,10] := 1;L1[50,11] := 1;L1[50,12] := 1;
  L1[51,11] := 1;L1[49,12] := 1;

  end;

Единственная проблема - рисунок будет мелким. Попробуйте переделать так, чтобы каждая клетка была не отдельным пикселом, а достаточно большим квадратиком, со стороной 2-3-5 пикселов. Для этого надо, собственно, переписать (с умом :) две строчки с присваиванием PaintBox1.Canvas.Pixels[x,y] - вместо пикселов аккуратно рассчитать кординату x,y-квадратика, и нарисовать его черным или белым цветом в правильном месте.

Тормозить такое рисование будет, наверно, прилично. Кому интересно или лень переделывать самому, можете посмотреть на сайте http://famlife.narod.ru/ . Там удобная программка для моделирования конфигураций, с встроенным редактором, симпатичным интерфейсом.

Если кого-то эта тема за-интересует, пишите, дам побольше ссылочек.


Задания.

45. Многострадальный музыкальный плеер :)

Многие, еще программированию не научившись, спрашивали, а как сделать например ведение списка любимых композиций итд. Без программирования тогда сделать это было сложновато, зато теперь - без проблем!

Делаем полноценный плеер. В нем сам компонент MediaPlayer надо спрятать, сделать невидимым на форме (свойство Visible ставим в false). А работу с ним будем эмулировать программно. Создадим (создадите вы :) на форме красивые кнопочки и скин симпатичный, нажатие на которые будет дергать этот MediaPlayer за ниточки - методы. Кстати, есть на панели Additional кнопка SpeedButton, если ее положить на графический фон и свойства Flat и Transparent в True, то сама кнопка будет прозрачной и невидимой. Ее можно поставить просто по границе нарисованной кнопки на фоне, и как будто она настоящая будет. А реагировать станет невидимая кнопка (ее границы появятся, если навести на нее курсор, но прозрачность сохранится).

Для начала воспроизведения файла надо вызвать метод Play, для остановки - Stop, для перемотки к началу - Rewind итд. Файл, который будет проигрываться (а еще лучше - добавление нового элемента в плей-лист), надо выбирать с помощью диалога открытия файлов, о чем намедни я рассказывал, полный путь к нему заносится в свойство FileName, после чего проигрыватель надо открыть командой Open. Как то так:

  MediaPlayer1.FileName := 'c:\muzon.mp3' ;
  MediaPlayer1.Open;
  MediaPlayer1.Play;

Более подробно про свойства и методы плеера можно узнать в хелпе к Дельфи.

Важное свойство плеера Wait показывает, надо ли ждать окончания воспроизведения, блокируя все функции программы. Лучше ставить false, чтобы программа продолжала работу, а музыка играла уже в фоне. Только тогда всегда надо будет программно останавливать/закрывать плеер, если музыка еще играет, а мы хотим его заставить что-то другое делать. Закрытие плеера - метод Close. Узнать, что он делает, можно с помощью свойства Mode, которое принимает значение mpPlaying, если плеер играет музыку.

  if MediaPlayer1.Mode = mpPlaying then
   begin
   MediaPlayer1.Stop;
   MediaPlayer1.Close;
   end;

  MediaPlayer1.FileName := 'c:\muzon.mp3' ;
  MediaPlayer1.Open;
  MediaPlayer1.Play;
  ...

Ну я уже за вас все сделал :)

Ведем список желаемых композиций, в каждой строчке - путь к файлу. Они берутся и проигрываются автоматом. Файлы в список загружаем сами, список желательно также сохранять на диск. Нужные кнопки сами продумайте.

А как узнать, закончил ли плеер играть текущий файл, чтобы к следующему автоматом перейти? Для этого задействуем таймер, который например раз в секунду проверяет свойство Mode плеера, и если оно не mpPlaying, значит можно пускать следующую песню.

46. Расслабитель ума :)

Эта программа, скажу сразу, не шутка! В нее (будет) заложено немало замечательных идей из психологии.

Окно, главная форма. В окне крупными буквами в поле-надписи пишем одно из семи слов, названий цвета (синий, зеленый, красный итд). Берем основные цвета. Размер букв выбираем очень большие, на полэкрана, под 70 (подсвойство Size свойства Font в Label). И шрифт подберите какой-нибудь (подсвойство Name в Font), чтобы выглядели слова не рубчато, а гладко.

Слова предъявляются в случайном порядке. Под словом - семь квадратиков-картинок с семью цветами. Квадратики делаем с помощью компонента Shape с панели Additional.

Время предъявления каждого слова - ограничено (например, 3 секунды). Это время можно корректировать в настройках программы.

За время предъявления очередного слова пользователь должен щелкнуть на квадратике соответствующего цвета (другими словами, указать - "предъявлен такой-то цвет"). Для этого для каждого из семи цветных квадратиков надо сделать обработчики щелчка (событие OnMouseUp). Если человек правильно определил цвет, значит, плюс балл. Если неправильно, значит ничего не получил.

Ведется статистика - число попыток, правильных ответов и процент правильности.

Далее. В дополнение к каждому слову - под ним - в небольшом уже окошке, поле-надписи показывается один из четырех вариантов (тоже случайно), как надо выбирать соответствующий квадратик, какой кнопкой мыши: Левой, Правой, Левой-наоборот, Правой-наоборот. Левой - значит, надо левой кнопкой щелкнуть на квадратике, правой - правой. Левой-наоборот - значит, правой, Правой-наоборот - значит левой.

А как узнать в обработчике, какая кнопка мыши использовалась? Мы взяли обработчик события OnMouseUp, который вызывается, когда над объектом отпущена любая из кнопок мыши. А какая, задается в параметре Button обработчика. Нас интересуют такие его значения, как mbLeft (левая) и mbRight (правая).

  procedure TForm1.Shape1MouseUp(Sender: TObject; Button:
  TMouseButton;
    Shift: TShiftState; X, Y: Integer);
  begin
  if Button = mbLeft then // левая отпущена!

И еще одно, очень важное (главное!) условие. Цвет слова и само слово должны всегда отличаться. То есть если в поле показывается слово "зеленый", то оно должно быть любого цвета из семи, кроме зеленого. И правильной считается попытка, когда указан цвет, не самим словом заданный, а цвет самого слова.

Например, слово "красный" показано синим цветом. Ответ красный (щелчок на красном квадратике) неправильный, ответ синий - правильный.

Также, засчитывать надо, только если действие выбора выполнено правильной кнопкой мыши. Например, слово "желтый" коричневого цвета, а под ним - "Правой-наоборот" (или "Левой"). Значит, надо коричневый выбрать левой кнопкой.

Еще одно, необязательное - только для продвинутых студентов :) Одновременно с предъявлением слова проигрывать файл (через плеер, MediaPlayer) с записью цвета голосом (своим собственным например :) wav-файлы можно в стандартной проге Звукозаписи подготовить). Например, слово "красный" показано синим цветом, и дополнительно звучит слово "зеленый". Выбрать надо все равно синий. То есть звук - еще один отвлекающий элемент. Слово звучащее (цвет) должно отличаться и от слова написанного, и от цвета этого написанного слова.

Программа получится очень мощная. Достаточно 10-15 минут занятия с ней, чтобы полностью переключить мозги из зазомбированности :) в активный и открытый режим восприятия. Задействуется сразу несколько систем восприятия, и мозг в режиме интенсивного переключения прекрасно освобождается от разных блоков и мусора, а также активизирует творческую составляющую психики. Когда появятся хорошие варианты, я к ней еще дополнительно опишу методику наиболее правильного использования.


С лямзиками проблемы появились у многих. Но, как говорил наш Великий Учитель Карлсон, "спокойствие и только спокойствие!" :)

А почему проблемы? Потому что пока нету навыков алгоритмического мышления. Это нормально совершенно. Собственно, приемов правильного мышления по-программицки я лично насчитал несколько десятков, штук тридцать. Овладев :) ими всеми, нам по силам будет абсолютно любую задачу запрограммировать на автомате. То есть когда эти приемы перейдут в освоенный навык, программирование превратится в мастерство, как профессиональный водитель может уверенно вести автомобиль, даже будучи в сильном подпитии.

То есть Программист, - это тот человек, который может об абсолютно любой задаче (допускающей, конечно, формальное решение) сказать, как ее решение запрограммировать. Не как найти решение (это математика), а как построить процесс ее решения, решать же будет машина. Другое дело, что ресурсов для такой работы может быть очень много. Но это уже другое дело.

Так что с лямзиками разберемся в ближайшее время. Научившись их делать, и любые другие задания схожие всегда уже будете знать, как.


(c) 2004-2005 Сергей Бобровский bobrovsky@russianenterprisesolutions.com

Школа программирования с нуля
Все предыдущие выпуски базового курса тут:
http://russianenterprisesolutions.com/sbo/

 

http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.prognull
Отписаться

В избранное