Клиент-сервер на Java (крестики-нолики)

  • В этой теме 0 ответов, 1 участник, последнее обновление 2 месяца назад сделано Васильев Владимир Сергеевич.
Просмотр 0 веток ответов
  • Автор
    Сообщения
    • #6784
      @admin
      StudLance.ru

      1 Анализ задания на разработку

      Разработать клиент-серверную игру «Крестики-нолики» на поле размером 20х20, где для победы необходимо собрать 5 крестиков или ноликов в ряд.

      Система состоит из двух приложений — клиента и сервера. Схематично из взаимодействие показано на рисунке 1 с помощью диаграммы последовательностей. Эта и последующие диаграммы выполнены с помощью онлайн-инструмента PlantUML.

      Рисунок 1 — Взаимодействие клиента и сервера:

      Сервер, дождавшись второго игрока, начинает игру, в ходе которой игроки выполняют ход по очереди. Перед каждым ходом сервер отправляет клиенту текущее состояние игры.

      Проверка корректности хода выполняется на стороне сервера. Приложение-клиент отправляет данные на сервер только если ход корректен, сервер же, при получении некорректных данных — игнорирует их и высылает клиентам текущее состояние игры повторно.

      1.1 Формат данных

      Сервер работает по протоколу TCP (смотри статью «Архитектура Клиент-Сервер на сокетах Java«). Между клиентом и сервером пересылаются сообщения в JSON формате.

      Сообщение с информацией о ходе игрока выглядит так:

      {
        "x": 3,
        "y": 7
      }

      В данном случае, игрок хочет поставить свой символ в клетку 3, 7.

      Сообщение с информацией о текущем состоянии игры выглядит так:

      {
        "gameboard": "____xoxxoo___xo",
        "your_type": "x",
        "move": "x"
        "winner": "_"
      }

      Тут:

      1. в строке gameboard при этом передается строка из 400 символов (в примере показана не целиком), содержащая последовательно все символы поля. При этом, символ «_» соответствует пустой клетке поля, «х» и «о» — крестики и нолики соответственно;
      2. your_type — соответствует типу игрока, получающего сообщение;
      3. move — тип игрока, выполняющего ход;
      4. winner — тип игрока-победителя (если игра еще не закончена — передается «_»).

      1.2 Приложение-сервер

      Приложение сервер является консольным имеет 2 состояния работы:
      1. ожидание подключения двух клиентов;
      2. игровой процесс.

      После подключения двух клиентов сервер:
      1. случайным образом выбирает игрока, который будет делать ход первым;
      2. генерирует пустое поле;
      3. переходит в состояние игрового процесса, при этом:
      3.1 отправляет игрокам сообщение с информацией о текущем состоянии игры;
      3.2 ожидает от активного игрока сообщение с информацией о ходе;
      3.3 проверяет ход и изменяет внутреннее состояние игры (поле и активного игрока).
      4. шаги 3.1-3.3 повторяются пока один из игроков не победит.

      1.3 Приложение-клиент

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

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

      При нажатии на кнопку выполняется посылка сообщения о ходе на сервер.

      Игровой процесс продолжается до получения от сервера сообщение, с полем winner, не равным «_».

      Внешний вид окна клиента до присоединения второго клиента показано на рисунке 2. Позже, в верхней части окна отображается текущее состояние игры.

      Рисунок 2 — Внешний вид окна клиента:

      2 Проектирование

      2.1 Взаимодействие клиента и сервера

      Клиент и сервер обмениваются сообщениями в формате JSON. Это текстовый формат сообщений, следовательно, необходима библиотека для преобразованиях объектов в этот формат и обратного преобразования. Обзор таких библиотек, а также примеры их использования приведен в [3], наиболее удобной для нашего случая выглядит библиотека GSON, так как для ее использования нам достаточно описать структуры для наших сообщений, а их преобразование в JSON выполнится автоматически.

      В нашем проекте используется фреймворк Maven, так как на сайте [4] описано как подключить эту библиотеку к Maven-проекту. В файл pom.xml добавлены следующие строки:

       <dependencies>
         <dependency>
           <groupId>com.google.code.gson</groupId>
           <artifactId>gson</artifactId>
           <version>2.8.6</version>
         </dependency>
        </dependencies>

      Далее, для каждого типа сообщений описана структура, в файле Messages.java.

      Между клиентом и сервером пересылаются сообщения в текстовом формате. Эти сообщения могут состоять из нескольких строк, передаются они в таком формате:

      <количество символов> <текст сообщения>

      Функции для чтения и записи таких сообщений с потоков Java помещены в файл Common.java.
      Файлы Messages.java и Comman.java одинаковы для клиента и сервера, так как связаны с их взаимодействием.
      Исходный код этих файлов доступен в репозитории.

      2.2 Приложение-Сервер

      Приложение сервер должно:

      • дождаться подключения игроков;
      • реализовывать игровую логику, то есть хранить текущее состояние поле и выполнять проверку корректности хода игрока и завершения игры;
      • принимать информацию о ходе от игроков и рассылать им текущее состояние.

      В соответствии с этим, приложение разделено на 3 части — класса, как показано на рисунке 3.

      Рисунок 3 — UML диаграмма классов приложения-сервера:

      Игровая логика помещена в класс GameBoard, который хранит поле и игрока, выполняющего ход. GameBoard умеет преобразовывать свое состоение в строку (метод toString) для передачи клиентам. Метод process вызывается после получения от игрока информации о сделанном ходе. Метод getWinner проверяет победу одного из игроков и возвращает "x", "o" или "_".

      Класс Player отвечает за логику взаимодействия с клиентом. Только этот класс выполняет отправку сообщений по сети — поэтому он связан с модулем Common, описанным ранее. Метод waitConnection дожидается соединения и инициализирует сокет (m_clientSocket), а также два потока для чтения и записи в него. Объект Player также хранит тип игрока — "x" или "o", этот параметр устанавливается объекту с помощью методы set_type, вызываемого из TcpServer. Функция readMoveIfActive инициализирует получение данных от игрока если сейчас его ход.

      Класс TcpServer объединяет игроков и игровую логику. Метод main создает серверный сокет (java.net.ServerSocket) и двух игроков (Player). После их подключения — создает пустое игровое поле, методом generateRandomTypes выдает игрокам тип (крестик или нолик). Затем, в цикле, выполняемом пока GameBoard.getWinner не вернет значение, отличное от "_", выполняет:

      1. рассылку текущего состояния поля игрокам — метдом Player.send(GameBoard);
      2. получение информации от активного игрока — методом Player.readMoveIfActive();
      3. обработку хода — методом GameBoard.process(Messages.Move).

      2.3 Приложение-Клиент

      Приложение клиент не содержит никакой игровой логики — то есть не выполняет проверку корректности хода. Эту задачу оно делегирует серверу. Основные задачи этого приложения — получени от сервера информации о текущем состоянии игры, визуализация этого состояния — реализация пользовательского интерфейса, обеспечение пользователю возможности сделать ход (выбрать клетку поля) и отправку информации о ходе на сервер. В соответствии с этим выполнена декомпозиция приложения на классы, показанные на рисунке 4.

      Рисунок 4 — UML диаграмма классов приложения-клиента:

      Работа клиента начинается с выполнения функции TcpClient.main, которая подключается к серверу и до тех пор, пока от сервера не придет сообщение, содержащее поле winner, отличное от "_", выполняет:

      1. получение от сервера текущего состояния игры — поэтому класс TcpClient связан с Common;
      2. отображение этого состояния на GameWindow — информация, полученная от сервера передается в метод GameWindow.load.

      Класс GameWindow реализует графический интерфейс, при этом:

      с помощью JLabel в верхней части окна отображает статус игры ("Ожидание подключения второго игрока", "Ваш ход", "Ожидание хода противника", "Конец игры:победа/поражение");

      отображает поле из 400 кнопок (20 строк по 20 столбцов). Для размещения кнопок используется стандартный GridLayout, размещающий элементы по сетке.

      Каждая кнопка (класс GameButton) наследуется от стандартного класса JButton и отображает крестик, нолик или пробел. После клика по кнопке, она (с помощью MoveActionListener) отправляет на сервер информацию о сделанно ходе, для этого она хранит свою позцию (m_x, m_y) и поток вывода в сокет. Обработка события клика выполнена стандартным образом — к кнопке методом addActionListener прикрепляется MoveActionListener. Так как этот класс выполняет запись информации в сокет — то он зависит от модуля Common.

      2.4 Модули системы

      Информация о модулях системы приведена в Таблице:

      Модуль

      Количество строк кода

      Приложение-клиент

      Приложение-сервер

      Common

      77

      да

      да

      Messages

      13

      да

      да

      TcpClient

      65

      да

      нет

      GameButton

      33

      да

      нет

      GameWindow

      77

      да

      нет

      GameBoard

      76

      нет

      да

      Player

      59

      нет

      да

      TcpServer

      79

      нет

      да

      Всего строк кода

      479

      265

      304

      Исходный код проекта находится в репозитории. Проект этот написан в свободное от работы время, но в качестве примера для студентов. На весь проект, вместе с отчетом, у меня ушел 1 день. Проект содержит ряд недостатков:

      • что-то вам подскажет компилятор Java (выдает несколько справедливых предупреждений);
      • стоит выполнить рефакторинг кода. В частности, очень легко ошибиться и ввести «о» вместо «o» (вы не видите разницы, но язык другой и эти строки не равны).
      • у сервера убогая схема работы с клиентами — было бы здорово реализовать возможность игры большего числа клиентов;
      • сервер создает поток на каждого клиента, стоит использовать Пул потоков чтобы он не падал при большом числе подключений.
      • можно добавить возможность регистрации игроков и ведение для них статистики.
      StudLance.ru

Просмотр 0 веток ответов
  • Для ответа в этой теме необходимо авторизоваться.
×