Сетевая карточная игра "King"
Разработка сетевой карточной игры "King" для операционной системы Windows XP. Реализация приложения с помощью интерфейса прикладного программирования Win32 API. Назначение серверной и клиентской части. Анализ исходных данных, тестирование приложения.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | курсовая работа |
Язык | русский |
Дата добавления | 24.01.2016 |
Размер файла | 209,3 K |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Размещено на http://www.allbest.ru/
Содержание
- Введение
- 1. Анализ исходных данных
- 2. Разработка интерфейса и структура приложения
- 3. Реализация и тестирование
- Заключение
- Литература
Введение
Целью данного курсового проекта является разработка сетевой карточной игры "King" для операционной системы Windows XP. Данное приложение структурно состоит из двух частей: серверная и клиентская. Серверная часть отвечает за проверку правил игры, загрузку и сохранение игры, оповещение клиентов о состоянии игры а также управление ими. Клиентская часть отвечает за взаимодействие пользователя с приложением, т.е. графический интерфейс приложения, оповещение пользователя о действиях сервера. Также была реализована возможность автоматического поиска серверов.
Приложение было реализовано с помощью интерфейса прикладного программирования Win32 API. Использование интерфейса прикладного программирования значительно усложняет работу программиста по сравнению с использованием библиотеки VCL в C++Builder. Однако, используя Win32 API, программист работает с операционной системой на прямую. Использование Win32 API позволяет лучше понять структуру операционной системы, понять взаимодействие программы с операционной системой.
Интерфейс прикладного программирования (API) - это набор необходимых функций, при помощи которых любое приложение может взаимодействовать с операционной системой. За последние 15 лет только в рамках Windows появлялось три API. Самый первый - Win16 - интерфейс шестнадцатиразрядной операционной сиcтемы Microsoft. Попытка увеличить разрядность существующей версии привела к появлению Win2s - тридцатидвухразрядного интерфейса для шестнадцатиразрядных систем. С появлением тридцатидвухразрядных систем Windows появился и тридцатидвухразрядный интерфейс - WIN32. API обеих систем представляют собой пересекающиеся подмножества "полного" API, т.е. в одном API есть функции, не реализованные в другом, и наоборот.
API - это связующее звено между приложениями и операционной системой. Win32 API содержит около двух тысяч функций и несколько сотен сообщений, макросов и предопределенных констант.
В таблице 1 приведены некоторые имеющиеся в WinAPI категории функций.
Таблица 1. - некоторые категории функций WinAPI.
Функции вызова стандартных диалоговых окон. |
Обеспечивают, например, выбор файла, шрифта и других часто используемых элементов такого типа. |
|
DDE-функции. |
Обеспечивают DDE-обмен (Dinamic Data Exchange) между приложениями. |
|
Drag&Drop-функции. |
Обеспечивают реализацию OLE - технологии Drag & Drop. |
|
GDI-функции. |
Эта группа включает в себя все функции графического отображения на экране линий, рамок, шрифтов. |
|
Kernel-функции. |
Базовые функции Windows (ввод/вывод, файловая система, память, подзадачи и т.д.) |
|
LZExpand - функции. |
Поддерживают сжатие/декомпрессию данных в соответствии с алгоритмом Лемпеля-Зива. |
|
OLE - функции. |
Функции этой группы поддерживают использование OLE в самом широком смысле слова. Хотя первоначально термин OLE представлял собой сокращение от Object Linking and Embedding, Microsoft предлагает и далее использовать этот термин. В настоящее время эта технология охватывает более широкую область, чем просто связывание и внедрение объектов. При выборе между DDE и OLE для передачи данных между приложениями рекомендуется отдавать предпочтение OLE, поскольку эта технология продолжает развиваться. |
|
Функции регистрации. |
Предназначены для редактирования регистрационной базы данных Windows. В ней содержится информация о приложениях системы. Эта информация используется для запуска приложений, а также при использовании технологий DDE и OLE в этих приложениях. |
|
Screensaver - функции. |
Управляют использованием Windows-заставок (ScreenSavers), а также их регистрацией и вызовом. |
|
Shell-функции. |
Позволяют расширить возможности Проводника (Explorer), напечатать файл и т.д. |
|
Stress-функции. |
Используются при распределении, присвоении и поиске блоков памяти для ресурсов с заданным дескриптором (Handle). |
|
Toolhelp - функции. |
Служат преимущественно для получения информации о различных ресурсах, например о модулях и объектах. |
|
User-функции. |
Обеспечивают диалог между пользователем и системой. |
|
Version - функции. |
Служат для инсталляции и идентификации приложений. |
|
Сетевые функции |
Служат для взаимодействия приложения с сетью. |
Для реализации работы по сети использовался программный интерфейс сетевых функций. В основе всех сетевых функций лежит интерфейс Windows Socket. Версия 2.0 WinSock поддерживается всеми платформами Windows, начиная с windows 98. Соответствующая библиотека называется WS2_32. DLL. Данный интерфейс поддерживает широко известные протоколы протоколы TCP (Transmission Control Protocol) и UDP (User Datagram Protocol), которые и будут использоваться в курсовом проекте.
Windows Socket - это часть архитектуры открытых сервисов Windows, разработанных MicroSoft (WOSA - Windows Open Services Architecture), которая определяет совокупность технологий для доступа к сервисам и данным масштаба предприятий. Спецификация Windows Socket определяет совместимый на уровне двоичного кода сетевой интерфейс программирования для Windows. Он позволяет приложению обмениваться информацией по сети. Цель создания интерфейса - создать абстрактное представление сети, по которой осуществляется обмен информацией, с тем чтобы можно было запускать ваше приложение в любой сети, которая поддерживает концепцию сокетов. Сокет - это абстрактный объект для обозначения одного из концов сетевого соединения в спецификации Windows Socket. Сокет имеет тип и ассоциируется с выполняемым процессом. Доступны сокеты двух видов: потоковые, датаграммные. Оба вида сокетов являются двухсторонними или дуплексными.
В данном курсовом проекте использовались API функции для работы с потоками, стандартными диалоговыми окнами (загрузка и сохранение файла, и т.п.), файлами, событиями, GDI-функции.
Событие - это то, что происходит в реальном времени и вызывает те или иные ответные действия. Ключом для программирования по событиям является определение ответных действий, соответствующих данному событию. При работе в среде Windows происходит много событий, однако должны быть обработаны только те сигналы, которые относятся к вашему приложению. Среда управлением по событиям, позволяет реагировать на нажатие клавиш и дает возможность сразу перейти к выполнению логически обусловленных операций Интерфейс WINSOCK поддерживает группу сетевых событий (FD_READ, FD_CLOSE и т.д.), с которыми можно связать пользовательские обработчики.
Подмножество API, называемое Интерфейс Графических Устройств (Graphics Device Interface, GDI), обеспечивает поддержку независимой от оборудования графики. Именно функции GDI делают возможным работу Windows на весьма разнообразном оборудовании.
Несмотря на все достоинства и недостатки Win32 API, его знание необходимо системному программисту для написания качественного кода.
1. Анализ исходных данных
В ходе данного курсового проекта необходимо реализовать сетевую карточную игру "Кинг".
Для реализации алгоритма игры "King" нужно определиться в правилах игры и заложить их в серверную часть игры.
Цель игры - набрать меньше всего очков.
Правила игры:
· В игре участвуют 3 игрока (оптимальное число игроков).
· Из колоды (36 карт) выкидываются 3 "шестерки" (пусть останется к примеру "шестерка" пик). В оставшейся колоде должно быть 8 "червовых" карт.
· Колода карт раздается целиком всем игрокам поровну. Обычно игру начинает тот игрок, который раздавал колоду, однако в нашем случае игру начинает тот игрок, у кого имеется "шесть" пик (первый кон, остальные - по часовой стрелке).
· Игра состоит из 7 конов. В конах имеются собственные правила:
1. Не забирать две последние взятки. Ходить картами с мастью "черви" можно. За одну взятку дается 8 очков.
2. Не забирать "валетов". Ходить картами с мастью "черви" можно. За одного "валета" игроку добавляется 4 очка.
3. Не забирать "дам". Ходить картами с мастью "черви" можно. За одну "даму" игроку добавляется 4 очка.
4. Не забирать "короля" масти "черви". Ходить картой мастью "черви" нельзя (если других мастей для захода нет, то можно). За "короля червей" игроку добавляется 16 очков.
5. Не забирать карты с мастью "черви". Ходить картами "черви" нельзя. За одну карту масти "черви" игроку добавляется два очка.
сетевая карточная игра приложение
6. Ничего из выше перечисленного не брать. Ходить картой масти "черви" нельзя. Очки суммируются в соответствии с предыдущими пунктами.
7. Забирать всё. Ходить картой масти "черви" можно. Очки суммируются в соответствии с пунктами 1 - 5 и вычитаются из общего числа очков у игрока.
· Выигрывает тот игрок, у которого меньше всего очков.
Игра является сетевой. Функции взаимодействия с сетью объявлены в WinSock2. h. В локальных сетях существует два способа передачи данных. Первый из них предполагает посылку пакетов от одного узла другому (или сразу нескольким узлам) без получения подтверждения о доставке и даже без гарантии того, что передаваемые пакеты будут получены в правильной последовательности. Такой способ передачи используется в датаграммном протоколе UDP.
Основные преимущества датаграммных протоколов заключаются в высоком быстродействии и возможности широковещательной передачи данных, когда один узел отправляет сообщения, а другие их получают, причем одновременно.
Второй способ передачи данных предполагает создание канала передачи данных между двумя различными узлами сети. При этом канал создается посредством датаграммных протоколов, однако доставка пакетов в канале является гарантированной. Пакеты всегда доходят в целостности и сохранности, причем в правильном порядке, хотя быстродействие получается в среднем ниже за счет посылки подтверждений. Примером протокола, использующего каналы связи, является протокол TCP.
При разработке данной игры использовались два протокола: UDP и TCP. По протоколу UDP осуществлялся поиск игроков, которые будут являться серверами. UDP-сервера, в стандартный порт игры (29886), где должны находиться игроки, являющиеся клиентами, посылают широковещательный пакет с определенным интервалом, в котором будет находиться информация о имени сервера и количестве уже подключенных игроков. UDP-клиенты получают данную информацию и дополнительно адрес и порт, откуда пришел пакет (стандартный порт UDP-серверов 29888). Далее пользователю остается только выбрать UDP-сервера, к которому необходимо подключиться.
У игрока, который запустил игру в качестве сервера, помимо UDP-сервера запускается TCP - сервер, который использует порт, отличающийся от порта UDP-сервера на единицу (29887). После того, как пользователь выбрал UDP-сервера, создается TCP-клиент, который и подсоединяется к TCP-серверу.
После того, как на TCP-сервер подключено два игрока, игрок, который выступал в качестве сервера, также подключается к TCP-серверу в качестве TCP-клиента. TCP-сервер отправляется на выполнение в отдельный поток, и в дальнейшем, через установленные каналы связи будет управлять клиентами.
Предусмотренные действия TCP-сервера:
· Рассылка имен игроков (игроков слева и справа).
· Раздача карт игрокам по предложению клиента, который запустил сервер.
· Определение игрока, который должен ходить и оповещение игрока, что он должен ходить.
· Проверка карты игрока в соответствии с правилами игры и ответ игроку о принятии карты (оповещение других игроков о выбранной карте).
· Оповещение следующего игрока, который должен ходить, если карта текущего игрока принята.
· Передача игрокам набранных очков.
· Оповещение игроков об окончании кона, игры.
· Предоставление игрокам информацию о занятых ими местах по окончании игры.
· Предложение игрокам начать новую игру. Если игроки не согласны, то завершить все TCP-соединения и завершение потока.
· Сохранение игры по предложению клиента, который запустил сервер.
· Загрузка игры из файла, если все клиенты согласны.
· Оповещение игроков о том, что один из игроков вышел из игры (разорвал соединение), предложение клиенту (запустившему сервер) сохранить текущую игру и завершение потока.
Действия TCP-клиента:
· Передача серверу сообщения о выбранной пользователем карте.
· Передача серверу сообщения о начале нового кона (только для клиента, запустившего сервер)
· Передача серверу сообщения о загрузке/сохранении игры (только для клиента, запустившего сервер).
· Работа с графикой. Т.е. клиентская часть должна визуализировать важные сообщения со стороны сервера (сообщение выбранных картах игроков, очки игроков и т.п.).
Для того, чтобы реализовать сетевое взаимодействие по TCP протоколу необходимо:
1. Инициализировать интерфейс WinSock2 (функция WSAStartup) для нашего приложения.
2. Создать сокет (функция socket).
3. Привязать сокет к конечной точке (IP адресу и порту), т.е. вызвать функцию bind (для сокета, принимающего подключения).
4. Принимающий подключения сокет поставить на прослушку (функция listen).
5. Ожидать подключения (функция accept) или подключиться к другому сокету (функция connect).
6. Проводить операции чтения/записи (функции recv или send).
7. Завершить соединение (функция closesocket).
8. Завершить интерфейс WinSock2 для нашего приложения (функция WSACleanup).
Для UDP протокола пункты 3,4,5 не нужны. Для включения возможности широковещания необходимо для сокета изменить опцию SO_BROADCAST на TRUE (функция setsockopt).
Для того чтобы сделать сокет асинхронным, можно присылать в окно сообщение об каком-то сетевом событии в конкретном сокете и обрабатывать данное сообщение (функция WSAAsyncSelect - привязывает передаваемый идентификатор сообщения к сетевому событию).
Поскольку в игре участвуют 3 игрока, сервер должен осуществлять взаимодействие со всеми тремя игроками.
Для этого можно для всех трех сокетов создать объекты-события (WSAEVENT) функцией WSACreateEvent; привязать полученные объекты-события к сетевым событиям (FD_READ, FD_CLOSE) функцией WSAEventSelect; передать полученные объекты-события в качестве параметров в ожидающую сигнала функцию WSAWaitForMultipleEvents, которая вернет индекс сигнализирующего события (все объекты-события находятся в массиве); по индексу можно узнать, для какого сокета пришло сетевое событие.
После выхода из функции WSAWaitForMultipleEvents необходимо перевести сигнализирующий объект в несигнальное состояние (функция WSAResetEvent).
Работа с графикой GDI будет производится при помощи API-функций. Для этого необходимо привязать свой обработчик на системное сообщение WM_PAINT. Чтобы рисовать на форме, необходимо:
· Подготовить окно для рисования (вызвать функцию BeginPaint)
· Получить Handle окна (функция FindWindow).
· Получить контекст устройства данного окна (функция GetDC).
· Создать другие необходимые объекты (функции CreateCompatibleDC, SelectObject и пр.).
· Произвести определенные действия над объектами.
· Скопировать объекты в конечный контекст устройства (функция BitBlt).
· Вызвать функцию EndPaint, свидетельствующую об окончании операций рисования.
· Удалить все объекты и контексты устройств из памяти.
Для того чтобы принудительно перерисовать конкретную область необходимо вызвать функцию InvalidateRect и передать в нее необходимый для перерисовки прямоугольник (структура RECT).
Все изображения карт находятся в библиотеке Windows cards. dll. Для получения изображений необходимо:
· Загрузить библиотеку функцией LoadLibrary
· Получить handle карты при помощи функции LoadBitmap.
· Освободить ресурсы, занимаемые библиотекой функцией FreeLibrary.
Чтобы исключить мерцание фона окна при перерисовке, данный фон рисуется сначала в памяти, а затем необходимая область из определенного места в памяти переносится на контекст устройства.
Работа с файлами производится также при помощи API-функций. Чтобы загрузить или сохранить файл, необходимо:
· Получить имя файла из стандартного диалогового окна (функции GetOpenFileName или GetSaveFileName).
· Открыть файл для чтения или записи (функция CreateFile).
· Прочитать или записать данные (функции ReadFile или WriteFile).
· Закрыть дескриптор файла (функция CloseHandle).
Для создания потока необходимо вызвать функцию CreateThread. В качестве параметров передается функция, которая будет выполняться в данном потоке, и аргументы к данной функции.
Чтобы принудительно уничтожить поток необходимо вызвать функцию TerminateThread, а после зарыть дескриптор потока функцией CloseHandle.
2. Разработка интерфейса и структура приложения
Приложение состоит из двух частей: клиентская и серверная. Для игроков, подключающихся в качестве клиентов игры приложение запускает только клиентскую часть. Для игрока, выступающего в качестве сервера игры, приложение запускает и клиентскую и серверную часть (игрок сервер подключается к своему серверу в качестве клиента - так удобней управлять процессом игры).
Серверная часть представляет собой поток, в котором выполняется функция, воспроизводящая алгоритм сервера (бесконечный цикл с ожиданием сетевого события, внутри которого распложен блок switch - case для реакции на действия клиента).
Клиентская часть имеет интерфейс Windows-приложения.
Рис. 1. - Интерфейс клиентской части приложения.
Рис. 2. - Диалог создания нового игрока.
Рис. 3. - Диалог выбора расположения игроков (для игрока-сервера)
И клиент и сервер могут посылать пакеты определенной структуры.0-й байт, тип сообщения. Для клиентов данный байт может быть:
· CheckUserCard (0x2) - проверка карты, индекс карты идет следующим байтом.
· ExpandCards (0x1) - предложение серверу раздать карты.
· LoadGame (0xa) - предложение серверу загрузить игру. Сервер в ответ на это посылает запрос об загрузке игры (в 1-м байте ACK=0x12), и клиент должен ответить, начинать игру или нет (в 1-м байте OK=0, CANCEL=1). Если хотя бы один клиент не согласен и с загрузкой игры, сервер пришлет клиенту ответ, что загрузка отменена другими пользователями (в 1-м байте AbortLoad=0x13).
· SaveGame (0x9) - предложение серверу сохранить игру.
· NewGame (0xB) - данное сообщение приходит со стороны сервера с запросом о новой игре (в 1-м байте ACK=0x12), и клиент должен ответить, начинать игру или нет (в 1-м байте OK=0, CANCEL=1). Далее, от сервера должна прийти информация о том, что все клиенты согласны (в 1-м байте OK=0) и игра продолжена, или хотя бы один клиент не согласен и игра закончена и сервер завершает свое выполнение (в 1-м байте ABORT=1).
Для сервера 0-й байт может быть:
· ExpandCards (0x1) - пересылка клиенту карт. Сами карты находятся с 1-го по 11-й байты сообщения.
· UserNames (0x4) - пересылка клиенту имен игроков. 1-й байт сообщения - расположение игрока относительно текущего (1 - слева, 2 - справа).2-й байт и далее - имя игрока.
· UserCard (0x7) - пересылка клиенту выбранной карты другого игрока.1-й байт - расположение игрока относительно текущего. 2-й байт - номер карты.
· NextCard (0x3) - сообщение клиенту о том, что сейчас его ход.
· UpdateSelections (0x8) - сообщение клиенту о том, что необходимо перерисовать выбранные карты фоновым рисунком.
· GetPoints (0x6) - сообщение клиенту о текущих очках игроков. Следующие три байта - очки игроков. 4-й байт - признак завершения кона (EndCon = 0 или NoEndCon = 0x1) или игры (EndGame = 0x3).
· CardCheck (0x5) - сообщение клиенту о результатах проверки карты в соответствии с правилами игры. 2-й байт - результат проверки (1 - карта принята, 0 - карта не принята, необходимо походить другой).
· NewGame - новая игра, 1-й байт - OK - новая игра начата; ABORT - новая игра отменена; ACK - спросить у игроков, не хотят ли они начать новую игру.
· LoadGame - загрузка игры.1-й байт:
ACK - спросить у игроков, не хотят ли они загрузить игру;
AbortLoad (0x13) - некоторые игроки не хотят загружать игру;
ExpandCards - пересылка карт, 2-й байт - число карт, далее идут номера карт, 14-й и 15-й байты - число карт у игроков слева и справа, 16-й байт - номер кона, 17-й-19-й байты - текущие очки игроков.
UpdateSelections - выбранные карты игроков. Следующие три байта - номера карт (-1 - если карта не выбрана).
GetPoints - очки игроков за все предыдущие коны. Следующие 21 байт - очки всех трех игроков.
StartLoad (0x10) - начало загрузки игры.
StopLoad (0x11) - конец загрузки игры.
· RuleName (0x14) - передача клиентам названия текущего кона игры. Остальные байты (1-й и далее) - название кона игры.
При автоматическом поиске серверов, формат широковещательного пакета имеет следующий вид:
– Первые 13 байт - "King Server: "
– 14-байт: p - сервер ожидает подключения клиентов, d - сервер соединен с достаточным для игры количеством клиентов.
– 15-й и далее - имя сервера.
Как было описано ранее прорисовка клиентского окна происходит путем перехвата системного сообщения WM_PAINT.
Для выбора пользователем карты, необходимо нажать два раза левую кнопку мыши. При первом нажатии выбранная карта поднимается вверх, при втором - на сервер посылается сообщение о проверке выбранной карты. Если карта прошла проверку успешно, она рисуется в поле выбранных карт (по центру), если карта не прошла проверку - то, на форме появится мигающее сообщение, что необходимо выбрать другую карту. Идентификация нажатия левой кнопки мыши осуществляется путем перехвата системного сообщения WM_LBUTTONDOWN.
Для раздачи карт необходимо щелкнуть дважды правой кнопкой мыши (перехват сообщения WM_RBUTTONDBLCLK).
При сетевых событиях в клиентское окно приходит соответствующее сообщение. Это было сделано следующим образом:
void __fastcall Window_Socket_Read (TMessage&);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER (WSA_NETEVENT_SERVER,TMessage,Window_Socket_Read);
END_MESSAGE_MAP (TForm);
Где WSA_NETEVENT_SERVER = WM_USER+1.
Для того, чтобы данное сообщение генерировалось нужно, к примеру, вызвать функцию:
WSAAsyncSelect (sockHandle,FindWindow (NULL,this->Caption. c_str (), WSA_NETEVENT_SERVER,FD_READ|FD_CLOSE).
И после этого, метод Window_Socket_Read будет вызываться при приеме сокетом sockHandle данных или закрытия соединения.
3. Реализация и тестирование
Программа реализована на функциях API в среде разработке Borland C++ 6.0. В программе реализован объектно-ориентированный подход, с помощью которого удалось выделить в отдельные классы части приложения.
Правила игры обеспечиваются несколькими классами.
Класс Card представляет собой сущность карты, и содержит в себе:
– вес карты (Six = 0, Seven = 1, Eight = 2, Nine = 3, Ten = 4, Jack = 5, Lady = 6, King = 7,Ace = 8);
– масть карты (Spades = 0, Crosses=1, Diamonds = 2, Hearts = 3);
– прямоугольник RECT rect в котором находится изображение карты относительно клиентской области окна;
– изображение карты (HBITMAP hbitmap);
– булевская переменная, указывающая, видна ли карта на экране или нет (visible).
– булевская переменная (isAbove) и свойство (IsAbove), указывающая (устанавливающая), находится ли карта на экране выше остальных или нет.
– Содержит свойство ID - возвращающее номер карты [0.32].
– Дополнительные статические методы, переводящие номер карты ID в вес и масть (GetWeight и GetSuit)
– Дружественные функции Reset (сброс карты: IsAbove = false, visible = true) и VisibleOff (visible = false) применяемых в функции VCL for_each.
Класс ForbiddenCard представляет собой запрещенную карту, т.е. наследует свойства класса Card и содержи дополнительно поле fine, представляющее собой штрафные очки.
Класс ForbiddenCardCollection представляет собой коллекцию запрещенных карт ForbiddenCard. Содержит внутри себя вектор cards (private). Переопределены методы вектора.
– ForbiddenCard& operator [] (int index) - индексатор.
– void Add (ForbiddenCard * card1,.) - метод добавления карт с неопределенным числом параметров.
– int IndexOf (char id) - возвращает индекс карты в массиве по ее номеру.
– void Reset () - очистка вектора.
– void Remove (int index) - удаление карты из вектора.
– __property int CardsCount = { read=GetCardsCount } - свойство, возвращающее количество карт в векторе.
– void Copy (const ForbiddenCardCollection & source) - метод копирования коллекции карт.
Класс Rule представляет собой правила кона игры. Содержит в себе поля:
– int count - число запрещенных карт, которые вышли из игры.
– char name [32] - название кона игры.
– bool hearts - FALSE - нельзя ходить червовыми картами; иначе - можно.
– bool twolast - TRUE - назначается штраф за две последние карты; иначе - нет.
– int fineTwoLast - штраф за две последние карты.
– int multiplier - множитель штрафных очков (1 или - 1).
– ForbiddenCardCollection cards - коллекция запрещенных карт.
Содержит в себе методы и свойства главными из которых являются:
– bool GameOver () - признак окончания кона.
– void Reset () - сброс счетчика выбывших карт count.
– void Refresh (char id) - увеличивает счетчик выбывших карт.
– bool CheckCard (char yourCard, char firstCard, vector<char> cards) - проверяет передаваемую карту на правила игры: yourCard - проверяемая карта (ее номер), firstCard - первая карта (-1 если первой карты нет), cards - список карт игрока; возвращает TRUE - если возможно ходить данной картой.
– int Next (int id, char card1, char card2, char card3, int count, int & points) - определение следующего для захода картой игрока: id - идентификатор ходившего первым игрока, card1…card3 - номера выбранных игроками карт, count - число карт на руках игроков, points - получаемые, за взятку штрафные очки.
– void SaveState (HANDLE hf) - сохранение счетчика выбывших карт count в файл hf.
– void LoadState (HANDLE hf) - загрузка счетчика выбывших карт count из файла.
Класс Rules представляет собой коллекцию правил конов игры (Rule). Содержит внутри себя поле:
vector<Rule> rules - список правил конов игры.
Класс Rules содержит в себе методы:
– void Reset () - сброс счетчиков вышедших карт из игры.
– int First (vector <char> cards1, vector <char> cards2, vector <char> cards3) - определение игрока, который будет ходить первым (игрока - у которого имеется "шесть пик" в картах).
– Rule& operator [] (int index) - индексатор.
– char Next (char id) - возвращает идентификатор следующего игрока относительно текущего (по часовой стрелке).
– char Prev (char id) - возвращает идентификатор предыдущего игрока относительно текущего (по часовой стрелке).
– void GetPlaces (char* place, char points1, char points2, char points3) - возвращает места (place) игроков, points1 - points3 - очки игроков.
– __property int Count = { read=GetCount } - свойство, возвращающее количество конов игры (количество элементов в векторе).
Класс Pack представляет собой колоду карт. Содержит в себе список номеров карт (vector<char> cardID) колоды. Класс Rule - содержит в себе следующие методы:
– void Shuffle () - растасовка колоды карт; используется генератор случайных чисел и системные часы для избегания повторения раздачи карт.
– void Expand (vector<char> &first, vector<char> &second, vector<char> &third) - раздача карт игрокам; возвращаемые вектора first, second, third отсортированы по возрастанию.
Класс User представляет собой игрока. Содержит поля:
– char name [16] - имя игрока.
– SOCKADDR_IN address - ip-адрес игрока.
– int id - идентификатор игрока.
– int points - очки игрока.
– SOCKET s - сокет игрока.
– vector <char> cards - список номеров карт, находящихся у игрока.
Важными методами класса User являются:
– void DeleteCard (char card) - удаление карты из списка cards.
– void SaveState (HANDLE hf) - сохранение состояния в файл hf (сохраняются список карт и очки).
– void LoadState (HANDLE hf) - загузка состояния из файла hf.
Работу с сетью обеспечивает класс GameSocket. Данный класс содержит себе следующие поля, методы и свойства:
private:
– char alternateName [16] - альтернативное имя сокета, введенное пользователем.
– SOCKET sock - дескриптор сокета.
– WSADATA wsaData - структура, содержащая в себе данные о сокетах (максимальная версия сокетов состеме, количество создаваемых сокетов и т.д.).
– sockaddr_in address - содержит адрес и порт, к которому привязан сокет функцией bind.
– HANDLE thread - дескриптор потока, который выполняет широковещание по сети (поток выполняет функцию ThreadFunc).
– char countUser - количество подключенных игроков.
Для данных полей объявлены свойства с такими же названиями.
protected:
unsigned short pingPort - номер порта, в который будет посылаться широковещательный запрос.
public:
– char hostName [64] - имя хоста.
– bool Server - TRUE - сокет выступает в качестве сервера, данное поле нигде не используется, оно необходимо только для пользователя.
– GameSocket (unsigned int type = SOCK_DGRAM) - конструктор; если type = SOCK_DGRAM, то создается датаграмный UDP сокет; если type = SOCK_STREAM - TCP сокет.
– void StartBroadCastSend (unsigned short port = 29886) - метод, запускающий широковещание UDP сокета (создается поток thread); в качестве аргумента выступает номер порта, в который будут посылаться широковещательные пакеты.
– void StopBroadCast () - метод, останавливающий широковещание (происходит уничтожение потока).
– int SetAsyncSelect (char *Window, unsigned int Msg, long Action), int SetAsyncSelect (SOCKET s, char * Window, unsigned int Msg, long Action) - методы устанавливающие сообщение Msg, которое будет генерироваться при возникновении сетевого события Action и посылаться в окно с названием Window (во втором случае передается внешний дескриптор сокета). Возвращает результат вызова функции WSAAsyncSelect.
– int ClearAsyncSelect (char * Window, unsigned int Msg), ClearAsyncSelect (SOCKET s, char * Window, unsigned int Msg) - методы, останавливающие генерирование сообщения Msg для окна с названием Window (во втором случае передается внешний дескриптор сокета). Возвращает результат вызова функции WSAAsyncSelect.
– int SendTo (char* buf, SOCKADDR_IN &addr, int buffsize = 32) - метод, посылающий данные buf размером buffsize в конечную точку addr. Данный метод используется для передачи через UDP сокеты, хотя его можно использовать для передачи и через TCP сокет. Возвращает результат вызова API функции sendto.
– int RecvFrom (char * buf, SOCKADDR_IN &addr, int flags = 0, int buffsize = 32) - метод, принимающий данные в buf из конечной точки addr (в addr возвращается адрес и порт, откуда пришло сообщение). Данный метод используется для UDP сокетов. Возвращает результат вызова API функции recvfrom.
– static char* GetHostName (char* address) - возвращает имя хоста по ее адресу. Использует API функцию, gethostbyaddr.
– void Bind (unsigned short int port) - метод, привязывающий сокет sock к конечной точке. Передается только порт. Адрес определяется путем вызова API функций gethostname и gethostbyname внутри метода.
– SOCKET Accept (sockaddr_in &addr) - метод, ожидающий подключения. Использует внутри функцию accept и возвращает дескриптор подключившегося сокета и адрес подключенного узла.
– void Listen (int queue) - метод, ставящий сокет sock на прослушку, передается количество пользователей, ожидающих подключения в очереди. Внутри используется функция listen.
– int Connect (SOCKADDR_IN addr) - метод, подключающий сокет к конечному узлу addr. Возвращает результат вызова функции connect.
– int Recv (SOCKET s, char* buf, int length = 36), int Recv (char* buf, int length = 36) - методы, принимающие данные (для TCP - сокетов). Возвращают результат вызова функции recv (в первом случае передается внешний дескриптор).
– int Send (SOCKET s, char* buf, int length = 36), int Send (char* buf, int length = 36) - методы, посылающие данные (для TCP - сокетов). Возвращают результат вызова функции send (в первом случае передается внешний дескриптор).
– int CloseSocket (SOCKET s), int CloseSocket () - методы, закрывающие сокет (в первом случае передается внешний дескриптор). Возвращает результат вызова функции closesocket.
– __property BOOL BroadCast = { read=GetBroadCast, write = SetBroad Cast }: SetBroadCast - использует функцию setsockopt для установки опции SO_BROADCAST, GetBroadCast - использует функцию getsockopt для получения значения опции SO_BROADCAST.
– friend DWORD WINAPI ThreadFunc (LPVOID lpParam) - функция, которая будет выполняться в потоке. Данная функция выглядит следующим образом.
DWORD WINAPI ThreadFunc (LPVOID lpParam)
{
GameSocket *sock = (GameSocket *) lpParam;
char sendBuf [32];
memset (sendBuf,0,32);
memcpy (sendBuf,"King Server: p",14);
SOCKADDR_IN RecvAddr; /*Адрес получателя*/
RecvAddr. sin_family = AF_INET;
RecvAddr. sin_port = htons (sock->pingPort);
RecvAddr. sin_addr. s_addr = htonl (INADDR_BROADCAST);
strcpy (sendBuf+strlen (sendBuf),sock->alternateName);
bool sleep = false;
for (;;)
{
sendBuf [30] = sock->countUser+0x30;
if (sock->countUser == 3)
sendBuf [12] ='d';
else
sendBuf [12] ='p';
if (! sleep)
sock->SendTo (sendBuf,RecvAddr);
if (sock->countUser == 3)
sleep = true;
else
sleep = false;
Sleep (2000); /*Останов цикла на 2 секунды*/
}
return 0;
}
Сервер игры представляется классом KingServer. Данный класс содержит поля и методы:
protected:
– WSAEVENT event [3] - объекты-события для подключенных сокетов.
– HANDLE thread - дескриптор потока, в котором будет выполняться главная функция сервера KingThreadFunc.
private:
– bool isStarted - TRUE - сервер запущен.
– bool __fastcall GetIsStarted () - возвращает значение поля isStarted.
public:
– Rules *rules - правила игры.
– GameSocket *sock - серверный сокет.
– Pack *pack - колода карт.
– char *window - имя клиентского окна.
– User users [3] - список игроков.
– KingServer (GameSocket * sock, char *window) - конструктор.
– void Start () - запуск серверного потока.
Тело метода Start ():
if (this->thread! = NULL) return;
this->event [0] = WSACreateEvent ();
this->event [1] = WSACreateEvent ();
this->event [2] = WSACreateEvent ();
this->isStarted = true;
/*Привязка объектов-событий к сетевым событиям*/
WSAEventSelect (this->users [0]. s,this->event [0],FD_READ| FD_CLOSE);
WSAEventSelect (this->users [1]. s,this->event [1],FD_READ| FD_CLOSE);
WSAEventSelect (this->users [2]. s,this->event [2],FD_READ| FD_CLOSE);
// Создание потока
this->thread = CreateThread (
NULL,
0,KingThreadFunc,
(void*) this,
CREATE_SUSPENDED,
NULL);
// Установка приоритета потока
SetThreadPriority (this->thread,THREAD_PRIORITY_NORMAL);
// Запуск потока
ResumeThread (this->thread);
– void Stop () - остановка серверного потока
Тело метода Stop:
// Останов потока
TerminateThread (this->thread,NULL);
CloseHandle (this->thread);
this->thread = NULL;
// Закрытие объектов событий
WSACloseEvent (this->event [0]);
WSACloseEvent (this->event [1]);
WSACloseEvent (this->event [2]);
– __property bool IsStarted = { read=GetIsStarted } - свойство, возвращает значение поля isStarted.
– friend DWORD WINAPI KingThreadFunc (LPVOID lpParam) - поточная серверная функция.
Тело данной функции:
DWORD WINAPI KingThreadFunc (LPVOID lpParam)
{
KingServer* server = (KingServer*) lpParam;
char buf [36]; // Буфер приемник
char con = - 1; // Номер кона
char next = 0; // Следующий игрок для подкидывания
char first = 0; // первый игрок для захода
char count = 0; // Количество выбранных карт [0.2]
char prev = 0; // Игрок, начавший первым этот кон
char point [3*7]; // Очки по конам
memset (point,0,3*7);
WSANETWORKEVENTS networkEvent; // содержит сетевое событие,
// сетевую ошибку и т.п.
char selectedCards [3] = {-1,-1,-1}; // Выбранные карты
Rule *rule = NULL; // Текущее правило игры
… /*Рассылка имен игроков*/
int index = 0;
while (1)
{
DWORD rc;
/*ожидание события*/
rc = WSAWaitForMultipleEvents (3,server->event,FALSE,
WSA_IN FINITE,FALSE);
index = rc - WSA_WAIT_EVENT_0;
// Уточнение типа события
WSAEnumNetworkEvents (server->users [index]. s,
server->event [index],
&networkEvent);
if (networkEvent. lNetworkEvents == FD_READ)
server->sock->Recv (server->users [index]. s,buf);
else if (networkEvent. lNetworkEvents == FD_CLOSE)
{
… /*Выход из цикла, не хватает игроков для
продолжения*/
}
// Сброс события в несигнальное состояние
WSAResetEvent (server->event [index]);
switch (buf [0])
{
case ExpandCards: /*Клиент предложил раздать карты*/
{
if (index == 0) /*Предлагать может только серверный
клиент*/
{
…/*Раздача карт если текущий кон завершен*/
}
break;
}
case CheckUserCard: /*Проверка пользовательской карты*/
{
… /*Проверка карты, раздача очков и т.д. */
break;
}
case SaveGame: /*Клиент предложил сохранить игру*/
{
if (index == 0 && con! = - 1)
{
… /*Вызов диалога сохранения файла, создание
фaйла и. т.д. */
}
break;
}
case LoadGame: /*Клиент предложил загрузить игру*/
{
if (index == 0)
{
… /*Опрос других игроков о загрузке игры, выбор
файла, загрузка игры и т.д. */
}
break;
}
}
}
/*Действия при выходе из цикла*/
WSACloseEvent (server->event [0]);
WSACloseEvent (server->event [1]);
WSACloseEvent (server->event [2]);
… /*Предложение сохранить игру, закрыть все сокеты и выйти из по-
тока (игры) */
return 0;
}
Клиентский обработчик действий сервера представлен ниже:
void __fastcall TfrmMain:: Window_Socket_Read (TMessage &msg)
{
char buf [36]; // Буфер
memset (buf,0,36);
if (msg. LParamLo == FD_CLOSE)
{
MessageBox (hwnd,"Сокет закрыт!","Disconnect",MB_OK);
return;
}
client->Recv (buf); // Прием данных, client - объект класса GameSocket.
switch (buf [0])
{
case ExpandCards: // Раздача карт
{
… // Запись карт в вектор и прорисовка их на форме
break;
}
case UserNames: // Имена игроков
{
… // Запись имен игроков и прорисовка их на форме
break;
}
case UserCard: // Карта игрока
{
… // Определение игрока (левого или правого)
// Вывод карты на экран.
break;
}
case NextCard: // Ваш ход
{
… // Установка мерцающего сообщения "Ваш ход".
// И разрешение ходить.
break;
}
case UpdateSelections: // Обновить поля выбранных карт
{
… // Обновление
break;
}
case GetPoints: // Очки игроков
{
… // Вывод очков игроков.
// И если кон завершен - то, написать "Кон
// завершен"
// И если игра завершена - то, написать "Игра
// завершена"
break;
}
case CardCheck: // Результат проверки карты
{
… // Если карта принята, то прорисовать ее в центре поля.
// Если нет, написать текст - "Другую…"
break;
}
case NewGame: // Новая игра
{
… // Передать серверу ответ пользователя (OK, CANCEL)
// Сообщить пользователю о результате (Новая игра
// началась или нет)
break;
}
case LoadGame: // Загрузка игры
{
… // Действия сервера и клиента по загрузке игры.
break;
}
case RuleName: // Название кона игры
{
… // Вывод на форму название кона игры.
break;
}
}
}
При визуализации процесса игры, пришлось создать несколько дополнительных классов.
Класс TextString - строка текста, которую необходимо вывести на форму. Данный класс содержит поля и методы:
private:
– char text [64] - текст.
– long x - горизонтальная координата.
– long y - вертикальная координата.
Для данных полей установлены соответствующие свойства.
public:
– bool visible - TRUE - текст выводится на окно.
– COLORREF color - цвет текста.
– void Draw (HDC hDC) - метод рисования текста, передается контекст устройства, в котором нужно написать текст.
– RECT GetRect (HWND hwnd) - получение прямоугольника, в который заключен текст, передается дескриптор окна, из которого получается контекст устройства, необходимый для получения размеров прямоугольника.
Класс GamerOptions необходим для сбора информации о клиенте или сервере. Содержит поля и методы:
private:
– char name [16] - имя клиента или сервера.
– SOCKADDR_IN address - адрес клиента или сервера.
Для данных полей установлены соответствующие свойства.
public:
– char countUsers - количество подключенных игроков (для клиентов)
– int pos - позиция данного объекта в таблице (StringGrid)
– SOCKET s - дескриптор сокета клиента.
– friend bool operator == (GamerOptions serv1,GamerOptions serv2) - оператор сравнения данных объектов, необходим для поиска их в векторе.
– friend void DecrementPos (GamerOptions &serv) - функция, уменьшает значение поля pos при удалении объектов из вектора.
– friend void CloseSocket (GamerOptions &gamer) - функция, закрывающая клиентские сокеты.
Класс PrintfList - заполняет данные класса GamerOptions в переданную таблицу StringGrid. Для этого класса перегружен оператор вызова функции (…), необходимого для применения в функции VCL for_each.
Реализация графического интерфейса приведена ниже:
void __fastcall TfrmMain:: Window_Paint (TMessage &msg)
{
HDC hdc;
HDC hdcBitmap;
HDC memdc;
HBITMAP membit; // Фоновое изображение
PAINTSTRUCT ps;
hdc = BeginPaint (hwnd,&ps); // Получаем контекст устройства
hdcBitmap = CreateCompatibleDC (hdc); // Совместимый контекст
// устройства
memdc = CreateCompatibleDC (hdc);
int width = clientRect. right - clientRect. left; // clientRect - прямоуголь
// ник
// клиентской области
int height = clientRect. bottom - clientRect. top;
TRIVERTEX vert [2]; // Создаем сине-зеленый градиент.
GRADIENT_RECT gRect;
vert [0]. x = clientRect. left;
vert [0]. y = clientRect. top;
vert [0]. Red = 0x0000;
vert [0]. Green = 0x0000;
vert [0]. Blue = 0x8b00;
vert [0]. Alpha = 0x0000;
vert [1]. x = clientRect. right;
vert [1]. y = clientRect. bottom;
vert [1]. Red = 0x0000;
vert [1]. Green = 0x8000;
vert [1]. Blue = 0x0000;
vert [1]. Alpha = 0x0000;
gRect. UpperLeft = 0;
gRect. LowerRight = 1;
// Рисуем градиент в памяти (memdc)
membit = CreateCompatibleBitmap (hdc,width,height);
SelectObject (memdc,membit);
GradientFill (memdc,vert,2,&gRect,1,GRADIENT_FILL_RECT_V);
// Копируем нужный прямоугольник в контекст устройства
// В ps (PAINTSTRUCT) - содержится необходимый для обновления
// прямоугольник
BitBlt (hdc,
ps. rcPaint. left,ps. rcPaint. top,
ps. rcPaint. right - ps. rcPaint. left,
ps. rcPaint. bottom - ps. rcPaint. top,
memdc,
ps. rcPaint. left,ps. rcPaint. top,
SRCCOPY);
// Далее идет прорисовка карт, прямоугольники которых пересекаются
// с необходимым для обновления прямоугольником ps. rcPaint.
…
// Вывод текста. Names [0.2], Points [0.2], message, ruleName - объекты
// класса TextString.
Names [0]. Draw (hdc);
Names [1]. Draw (hdc);
Names [2]. Draw (hdc);
Points [0]. Draw (hdc);
Points [1]. Draw (hdc);
Points [2]. Draw (hdc);
message. Draw (hdc);
ruleName. Draw (hdc);
EndPaint (hwnd,&ps);
// Уничтожение объектов в памяти.
ReleaseDC (hwnd, hdc);
DeleteDC (hdcBitmap);
DeleteObject (membit);
DeleteDC (memdc);
}
В результате такой реализации графического интерфейса получается немерцающее поле, т.к. прорисовываются только необходимые элементы.
Тестирование проекта производилось с начала его разработки, что позволило избежать в дальнейшем многих ошибок в проектировании. Проект разрабатывался одним разработчиками, поэтому реализация схемы взаимодействия клиента и сервера не составила больших трудностей.
Приложение разрабатывалось в несколько этапов.
На первом этапе реализовывались правила игры и проверялись на работоспособность.
На втором этапе разрабатывался графический интерфейс. Оптимизировался вывод графики. Программное управление мышкой: определение места, где был произведен щелчок левой кнопкой (над картой или нет); определение двойного щелчка правой кнопкой мыши. В ходе испытаний выяснилось, что функция InvalidateRect не всегда срабатывает с первого раза. Поэтому необходимо повторять вызов данной функции, пока она не завершится корректно.
На третьем этапе реализовывалось сетевое взаимодействие. Т.к. схема взаимодействия реализовывалась одним разработчиком, то на данном этапе серьезных ошибок не было.
На четвертом этапе производилась глобальная отладка приложения. На данном этапе возникла проблема проверки работы приложения в сети (работа с тремя клиентами).
Заключение
В результате данного курсового проектирования была реализована сетевая карточная игра "King". Программа реализована при помощи программного интерфейса Win32 API. Разработка приложений на API удлиняет код и делает его сложнее. Для более сложных приложений лучше всего использовать другие платформы, такие как: MFC, VCL,.net Famework и др. Однако, опыт работы с API функциями дает лучшее понимание функционирования процессов в операционной системы, что повысит качество разрабатываемого кода в любом языке программирования.
В данном курсовом проекте была реализована работа с программными интерфейсами Windows GDI, Windows Socket, Kernel (файловый ввод вывод, потоки), а также функциями вызова стандартных диалоговых окон.
В данном курсовом проекте были изучены протоколы TCP и UDP. Для использования асинхронных сокетов были изучены техника посылки сообщения и его обработка. Реализована работа с сигнальными обектами-событиями (WSAEVENT).
Выполнение поставленной задачи показало, что для написания сетевого приложения, системный программист должен знать структуру операционной системы, специфику ее функционирования, архитектуру компьютера, а также должен уметь применить низкоуровневое программирование для решения проблемы.
Литература
1. Джеффри Рихтер. Windows для профессионалов: издание четвертое - С. П.: Русская редакция, 2011 г.
2. Фень Юань Программирование графики для Windows - С. П.: Питер, 2012 г.
3. Platform SDK for Windows XP SP2 documentation.
Размещено на Allbest.ru
Подобные документы
Общая характеристика сетевой игры с несколькими клиентами в программной среде MS Visual Studio 2010 на языке программирования C++ с использованием функций работы с сокетами. Реализация системного сервиса, разработки интерфейса, алгоритм его тестирования.
курсовая работа [495,3 K], добавлен 06.01.2013Разработка исполняемого Win32 приложения с визуальным интерфейсом, обеспечивающим построение функций принадлежности. Проектирование визуального интерфейса приложения, включающего кнопки доступа к функциям построения графика, полей ввода исходных данных.
дипломная работа [343,8 K], добавлен 06.06.2010Общая характеристика и функциональное назначение проектируемого программного обеспечения, требования к нему. Разработка и описание интерфейса клиентской и серверной части. Описание алгоритма и программной реализации приложения. Схема базы данных.
курсовая работа [35,4 K], добавлен 12.05.2013Разработка приложения на WinAPI с реализацией логической структуры в игре "Сапер". Реализация графической части приложения. Проверка на корректность поведения интерфейса программы, работы логической части игры, корректности записи и чтения файла.
курсовая работа [1,1 M], добавлен 17.10.2012Интерфейс API, реализация функций API на уровне ОС, системы программирования и с помощью внешних библиотек. Характеристики сетевого интерфейса прикладного программирования Winsock, особенности его применения в операционных системах UNIX и Windows.
контрольная работа [74,2 K], добавлен 04.06.2015Анализ игровых жанров для мобильных устройств и целевой аудитории. Разработка концепции игрового приложения, основной механики, меню и интерфейса игры. Описание переменных скриптов. Реализация выбора цели и стрельбы. Настройка работоспособности игры.
дипломная работа [1,4 M], добавлен 19.01.2017Разработка и создание игры "Змейка". Использование динамически-активных принципов языка Java. Графические объекты программы. Описание игры, правила, теоретические сведения. Классы приложения. Типы данных. Реализация. Метод. Объект. Блок-схема игры.
курсовая работа [12,4 K], добавлен 18.06.2008Описание платформы NET Framework. База данных Microsoft Access. Разработка Windows приложения. Модель программирования Windows Forms. Функциональное назначение программы. Входные и выходные данные. Требования к техническому и программному обеспечению.
курсовая работа [2,2 M], добавлен 15.03.2015История развития языка программирования Java. История тетриса - культовой компьютерной игры, изобретённой в СССР. Правила проведения игры, особенности начисления очков. Создание интерфейса программы, ее реализация в среде Java, кодирование, тестирование.
курсовая работа [168,1 K], добавлен 27.09.2013Проектирование и разработка базы данных в РСУБД Firebird. Последовательность создания приложения, основанного на клиент-серверной технологии и работающего в операционной системе Windows. Хранимые процедуры и триггеры. Доступ к сети и транзакции.
курсовая работа [2,6 M], добавлен 27.07.2013