Последние записи
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
- wprintf как напечатать кириллицу
- Взаимодействие через командную строку
- Сравнение языков на массивах. Часть 1
- Сравнение языков по скорости
- Чтение огромных xml-файлов
- Как в Python+Selenium webdriver открыть новую вкладку в уже открытом браузере?
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
23rd
Июн
Персональный язык программирования
Статья имеет целью познакомить читателя с одним представителем огромной армии неизвестных языков программирования. Этот язык я придумал сам для себя, он ни на какой язык не похож и этим может быть интересен.
Виктор Кон
by http://kohnvict.narod.ru
Про языки программирования написано очень много статей и философских размышлений. Их много и есть необходимость в их классификации. Главный аспект – это то, что языки могут быть многоуровневыми. Есть ассемблер, есть базовые языки более высокого уровня: Basic, C++, Java и много других. С их помощью можно написать компиляторы и интерпретаторы других языков еще более высокого уровня и так далее. Второй аспект – разделение языков на компилируемые и интерпретируемые. Первые используются для создания независимых и конечных программ, например программы решения квадратного уравнения. Вторые являются текстовыми инструкциями для одной единственной программы – интерпретатора. Бывают и пересечения, когда программа частично компилируется в промежуточный код, который затем интерпретируется. Ну и, наконец, третий аспект – структура самой программы, тут три уровня. Вся программа одним куском, программа с процедурами, программа с объектами. Какой язык и какая программа удобнее для конкретного пользователя зависит от свойств самого пользователя.
Вместо введения
Я хочу рассказать о себе. Я пользователь в том смысле, что пишу программы для своей научной работы. Почти все программы моделируют те или другие физические процессы и часто используются только один раз. Условно конечно, много раз в процессе отладки до тех пор пока не получен достоверный результат. После того, как результат получен – программа уже не нужна. Опять неточно, нужен ее код, чтобы развивать его дальше и учесть более сложные процессы, но на каждом этапе программа пишется на результат, который получается конечным числом ее запусков. Как писать такие программы? Можно конечно одним куском и на ассемблере. Но это будет огромный труд с очень низким коэффициентом полезного действия. Можно писать процедуры и компилировать их в новые сборки программ. Но и это не оптимально. В каждой программе будет очень много одного и того же кода, который будет повторяться, засоряя носители информации. Это не так важно сейчас, но было важно раньше, когда компьютеры были маломощные. Сама практика подсказала, что наиболее оптимальным путем является программа-интерпретатор, выполняющая команды одну за другой, а наиболее удобным и простым для выполнения конкретных работ является командный язык программирования с возможностью группировать код в процедуры.
Так получилось, что я пришел к этому выводу в далеком 1991-м году, когда программ еще было мало, все работали в операционной системе ДОС, которая мало что умела и о программах типа Mathematica, IGOR-pro и им аналогичных даже и не мечтали. Моя идея состояла в том, что нужно сделать интерпретатор языка программирования высокого уровня, который имел бы команды всех уровней, начиная от прерываний ассемблера и кончая запуском редактора текстов или программы построения графика одной командой. В то же время структура языка должна быть такой, чтобы четкие и конечные операции можно было сделать написав несколько строк текста. Я потратил два года работы и сделал себе такую программу. Работая в ДОС, она имела графические возможности, необычный редактор текстов с уникальными возможностями, хорошие возможности по интерфейсу и очень мне помогала в работе. Самый главный плюс такой программы состоял в том, что я мог очень быстро получить ответ на многие сложные вопросы. А быстрота получения ответа иногда имеет решающее значение. Часто просто нет времени на написание и отладку огромного кода, ответ нужен, как говорят, еще вчера. И я мог это делать.
Минусы у программы тоже были – отсутствие шрифтов. Конечно, я создал свой масштабируемый фонт, который можно было использовать, но это было не очень быстро и не очень красиво. Когда появилась Виндовс, а компьютеры стали более мощными, необходимость в интерпретаторе отпала. Самый главный минус был в том, что программа могла работать только в ДОС. Виндовс не разрешал многие операции, которые она могла делать, да и просто ее не запускал по соображениям безопасности. Я снова рассыпал программу на отдельные куски, но и они постепенно становились все менее привлекательными.
Прошло время – появились КПК (карманные персональные компютеры), это чудо на ладони с большими возможностями по части поиграть музыку или показать кино. Но они не умели делать вычисления, не было программ. А уж о том, чтобы программировать прямо на КПК не было и речи. Я снова вспомнил про свой язык программирования и свой интерпретатор. Код интерпретатора был написан на фортране, но для КПК нужно было писать на чем-то другом. Так получилось, что я в то время (это уже был 2004 год) начал работать на Java, поэтому выбор был сделан. Я просто перетранслировал вручную весь код с фортрана на Java, даже порой уже не понимая, что этот код конкретно делает. И все получилось – код заработал. Конечно, пришлось отказаться от прерываний, немного по-другому написать редактор текстов, по-другому сделать интерфейс. Но сам язык программирования и его интерпретация почти не изменились. Эта программа до сих пор работает у меня на КПК, который я как раз и купил в начале 2004 года. Он до сих пор работает с той же самой батареей. Это отдельная тема и я не буду ее развивать здесь.
Важно, что мне снова понравилось работать на своем языке, и я сделал клон этой программы также и для ПК, то есть настольного компьютера и ноутбука. Эта программа имеет больше возможностей и настолько удобна, по крайней мере, для меня, что я уже не мыслю себе другой жизни. Вот вчера мне понадобилось выделить из pdf файла книги в 150 страниц всего 4 страницы и записать в другой pdf файл. На моем яыке это делается в несколько строк, но и их писать не надо – они уже написаны. А вычислить, скажем, таблицу значений функции Бесселя и построить график можно с помощью программы в одну строчку.
Но это только примеры. Реально программа умеет очень много всего, практически все, что лично мне нужно и самое главное – если чего-то нехватает, то просто дописываем интерпретатор и создаем новую команду. Сложность только в том, что, как и у любого интерпретируемого языка, команд очень много. Но они хорошо структурированы и есть описание с хорошей навигацией. Так что перечитав описание можно все легко вспомнить. Наизусть я свой язык не помню.
Вместо рекламы
Цель этой статьи – просто познакомить уважаемую публику с тем, что у меня есть. С самого начала я не стремился делать коммерческий продукт, но в какой-то момент работы над программой я подготовил вариант, который содержит только команды общего назначения, могущие представлять интерес для всех. Этот вариант я выложил в интернет на отдельный сайт http://vkacl.narod.ru, а также разместил в каталоги программ. Неожиданно программа стала популярной на сайте FreeSoft, где ее скачали уже более 40 тыс. раз. Были и письма, из которых я понял, что кое-кто действительно научился пользоваться. У программы есть несколько вариантов. Первый, основной, содержит интерпретатор языка, который я назвал ACL (advanced command language), среду разработки, включающую встроенное описание языка, и примеры готовых программ. Эта программа называется vkACL.jar. Как любая Java программа, она сама запускается через виртуальную машину Java или JRE, которую предварительно надо установить на компьютер. Дистрибутив JRE можно бесплатно скачать хоть с родного сайта, хоть с разных зеркальных сайтов, например [1]. Скриншот программы можно посмотреть вот по этому адресу [2].
Есть также вариант проигрывателя с окном, то есть это программа, которую можно настроить таким образом, что она будет показывать меню и выполнять (интерпретировать) одну ACL программу. Такой вариант не содержит помощи и выглядит как конечная программа на одну функцию. При этом jar-файл может иметь любое имя. Программу в таком варианте я использую для разработки конкретных программ, с которыми могли бы работать мои коллеги, не умеющие программировать. Эти программы выглядят как обычные Java программы. Есть еще и второй вариант проигрывателя, который совсем не имеет головного окна. Такая программа либо выполняет свою работу, не общаясь с пользователем совсем (так работали старые программы в эпоху до ПК), либо средства общения с пользователем написаны в самой ACL программе. Иногда такой вариант бывает оптимальным.
Наконец, есть вариант программы в виде Java-апплета, который сразу работает в интернете и запускается вот по этому адресу [3]. Этот вариант сделан совсем недавно и я его сейчас развиваю. Если у кого есть быстрый интернет, то можно сразу пользоваться программой с любого компьютера. К сожалению Java-апплет не может работать с файлами пользователя, но я организовал ввод и вывод данных через редактор текстов. Любую информацию, в том числе и картинки, можно ввести в программу и вывести из программы копированием текста через буфер обмена (клавиши Ctrl-A, Ctrl-C, Ctrl-V), а готовые картинки можно скопировать с экрана, используя любую программу с функцией snapshot, то есть фотографирования части экрана в файл. Таких программ много, подробности можно прочитать в моей статье http://kohnvict.narod.ru/Data/advice.htm. Java — апплет содержит несколько готовых программ общего назначения, в частности таблицу Менделеева в виде книги, а также мои научные программы для коллег. Также каждый может скопировать и выполнить свою программу.
Что такое ACL?
Невозможно в двух словах объяснить, как работает космический корабль, слишком там много деталей. Такая же проблема с языком программирования, который рассчитан на то, чтобы делать все на свете. Когда я начинал, то не имел никакого опыта, да тогда вообще все было в очень ограниченном ассортименте. Поэтому я ничего не читал, а все выдумал сам. Первый момент – никаких строк и меток. Символ конца строки ничего не значит. В эпоху ДОС это было революционно. Программа пишется таким образом, что как раз комментарии ничем не выделяются, они пишутся свободно, а вот сами команды языка программирования начинаются с символа (#). Этот символ для интерпретатора служит знаком, что пора начинать работу. После этого знака пишется имя команды. Принцип написания всех имен следующий: можно писать сколько угодно букв, но слитно (без пробелов). Реально команды различаются максимум по трех первым буквам, а часто даже просто по одной букве. Я не люблю много писать, поэтому мне не нравится даже Java, хотя я считаю ее лучшим языком программирования. Команды из одной или двух букв абстрактны, но это дело привычки. Любой язык хорош, который знаешь. Вот у меня фамилия Кон. И это намного удобнее, чем Константинополев. Итак, читается слово до пробела и выбирается три первые буквы или меньше, если их меньше.
Что дальше?
Каждая команда – это отдельный блок программы, который знает, умеет и выполняет свою работу. Но часто ему нужны данные, конкретизирующие задачу. Входные данные раделены на два типа. Первый тип – это целочисленные параметры, которые имеют имена. Сделано это исключительно для более удобного понимания программы. Параметры универсальные и используются разными командами, причем один раз определенный параметр сохраняет свое значение до следующего определения. Определять параметры можно после каждой команды в поле, заключенном в квадратные скобки. Есть дополнительно к целочисленным несколько текстовых параметров. А всю остальную информацию надо записывать как аргументы сразу после квадратных скобок. Аргументы задаются как во многих командах многих интерпретаторов, хоть в ДОС (батч- файлы), хоть в компиляторах и прочих программах с командной строкой. Потому язык и называется командным.
Самое интересное, что больше ничего и нет. Точнее нет грамматики языка. Одни команды. И вся грамматика тоже реализована через команды. Одна команда запускает огромную готовую программу, другая реализует грамматику, но структурно они неразличимы. Возникает законный вопрос – а как делать вычисления? А где циклы, условия, логика? Ведь языки программирования, как и разговорные языки – как ни пиши, а предмет то один и тот же. Все верно. Но у меня «быстрый» язык, он кстати в первой редакции назывался quick. Для него главное – запускать готовые блоки, а все остальное вспомогательное. Поэтому все остальное реализовано по минимуму. Переменные все реальные и их имена короткие – одной буквой от (a) до (z), затем от (A) до (Z) и еще три символа ($), (%). Можно писать одну букву и одну цифру, например (z1=x3;). И все – больше переменных нет, а зачем много? Массивы – есть один целый массив i(), один реальный массив r() и один массив символов (уникодов) t(). Индексы в круглых скобках. Каждый из массивов имеет фиксированный (большой) размер, а все команды работают с частями этих массивов, причем есть специальные параметры (b [begin]) и (le [length]), которые как раз и выделяют индексы массивов, используемые в программе. То есть фактически вместо имени массива используется его начальный индекс в большом массиве. А зачем имена? Проблема выбора для некоторых людей – непреодолимое препятствие. А так все массивы плотно и динамически упаковываются в одном. Нет строк. Массив символов и строка – одно и то же. Более того, символы и числа – тоже одно и то же. Элементы символьного массива могут участвовать в вычислениях.
Как делаются вычисления? А очень просто. Открывается команда вычислений #cal или #c или просто #. Это единственная команда, которая может иметь нулевое имя. И все формулы вычислений являются ее аргументами. Она вычисляет и переопределяет переменные и массивы, это ее работа. В вычислениях можно использовать имена функций, кроме стандартных есть еще функции Бессела и интегралы Френеля. Пример: # z=sin(3.14*20/180); X1=5/exp(-2.7)+r(25); #% А что с циклами? Есть простой цикл, релизованный командой #rep N (repeat) с одним аргументом (числом повторений). Все команды после этой выполняются до команды #end , которая возвращает сканирование программы в самую первую команду после #rep и так ровно N раз. Все индексы и переменные должны переопределяться внутри цикла вручную. А условные циклы? Вот тут интереснее. Все сделано с помощью всего одной команды – и условия, и условные циклы. Зачем много? Опять проклятая проблема выбора. Просто есть еще одна команда цикла, она называется #case N.
Эта команда так же точно, выполняет все команды до команды #end. Но при одном условии, что ее аргумент совпадает со значением переменной (&). Если не совпадает, то вообще ничего не делается. В данном случае команда #end снова проверяет это же самое условие. Если не изменить переменную (&), то цикл будет делаться бесконечно. Поэтому внутри тела цикла ее надо изменить как раз тогда, когда надо. Таким способом можно сделать выбор: # &=2; #case 1 . . . #end | #case 2 . . . #end | #case 3 . . . #end | Вот в этом коде выполнится только многоточие после #case 2. Знак вертикальной черты (|) после #end через пробел означает автоматическую смену переменной (&) на значение 12345. Таким образом, тело цикла выполнится один раз. Перебор вариантов мы делаем. Но нам нужна логика. Программа должна уметь мыслить.
На это я могу авторитетно заявить. Не умеет компьютер мыслить. Все что он умеет – это сравнивать два числа на больше и меньше, а еще точнее – определять знак числа. И одной этой операции ему хватает, чтобы обыгрывать человека в шахматы. И не только в шахматы, а вообще делать разумную работу. Я не стал напрягаться и делать как у всех. Я просто ввел две новые операции < и > в математические вычисления. Результатом операции a<b является минимум из двух значений a и b. Аналогично a>b вычисляет максимум. И все, больше ничего не надо. Это позволяет писать полноценные программы, реализующие любую логику при переборе вариантов.
Заключение
Есть еще много интересных нюансов в языке и много такого, что позволяет ему иметь неограниченные возможности по созданию очень большой программы, включающей разные куски кода, написанные в разных файлах где угодно, в том числе и на серверах в интернете. Есть готовая научная графика, возможность работы с картинками, zip- архивами, делать любые операции с файлами и текстом. Но об этом можно очень долго писать. Моя задача состояла в том, чтобы заинтересовать читателя обратиться на указанные выше сайты за более подробной информацией. Я надеюсь, что эту задачу я выполнил. Напоследок скажу чего нет. Нет работы с базами данных стандартного вида, хотя аппарат работы с небольшими базами данных есть. Я не работаю с базами данных и мне это не нужно. До сих пор я не освоил аппарат работы с xml файлами, каюсь. Но и это мне не нужно. Персональный язык программирования не обязан быть универсальным.
Ресурсы
- Дистрибутив JRE http://java.sun.com или http://www.java.com/ru/download/index.jsp
- Скриншот программы http://img-fotki.yandex.ru/get/3904/kohnvict.12/0_24d12_9b5a6cf9_orig
- Вариант программы в виде Java-апплета http://vkacl.narod.ru/applets/00/vkACLa.htm
Обсудить на форуме – Персональный язык программирования
Статья из первого выпуска журнала «ПРОграммист».
23rd
GAMEDEV на Delphi. Делаем змейку
Вэтой статье я подробно рассмотрю создание игры «Змейка» с использованием ООП (объектно-ориентированного программирования). Делать игру мы будем средствами GDI на Delphi 7 (хотя должна подойти и любая другая версия). Для полного осознания того, о чем говорится в статье желательно иметь хоть какой-нибудь опыт программирования, знать что такое Сanvas и TBitmap, что такое классы и в чем заключаются их особенности.
Вадим Буренков
Вообще, при создании любой игры можно выделить три момента:
- Создание идеи, примерный план реализации. При желании разработчиком пишется «диздок» (дизайн-документ — это документ, который описывает то, что вы хотите создать).
- Алгоритмизация. То есть мы представляем план строения игры, какие типы данных будут использоваться. Обдумываются алгоритмы работы и взаимодействия элементов игры.
- Все выше мы записываем в виде кода, то есть программируем.
В принципе, игра не использует наследования, поэтому можно было обойтись и без классов, но мне захотелось сделать так.
Краткий экскурс…
При создании любой игры нужно придерживаться определенного строения кода.
Каждая игра имеет следующие секции:
- инициализация (тут происходит настройка окна для рендера (вывода изображения), загрузка всех игровых ресурсов и уровня)
- обработка (всех игровых объектов, их взаимодействие друг с другом. Эта секция в отличие от инициализации и деинициализации выполняется постоянно, причем скорость выполнения измеряется в миллисекундах и обычно** равна 10-100 мс)
- деинициализация (выполняется при завершении работы приложения и служит для очищения занятой приложением памяти)
Обратите внимание! Компонент TTimer имеет низкую точность. Ставить на нем низкие значения (менее 30 мс) не имеет смысла.
Сделаем основу для игры
Откройте Delphi, создайте новое приложение. Сразу хочу заметить, что именам всех переменных, окон, файлов, и.т.п. надо давать осмысленные имена. Конечно, кто-то скажет что и так запомнит что такое Form1 и Label2, но когда пишется проект не на 10 а на 10000 строк и используется не одна, а пятнадцать форм… Также не надо мелочиться на комментарии, поскольку смысл определенного кода через месяц простоя не поймет и тот, кто его написал, не говоря уже о том – что вам может понадобиться сторонняя помощь.
Назовем форму MainForm, модуль GameMain, а проект SnakeProject. Поскольку эта игра простая и небольшая, то я буду использовать один модуль, но в остальных случаях рекомендуется использовать свой модуль для отдельной игровой части. Создадим два события формы: OnCreate и OnDestroy которые и будут инициализатором и деинициализатором игры. За обработку игры будет отвечать компонент-таймер TTimer (поместите его на форму из вкладки System, назовем его MainTimer), а именно его событие – OnTimer.
Я не буду делать игру очень гибкой в плане игрового поля (его размеры нельзя будет менять. Поле будет 16х12 клеток, но о них будет написано ниже, основные параметры будут константами. Игровое же поле 640х480 по центру экрана без возможности перемещения (без синей рамки). Объявим константы:
SCR_WIDTH=640;
SCR_HEIGHT=480;
Можно не выставлять параметры окна в среде разработки, а написать их в коде (событие OnCreate):
MainForm.Width := SCR_WIDTH;
MainForm.Height:= SCR_HEIGHT;
// параметры формы
MainForm.BorderStyle:= bsNone; // без рамки
MainForm.Position := poDesktopCenter; // по центру экрана
Теперь заставим работать таймер (изначально параметр таймера должен быть enable = false):
MainTimer.Enabled:= true;
Создадим и добавим в событие OnTimer две процедуры (пока пустые) – Main_Update() и Main_Draw(). Для чего это? Мы ведь можем писать весь код напрямую в таймер. Поясню. Например, если мы захотим сделать в игре меню, то должны быть отдельные процедуры обработки/рисования для меню и игры и выполняться они будут в зависимости от того, где игрок находится:
if Scene = sMenu then Menu_Draw;
if Scene = sGame then Main_Draw;
Отрисовка
В этой статье я не буду делать меню, но все же. Итак, мы получили некую заготовку игры. Теперь надо правильно настроить рендер. Canvas отличается простотой, но его недостаток – медленная скорость. Если просто выводить спрайты (изображения) на форму, то будет видно – что они выводятся не сразу, а один за другим и появляется эффект мерцания изображения. Чтобы избежать этого – нужно выводить спрайты не на экран, а в буфер памяти. После того как финальное изображение будет построено, можно вывести его на форму. После вывода буфер можно очистить и он будет готов для построения следующего кадра.
Теперь реализуем данную систему. Объявим переменную – буфер scr_Buffer как Tbitmap. Перед использованием, буфер нужно проинициализировать и установить размеры:
scr_Buffer:= TBitmap.Create;
scr_Buffer.Width := SCR_WIDTH;
scr_buffer.Height:= SCR_HEIGHT;
И в событии Main_Draw() напишем код отрисовки буфера (все рисование должно происходить до этих строчек):
scr_Buffer.Canvas.Rectangle(0, 0, SCR_WIDTH, SCR_HEIGHT); // очищаем буфер ***
Тут же можем проверить, как наша заготовка справляется с рисованием. Загрузим фон для игры в переменную BackImage:
BackImage:= Tbitmap.Create;
BackImage.LoadFromFile(‘fon.bmp’);
// отрисовка
scr_Buffer.Canvas.Draw(0 ,0, BackImage);
// очищение
BackImage.Free;
Нажимаем клавишу <F9> и любуемся на результат (см. рис.1):
Рис. 1. Вывод фоновой картинки игры
Концепция игры. Долгосрочная стратегия
Итак, мы сделали «скелет» игры, на который будем насаживать игровой код. Обдумаем, что же будет представлять из себя игра? Есть змейка, она ползет по уровню, маневрирует между препятствиями и ест все съедобное на ее пути. Но змея не должна есть стены и себя, иначе она погибнет. Из выше написанного подобия «диздока» можно выделить два класса, которые мы будем использовать в игре: змея (назовем ее TSnake) и остальные объекты (TGameObject). Объекты будут иметь переменную, которая определяет их тип: либо это еда, либо препятствие. Каждый объект имеет координату. Поле будет поделено на клетки, это позволит легко определять столкновения (если координаты двух объектов совпадают, то они столкнулись). Размер каждой клетки будет определяться константой CAGE_SIZE = 40. Зная координату клетки можно получить координату на экране, умножив ее на размер клетки. Это нам понадобится при рисовании****. Эти вспомогательные функции делают следующие:
begin
result:= x*CAGE_SIZE
end;
Function G_Y(Y:integer):integer;
begin
result:= Y*CAGE_SIZE
end;
Программируем поведение змейки
Обдумаем, как же будет работать змейка. Каждая деталь змейки – это отдельный элемент со своими координатами. Когда змейка движется – первый элемент (голова) занимает новую позицию, а второй элемент занимает позицию предыдущего до перемещения (там, где раньше была голова), третий позицию второго и.т.д. Это самый логичный вариант, который приходит в голову. Но тогда надо сохранять координаты, где было тело змейки. Для упрощения кода есть более легкий способ – двигать змейку с хвоста. Перейдем к созданию класса змейки:
TSnake=class
Sprite:TBitmap; // спрайт составной части змейки
len:integer; // длина змейки (из какого кол-ва частей состоит змейка)
BodyPos: array of vect2D; // динамический (!) массив с координатами частей змейки
Speed:vect2D; // скорость змейки (сколько клеток она проходит за ход)
MoveTo:(left,right,up,down); // показатель направления движения
Constructor Create(spr_name:string;pos:vect2D;size:integer); // создание змейки со спрайтом,
// позицией и длиной
Procedure SetSize(size:integer);// изменение размера
Procedure Drive; // управление змейкой
Procedure Update; // один ход змейки
Procedure Draw; // отрисовка змейки
Destructor Free; // удаление змейки
end;
Как и в любой игре, в этой нельзя обойтись без математики. Тип «Vect2D» облегчает нам задачу хранения координат и взаимодействия с ним:
vect2D = record
X,Y: integer;
end;
Function v2(x,y:integer):vect2D; // создание
begin
result.X:= X;
result.Y:= Y
end;
Function v2ident(v1,v2:vect2D):boolean; // проверка одинаковых координат
begin
result:= false;
if (v1.x=v2.x) and (v1.y=v2.y) then result:=true
end;
Function v2add(v1,v2:vect2D):vect2D; // сложение координат
begin
result.X:= v1.x+v2.x;
result.Y:= v1.y+v2.y
end;
И вот код всех процедур класса:
Constructor TSnake.Create(spr_name:string;pos:vect2D;size:integer);
Begin
Sprite:= TBitmap.create; // загружаем спрайт
Sprite.LoadFromFile(spr_name);
Sprite.Transparent:= true;
SetSize(size); // установка длины
BodyPos[0]:= pos; // установка положения
MoveTo:= left; // изначальное направление
end;
// установка длины массива частей змейки:
Procedure TSnake.SetSize(size:integer);
begin
len:=size;
SetLength(BodyPos, len)
end;
// вот тут самое сложенное и интересное. Относительно MoveTo устанавливается скорость змейки
// и происходит сдвиг змейки в сторону движения
procedure TSnake.Update;
var i: integer;
begin
if MoveTo=Up then Speed:=v2( 0,-1);
if MoveTo=Down then Speed:=v2( 0, 1);
if MoveTo=Left then Speed:=v2(-1, 0);
if MoveTo=right then Speed:=v2( 1, 0);
// двигаем хвост (downto означает, что мы идет от большего к меньшему)
for i:= len-1 downto 1 do
BodyPos[i]:= BodyPos[i-1];
// двигаем первый элемент (голову) прибавляя скорость
BodyPos[0]:= v2add(BodyPos[0], Speed);
// проверка столкновения головы змейки с хвостом
for i:=1 to len-1 do if v2Ident(BodyPos[0],BodyPos[i]) then TryAgain;
end;
TryAgain() – процедура вызываемая при проигрыше. К ней вернемся позже. В следующей процедуре мы управляем змейкой, задавая ее направление движения через MoveTo(). Это отдельная от Tsnake.Update процедура, поскольку Update() будет вызываться реже таймера, чтобы контролировать скорость движения змейки. При этом, ловить нажатые клавиши управления надо постоянно. Определение нажатых клавиш происходит через функцию Key_Press():
var
keys: TKeyboardState;
begin
result:=false;
GetKeyboardState(keys);
If (keys[key] = 128) or (keys[key] = 129) then result:= true
end;
Коды клавиш можно определить специальной программой [2]. Те, что нам понадобятся, я занес в константы:
KEY_UP= 38;
KEY_DOWN= 40;
KEY_LEFT= 37;
KEY_RIGHT= 39;
KEY_ESC= 27;
Поскольку змейка может только поворачивать влево и вправо, а не может изменять направления движения мгновенно на 180 градусов, введены проверки типа v2Ident(v2(BodyPos[0].x,BodyPos[0].y-1),BodyPos[1])=false, которые не позволяют ей этого сделать:
begin
if Key_Press(KEY_UP) then if v2Ident(v2(BodyPos[0].x,BodyPos[0].y-1),BodyPos[1])=false then MoveTo:=Up;
if Key_Press(KEY_DOWN) then if v2Ident(v2(BodyPos[0].x,BodyPos[0].y+1),BodyPos[1])=false then MoveTo:=Down;
if Key_Press(KEY_LEFT) then if v2Ident(v2(BodyPos[0].x-1,BodyPos[0].y),BodyPos[1])=false then MoveTo:=Left;
if Key_Press(KEY_RIGHT) then if v2Ident(v2(BodyPos[0].x+1,BodyPos[0].y),BodyPos[1])=false then MoveTo:=Right;
end;
И две последние процедуры. Отрисовка отобразит все части змейки по координатам (голова отрисовывается два раза для наглядности):
var i: integer;
begin
for i:=0 to len-1 do if not v2Ident(BodyPos[i],v2(0,0)) then begin
// не отрисовываются части с нулевыми координатами,
// так как их имеют новые части змейки до движения
if i=0 then scr_Buffer.Canvas.draw(G_X(BodyPos[i].x)+Speed.x*5,G_Y(BodyPos[i].y)+Speed.y*5,sprite);
scr_Buffer.Canvas.draw(G_X(BodyPos[i].x),G_Y(BodyPos[i].y),sprite);
end
end;
// при удалении змейки очищается спрайт
destructor TSnake.Free;
begin
Sprite.Free;
end;
Рис. 2. Отображение нашей змейки
Объекты в игре. Не дадим змейке «помереть с голоду»
Теперь необходимо добавить объекты. Они будут двух типов:
TObjType=(oWall,oFood); // стены и еда
type
TgameObject = class
ObjType:TObjType; // тип объекта
Sprite:TBitmap; // спрайт
Pos:vect2D; // положение
Constructor Create(spr_name:string;oType:TObjType;p:vect2D); // создание
procedure Update(Snake:TSnake); // обновление (проверка столкновения со змеей)
procedure Draw; // отрисовка
destructor Free; // удаление
end;
Тут ничего сложного нет. Многие процедуры аналогичны змейке:
begin
Sprite:=TBitmap.create;
Sprite.LoadFromFile(spr_name);
Sprite.Transparent:=true;
pos:= p;
ObjType:= oType
end;
Procedure TGameObject.Update(Snake:TSnake);
begin
if v2Ident(Snake.BodyPos[0],pos) then begin // если змея столкнулась с объектом
if ObjType = oWall then TryAgain;
if ObjType = oFood then begin Snake.SetSize(Snake.len+1);
pos:= RandomFieldPos
end;
// увеличиваем размер змеи, перемещаем еду в другое место
// что такое RandomFieldPos см. ниже
end;
end;
Procedure TGameObject.Draw;
begin
scr_Buffer.Canvas.Draw(G_X(pos.x),G_Y(pos.y),sprite)
end;
Destructor TGameObject.Free;
begin
Sprite.Free
end;
Используем классы в игре
Итак, компоненты игры написаны. Теперь их надо создать, добавить обработку и отрисовку в Main_Update() и Main_Draw(). Вот полный код инициализации змейки и стен. Объявим следующие переменные:
Wall: array of TGameObject; // все стены игры
WallNum:integer; // их кол-во
Food:TGameObject; // еда для змейки
И добавим в инициализацию следующий код:
MySnake:= TSnake.Create(‘snake.bmp’,v2(7,7),2);
// теперь создам еду. А на ужин у нас аппетитненький пингвинчик
Food:=TGameObject.Create(‘food.bmp’,oFood,RandomFieldPos);
Чтобы не вводить координату каждой стенки уровня, так как загрузчика уровней нет, я просчитал создание всех стен в операторе for:
SetLength(Wall,WallNum);
for i:=0 to 15 do Wall[i] := TGameObject.Create(‘wall.bmp’, oWall, v2(i,0)); // верхняя
for i:=16 to 31 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(i-16,11)); // нижняя
for i:=32 to 42 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(0,i-32)); // левая
for i:=43 to 53 do Wall[i]:= TGameObject.Create(‘wall.bmp’, oWall, v2(15,i-43)); // правая
Выше я писал о функции RandomFieldPos(), которая возвращает случайную координату на поле, где нет стен. В OnCreate() надо поставить randomize() для инициализации генератора случайных чисел:
begin
result:=v2(random(13)+1,random(9)+1); // я просчитал допустимые значения
// по X от 1 до 14, по Y от 1 до 10
// тут ничего сложного нет.
end;
Собираем запчасти
Теперь надо добавить нашу змейку, стены и пингвинчика в обработку и отрисовку. Поскольку скорость движения змейки надо ограничить, мы заводим переменную-счетчик WaitTime. Она считает до 5 и выполняет процедуру движения и сбрасывает себя на 0. В итоге, MySnake.Update() срабатывает в 5 раз реже таймера. Ошибкой многих начинающих разработчиков является использование большого количества таймеров, что сильно усложняет код. Чтобы из игры можно было нормально выйти, сделаем условие нажатия на клавишу <ESCAPE>:
var i: integer;
begin
MySnake.Drive;
for i:=0 to WallNum-1 do wall[i].Update(MySnake);
Food.Update(MySnake);
if WaitTime>5 then begin MySnake.Update; WaitTime:= 0; end else inc(WaitTime);
if Key_Press(KEY_ESC) then Application.Terminate
end;
procedure Main_Draw;
var i: integer;
begin
scr_Buffer.Canvas.Draw(0, 0, BackImage);
MySnake.Draw;
for i:=0 to WallNum-1 do Wall[i].Draw;
Food.Draw;
MainForm.Canvas.Draw(0, 0, scr_Buffer);
scr_Buffer.Canvas.Rectangle(0, 0, SCR_WIDTH, SCR_HEIGHT);
end;
Напишем «правильное очищение» всех ресурсов игры:
var i: integer;
begin
scr_Buffer.Free;
BackImage.Free;
MySnake.Free;
for i:=0 to wallnum-1 do wall[i].Free
end;
После чего остается процедура «проигрыша» TryAgain(). В ней сбрасывается длина змейки, а сама она перемещается на стартовую позицию:
begin
MySnake.SetSize(0);
MySnake.SetSize(2);
MySnake.BodyPos[0]:= v2(7,7);
MySnake.MoveTo:= left;
Food.Pos:=RandomFieldPos
end;
Размер устанавливается два раза: первый для того, чтобы сбросить координаты частей змейки на нулевые значения, второй для установки начальной длины. И вот результат (см. рис.3):
Рис. 3. Результат наших трудов. Любуемся игрой
Заключение
Теперь можно запустить игру и наслаждаться результатом. Посмотрев на финальный код можно увидеть, что разработка простой игры не требует каких-либо особых усилий и знаний. Мной эта игра для статьи была написана за 2-3 часа. Весь исходный код проекта, а также текстуры, используемые для создания игры можно найти на http://www.programmersforum.ru в разделе «Журнал клуба программистов. Второй выпуск».
Обсудить на форуме – GAMEDEV на Delphi. Делаем змейку
Статья из второго выпуска журнала «ПРОграммист».
23rd
Создание спектрограммы в Дельфи
Многие начинающие программисты пробуют свои силы в создании аудиоплеера. И вот когда у них более-менее получается создать функциональную основную часть и музыка уже играет, хочется добавить еще чего-нибудь крутого. Часто это спектроанализатор или спектрограмма, которую многие ошибочно называют эквалайзером (эквалайзер – это средство настройки, а не анализа звука) [1].
Александр Терлецкий
В этом уроке мы попробуем создать спектрограмму на Delphi, а прикручивать ее мы будем к движку BASS. Скажу сразу, что существует Delphi оболочка для BASS от нашего корейского коллеги под названием TBassPlayer, в которой есть спектрограмма, но наша задача – научиться самим, а не использовать готовые решения. Однако вам никто не запрещает просмотреть исходный код этого компонента (он свободный), это никогда не повредит. Изучив этот урок, вы научитесь создавать свою собственную, уникальную своим внешним видом спектрограмму. Желательно, чтобы вы имели основные понятия об ООП, так как я не буду подробно останавливаться на этом и больше внимания уделю вопросам вывода графики.
Урок разбит на две части. В первой – пишем класс спектрограммы и отлаживаем его, во второй – прикручиваем его к звуковому движку BASS (вы можете использовать и другой движок, FMOD например, но вам придется разобраться самостоятельно как получить уровни частот). Вариант с самостоятельным разбиением спектра с помощью преобразования Фурье, без использования каких либо звуковых библиотек, я не рассматриваю вообще, если кому очень интересно попробовать, ищите информацию в Интернете, он большой и там все есть*.
Часть 1. Создание класса спектрограммы
Листинг программы смотрите в приложении к журналу [12]. Создадим новый модуль для нашего класса, назовем его Spectrum. В нем описываем главный класс спектрограммы TAnalizer, и вспомагательные TChannel и TChannels для каналов частот. Класс TAnalizer мы наследуем от TPaintBox и добавляем к нему несколько нужных нам полей, это:
- Timer (нужен для анимации ниспадающих пиков, в принципе можно было бы обойтись и без собственного таймера, но так наш анализатор получает большую независимость от деталей реализации использующего его приложения)
- Buffer: TBitmap (буфер нужен чтобы не было мерцания при обновлении изображения)
- Channels (массив каналов, кол-во их будет настраиваемое)
Сами каналы у нас будут иметь градиентную заливку, которая задается полями LowColor: TColor; и HighColor: TColor.
Количество каналов и их координаты на экране задается в методе TChannels.SetCount. Для обратной связи с хозяином в классе TChannels предусмотрено поле Owner, которое заполняется сразу после его создания в конструкторе класса TSpectrum. Возможно, здесь я отошел от канонов. Обычно поле Owner в VCL используется немного по-другому, но в данном случае мне было удобнее сделать так и вообще я не претендую на академичность.
Подробно остановлюсь на методе Draw. В нем происходит обновление изображения в такой последовательности. Очищается буфер и заливается черным фоном, затем с помощью Win API функции GradientFill() мы заливаем градиентом все прямоугольники каналов. Функция GradientFill() довольно сложная и имеет много параметров, так что перед ее вызовом много строк кода занимает заполнение нужных структур для передачи в нее. Затем мы проверяем, есть ли пики, которые стали ниже уровня соответствующей им частоты с момента последней отрисовки, если таковые есть, обновляем их положение. Те пики, которые наоборот стали выше текущего уровня, мы пока не трогаем, они будут обработаны в таймере, чтобы они плавно падали вниз (но рисуются все они в этой процедуре, в таймере только смена позиций и вызов отрисовки). После этого мы закрасим черным (цветом фона) верхушки тех каналов, уровни которых ниже максимального. БылаТут я использованал функцияю из WinAPI – OffsetRect() для смещения прямоугольника канала вверх, иа затем, после закрашивания, повторный вызов OffsetRect(), только теперь вниз, для восстановления прежних координат прямоугольника канала. И в завершении рисуем пики (короткие горизонтальные линии) для всех каналов. После этого у нас имеется обновленное изображение спектрограммы в буфере, и мы выводим его на экран, а точнее на канву нашего объекта.
Чтобы изображение не пропадало с нашего компонента при перерисовке окна, мы обрабатываем унаследованный метод Paint и в ней вызываем нашу процедуру обновления Draw(). Хотя с тех пор, как я добавил таймер, это уже лишнее, можете убрать обработку Paint() если хотите, но если нет таймера, она необходима.
Класс написан, пора его протестировать. Для этого создадим простое приложение. Для чего, поместим на форму TGroupBox, чтобы удобнее было размещать наш анализатор, и две кнопки, по нажатию одной – будет задаваться случайный уровень для всех каналов (см. рисунок 1):
Рис. 1. Тестовая форма. Спектрограмма случайных величин
По нажатию другой – пройдет волна (см. рисунок 2):
Рис. 2. Тестовая форма. Спектрограмма волновой функции
Для волны нам понадобится таймер и подключение в Uses модуля Math, так как мы будем использовать синус. По нажатию третьей – все каналы по очереди заполнятся пиковым значением. Визуально это будет, похоже, как будто кто-то пальцем провел по клавишам пианино (см. рисунок 3):
Рис. 3. Тестовая форма. Спектрограмма в виде пилы
Смотрите листинг, компилируйте ,и разбирайтесь. Если все понятно, то переходим к части 2, игде будем подключать анализатор к плееру.
Часть 2. Подключение к BASS
Как это осуществить? Здесь я отдельно рассмотрю два случая, подключение к компоненту-оболочке TBassPlayer и непосредственно к BASS. Ну что-ж, приступим…
TBassPlayer
Хочу сразу обратить ваше внимание на нюансы с различными версиями как Delphi, так и TBassPlayer. Как вы знаете, начиная с D2009, для строк используется Unicode. Если у вас Delphi более ранних версий, можете дальше не читать и переходить к коду. Если же у вас Unicode версия, то придется внести некоторые изменения в исходники TBassPlayer, в его главный модуль, иначе работать не будет (у меня в D2009 возникали ошибки во время выполнения, связанные с некорректным форматом строк). Хотя в последней его версии и указана совместимость с D2009, да и по времени выхода (май 2009) уже по идее должна быть поддержка юникода, но мне пришлось лезть в исходники даже последней версии. Исправленный модуль вы найдете в файлах к статье [1].
Добавлю еще, что помимо функций оболочки над <bass.dll>, TBassPlayer добавляет поддержку Winamp — плагинов, визуализации и другие функции. Подробнее читайте в справке к компоненту.
Итак, вернемся к нашим баранам. Скопируйте в папку с проектом файл <Spectrum.pas> с нашим анализатором и подключите его в Uses. Я не стал устанавливать в среду TBassPlayer, не люблю я все подряд устанавливать, а просто включил его в Uses. Пути к компоненту можете указать в свойствах среды, а в директорию с программой нужно скопировать файл <bass.dll> подходящей версии (разные версии TBassPlayer работают с разной версией BASS, последняя версия компонента 2.1 может работать с последним BASS 2.4.5). В обработчике события OnCreate() главной формы создаем экземпляр TBassPlayer и назначаем ему на событие OnNewFFTData() процедуру DisplayFFTBand. Затем создаем объект анализатора и назначаем ему количество каналов соответствующее количеству каналов в TBassPlayer (константа NumFFTBands).
Код открытия файла и запуска я скопировал из «демки» к TBassPlayer, заодно там и отображение некоторых свойств файла я оставил, будем выводить их в метки TLabel. Своего я добавил только кнопку Play/Pause, для определения режима работы кнопки используется ее поле Tag (меняем с «0» на «1» попеременно), при нажатии кнопки читаем ее Tag и производим необходимые действия: меняем название, запускаем или останавливаем воспроизведение и меняем Tag.
Кроме этого, мы создаем процедуру ClearChannels. В ней происходит обнуление каналов, это будет происходить при паузе и остановке. Ну и самое интересное для нас – это метод DisplayFFTBand, который привязан к событию обновления данных спектра, в него приходит состояние каналов в виде объекта Bands: TBandOut. Мы просто перегоняем значения в наш Analizer.Channels и запускаем обновление Analizer.Draw. Вот и все. Как видите, подключение элементарное, инкапсуляция проявляет себя во всей красе, все детали скрыты в модулях с классами, и нужно сделать всего несколько телодвижений, для того чтобы приложение заработало.
BASS
Пример кода подключения к <bass.dll> и получения данных спектра любезно предоставил наш форумчанин Зарипов Равиль (ZuBy). Кстати он разрабатывает плеер на базе BASS, можете подробнее ознакомиться на его сайте www.zubymplayer.com.
Библиотеку BASS и API для разных языков вы можете скачать с официального сайта проекта www.un4seen.com.
В поставке с BASS идет оболочка для Delphi <bass.pas>. Скопируйте ее в папку с проектом и подключите ее в Uses. Тоже самое проделайте с <Spectrum.pas>. По сути, подключение анализатора мало отличается от случая с TBassPlayer. Также создаем объект, назначаем ему родителя, координаты и количество каналов.
Небольшие отличия есть в механизме получения данных о спектре. Если кто не знает, FFT – означает Fast Fourier Transorm, по-русски – Быстрое Преобразование Фурье или БПФ. Таким образом, FFT Data переводится как БПФ данные. В таймере мы получаем от BASS эти самые БПФ данные в массив FData и запускаем процедуру SpectrumRender. В ней проверяется, остановлено ли воспроизведение, если остановлено, то обнуляется массив FData. В связи с этим хочу заметить, что в этом случае наш таймер в классе TAnalizer является «пятым колесом», так как отрисовка спектра будет вызвана все равно, даже если воспроизведение остановлено. Он не мешает (нагрузку большую он не дает), но и не помогает, если хотите, можете встроить в класс команду по его отключению или вообще убрать таймер, тогда немного переделать функцию Draw нужно будет. Однако, в случае с TBassPlayer собственный таймер нам пригодился.
Но вернемся к процедуре – SpectrumRender. После проверки на остановку, мы заполняем значениями каналы анализатора и вызываем его отрисовку. В отличие от случая с TBassPlayer, где мы просто переносили значения без изменений, здесь мы имеем сырые БПФ данные, и расшифровку их мы должны делать самостоятельно. В данном случае мы берем, из массива с БПФ, данные, начиная с 5-го элемента, в количестве равном количеству каналов в спектрограмме. Правильно это или нет, я не знаю, и похоже мало кто вообще знает как правильно**, и в основном все возможно больше ориентируются на красивый вид спектрограммы, чем на точные значения частот. В любом случае, мы каналы не подписываем точным значением частоты, так что с нас взятки гладки.
Автор TBassPlayer применяет другой подход. Можете ознакомиться с ним в исходниках к компоненту [22]. А так как цель статьи – показать, как можно вывести на экран спектрограмму, а не разбор БПФ данных, то на этом скажем Фурье до-свидания и продолжим рассмотрение процедуры.
Итак, мы определились, какие элементы из массива будем брать. Затем мы умножаем значение на коэффициент, найденный пробным путем, он зависит от высоты спектрограммы. Судя по тому, что к полученному значению применена функция модуля Abs, значения могут иногда быть и отрицательными, оставляем это без изменений (напоминаю, что эта часть кода не моя, я только прикрутил сюда заполнение анализатора). Откидываем дробную часть с помощью функции Trunc(), и можем заполнять этими значениями каналы нашей спектрограммы. После заполнения вызываем отрисовку методом Analizer.Draw.
Таким образом, при каждом срабатывании таймера, анализатор будет заполняться новыми значениями и будет вызываться его отрисовка. В случае, если трек будет остановлен или поставлен на паузу, так же будет заполняться анализатор, только в этом случае нулями, и так же будет вызываться отрисовка. Это позволит нам увидеть, как пики плавно падают вниз. На этом закончим рассмотрение подключения к BASS.
Заключение
Итак, мы создали свой класс спектрограммы, который умеет вывести себя на экран, как по событию перерисовки, так и по таймеру, к тому же он еще и не мерцает при отрисовке и имеет удобный интерфейс. Также мы имеем образец повторно используемого кода, мы ведь его уже использовали как минимум три раза в разных проектах, первый раз в тесте, второй – при подключении к TBassPlayer, и третий раз при подключении напрямую к BASS.
Исходники тестовых модулей получения спектрограмм прилагаются в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом.
Ресурсы
- Типичные примеры обсуждения на форуме по получению спектрограммы через библиотеку BASS
http://www.programmersforum.ru/showthread.php?t=71069&highlight=%FD%EA%E2%E0%EB%E0%E9%E7%E5%F0
http://www.programmersforum.ru/showthread.php?t=89708&highlight=%FD%EA%E2%E0%EB%E0%E9%E7%E5%F0
http://www.programmersforum.ru/showthread.php?t=94224&highlight=%FD%EA%E2%E0%EB%E0%E9%E7%E5%F0
Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro3.zip. Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro3.zip
Статья из третьего выпуска журнала «ПРОграммист».
13th
Июн
Кое-что о ремонте…
Почти несерьезная статья о серьезной работе: аппаратном ремонте компьютера. Коротко рассказано об основных неисправностях компьютера, и методах их устранения. Приведены основные направления поиска неисправностей, в основном статья предназначена для начинающих ремонтников, а также тех, кто хочет отремонтировать свой компьютер, но не знает, с чего начать…
Дмитрий Дмитренко
ddn.research@gmail.com
Скажу сразу: полностью «спалить» компьютер можно только в печке, если что-то в нем выходит со строя, то обычно какая-то отдельная часть, блок, который вполне возможно восстановить собственными силами, в крайнем случае – купить. Так что, если паяльник Вы держите в руках без заметного волнения, то «лечение» Вашего доброго помощника, советчика, а для некоторых, и «сожителя», Вам очень даже по плечу! Дело за малым: определить, какая именно часть «тела» Вашего «друга» отказала.
Краткий экскурс…
Я не занимаюсь специализированным ремонтом компьютеров, сфера моей деятельности немного не та [1, если кому интересно], но у меня есть много знакомых, друзей и родственников, у которых есть компьютеры, имеющие дурную привычку ломаться. Поэтому я решил описать здесь большинство поломок, имеющих наглость встречаться лично мне. Конечно, можно было бы привести кучу скопированного текста с разных сайтов и умных книжек, но я ограничусь своим собственным мнением на эту тему.
Лично я разделяю проявления неисправностей компьютеров на следующие группы:
- Компьютер не включается (не запускается) – при этом не подаются никакие признаки жизни, максимум, что можно от него «добиться», это включения и последующего отключения. Как ни странно, но для меня такого рода поломки – одни из самых легких, хотя, частенько бывают самые материалозатратные…
- Компьютер включается, но останавливается на стадии BIOS, то есть зависает при появлении информации о системе, или до начала загрузки операционной системы на экране появляются различные сообщения с ключевыми словами «failure» или «failed», или, наконец, раздаются различные кодированные сигналы внутренним динамиком. Такие неисправности также удачно «лечатся», если знать особенности Вашей материнской платы (я имею виду расшифровки этих самых звуковых сигналов).
- Не запускается операционная система. Это, конечно, тяжелый случай, но исправимый. Если операционка не грузится, а в общем компьютер проявляет все радости жизни, то, вероятнее всего, виноваты Вы, когда беспардонно лазя по Интернету или всовывая в порты флешки и диски непроверенные антивирусом, поймали вирус, также, как и, когда удаляли какую-нибудь архисложную программу, удалили вместе с ней очень важный системный файл… Да много еще чего может быть! Я в таких случаях восстанавливаю систему, в крайнем случае – переустанавливаю. Но эту неисправность паяльником не вылечить, поэтому здесь рассматривать не будем.
- Сбои во время работы. Самая сложная неисправность, причин может быть много, но, зная принципы возникновения этих причин, а также физические свойства полупроводников, проводников и диэлектриков, можно с легкостью одолеть и их.
Что ж, начнем…
С радостью прибежав с работы, наспех разувшись и раздевшись, не поевши и не поздоровавшись с родными, с мыслями о вчерашней «недобитой» игре, недописанному сообщении друзьям, недоперенабранной давеча статье, подходите к компьютеру, включаете его, нажимаете заветные кнопки, и… Нет повести печальнее на свете, чем время, проведенное не в нете! Что делать, спросите Вы? Конечно, можно и повеситься, но не сейчас!
Для начала давайте проверим, а поступает ли вообще напряжение на наш компьютер. При наличии тестера это сделать проще простого, вплоть до того, что замерять напряжение на штекере питания компа. Если напруга есть – идем дальше*. Снимаем крышку корпуса и смотрим умоляющими глазами на это разобранное безобразие. Скорее всего, сейчас Вам понадобиться пылесос. Не побрезгуйте, сделайте милость Вашему помощнику – почистите его. Кстати, очень рекомендую как можно чаще чистить внутренности компьютеру, развитие передовых технологий в наше время привело к тому, что даже пыль может влиять на стабильность работы любого электронного прибора…
Ну вот, и почистили его, и поумоляли, и с бубном потанцевали, а он молчит, гад! Сейчас сразу определимся: Ваш комп вообще не включается, так как если он включается, даже на короткое время, а потом отключается, то это уже может быть другая причина.
Значится так, придется опять брать в руки тестер, если Вы его уже брали до этого… Будем измерять выходные напряжения питания.
Выходные – в смысле, не потому, что отдыхают, а потому что на выходе.
Находим разъем питания на материнской плате, его можно издалека заметить, он немаленький, и практически все идущие от него провода далеко не худенькие. Сейчас появилось множество стандартов разводки разъемов блока питания, значения напряжений на нем можно посмотреть в инструкции к материнке, поэтому я здесь конкретно останавливаться на этом не буду. Вот, например, на рисунке 1 приведены чертежи разводки некоторых особенно часто встречающихся в последнее время разъемов. Ну как, измерили? Здесь возможны три с половиной варианта: все напряжения отсутствуют, одно-два (но не все) из напряжений не соответствует номинальному значению (номинальное значение на должно отклоняться более чем на 5%), или вообще отсутствует, все напряжения в норме. Сразу скажу, что последний вариант не рассматриваю, потому что это – практически из области фантастики, очень редкий случай! По поводу первых двух вариантов немного Вас огорчу – придется разбирать блок питания. Естественно, возможны и повышенные значения напряжений, но это довольно редкий и опасный случай, который встречается чаще всего у некачественных блоков питания в основном из-за выхода из строя цепей обратной связи (оптопара и ее обвязка) или микросхемы управления. В таком случае вся надежда на «здоровье» материнки и периферии: смогут ли они выдержать повышенное напряжение, или нет, есть ли у них защита от повышенного напряжения питания, или нет…
Рисунок 1. Разъем источника питания
Стоп! Забыл… Если отсутствуют все напряжения, кроме 5 и (или) 3,3 Вольта, например, цепи 5VSB, то еще проверьте сигнал включения источника питания PS_ON (чаще всего он зеленого цвета), поступающий с процессора. Если его нет – проверьте цепь, в крайнем случае – откиньте этот провод от материнки, и подайте на него «общий» питания, или «массу». Если блок питания (не компьютер!!!) запустился, об этом судят по запуску его вентилятора, то, вероятно, у Вас тяжелый случай – что-то с процессором или материнкой… Если же этот номер оказался «дохлым», с чем Вас пока и поздравляю, то переходим к следующему этапу – разборке источника питания.
Следует обратить ваше внимание на опасность статического электричества для компьютерных комплектующих. И хоть времена КМОП без защитных диодов на входе отошли, но лучше перестраховаться. Прежде чем прикасаться к чему-либо внутри компьютера, избавьтесь от заряда статического электричества, прикоснувшись к неокрашенной металлической поверхности, например к металлической части на задней панели.
В процессе работы периодически дотрагивайтесь до неокрашенных металлических поверхностей на корпусе компьютера для снятия статического напряжения, которое может повредить внутренние компоненты. Отсоедините компьютер и все подключенные к нему устройства от электросети. Также отключите от компьютера все телефонные и телекоммуникационные линии. Это уменьшает риск несчастного случая или удара электрическим током.
Кроме того, придерживайтесь следующих правил техники безопасности… При отключении кабеля от сети беритесь за вилку или за специальную петлю на вилке. Не тяните за кабель. Некоторые кабели имеют фиксаторы на разъемах. Чтобы отсоединить такие кабели, нужно предварительно нажать на эти фиксаторы. Разъединяя разъемы, держите их прямо, чтобы не погнуть контакты. Аналогично, перед подключением кабеля убедитесь в правильной ориентации и соответствии частей разъемов. При работе с компонентами и платами соблюдайте осторожность. Не касайтесь компонентов и контактов плат. Держите плату за края или за металлические кронштейны. Держите компоненты, например микропроцессор, за края, не дотрагиваясь до контактов.
Проверьте наличие коротких замыканий в самой схеме компа. Отключите разъем, подайте «массу» на вывод включения источника питания PS_ON, и измерьте напряжения (или именно то напряжение, которое отсутствовало). Если все чудесным образом пришло в норму, или оказалось несколько завышено (такое бывает при работе блока питания на «холостом ходу»), то Вам следует искать причину в самом компьютере, а блок питания пока не трогать. Но об этом позже. А сейчас…
Разобрали? Опять пыль? Пылесос в студию! Первым делом проверьте предохранитель. Целый? Тогда нам опять не повезло…
Практически все источники питания собраны по идентичным схемам, впрочем, как и все остальные импульсные блоки питания. Поначалу кажущийся таким сложным, он состоит из высоковольтной и низковольтной частей, и трансформатора. Не будем вникать в технические и технологические подробности, начнем ремонтировать. Только помните – ремонт производят при отключенном от сети устройстве!
Я не буду останавливаться на нюансах ремонта блоков питания, так же, как и на подробных принципах их работы, это тема отдельного и очень длинного разговора, я дам лишь краткие сведения, без описания физических данных блоков, коих сейчас выпущено так много, что всех их и не рассмотришь и не опишешь.
Сначала производим внешний осмотр. Если не выявлено явных повреждений в виде «горелых», оплавленных, треснутых от кропотливой работы деталей, переходим к следующей стадии ремонта, если выявлены, то определяем вышедшую из строя запчасть, и… переходим к следующей стадии ремонта.
Проверяем низковольтную часть на наличие пробитых выпрямительных диодов. Это – самая частая неисправность. В качестве таких диодов используют мощные высокочастотные диоды Шоттки**. На рисунке 2 изображен стандартный разобранный блок питания, все основные элементы подписаны, диоды тоже. Измерять обычным тестером, как обычные диоды, они проводят ток в одном направлении, в другом – не проводят.
Затем проверяем высоковольтную часть: входные диоды, задающий транзистор. Я не буду расска- зывать, как их проверять, скажу только, что лучше их проверять выпаянными из платы. Затем на очереди – конденса- торы фильтра питания.
Также можно проверить микросхему, и оптопару, если она присутствует, и их обвязку. Для некоторых, конечно, это высший пилотаж, значения напряжений на выводах микросхемы можно посмотреть только в инструкции к блоку питания, или в datasheet на эту микросхему. В общем – Google рулит! Также проверяем трансформатор на наличие обрывов и, если есть чем измерять, короткозамкнутых витков. Впрочем, если «вылетел» трансформатор, в большинстве случаев придется покупать новый блок питания, хотя, если у Вас есть друзья на «разборке», или Вы специалист по перематыванию импульсных трансформаторов с кучей свободного времени, то Вам невероятно повезло… И, напоследок, проверка емкостей фильтра на выходах. Они бывают как обрывной, потерявшие емкость, так и короткозамкнутые. Конденсаторы, которые не соответствуют стандартным, «строгим», размерам (например, раздутые до неприличия, или с подтеками какой-то желтой жидкости), должны быть проверены тщательнейшим образом!
Ну вот, в принципе, и все, что я хотел сказать про блок питания…
Теперь поговорим о случае, когда при- сутствует замыка- ние по шине питания в самом компьютере, а блок питания вполне исправен.
Сразу оговоримся, что если КЗ «распо- ложено» на материн- ской плате, то у Вас ничего не получится! Но попытаемся…
Рисунок 2. Ремонтируем жесткий диск
Рецепт прост: отклю- чаем сеть, отключаем по одному устройству и включаем компью- тер. Очередность приблизительно такая: винчестер (очень часто встречается, во многих винчестерах установлен защитный стабилитрон на входе питания, который при, даже маленьком, превышении значения напряжения питания сразу «коротит» шину питания на землю), приводы CDDVD- флоппи (причина та же, что и у «винтов»), периферия, расположенная на самой материнке (видеокарта, TV-тюнер и прочее).
Если после отключения какого-либо устройства компьютер заработал или хотя бы включился вентилятор процессора, то виновник видимо определен. Кстати, совсем забыл о USB, COM и прочих портах. Отключите их в самую первую очередь. Никаких флешек, принтеров, джойстиков!
Сейчас немного отвлекусь от темы и расскажу, что можно сделать, если все-таки вышел из строя защитный стабилитрон. На рисунке 3 изображен жесткий диск производства Samsung (не новый, правда). Измеряем сопротивление стабилитрона, если он «коротит», то есть проводит ток во всех направлениях, притом его сопротивление приближается к нулю, то это он, 100%! Что можно сделать? Да выпаять его, и все тут! Работать будет, но… мало ли, от чего произошел перепад напряжения, из-за чего этот стабилитрон закоротило… Лучше поставить другой, необязательно такой же, просто смотрим, какое напряжение выло закорочено, и, в зависимости от этого ставим новый. Если закорочена пятивольтовая шина – стабилитрон на 6.3В, можно и 5,6В, если двенадцативольтовая – 12.6 или 13.2В. Если после замены стабилитрона и движений бубном устройство не заработало, то имеет смысл его покрасить в синий цвет, и выбросить.
Рисунок 3. Блок питания ПК
Если виновник торжества – видеокарта, или прочая периферия, то тут делать нечего, скорее всего придется покупать новую, а старую похоронить с почестями и салютом. Все, по первому пункту я уже все, что знал – рассказал. Переходим ко второму…
Начнем с теории
Как известно, прежде операционной системы в компьютере запускается встроенная в чип материнской платы программа BIOS (Base Input/Output System, основная система ввода- вывода). Назначение этого небольшого (256 Кб) программного кода — свести к «общему знаменателю» аппаратные различия компьютерного оборудования. Параметры настройки BIOS хранятся в энергозависимой CMOS RAM, которая питается от батарейки на материнской плате. Отсюда вывод: если у вас часто «слетают» установки компьютера или «вредничают» часы — скорее всего, пора менять батарейку. После включения питания напряжение подается на центральный процессор и другие микросхемы материнской платы. «Проснувшись», CPU запускает из микросхемы программу BIOS — и начинается процедура POST (Power On Self Test, инициализация при первом включении). Ее задача — просканировать и настроить все «железо». Прежде всего формируется логическая архитектура компьютера. Подается питание на все чипсеты, в их регистрах устанавливаются нужные значения. Затем определяется объем ОЗУ (этот процесс можно наблюдать на экране), включается клавиатура, распознаются LPT- и COM- порты. На следующем этапе определяются блочные устройства — жесткие диски IDE и SCSI, флоп-дисководы. Для устройств SCSI процедура несколько усложняется наличием собственной BIOS, которая берет на себя работу с соответствующим оборудованием и имеет собственную программу настройки. На заключительной стадии происходит отображение итоговой информации.
После окончания работы POST, BIOS ищет загрузочную запись. Эта запись, в зависимости от настройки, находится на первом или втором жестком диске, флоп-диске, ZIP или CDROM. После того как загрузочная записи найдена, она загружается в память и управление передается ей [2].
Если во время работы POST будут обнаружены ошибки, устройство попытается пользователя об этом предупредить. При этом вполне вероятна полная остановка работы компьютера. Задача пользователя – расшифровать сигналы ошибок, передаваемые POST, перевести их на человеческий язык. Для этого существуют специальные POST-карты. Диагностика материнской платы с помощью индикатора POST кодов, превращается в простое и увлекательное дело. Действительно, что может быть проще: вставил POST карту в слот (ISA, часто встречающаяся в «немолодых» компьютерах, или PCI) запустил плату и смотри на результат. В зависимости от модели POST-card коды ошибок выводятся в десятичном или шестнадцатеричном виде на цифровом индикаторе или простыми светодиодами. На некоторых современных материнских платах уже встроены функции заменяющие POST-карту и коды отображаются на находящемся на материнской плате индикаторе.
Но имейте в виду, что POST-карта не может являтся спасением от всех бед, так как она отображает только результат выполнения тестовой процедуры которая находится внутри микросхемы BIOS и при каждом включении и перезагрузке компьютера запускает функции самотестирования основных компонентов и подсистем ПК (таких как процессор, память, чипсет, видеокарту, клавиатуру, жесткие и гибкие диски и т.д.). Если в ходе выполнения обнаружена ошибка, система выдает звуковой сигнал и выводит сообщение на экран (конечно только в случае, если ошибка не была обнаружена раньше, чем например, видеоадаптер). Соответственно, POST карта не покажет полезной информации при неисправности самой микросхемы BIOS, ее обвязке, системах запуска и питания материнской платы. В других, не особенно тяжких, случаях с материнской платой диагностическая POST карта может существенно упро- стить поиск неисправности компонента платы или других деталей компь- ютера. Таблицы POST кодов можно легко скачать с сайтов производителей микросхем BIOS. Также можно их посмотреть в источниках [2, 3]. Единс- твенная проблема – наличие собственно этой POST-карты. Если вы не занимаетесь специализированным ремонтов компьютеров, то она Вам нужна, как собаке пятая нога, тем более в денежном выражении она «нормально стоит». Поэтому остановимся на другой индикации ошибок POST – звуковой. Если во время самотестирования BIOS не может вывести информацию на монитор, используется звуковой сигнал или голос, воспроизводимый при помощи встроенного динамика.
Для различных материнских плат применяется различная звуковая «кодировка». Остановимся только на самых распространенных [4], и тех, что мне встречались чаще всего (см. табл.1-3**).
Звуковые коды в данном случае представлены в количестве звуковых сигналов. Например, 1-2-2 означает 1 звуковой сигнал, пауза, 2 звуковых сигнала, снова пауза, и опять 2 звуковых сигнала.
таблица 1
таблица 2
Все, пожалуй, хватит на сегодня…
Как пользоваться данными таблицами? Ну вот, например, при включении Вы услышали один повторяющийся длинный «пик» динамика, и Вы знаете точно, что у вас Award BIOS. Разбираем компьютер и смотрим ОЗУ, это такая планка, «память» иногда называется (как оказалось в нашем случае – не вечная). Можно попытаться ее вынуть из разъема и почистить контакты, например, обычным ластиком, и снова поставить на место, иногда помогает. А теперь переходим к самому «страшному» пункту моей классификации неисправнос- тей – четвер- тому. Сбой во время работы – это и испор- ченные нервы, и, нередко, разбитый мо- нитор! Поэто- му с этим нужно что-то делать.
Определить причину та- кого сбоя непрофессио- налу можно, в основном, то- лько методом «научного ты- ка». К приме- ру, у меня был случай, когда во время работы ком- пьютер просто «виснул» без всяких при- чин. Причина выяснялась довольно до- лго, пока, во время работы компьютера, я случайно не задел рукой шлейф жесткого диска. Оказалось, окислился контакт, заменил шлейф и «зависания» прошли. Еще когда-то принесли ноутбук, у которого при включении было видно, что загрузка идет, а экран как был темным, так и оставался, только подсветка немного светила. Такое, как оказалось позже, часто встречается при перегреве чипсета видеокарты. В том случае проблема решилась просто: я снял радиатор с этого чипсета, включил компьютер и немного подождал, пока не сработала защита от перегревания, такая защита есть практически у всех компьютеров и ноутбуков.
таблица 3
И, о чудо, при последующем включении все чудесным образом заработало! Конечно, ненадолго, но причина была установлена быстро и дешево – видеоплата. Ведь до этого в сервисном центре предлагали поменять не только видеокарту, но и ОЗУ. Непонятно только – зачем?
Так что, экономия – на лице… заказчика. Вообще, практически все спецэффекты, иьтаситьтаскоторые могут наблюдаться на экране монитора, будь то квадратики-ромбики или цветовые «галлюцинации» происходят по вине видеокарты, а уж затем среди виноватых ищем оперативку или даже процессор.
Заключение
Кстати, перегревания не такая уж редкость в наше пыльное время. И чтобы этого не произошло, и Вам действительно ценно здоровье Вашего компьютера, производите хотя бы раз в полгода- год плановые «медосмотры» с присутствием острого глаза и пылесоса. Подправьте все разъемы, уложите провода, затяните гайки и болты. Не поленитесь также, я уже об этом говорил, очистить от пыли все внутренности, и будет Вам счастье в виде незабываемых минут с развлечениями или работой без нервов и «зависаний»!
Очень полезны в плане ремонта и обслуживания сайты [5-8, 10], есть что почитать, а если нужное не нашли, то можно и лично вопрос задать. В данной статье я, в основном, описал методику поиска простейших, наиболее часто встречающихся неисправностей, которые можно выявить с помощью отвертки и обычного мультиметра и устранить с помощью обычных рук и обычного паяльника. Для более сложных поломок я и рекомендую эти сайты, иногда полезнее воспользоваться чьим-то опытом, чем нарабатывать свой. Это может слишком дорого стоить…
А вообще – опыт приходит с годами, главное – не бояться и соблюдать меры предосторожности, особенно при работе с металлическими предметами (отвертками, пинцетами) под напряжением. И, я уверен, у Вас все получится.
Диод Шоттки – полупроводниковый диод с малым падением напряжения при прямом включении. Назван в честь немецкого физика Вальтера Шоттки. Диоды Шоттки используют переход металл- полупроводник в качестве барьера Шоттки (вместо p-n перехода, как у обычных диодов). Допустимое обратное напряжение промышленно выпускаемых диодов Шоттки ограничено 250 В (MBR40250 и аналоги), на практике большинство диодов Шоттки применяется в низковольтных цепях при обратном напряжении порядка единиц и нескольких десятков вольт.
Оптопара (оптрон) – электронный прибор, состоящий из излучателя света (обычно — светодиод, в ранних изделиях — миниатюрная лампа накаливания) и фотоприемника (биполярных и полевых фототранзисторов, фотодиодов, фототиристоров, фоторезисторов), связанных оптическим каналом и как правило объединенных в общем корпусе. Принцип работы оптрона заключается в преобразовании электрического сигнала в свет, его передаче по оптическому каналу и последующем преобразовании обратно в электрический сигнал. В оптроне входная и выходная цепи гальванически развязаны между собой… Нижняя рабочая частота оптрона не ограничена – оптроны могут работать в цепях постоянного тока. Верхняя рабочая частота оптронов, оптимизированных под высокочастотную передачу цифровых сигналов, достигает сотен МГц / Википедия.
Источники и ссылки
- Сайт автора статьи http://ddn.at.ua
- Программирование под Windows http://www.ru-coding.com
- Ремонт материнских плат http://materinki.narod.ru
- Звуковые коды BIOS http://www.umopit.ru/CompLab/BIOSbeeps.htm
- Форум, посвященный ремонту ноутбуков http://notebook1.ru/forma1
- Конференция по ремонту аппаратуры http://monitor.net.ru
- Конференция IXBT http://forum.ixbt.com
- Ремонт и настройка компьютеров http://comp-remont.info
Статья из четвёртого выпуска журнала «ПРОграммист».
4th
Июн
Работа с LPT портом в Дельфи или компьютер в роли управляющего контроллера. Часть 1
Большинство пользователей персонального компьютера привыкли к тому, что весь результат деятельности на компьютере в той или иной степени все равно отражается в самом компьютере. В крайнем случае, отправляется на принтер или в Интернет, или же запись информации происходит на внешние носители (диски, флеш-память и т.п.). И уж мало кто задумывается, что с помощью простого РС – компьютера можно управлять различными внешними физическими устройствами.
Владимир Дегтярь
by DeKot degvv@mail.ru
Тем не менее, на форуме http://www.programmersforum.ru (да и на других также) часто появляются вопросы: «Как с компьютера зажечь светодиод?», «Как управлять освещением?» или «Можно ли компьютером закрывать шторы на окне?». Ответ: «…однозначно да». С помощью современного персонального компьютера можно создавать, воспроизводить, управлять, хранить, моделировать, обучать и еще реализовать много и много других функций.
Введение
Мы же рассмотрим возможности управления внешними устройствами – при этом возможно управление, как простым вентилятором или светодиодом, так производственными устройствами. Как известно, любой персональный компьютер, да и не только персональный, а и любое компьютерное (еще одно общепринятое название – микропроцессорное) устройство имеет устройства ввода и вывода информации, называемые портами. Для нас пользователей – это просто разъемы для подключения (клавиатура, мышь, модем, флешка и т. д.). Следует заметить, что по одному и тому порту (разъему)
можно как выводить информацию, так и вводить. Кроме этого понятие порт – это не только физически внешний элемент (разъем), а больше наоборот – внутренняя логическая структура устройства компьютера, часть его архитектуры.
Думаю что уже достаточно теории. Перейдем к практической реализации – управлению реальными устройствами с помощью компьютера. Более всего для этого подходит «параллельный» порт LPT. Более подробно смотрим о LPT http://ru.wikipedia.org/wiki/IEEE_1284.
Параллельный порт LPT
Итак, почему параллельный, а не перпендикулярный? А какие еще бывают? Параллельный, потому что информация через такое устройство передается параллельным способом. А еще может передаваться последовательным – тогда устройство (порт) называется последовательным (последовательные порты компьютера: COM-порт, USB- порт). Наглядно это можно увидеть на рисунке 2:
Аналогично ввод информации также может быть параллельным или последовательным. Порты (устройства) через которые можно вводить и выводить информацию называют двунаправленными устройствами или устройствами ввода/вывода. Таким и есть параллельный порт LPT, имеющийся в большинстве компьютеров. Посмотрим, что из себя представляет этот порт (см. рисунок 3):
где С0…С3 – регистры контроля, S3…S7 – регистры статуса, D0…D7 – регистры данных
Как видите, это такой разъем с 25-ю выводами на задней стенке системного блока компьютера. Итак, имеем физический порт LPT, который фактически состоит из трех регистров. Состав регистров и распределение по контактам разъема приведены в таблице 1:
Каждый из регистров может содержать байт информации (256 состояний). Часть битов не используется или используется только во внутренней архитектуре компьютера для организации прерываний при работе с принтером или для переключения режимов ввод/вывод регистра «Data».
Регистр данных «Data», номер регистра в шестнадцатеричной системе счисления $378 (в десятичной 888) – двунаправленный, восьмибитный. Данные через этот регистр можно как вводить, так и выводить с компьютера, программно устанавливая уровни на выходе порта или же вводить в компьютер, также программно считывая уровни, устанавливаемые внешними устройствами.
Регистр управления «Control» $37A (890). Через него можно только выводить информацию из компьютера. На разъем LPT выводятся четыре младших байта.
Регистр статуса «Status» $379 (889). Через порт можно только считывать уровни, установленные внешними устройствами. На разъем LPT выведены пять старших байтов.
Таким образом, на разъеме LPT задействовано 17 сигнальных контактов (8 двунаправленных – регистр 888, четыре только на вывод информации – регистр 890 и пять только на ввод информации – регистр 889).
Порт LPT разрабатывался еще на заре создания персональных компьютеров для подключения печатающих устройств (принтер). Этим объясняется специфическое наименования цепей и инверсия некоторых битов, из-за которой наблюдается несоответствие уровней напряжения на контактах и логических кодов регистров. В связи с этим, операции с информацией через LPT порт требуют учитывать эти особенности соответствия физических уровней и логических кодов.
Следует отметить, что в некоторых компьютерах может быть до трех портов LPT: LPT1, LPT2, LPT3 (в современных компьютерах LPT порты зачастую вообще отсутствуют). Адрес самого LPT порта соответствует адресу регистра «Data» и может быть $278, $378 или $3BC. Регистры в соответствующих портах имеют адресацию +1 и +2.
Следующие таблицы (см. таблицы 2, 3) показывают эти особенности данного порта. Для регистра $378 (888) соблюдается полное соответствие между уровнями напряжения на контактах и логическим кодом. А вот с регистрами $379 (889) и $37A (890) все обстоит по-другому:
Управление регистрами
В компьютерах с операционными системами MS-DOS, Windows 9x возможен доступ к портам непосредственно из самой операционной системы, тогда как в системах с NT такой прямой доступ невозможен. Для этих целей используются драйвера в виде библиотек (Inpout32.dll, WinIO, Giveo). Подключив соответствующие библиотеки к средам программирования, получаем возможность программно работать с портами (считывание состояния порта или установка выводов порта в необходимое состояние).
* Автор отдает предпочтение библиотеке <inpout32.dll> для работы в среде Дельфи. Библиотека
<inpout32.dll> свободно распространяется в Интернете (см. ресурсы к статье).
Итак, во-первых – помещаем <inpout32.dll> в папке с проектом. Далее в коде модуля Unit после раздела uses размещаем объявления необходимых нам функций из библиотеки:
// импорт функций inpout32.dll
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,StdCtrls, ExtCtrls, ComCtrls;
function Inp32(PortAdr: word): byte; stdcall; external 'inpout32.dll';
function Out32(PortAdr: word; Data: byte): byte; stdcall; external 'inpout32.dll';
Функция Inp32(PortAdr) возвращает число (тип – байт), соответствующее коду, находящемся в регистре PortAdr. Функция Out32(PortAdr, Data) возвращает число (Data, тип – байт), которое запишется в регистр PortAdr. Проще сказать Inp32 считывает значение регистра, а Out32 устанавливает значение в регистр. Пример применения функций:
var val1, val2: byte;
val1:= Inp32($378); // значение регистра «data» запишется в переменную val1
val2:= 54;
Out32($378,val2); // в регистр «data» запишется число 54 (b 0 0 1 1 0 1 1 0)
При этом следует учитывать, что состояние уровней напряжения на выходных контактах LPT порта соответствует таблице 2 и 3. При необходимости управления отдельными битами регистров можно применить следующие функции преобразований десятичного числа в двоичное и наоборот:
// weight (для byte(8 разр.)) = 128, j = 7; weight( для word (16 разр.)) = 32 768, j = 15 ;
function Dec_Bin(N_dec: integer; weight: integer; _bit: byte): byte;
var i ,j : byte;
mas_bin: array[0..j] of byte;
N_dec: integer;
begin
for i:= 0 to j do
begin
mas_bin:= N_dec div weight;
if mas_bin = 1 then N_dec:= N_dec - weight;
weight:= weight div 2;
end;
Result:= mas_bin[_bit]; // возвращает значение бита (0 или 1) двоичного числа,
// соответствующего N_dec
end;
function Bin_Dec(weight: integer): integer;
var i , j: byte;
mas_bin: array[0..j] of byte;
N_dec: integer;
begin
N_dec:= 0;
for i:= 0 to j do
begin
N_dec:= N_dec + mas_bin * weight;
weight:= weight div 2;
end;
Result:= N_dec; // возвращает десятичное число, соответствующее двоичному
// в виде массива битов mas_bin [ 0 .. j ]
end;
Пример применения функций:
var bit : byte;
bit := Dec_Bin(Inp32($378))[3]; // переменная bit принимает значение 3-го бита регистра «data»
** Переключение регистра «data» ($378) c «выхода» на « вход»
Возможность использования регистра «data» ($378) в качестве порта ввода определяется в настройках
BIOS для параллельного порта. Следует установить Parallel Port в EPP. Переключение регистра на «вход»
осуществляется программно, путем установки 5-го бита регистра «control» ($37A) в «1» (при этом все биты
регистра «data» устанавливаются в «1»). Следует отметить, что данная процедура возможна не на каждом
компьютере и определяется, кроме настроек, еще и конструктивными особенностями порта LPT или
материнской платы.
Вариант тестовой утилиты управления и считывания состояния регистров LPT представлен на рисунке 4 и приведен в ресурсах к статье [1]:
Заключение
Продолжение смотрите в следующем выпуске журнала «ПРОграммист»…
Ресурсы
. Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro3.zip
. Сайт Валерия Ковтуна с множеством интересных программ для работы с LPT портом
http://valery-us4leh.narod.ru/main.html
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
30th
Май
Как работать с графикой на канве в среде Дельфи
Здравствуйте, уважаемые читатели. Как и обещал, сегодня с вами мы подробно рассмотрим процедуры работы с графическими объектами, вынесенными в отдельный модуль, позволяющий использовать универсальные методы для создания движущихся изображений, находящихся в файлах, обычно в виде спрайтов…
Продолжение. Начало цикла смотрите в первом и втором выпусках журнала…
Владимир Дегтярь
DeKot degvv@mail.ru
Создание проекта с несколькими движущимися объектами. Урок 5
Создадим новый проект <Lesson 3> аналогично предыдущим. Введем в него новые движущиеся графические объекты (в папке <data> добавлено еще четыре звездолета. Размер каждого спрайта 100х80 pix. Новые звездолеты будут появляться по случайному закону (используем функцию Randomize) и двигаться будут сверху вниз. Вывод фона и основного звездолета ’ship1′ осуществляется также как и в предыдущем проекте <Lesson 2>. Для новых объектов вводим дополнительно BufShipR и BufPicR. Также объявим новые переменные – координаты вывода новых звездолетов и приращения этих координат.
Одновременно у нас будут отображаться основной звездолет ’ship 1′ и два из ’ship 2′ – ’ship 5′, выбираемые по случайному закону (см. листинг 1):
ЛИСТИНГ 1
Var
Form1: TForm1;
BufFon,BufFonDop,Buffer: TBitMap;
BufShip1,BufShipR: TBitMap; // буферы спрайтов
BufPicS1,BufPicR: TBitMap; // буферы изображений одного спрайта
xf,yf: integer; // координаты вывода общего буфера на форму
dyf: integer; // приращение изменения координаты yf по вертикали
xS1,yS1: integer; // координаты звездолета 'ship1'
dxS1,dyS1: integer; // приращение координат 'ship1' по гориз. и вертик.
xR1,yR1,xR2,yR2: integer; // координаты звездолетов 'ship2 - ship5'
dyR1,dxR2,dyR2: integer; // приращение координат 'ship2 - 5'
ns,nr1: byte; // номер спрайта и выбор ship2 - ship5
nr2: byte = 3;
implementation
В процедуре OnCreate формы проведем инициализацию буферов и введем начальные данные для переменных. Процедура DrawShipR(i,j: byte) для вывода новых объектов (’ship2′ – ’ship5′) имеет два
параметра: i (изменение номера рисунка в файле спрайтов) и j (переменная для номера файла спрайтов). Т.к. выбор файла спрайта происходит по передаваемому параметру j, то инициализация буфера BufShipR и загрузка в него файла спрайтов находится в процедуре DrawShipR(i,j: byte) (см. листинг 2):
ЛИСТИНГ 2
procedure DrawShip1(i: byte);
begin
// загрузка одного спрайта в буфер рисунка
BufPicS1.Canvas.CopyRect(bounds(0,0,BufPicS1.Width,
BufPicS1.Height),
BufShip1.Canvas,
bounds(i * 66,0,
BufPicS1.Width,
BufPicS1.Height));
BufPicS1.Transparent:= true; // зададим прозрачность фона рисунка спрайта
BufPicS1.TransparentColor:= BufPicS1.Canvas.Pixels[1,1];
end;
Все движения организованы в обработчике таймера. Звездолеты ’ship2′ — ’ship5′ выводятся в Buffer в координаты xR1, yR1 и xR2, yR2 вне видимого окна формы выше. В каждом такте таймера происходит приращение координат для одного dyR1 по вертикали, для другого dxR2 и dyR2 – по горизонтали и вертикали. После того, как объекты выходят за пределы видимого окна формы внизу, вызываются методы random( ) для задания новых координат xR1, xR2 и номера файла спрайта (nr1, nr2). Координаты yR1, yR2 привязаны к координате yS1 , так как ’ship1′ неподвижен в координатах окна формы. Функция
random ( 4 ) возвращает числа в дипазоне 0 .. 3, а файлы спрайтов встречных звездолетов имеют номера 2 .. 5. Поэтому в процедуре загрузки спрайтов BufShipR.LoadFromFile(’data/ship’ + IntToStr(j+2) + ‘.bmp’) номер загружаемого файла определяется как IntToStr(j + 2)… В остальном процедуры обработчиков таймера и нажатия клавиш не отличаются от проекта <Lesson 2>.
Использование универсального модуля для работы с графикой. Урок 6
Если рассмотреть внимательно код программы в проекте <Lesson 3>, можно заметить, что многие методы часто повторяются для разных графических объектов (создание буферов, загрузка изображений из файлов, копирование и т.п.). При этом для упрощения, я сознательно применил файлы спрайтов одинакового размера и с равным количеством рисунков в файлах. А если файлов спрайтов будет не пять, а больше и если количество рисунков в каждом файле будет разным? Придется значительно увеличивать код для
каждого вида спрайтов. Следовательно, необходимо оптимизировать код программы. Выход здесь в написании методов обработки объектов, не зависящих от количества объектов и применимых для разных изображений.
Даная задача реализована в отдельном модуле <LoadObjectToBufferMod>, позволяющий использовать универсальные методы для создания движущихся графических объектов (находящихся в файлах, обычно в виде спрайтов), имеющих различный размер и разное количество изображений отдельных рисунков.
Модуль находится в папке <Lesson 4> (см. ресурсы к статье). Принцип организации модуля следующий:
- вся работа с графическими объектами проводится через битовые образы TBitMap и области
копирования битовых образов TRect
- для работы с фоном используются процедуры InitFon (инициализация) и LoadFon (загрузка фона из
файлов)
- функция InitSprite предназначена для инициализации и загрузки рисунков спрайтов
- для вывода фона и изображений спрайтов использован общий буфер типа TBitMap
- в процедуре InitBuff происходит инициализация общего буфера, а в процедуре FreeBuff ”переустановка”,
т.е. уничтожение общего буфера и создание снова, но уже без изображений спрайтов
- в процедуре LoadBuff происходит наложение изображений спрайтов на фон
Подробно работа модуля показана ниже…
Применение модуля LoadObjectToBufferMod
1. procedure InitFon(nw, nh: byte; FileName: string)
Создаем дополнительный и основной буферы фона:
BufFonD:= TBitmap.Create;
BufFon := TBitmap.Create;
Далее загружаем рисунок одного из фонов в дополнительный буфер:
BufFonD.LoadFromFile(FileName) или
LoadFromResourceName(hinstance.filename);
По загруженному рисунку получаем размер одного рисунка фона (см. рисунок 1):
Причем, размер буфера фона определяем как:
WF:= nw * WFD;
HF:= nh * HFD;
2. procedure LoadFon(xf, yf: integer; FileName: string)
Загружаем все рисунки фонов в буфер фона через дополнительный буфер (см. рисунок 2):
BufFonD.LoadFromFile(FileName) или
LoadFromResourceName(hinstance.filename);
3. procedure initBuffer
Создаем основной буфер (Buffer) через который выводим спрайты на форму:
Buffer:= TBitmap.Create;
Размер основного буфера устанавливаем равным размеру буфера фона WF и HF. Загружаем в основной буфер весь фон (cм. рисунок 3):
Buffer.Canvas.Draw(0, 0, BufFon);
4. procedure FreeBuffer
Процедура уничтожаем основной буфер Buffer. Применяется когда необходимо убрать какой-либо спрайт с формы:
Buffer.Free;
Восстанавливаем-же основной буфер с фоном так:
InitBuffer;
Спрайты, которые должны оставаться на форме, следует перерисовать по новому (см. процедуру InitStprite)…
5. procedure InitStprite(SpriteName: string; N_goriz, N_vertic, N_stroka, N_kadr: byte): byte
Cоздаем буфер массива спрайтов и загружаем туда файл спрайтов (см. рисунок 4):
BufSprite:= TBitmap.Create; BufSprite.LoadFromFile(SpriteName) или LoadFromResourceName(hinstance.spritename);
Создаем буфер рисунка (одного спрайта):
BufPic:= TBitmap.Create;
Далее определяем размеры буферов массива спрайтов и буфера рисунка, а также области (типа TRoot) загрузки рисунка. Загружаем спрайт в буфер рисунка (см. рисунок 5):
BufPic.Canvas.CopyRect(RectPic.BufSprite.Canvas, rectSprite);
Задаем прозрачность рисунку:
BufPic.Transparent:= true;
Для вывода следующего спрайта функция возвращает (правильнее сказать – функция принимает значение = Result) значение следующего номера спрайта N_kadr:
Result:= N_kadr;
Уничтожаем буфер массива спрайтов
BufSprite.Free;
6. procedure LoadBuffer(xf, yf, xs, ys, bs: integer)
В этой процедуре на Buffer выводится участок фона с координатами ранее выведенного спрайта, а затем очередное положение спрайта. Определяем область фона и область дополнительного буфера (см. рисунок 6):
Выводим участок фона в буфер, т.е. затираем спрайт фоном (см. рисунок 7):
Выводим очередной спрайт в дополнительный буфер Buffer (см. рисунок 8):
Buffer.Canvas.StretchDraw(Bounds(xs, ys, WP + bs, HP + bs), BufPic);
Уничтожаем буфер рисунка:
BufPic.Free;
Далее в программе выводим дополнительный буфер Buffer на форму методом Draw:
Form1.Canvas.Draw(x, y, Buffer);
Битовые образы фона (BufFon и BufFonD инициализируются (создаются – Create) в программе проекта всего один раз при инициализации программы (обычно вызовом процедуры InitFon в событиях OnCreate или OnActivate). Методы InitBuff, FreeBuff, InitSprite, LoadBuf в программе вызываются неоднократно*. Соответственно и объекты Buffer, BufSprite, BufPic создаются многократно. Поэтому после окончания действия каждого из методов происходит уничтожение битовых образов Buffer, BufSprite, BufPic методом Free.
Комментарий автора.
Загрузку фона можно производить из n – количества файлов, c одинаковым размером не более 1024 х
1024. Для этого вызывать процедуру LoadFon n -раз для разных файлов FonName и изменяя
координаты xf и yf.
Модуль можно применять и для простых объектов (один рисунок в файле SpriteName). Присвойте
переменным N_goriz и N_vertic значения = 1. Если размер спрайта не изменяется, присвойте
переменной bs значение = 0.
Можно загружать рисунки из файлов .jpg. Для этого вместо TBitMap применять класс TJpegImage и в
разделах uses LoadObjectToBufferMod и uses Unit1 добавить модуль Jpeg.
Как работать с модулем?
Для этого необходимо выполнить следующие действия:
1. В процедуре FormActivate (можно в FormCreate) инициализируем буфер фона. Вызываем procedure InitFon(nw,nh: byte; FonName: string) с одним из файлов фонов:
. n раз вызываем procedure LoadFon(xf,yf: integer; FonName: string), последовательно прикрепляя рисунки фонов как бы друг к другу
. инициализируем дополнительный буфер Buffer, вызвав procedure InitBuff. Он получит размер равный сумме размеров всех файлов фонов.
2. Для вывода необходимых спрайтов в нужном месте программы вызываем function InitSprite(SpriteName: string; N_goriz,N_vertic,N_stroka, N_kadr: byte). Функция возвращает очередной номер спрайта для последующего вывода очередного спрайта. Этот номер (N_kadr) необходимо передавать в функцию при каждом ее вызове. Причем, функцию можно использовать для вывода нескольких спрайтов, не забывая передавать ей значение N_kadr для каждых спрайтов.
Далее, вызвав процедуру LoadBuff(xf,yf,xs,ys,bs: integer), выводим спрайт на канву дополнительного буфера поверх фона. Окончательный вывод дополнительного буфера на канву формы производим методом Draw().
Заключение
Рассматриваемые в данной статье проекты полностью приведены в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом (папка Lesson3). Продолжение наших уроков смотрите в следующем выпуске журнала «ПРОграммист»…
Комментарий автора.
Перед запуском в среде Дельфи скопируйте в папку с проектом папку data с графическими файлами.
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Как работать с графикой на канве в среде Дельфи
27th
Май
Передача звука по сети. Прототип VoIP телефона
Данная статья будет полезна начинающим программистам, которые никогда не имели дело со звуком и его передачей по сети. Смысл этой статьи заключается в изучении и применении: WINAPI функций ввода и вывода звука WaveIn() и WaveOut() в среде разработки Delphi 7.0, самих компонентов TIdUDPServerSocket и TIdUDPClientSocket. Данные компоненты можно найти в библиотеке Indy, которая в свою очередь находится в свободном распространении на просторах Internet’а.
Передача звука по сети. Прототип VoIP телефона
Уколов Александр Владимирович
by ImmortalAlexSan st_devil@mail.ru
Комментарий автора.
Если вы никогда не программировали в Delphi 7.0, версиями ниже или выше, если вы вообще никогда не программировали на подобных ЯВУ, то эта статья не для вас.
Введение
К написанию программы для передачи звука по сети меня побудило желание получить-таки зачет по УИРС (это что-то вроде НИР – научно исследовательской работы студента) у преподавателя, ведущего мой основной предмет, и являющимся моим дипломным руководителем. Перед тем как сесть за Delphi и начать набирать код, предварительно, я изучил кучу литературы в бумажном и электронном виде о принципах упаковки звука и его передачи, о функциях ввода и вывода в самом Delphi и многом другом [1, 2]. Именно ввод и вывод заставил меня задуматься о сложности преподносимого материала. Для человека, никогда не имевшего с этим дело, разобраться в этой области очень сложно, имея под рукой множество кода без комментариев с непонятными процедурами и функциями непонятного WIN API, а если эти процедуры и функции описаны, то это описание предназначено не для начинающих программистов, приходилось все додумывать самому: смотреть подноготную каждой процедуры, и методом проб и ошибок идти медленно, но уверенно к вершине созидания. Но в конечном итоге я добился поставленной цели. И сейчас, разложив всю информацию, предоставленную мне в кашеобразном виде, по полочкам, я готов поделиться своими знаниями с вами, дорогие читатели! Итак, приступим…
Средства разработки
Прежде всего, для работы нам понадобится:
. IDE Delphi версии 7.0 и выше
. Библиотека Indy для Delphi 7.0 (TIdUDPServerSocket и TIdUDPClientSocket) [3, 4]
. колонки и микрофон
Сразу же перейдем к практической части. По мере появления неизвестных функций и процедур в листинге, они будут незамедлительно описываться…
Практическая часть. Создадим клиента
Передача звука в моей программе осуществляется с клиента на сервер, т.е. в одном направлении. Клиент может только писать и передавать, сервер – только принимать и воспроизводить. Первым делом начнем писать клиент.
Для этого, создадим новый проект в Дельфи, разместим на форме кнопку TButton и изменим ее свойство Caption на «начать отправку». После чего, разместим на форме компонент из библиотеки Indy TIdUDPClientSocket (см. рисунок 1):
Так как тестирование программы будет проводиться на локальном компьютере, то изменим значение свойства Host компонента TIdUDPClientSocket на «localhost». Далее я просто перечислю свойства компонента и их значения, что должны быть установлены: Active (false), BroadCastEnabled (false), BufferSize (8192), Name (IdUDPClient1), Port (0), ReceiveTimeOut (-2), Tag (0).
Примечание: описание некоторых вышеуказанных свойств выходит за рамки данной статьи.
Теперь, нажимаем двойным щелчком по вынесенному на форму компоненту TButton и появится обработчик события Button1Click(), где Button1 – это значение свойства Name данного компонента. В этом обработчике пишем или копируем следующий код:
procedure TForm1.Button1Click(Sender: TObject);
begin
// если на кнопке написано «начать отправку» то
If button1.Caption='Начать отправку' then Begin // выполняем этот код, где:
// готовим заголовок для буфера, здесь WaveIn – переменная типа интегер, для
// указания идентификатора устройства ввода (микрофона например), @WaveHdr –
// указатель на структуру TWaveHdr, sizeof(Twavehdr) – размер данной структуры в байтах.
waveInPrepareHeader(waveIn,@WaveHdr,sizeof(Twavehdr));
// заносим данные в буфер
waveInAddBuffer(wavein,@WaveHdr,sizeof(TwaveHdr));
// активируем сокет клиента
IdUDPClient1.Active:= true;
// считываем данные с микрофона
waveInStart(waveIn);
// в едит для наглядности заносим количество записанных байт (делал для себя,
// чтобы проверять, пишется звук или нет)
Edit1.Text:= inttostr(WaveHdr.dwBufferLength);
// меняем название кнопки, чтобы создать возможность прервать отправку пакетов
button1.Caption:='Остановить отправку'
end else Begin // если название кнопки «остановить отправку» то
//переименовываем её
button1.Caption:='Начать отправку';
//закрываем сокет клиента
IdUDPClient1.Active:=false;
//разгружаем буфер
waveInUnprepareHeader(Wavein,@WaveHdr,sizeof(TwaveHdr));
// приостанавливаем считывание. ЗАМЕТЬТЕ! ПРИОСТАНАВЛИВАЕМ! Если мы
// напишем waveInClose(Wavein), то устройство будет закрыто, и при повторном
// нажатии на кнопку, не будет никакого результата.
waveInStop(Wavein);
// смотрим кол-во не записанных байт
Edit1.Text:=inttostr(Wavehdr.dwBytesRecorded);
end
end;
Вы спросите, а что же такое waveInPrepareHeader? Это функция, выполняющая подготовку буфера для операции загрузки данных. Общий вид:
function waveInPrepareHeader(
hWaveIn: HWAVEIN;
lpWaveInHdr: PWaveHdr;
uSize: UINT
): MMRESULT; stdcall;
Здесь:
HWaveIn – идентификатор открытого устройства
LpWaveInHdr – адрес структуры WaveHdr
type TWaveHdr = record
lpData: PChar; { указатель на буфер}
dwBufferLength: DWORD; { длина буфера }
dwBytesRecorded: DWORD; { записанный байты }
dwUser: DWORD; { переменная для использования её пользователем }
dwFlags: DWORD; { флаги }
dwLoops: DWORD; { контролер }
lpNext: PWaveHdr; { переменная для драйвера }
reserved: DWORD; { переменная для драйвера }
end;
Здесь:
lpData – адрес буфера для загрузки данных
dwBufferLength – длина буфера в байтах
dwBytesRecorded – для режима загрузки данных определяет количество загруженных в буфер байт
dwUser – пользовательские данные
dwFlags – флаги. Могут иметь следующие значения: WHDR_DONE устанавливается
драйвером при завершении загрузки буфера данными
WHDR_PREPARED – устанавливается системой. Показывает готовность буфера к загрузке данных
WHDR_INQUEUE – устанавливается системой, когда буфер установлен в очередь
dwLoops – используется только при воспроизведении. При записи звука всегда 0
lpNext – зарезервировано
reserved – зарезервировано
uSize – размер структуры WaveHdr в байтах
Функция waveInPrepareHeader вызывается только один раз для каждого устанавливаемого в очередь загрузки буфера. Что такое waveInAddBuffer()? Функция waveInAddBuffer() ставит в очередь на загрузку данными буфер памяти. Когда буфер заполнен, система уведомляет об этом приложение:
function waveInAddBuffer(
hWaveIn: HWAVEIN;
lpWaveInHdr: PWaveHdr;
uSize: UINT
): MMRESULT; stdcall;
Здесь:
hWaveIn – идентификатор открытого Waveform audio устройства ввода
lpWaveInHdr – адрес структуры TWaveHdr
uSize – размер WaveHdr в байтах
Что такое waveInStart(), waveInStop(), waveInClose()? Общий вид записи таков:
function waveInStart(hWaveIn: HWAVEIN): MMRESULT; stdcall;
waveInStop(), waveInClose() имеют совершенно одинаковый параметр – как и WaveInStart(), которую описывать не имеет смысла, ибо и так понятно, что она начинает считывать данные с устройства ввода, а вот waveInClose() закрывает устройство для записи, и его снова придется открывать с помощью WaveInOpen(), но об этом ниже… А вот waveInStop(), ставит запись как бы на паузу, и нам не надо повторно использовать WaveInOpen().
Что такое waveInUnprepareHeader? Функция аналогичная waveInPrepareHeader(), однако она возвращает выделенную память на буфер, т.е. как бы «уничтожая» его.
Как узнать, что уже можно передавать данные?
Мы разобрали некоторые функции WIN API, относящиеся к вводу данных. Не устали? Нет? Тогда двигаемся дальше! Создадим собственную процедуру для определения завершения передачи данных в блок памяти посредством WaveInAddBuffer(). А выглядит она так:
procedure TForm1.OnWaveMessage(var msg:TMessage);
begin
waveInPrepareHeader(waveIn,@WaveHdr,sizeof(Twavehdr));
waveInAddBuffer(wavein,@WaveHdr,sizeof(TwaveHdr));
// отправляем буфер на сервер, где WaveHdr.lpData^ - это ссылка на память, где
// хранятся считанные с микрофона данные, уже преобразованные в
// последовательность нулей и единиц, WaveHdr.dwBufferLength – длина буфера данных
idUDPClient1.Sendbuffer(WaveHdr.lpData^,WaveHdr.dwBufferLength);
// В переменную заносим количество отправленных байт
Bytes:=Bytes+WaveHdr.dwBufferLength;
// Формат строки. Посмотрите в google фразу format дельфи
Caption:=Format ('%u',[Bytes]);
UpDate
end;
В этой процедуре используются уже известные вам функции, по этому второй раз описывать их не будем. Пишем её сразу после строки {$R *.dfm}. А описываем эту процедуру в разделе private класса TForm1 как:
procedure OnWaveMessage(var msg:TMessage); message MM_WIM_DATA;
Эта процедура будет выполняться каждый раз как только передача данных в буфер будет завершена и система сгенерирует сообщение WIM_DATA. Заполним обработчик события формы OnClose():
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// завершаем все действия
Action:= caFree;
// деактивируем сокет
IdUDPClient1.Active:=false;
// закрываем, теперь уже совсем, устройство записи
waveInClose(Wavein);
end;
И конечно же, заполним обработчик события формы OnCreate():
procedure TForm1.FormCreate(Sender: TObject);
begin
// with – оператор, благодаря которому можно не писать переменные, а указывать
// сразу их свойства. В данном случае WaveFormat: TWAVEFORMATEX – отвечает
// за сигнал, т.е. за все его характеристики, описан ниже.
with waveformat do begin
nChannels:=1;
wFormatTag:=WAVE_FORMAT_PCM;
nSamplesPerSec:=8000;
wBitsPerSample:=8;
nBlockAlign:=1;
nAvgBytesPerSec:=8000;
cbSize:=0;
end;
// для удобства загоняем размер буфера в переменную, которую будем вызывать
bufsize:= waveformat.nAvgBytesPerSec*2 div 16;
// размеру буфера сокета присваиваем размер буфера bufsize
IdUDPClient1.BufferSize:=bufsize;
// waveInOpen опишем чуть ниже, как и обещал, WAVE_MAPPER – система
// сама выбирает устройство
waveInOpen(@Wavein,WAVE_MAPPER,addr(waveformat),self.Handle,0,CALLBACK_WINDOW);
// выделяем память под заголовок буфера данных
WaveHdr.lpData:=Pchar(GlobalAlloc(GMEM_FIXED, bufsize));
// присваиваем длину буфера TWaveHdr’у
WaveHdr.dwBufferLength:=bufsize;
// сбрасываем флаги
WaveHdr.dwFlags:=0;
// устанавливаем порт подключения для клиента
IdUDPClient1.Port:= 10090
end;
Что же такое WaveInOpen()?
Функция waveInOpen() открывает имеющееся устройство ввода Waveform Audio для оцифровки сигнала. Типичная ее структура выглядит следующим образом:
function waveInOpen(
lphWaveIn: PHWAVEIN;
uDeviceID: UINT;
lpFormatEx: PWaveFormatEx;
dwCallback,
dwInstance,
dwFlags: DWORD
): MMRESULT; stdcall;
Здесь:
lphWaveIn – указатель на идентификатор открытого Waveform audio устройства. Идентификатор используется после того, как устройство открыто, в других функциях Waveform audio;
uDeviceID – номер открываемого устройства (см. waveInGetNumDevs). Это может быть также идентификатор уже открытого ранее устройства. Вы можете использовать значение WAVE_MAPPER для того, чтобы функция автоматически выбрала совместимое с требуемым форматом данных устройство;
lpFormatEx - указатель на структуру типа TWaveFormatEx
type TWaveFormatEx = packed record
wFormatTag: Word; { format type }
nChannels: Word; { number of channels (i.e. mono, stereo, etc.) }
nSamplesPerSec: DWORD; { sample rate }
nAvgBytesPerSec: DWORD; { for buffer estimation }
nBlockAlign: Word; { block size of data }
wBitsPerSample: Word; { number of bits per sample of mono data }
cbSize: Word; { the count in bytes of the size of }
end;
В этой структуре значения полей следующие:
wFormatTag – формат Waveform audio. Мы будем использовать значение WAVE_FORMAT_PCM
(это означает импульсно-кодовая модуляция) другие возможные значения
смотрите в заголовочном файле MMREG.H;
nChannels – количество каналов. Обычно 1 (моно) или 2(стерео);
nSamplesPerSec – частота дискретизации. Для формата PCM – в классическом смысле, т.е.
количество выборок в секунду. Согласно теореме отсчетов должна вдвое
превышать частоту оцифровываемого сигнала. Обычно находится в диапазоне от
8000 до 44100 выборок в секунду;
nAvgBytesPerSec – средняя скорость передачи данных. Для PCM равна nSamplesPerSec*nBlockAlign;
nBlockAlign – для PCM равен (nChannels*wBitsPerSample)/8;
wBitsPerSample – количество бит в одной выборке. Для PCM равно 8 или 16;
cbSize – равно 0. Подробности в Microsoft Multimedia Programmer’s Reference;
dwCallback – адрес callback-функции, идентификатор окна или потока, вызываемого при
наступлении события;
dwInstance – пользовательский параметр в callback-механизме. Сам по себе не используется
dwFlags – флаги для открываемого устройства:CALLBACK_EVENT dwCallback-параметр –
код сообщения (an event handle);
CALLBACK_FUNCTION dwCallback – параметр – адрес процедуры-обработчика
CALLBACK_NULL dwCallback – параметр не используется
CALLBACK_THREAD dwCallback – параметр – идентификатор потока команд;
CALLBACK_WINDOW dwCallback – параметр – идентификатор окна
WAVE_FORMAT_DIRECT если указан этот флаг, ACM-драйвер не выполняет преобразование данных
WAVE_FORMAT_QUERY функция запрашивает устройство для определения
поддерживает ли оно указанный формат, но не открывает его
Мы использовали callback функцию в OnWaveMessage(). В последнюю очередь я опишу переменные, которые использовались:
type
TForm1 = class(TForm)
IdUDPClient1: TIdUDPClient;
Button1: TButton;
Edit1: TEdit;
procedure Button1Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
procedure OnWaveMessage(var msg:TMessage); message MM_WIM_DATA;
{ Private declarations }
public
{ Public declarations }
Wavein:HWAVEIN;
WaveHdr:TWaveHdr;
bufsize:Cardinal;
end;
var
Form1: TForm1;
WaveDataLength:integer;
bytes:integer;
device:word;
waveformat: TWAVEFORMATEX;
a:integer;
Так же для работы программы необходимо добавить модуль MMSystem в раздел uses. Клиент готов! Как видите, не так страшен черт, как его малюют! Перед тем как перейти к написанию сервера, я бы вам настоятельно рекомендовал бы покопаться в генофонде всех выше описанных функций и самостоятельно глубже разобраться в том, как они устроены. Так для более углубленного изучения, советую переворошить содержимое таких компонентов из серии ACM как AcmIn, AcmOut. Только самообучением можно чего-нибудь добиться.
А что же сервер?
С чистой перед клиентом совестью, можем приступить к написанию сервера! Возможно, эта процедура покажется вам более сложной, но, разобравшись в ней, вы поймете, что это не так. Единственное, что работать мы будем не с одним буфером, а с восьмью, для удобства воспроизведения звука. В один записываем, воспроизводим, очищаем, готовим, записываем и т.д. по очереди каждый из восьми. Так же будет рассмотрена работа с флагами (dwflags) и приема потока данных (TMemoryStream) на сервер. Приступим, нетерпеливые мои!
Как обычно, создадим новый проект и вынесем на форму компонент TMemo (name=memo1) (опять же-таки я использовал его в целях определения получения потока данных, перегоняя его в шестнадцатиричный формат), кнопку TButton и IdUDPServerSocket (см. рисунок 2):
Пожалуй, начнем с простого. Напишем ниже приведенный код в обработчике события OnClose() формы:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
// завершаем действия
Action:= caFree;
// выключаем сервер
IdUDPServer1.Active:= False
end;
Далее займемся обработчиком события OnClick() кнопки TButton1 (см. код):
procedure TForm1.Button1Click(Sender: TObject);
begin
If button1.Caption='Включить сервер' then Begin
// активируем сокет сервера
IdUDPServer1.Active:= true;
button1.Caption:='Выключить сервер'
end else begin
// деактивируем сокет сервера
IdUDPServer1.Active:= false;
button1.Caption:= 'Включить сервер'
end
end;
Теперь напишем процедуру, которую мы будем использовать для воспроизведения принятого звука:
procedure TForm1.playsound(s:Tstream); // получаемый поток
Var // переменная типа сообщения
msg:Tmessage;
begin
// пока а не равно нашему количеству буферов выполняем следующее
While a<>CWaveBufferCount do Begin
// проверку пользовательской установки на то, что буфер готов к записи
If FHeaders[a].dwUser=0 then begin
// записываем в буфер данные из потока, пришедшего от клиента
s.Read(Fheaders[a].lpdata^,bufsize);
// процедура waveOutPrepareHeader аналогична процедуре waveInPrepareHeader
waveOutPrepareHeader(WaveOut,@FHeaders[a],sizeof(FHeaders));
// Процедура waveOutWrite аналогична процедуре waveInAddBuffer, только она
// осуществляет воспроизведение данных из буфера
waveOutWrite(WaveOut,@FHeaders[a],sizeof(FHeaders));
memo1.Lines.Add('...Двоичный код потока...');
// обнуляем флаги буфера/ов в цикле
FHeaders[a].dwFlags:= 0;
// уже знакомая нам структура
With FHeaders[a] do begin
dwBufferLength:= bufsize;
dwBytesRecorded:= 0;
dwUser := 0;
dwLoops:= 1;
// А вот здесь мы присваиваем флагу только что воспроизведенного буфера
// значение, которое отвечает за то что буфер установлен в очередь, т.е. мы как бы
// циклично используем эти 8 буферов
dwFlags:= WHDR_INQUEUE
end;
// Увеличиваем индекс, чтобы перейти к следующему буферу
inc(a);
// соответственно после воспроизведения и подготовки нам больше не нужен цикл и
// мы выходим из него
exit;
end
end
end;
Процедура разобрана, осталось ей воспользоваться… Как это осуществить? Все просто, достаточно в обработчике события OnUDPRead() idUDPServerSocket-a написать следующий код:
procedure TForm1.IdUDPServer1UDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
Begin
// если мы воспроизвели последний буфер то, начинаем всё сначала (с первого)
If a = CWaveBufferCount then
a:= 0;
//вызываем нашу процедуру, в скобках пишем наш поток, пришедший на сервер,
//смотрите процедуру сокета.
playsound(Adata);
// определяем сколько байт мы приняли
Bytes:=Bytes + aData.Size;
// показываем это в названии формы
Caption:= 'Принятых байт' + Format('%u', [Bytes]);
// обновляем форму
UpDate
end;
И не забыть при создании формы проинициализировать наши аудиоустройства. Для этого в обработчике OnCreate() формы запишем:
procedure TForm1.FormCreate(Sender: TObject);
begin
bytes:= 0;
WaveOut:= 0;
With WaveFormatOut do begin
nChannels:= 1;
wFormatTag:= WAVE_FORMAT_PCM;
nSamplesPerSec:= 8000;
wBitsPerSample:= 8;
nBlockAlign:= 1;
nAvgBytesPerSec:= 8000;
cbSize:= 0
end;
bufsize:= WaveFormatOut.nAvgBytesPerSec*2 div 16;
For a:= 0 to CWaveBufferCount-1 do
With FHeaders[a] do begin
dwFlags:= WHDR_INQUEUE;
dwBufferLength:= bufsize;
dwBytesRecorded:= 0;
dwUser:= 0;
dwLoops:= 1;
GetMem(Fheaders[a].lpData, bufsize);
end;
IdUDPServer1.BufferSize:= bufsize;
IdUDPServer1.DefaultPort:= 10090;
waveOutOpen(@WaveOut, WAVE_MAPPER, @WaveFormatOut, self.Handle, 0, CALLBACK_WINDOW);
end;
Уважаемые читатели, здесь я пишу без комментариев только для того, что дать вам возможность самим додуматься, что здесь к чему, это не так сложно, тем более, что вы это уже все знаете (мы с вами выше подробно разбирали эти аналогичные функции ввода и вывода и работы с сокетами).
Далее осталось описать переменные и константы:
Const
CwaveBufferCount = 8;
type
TForm1 = class(TForm)
IdUDPServer1: TIdUDPServer;
Button1: TButton;
Memo1: TMemo;
procedure IdUDPServer1UDPRead(Sender: TObject; AData: TStream;
ABinding: TIdSocketHandle);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure Button1Click(Sender: TObject);
procedure playsound(s:Tstream);
private
hdr: PwaveHdr;
{ Private declarations }
public
{ Public declarations }
WaveOut:HWAVEOUT;
WaveHdrOut,WaveHdrOut2:TWaveHdr;
WaveFormatOut:tWAVEFORMATEX;
bufsize:word;
FBuffer:Pointer;
FSndBuffer:Pointer;
FHeaders:array[0..CWaveBufferCount-1] of TWAVEHDR;
FBufSize:Cardinal;
end;
var
Form1: TForm1;
bytes:Cardinal;
WaveOut: HWAVEOUT;
WaveHdrOut,WaveHdrOut2: TWaveHdr;
WaveFormatOut: tWAVEFORMATEX;
bufsize:word;
a:integer;
Я не стал описывать процедуру перегонки потока в HEX-формат, так как писал ради передачи данных в TMемо. В конце концов, вы сами запросто можете убрать ненужные строки, относящиеся к ней.
Заключение
Хочу заметить, что размеры буферов сокетов на сервере и клиенте должны быть равны размерам буферов структуры TWaveHdr, иначе вы не получите никаких звуков на выходе, кроме шипения с прерываниями, равными по длительности размеру вашего воспроизводимого буфера. Также для более быстрой реакции на события приема звука используйте меньшие размеры буферов, но и соответственно увеличьте их количество (8-ми вполне хватит). При желании, лучше использовать динамический.
Статья была написана специально для форума Клуба ПРОграммистов www.programmersforum.ru. Исходники тестового проекта (клиента и сервера) прилагаются в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом [5].
Выражаю огромную благодарность человеку, чей ник на вышеуказанном форуме raxp, который активно помогал мне в изучении этого материала кодами и советами.
Ресурсы
. Азбука WIN API http://letitbit.net/download/1868.1502ee9dae8ee96cec9816babb/Azbuka_WIN_API.rar.html
. Описание звуковых функций http://www.delphikingdom.com/asp/viewitem.asp?catalogid=213
. Репозитарий Indy 9: https://svn.atozed.com:444/svn/Indy9 (имя пользователя: Indy-Public-RO)
. Репозитарий Indy 10: https://svn.atozed.com:444/svn/Indy10 (имя пользователя: Indy-Public-RO)
. Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro3.zip
. Обсуждение на форуме разработки прототипа VoIP телефона
http://www.programmersforum.ru/showthread.php?t=91506
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Передача звука по сети. Прототип VoIP телефона
25th
Май
Поиск пути
Многие начинающие игроделы сталкиваются с проблемой автоматической прокладки маршрутов ботами на карте. Основных проблем две – генерация вейпоинтов и прокладка по ним кратчайшего (либо оптимального по другим параметрам) маршрута. Данная статья, делает небольшой экскурс по реализации алгоритма поиска кратчайшего пути по ранее установленным вейпоинтам (на основе алгоритма Дейкстры).
ПОИСК ПУТИ
На пути постижения мудрости не надо бояться, что свернёшь не туда.
Пауло Коэльо
Автор Utkin www.programmersforum.ru
Прокладка маршрута называется навигацией. Она бывает двух видов: автономная – когда объект (бот) самостоятельно прокладывает маршрут из одной точки карты в другую, запоминает его индивидуально (либо в общем, хранилище маршрутов для одной группы юнитов), и предварительная – когда маршруты уже проложены во время проектирования карты (опять же автоматически или программистом), или же на этапе загрузки карты игрового мира. Маршрут обычно представляет собой некоторую совокупность точек (или их координат) со связями между ними, то есть это маршрут, проложенный на графе. Сами точки называются вейпоинтами – это углы (или вершины) графа. Соответственно подавляющее большинство алгоритмов поиска пути есть алгоритмы по работе с графами.
Краткий экскурс…
Наиболее простой способ – это проложить ключевые точки уже на карте (в момент ее проектирования), а уже на основании имеющейся информации вырабатывать маршрут в зависимости от игрового процесса. Как уже было указано выше, маршрут имеет не только точки, но также и взаимосвязи между ними. В самом простом случае это ссылки на те точки, на которые можно попасть из данной точки, а также отношения между ними (например, это может быть время прохождения или расстояние между точками). В случае если вейпоинты генерируются до игры (во время разработки карты, а не ботом во время игрового процесса) отношения также должны быть уже рассчитаны (например, как расстояния между доступными точками). Также иногда некоторые маршруты уже изначально заложены и бот «знает» куда идти, но такой вариант должен комбинироваться с алгоритмами самостоятельной выработки маршрута по ряду причин – это делает игровой процесс более динамичным, карта может изменять свои параметры (например, произошел обрыв моста, тогда связи, отношения между двумя точками разрушаются, и требуется новый путь) и т.д.
Особенностью игрового мира является тот факт, что любая ситуация может быть промоделирована, поэтому для простых карт (имеющих малое количество вейпоинтов) можно просчитать оптимальные маршруты до каждой точки на этапе проектирования карты. Для карт, имеющих большое количество вейпоинтов, можно просчитать оптимальные маршруты только для тактически важных точек (какие это точки определяет разработчик), например, от базы до основных ресурсов или от одной лестницы до другой и т.д., остальные маршруты все равно придется просчитывать во время игры.
Предварительный просчет точек осуществлен, например, в игре StarСraft. Это видно если отправить рабочего добывать ресурсы, он смело перемещается через туман войны и по неисследованной области, способен находить подъемы на другой уровень плоскости и сразу определять местоположение моста.
Алгоритм Дейкстры
Этот алгоритм находит кратчайшее расстояние от одной из вершин графа до всех остальных.
Сначала рассмотрим граф без применения алгоритма Дейкстры, это упростит понимание алгоритма в дальнейшем. Для удобства восприятия обозначим каждую вершину идентификатором, пусть это будет номер вершины (порядок нумерации на графе значения не имеет). В качестве отношения между точками возьмем расстояние. Получается, точка имеет: идентификатор, список точек, куда можно попасть из данной точки и расстояния до каждой из доступных точек.
Вот образец графа (см. рисунок 1):
Представьте, что нам нужно попасть из точки 1 в точку 5. Сколько имеется путей достижения точки 5? Давайте посчитаем:
а) 1-2-5
б) 1-2-6-5
в) 1-6-5
г) 1-6-2-5
Итого четыре маршрута. Вычислим расстояния по каждому маршруту (суммированием расстояний между точками). Вот расстояния между точками:
а) 1-2 расстояние 6
б) 2-5 расстояние 4
в) 2-6 расстояние 6
г) 6-5 расстояние 5
д) 1-6 расстояние 8
е) 6-2, он же 2-6 расстояние 6
Общее расстояние всего маршрута:
а) 1-2-5 расстояние 6+4=10
б) 1-2-6-5 расстояние 6+6+5=17
в) 1-6-5 расстояние 8+5=13
г) 1-6-2-5 расстояние 8+6+4=18
Самый оптимальный (для нашего примера) является путь а) расстояние всего 10. Если же будут обнаружено два и более маршрутов, имеющих одинаковую длину, то выбирается, как правило, первый (либо заранее продумано, какой из маршрутов, лишь бы выбор был осуществлен).
Также нужно обратить внимание, что на первый взгляд маршруты б) и г) равны (казалось бы, от перемены мест слагаемых сумма не меняется), однако следует не забывать, что идентификаторы здесь не несут математического смысла это просто имена точек. Таким же образом мы могли бы назвать и a, b, c и т.д. Операции идут над расстояниями, а суммы пар расстояний (1-2; 2-6) и (1-6; 2-6) не эквивалентны (не равны) между собой.
Теперь сам алгоритм. Он предназначен для поиска всех наикратчайших путей от указанной вершины до всех остальных. Изначально расстояния нам не известны, поэтому будем считать, что они равны максимально возможному расстоянию до каждой из вершин (точек) графа (кроме исходной, до нее расстояние естественно равно нулю). Далее, необходимо отмечать рассмотренные точки графа (чтобы не повторяться)…
Перейдем к алгоритму
Итак, рассмотрим действие алгоритма для первой вершины нашего графа. Вершина 1 имеет отношения (в дальнейшем будем считать отношения просто расстояниями между вершинами графа) с вершинами 2 и 6. Ближайшей точкой будет являться точка 2, поскольку расстояние до нее меньше и составляет 6 единиц (в примере неважно каких единиц, в игре это могут быть условные единицы реальных км, м и т.д., единицы местоположения юнита на карте, число пикселей с привязкой к координатной сетке области отображения и т.д.). Считаем точку 1 пройденной, поскольку нам известны кратчайшие расстояния до точек 2 и 6.
Следующей рассмотрим точку 2 (потому что она ближе к 1 точке). Для нашего графа соседями точки 2 являются 1, 6, 5 и 3. Точку 1 мы рассматривать не будем, поскольку уже было отмечено, что она была просмотрена ранее. Вот расстояния:
. до точки 6 расстояние 6;
. до точки 5 расстояние 4;
. до точки 3 расстояние 5.
Отсюда следует, что ближайшей точкой к вершине 2 будет точка 5, поскольку расстояние до нее минимально, по сравнению с вершинами 6 и 3.
На данном этапе:
. расстояние до точки 1 составляет 0;
. расстояние до точки 2 составляет 6;
. расстояние до точки 5 составляет 10 (6+4);
. маршрут до точки 5 следующий – 1-2-5 (и никакой другой до данной точки более не рассматривается);
. следующей точкой будет являться точка 5;
. точка 2, также как и точка 1 считается отмеченной и больше не рассматривается;
. расстояние до точки 6 (маршрут 1-6) равен 8 (0+8), а не 12 (маршрут 1-2-6, расстояние 6+6), поскольку хоть точка 6 и не отмечена, но текущее расстояние до нее уже было вычислено и оно менее текущего (не забываем, что изначально расстояние до каждой точки равно максимально возможному для данного графа).
Собственно этих данных достаточно для того, чтобы выполнить следующие итерации для всех оставшихся точек нашего графа. Несмотря на такое простое описание реализации алгоритма Дейкстры может оставаться трудной, если не представлять, в виде каких структур выражать работу алгоритма. Вот классический вариант:
Представление точек графа
Граф можно представлять многими способами, например, в виде таблицы смежности (ru.wikipedia.org/wiki/Список_ребер). Такой способ представления удобен с математической точки зрения, но абсолютно не удобен с практической. Например, точку можно представить как ее координаты и набор отношений (где отношение можно представить как пару – указатель на точку-соседа и расстояние до точки-соседа). Если отношение представляет собой расстояние между двумя точками в данной системе координат, то его можно вычислять автоматически по формуле вычисления расстояния между двумя точками (школьный курс геометрии), при этом расстояние для системы координат с числом осей более двух вычисляется аналогично по обобщенной формуле. Собственно совокупность точек графа и будут являться самим графом.
Отмеченные точки графа
Здесь необходимо отмечать те точки графа, которые были пройдены во время работы алгоритма, чтобы не проходить их повторно. Считаю, эти данные должны лежать отдельно от точек графа, потому как такая информация собственно к графу отношения не имеет (а вот к алгоритму самое прямое).
Таблица кратчайших расстояний
Согласно алгоритму изначально расстояние до точек должно представляться максимально возможным расстоянием (за исключением стартовой, до нее расстояние минимально – 0). Затем эти расстояния заполняются в процессе выполнения алгоритма. Также эти расстояния нужны для сравнения имеющихся и предлагаемых расстояний (расстояние до точки 6 для нашего примера).
Маршруты от начальной до остальных точек
Собственно сами маршруты, по которым в дальнейшем будут осуществляться перемещения. Вообще алгоритм Дейкстры не предназначен для поиска маршрутов (только расстояний), но его работа построена таким образом, что формирование маршрута по данному алгоритму не представляет никаких сложностей (в момент оценки и внесения в таблицу кратчайшего расстояния).
Заключение
Несмотря на кажущуюся сложность данного алгоритма, при четком понимании работы, его реализация очень проста, а скорость работы вполне приемлема для не очень больших графов.
Исходники тестового проекта прилагаются в виде ресурсов в теме «Журнал клуба программистов. Третий выпуск» или непосредственно в архиве с журналом [5].
Ресурсы
. Реализация алгоритма Дейкстры http://plagiata.net.ru/?p=90
. Описание алгоритма Дейкстры http://algolist.ru/maths/graphs/shortpath/dijkstra.php
. Математическое и неформальное описание алгоритма Дейкстры
http://ru.wikipedia.org/wiki/Алгоритм_Дейкстры
. Дополнительные материалы http://borisvolfson.h11.ru/show_article.php?article_id=0000020 и
http://www.excode.ru/art6837p1.html
. Модули и проекты, использованные в статье http://programmersclub.ru/pro/pro3.zip
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
25th
Разработка ресурса для журнала. Часть 1
С выходом третьего номера журнала [ПРОграммист] у него появился собственный сайт. Разработке этого ресурса и посвящен цикл статей.
Для начала оговорим, что мы решили разбить статью на две части, т.к это не поместиться в объеме одной статьи. Первая часть будет о написании ленты новостей, гостевой книги и вообще всего интерфейса пользователя, а вторая будет посвящена написанию панели администратора.
Первую часть будет вести Егор Горохов aka Revival1002, вторую — Алексей Шульга aka Levsha100, мы будем помогать друг другу в этом нелегком труде. Итак, начнем.
1. Дизайн
Для начала был нарисован PSD шаблон сайта в графическом редакторе Photoshop. Оттуда же мы потом и «выдирали» картинки для сайта. В результате у нас получилось это (см. рисунок 1):
Плюс такого дизайна в том, что можно оченьбыстро сделать редизайн. Для этого достаточносменить фоновую картинку и немного подкорректировать таблицы стилей CSS.
2. Верстка
Для создания закругленных углов использовалось свойство border-radius. К сожалению, пока что свойство работает не во всех браузерах, например в IE 6…8 оно игнорируется. Конечно существуют и альтернативные способы создания круглых углов, но они зачастую требуют до 9 (!) картинок блока (4 угла, 4 стороны и центр). Так как мы «очень ленивые», да и острые углы не очень портят дизайн, мы использовали простейшее решение, с надеждой на то, что вскоре все браузеры будут поддерживать данное свойство…
Для полупрозрачности главного div не было найдено (возможно мы плохо «гуглили») кроссбраузерного решения, поэтому мы пошли на хитрость, создав однопиксельный полупрозрачный png, и поставили его на фон. Но и этот способ, как оказалось имеет свои минусы. Например*, ненавистный всеми дизайнерами/верстальщиками IE6 его игнорирует.
* Комментарий автора.
Кстати, я уже давно не видел людей использующих IE6. Все мои знакомые, даже если они мало понимают в компьютерах (например девушки) используют браузеры типа Opera и Mozilla Firefox. Поэтому причин продолжать верстать под MSIE6 я уже не вижу.
Ознакомится с CSS можно по адресу:
http://procoder.info/style.css
3. Написание скриптов
Для начала, вынесем все наиболее часто используемые переменные в отдельный файл “config.php”, и потом будем его «инклудить» во все скрипты. Мы поместили туда логин\пароль от БД, путь к сайту и т.п. В дальнейшем, если мы захотим изменить что-либо, то это займет всего- лишь несколько секунд, и не потребуется вносить изменения во множество скриптов.
Итак, при заходе на сайт пользователь должен видеть: ленту новостей, журналы доступные для скачивания, гостевую книгу. Реализция происходит практически одинаково. Покажу на примере гостевой книги (см. листинг 1):
<?php require_once "config.php"; // Подключение файла конфигурации // Функция обрабатывает строку, и заменяет все bb-коды на html function bbcodes($str) { $bbcode = array( "/\[b\](.*?)\[\/b\]/is" =] "<strong>$1</strong>", "/\[u\](.*?)\[\/u\]/is" =] "<u>$1</u>", "/\[url\=(.*?)\](.*?)\[\/url\]/is" =] "<a href='$1'>$2</a>", "/\[color\=(.*?)\](.*?)\[\/color\]/is" =] "<font color='$1'>$2</font>", "/\[i\](.*?)\[\/i\]/is" =] "<i>$1</i>]", "/\[quote\](.*?)\[\/quote\]/is" =] "<div class=\"quote\">$1</div>", "/\[img\](.*?)\[\/img\]/is" =] "<img src=\"$1\">" ); $str = preg_replace(array_keys($bbcode), array_values($bbcode), $str); return $str; } // Функция взята из мануалов по PHP // Подключение к БД $db = mysql_connect($db_server, $db_user, $db_pass); mysql_select_db($db_name); // Выбираем из таблицы table_gb последние 30 записей и сортируем их по id $db_query=mysql_query("SELECT * FROM `table_gb` ORDER BY `id` DESC LIMIT 0 , 30", $db); while ($res = mysql_fetch_array($db_query)) // Вывод записей { echo "<div class=\"guest_comment\"><b>".$res['name']."- ".$res['date']."</b><br>"; echo bbcodes(wordwrap("<pre>".$res['text']."</pre>", 75)); echo "</div><br>"; } ?> // Тут находится форма, для добавления записей в БД, которая передает скрипту gb.php данные
А теперь скрипт добавляющий записи в БД (см.листинг 2):
<?php require_once "config.php"; $db = mysql_connect($db_server, $db_user, $db_pass); mysql_select_db($db_name); // Получаем данные и убираем из них спец-символы, // для защиты от XSS-атак $name = htmlspecialchars($_POST['name']); $text = htmlspecialchars($_POST['text']); $date_array = getdate(time()); $date = $date_array['mday'].".".$date_array['mon'].".".$date_array['year']; if (($text=='') or ($text=='Введите текст')) { echo "<script>alert('Запись не добавлена.');</script>"; echo '<meta http-equiv="Refresh" content="0; URL='."$index_path".'">'; } else { if (($name=='Ваше имя') or ($name=='')) {$name='Аноним';} // Добавляем запись в БД $db_query = mysql_query("INSERT INTO `table_db` ( `id` , `name` , `text` , `date` ) VALUES ('', '$name', '$text', '$date');", $db); mysql_close($db); echo "<script]alert('Запись добавлена');</script>"; echo '<meta http-equiv="Refresh" content="0; URL='."$index_path".'">'; } ?>
Аналогичным образом выводятся записи для новостей, журналов для закачки и другая информация (см. рисунок 2):
Подведем предварительные итоги…
Что есть и что планируется? Пока сделано очень мало, и нововведения** будут появляться практически каждый день. Совсем скоро будет система регистрации, что позволит комментировать статьи, новости и выставлять им оценки. У каждого пользователя будет личный кабинет. Создавать отдельный форум для сайта не планируется, так как это совместный проект Клуба ПРОграммистов www.programmersclub.ru и форум уже есть. Существующие алгоритмы тоже будут улучшены. Все предложения оставляйте либо на форуме, либо отправляйте на электронный ящик редакции.
Это статья из третьего номера журнала “ПРОграммист”.
Скачать его можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Разработка ресурса для журнала. Часть 1
23rd
Май
Этот удивительный электрет
Многим из нас знаком советский электретный микрофон типа МКЭ-3, а те, кто не знаком, все равно применяют его или его аналоги в повсеместной практике, даже не догадываясь о замечательных свойствах материала – электрета (поляризованного диэлектрика), используемого в нем.
Не менее примечательна история появления этого замечательного материала… В 1943 году во время боевых действий на Тихом Океане американский флот захватил японский эсминец. Для изучения вражеской техники на корабль прибыли специалисты флота, осмотрели все – от трюма до капитанской рубки. Связист подробно изучил систему телефонной связи, она работала как часы. Одного он не мог понять, как система может работать без источника питания, ни батарей, ни аккумуляторов не было и в помине!
Физики разобрались в принципе работы телефонной связи на японском корабле. Ее работа стала возможной благодаря открытию японского физика Егучи, еще в 1922 году получившему новые материалы – электреты (с 1922 года работу с электретами Егучи проводил в обстановке строжайшей секретности для Министерства обороны Японии). Егучи получал электретные материалы из смеси смолы карнаубской пальмы и воска. Нагрев смесь до расплавленного состояния Егучи подал на нее напряжение 10 кВ, после застывания таблетка электрета сохраняла электростатический заряд высокой напряженности в течение нескольких часов (см. рецептуры). Современные электреты могут сохранять заряд до 100 лет, величина заряда достигает 5*10-8 Кл/см2.
В настоящее время электреты получают из таких материалов как: политетрафторэтилен (макс. Поверхностный потенциал 527 В), полиметилметакрилат или органическое стекло (3965 В), рутиловая керамика, смолы, воск, полимеры, титанаты щелочноземельных металлов и даже растворы органических веществ в летучих растворителях.
Рецептура N1
Нагреваем 2-5 г касторового масла до температуры 75-90 °С и высыпаем в него при помешивании 50 г мелкоизмельченной канифоли (если возится неохота, то возьмите и используйте парафин вместо канифоли и масла, расплавьте его и поместите между электродами – после остывания электрет готов). После чего расплав наливаем в плоскую баночку (стеклянную) или на предметное стекло и помещаем его между электродами, на которые подается напряжение от высоковольтного выпрямителя (5-10 кВ) или электростатической машины. Толщина слоя диэлектрика должна быть менее 4 мм. После остывания канифоль достается из баночки. Электрет готов.
Докажем, что электростатическое поле электрета имеет высокую напряженность. Если около неоновой лампочки быстро провести электретом, то она ярко вспыхнет, так как при пересечении лампой линий электростатического поля на электродах лампы наводится переменное высокое напряжение и лампа начинает светиться. Застывший диэлектрик – электрет способен сохранять заряд в течение нескольких суток. Хранить электреты можно завернутыми в алюминиевую фольгу.
Более стабильные электреты можно получить при нагреве диэлектриков до температуры меньшей или равной температуре плавления, а затем охлаждая их в сильном электрическом поле. При застывании органических растворов в сильном электрическом поле получают так называемые «криоэлектреты». Существуют и другие разновидности электретов: фотоэлектреты, трибоэлектреты и др.
Рецептура N2
Вместо канифоли и касторового масла можно между электродами в баночке поместить какой-нибудь полимер, например – капрон, растворенный в небольшом количестве растворителя. Подать высокое напряжение и после полного испарения растворителя – электрет готов.
Это заметка с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
Облако меток
css реестр ассемблер timer SaveToFile ShellExecute программы массив советы word MySQL SQL ListView pos random компоненты дата LoadFromFile form база данных сеть html php RichEdit indy строки Win Api tstringlist Image мысли макросы Edit ListBox office C/C++ memo графика StringGrid canvas поиск файл Pascal форма Файлы интернет excel Microsoft Office Excel winapi журнал ПРОграммист DelphiКупить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)