gb-java-devel/jtd1-06-workshop.tex

855 lines
52 KiB
TeX
Raw Permalink Normal View History

2023-05-23 22:25:16 +03:00
\documentclass[j-spec.tex]{subfiles}
\usepackage{spreadtab}
2023-02-05 23:50:05 +03:00
2023-05-23 22:25:16 +03:00
\begin{document}
\section{Семинар: Простейшие интерфейсы пользователя}
\subsection{Инструментарий}
\begin{itemize}
2024-05-12 20:23:51 +03:00
\item \href{https://docs.google.com/presentation/d/1ji8J8XfgjWyAmoIkQyHOyoWGsnAbD3Sk7-D7YBlaQIM}{Презентация} для преподавателя, ведущего семинар;
2023-05-23 22:25:16 +03:00
\item \href{https://drive.google.com/file/d/1LWyE8aEy4-1gsognqhXIXwDcoLviVge4/view}{Фон} GeekBrains для проведения семинара в Zoom;
\item JDK любая 11 версии и выше;
\item \href{https://www.jetbrains.com/idea/download}{IntelliJ IDEA Community Edition} для практики и примеров используется IDEA.
\end{itemize}
\subsection{Цели семинара}
\begin{itemize}
\item Практика создания простых экранных форм -- кнопки, компоненты форм;
\item передача данных между формами и внутри формы;
\item написание полноценного окна клиента чата;
\item написание графической оболочки для настроек игры в крестики-нолики.
\end{itemize}
\subsection{План-содержание}
\noindent
\begin{spreadtab}{{longtable}{|p{37mm}|l|l|p{90mm}|}}
\hline
@ Что происходит & @ Время & @ Слайды & @ Описание \\
\hline
\endhead
@ Организационный момент & 5 tag(beg) & @ 1-5 & @ Преподаватель ожидает студентов, поддерживает активность и коммуникацию в чате, озвучивает цели и планы на семинар. Важно упомянуть, что выполнение домашних заданий с лекции является, фактически, подготовкой к семинару \\
\hline
@ Quiz & 5 & @ 6-18 & @ Преподаватель задаёт вопросы викторины, через 30 секунд демонстрирует слайд-подсказку и ожидает ответов (по минуте на ответ) \\
\hline
2024-05-12 20:23:51 +03:00
@ Рассмотрение ДЗ лекции & 15-20 & @ 19-23 & @ Преподаватель демонстрирует свой вариант решения домашнего задания с лекции, возможно, по предварительному опросу, демонстрирует и разбирает вариант решения одного из студентов \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Вопросы и ответы & 10 & @ 24 & @ Преподаватель ожидает вопросов по теме прошедшей лекции, викторины и продемонстрированной работы \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Задание 1 & 15 & @ 25-30 & @ Создание и компоновка элементов управления \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Задание 2 & 15 & @ 31-35 & @ Связь компонентов и сбор сведений о состоянии компонента \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Задание 3 & 10 & @ 36-38 & @ Передача данных с компонентов \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Перерыв (если нужен) & 5 & @ 39 & @ Преподаватель предлагает студентам перерыв на 5 минут (студенты голосуют) \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Задание 4 & 15 & @ 40-44 & @ Создание окна управления сервером \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Задание 5 & 30 & @ 45-48 & @ Создание окна клиентской части текстового чата \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Домашнее задание & 5 & @ 49-50 & @ Объясните домашнее задание, подведите итоги урока \\
2023-05-23 22:25:16 +03:00
\hline
2024-05-12 20:23:51 +03:00
@ Рефлексия & 10 tag(end) & @ 51-52 & @ Преподаватель запрашивает обратную связь \\
2023-05-23 22:25:16 +03:00
\hline
@ Длительность & sum(cell(beg):cell(end)) & & \\
\hline
\end{spreadtab}
\subsection{Подробности}
\subsubsection*{Организационный момент}
\begin{itemize}
\item \textbf{Цель этапа:} Позитивно начать урок, создать комфортную среду для обучения.
\item \textbf{Тайминг:} 3-5 минут.
\item \textbf{Действия преподавателя:}
\begin{itemize}
\item Запрашивает активность от аудитории в чате;
\item Презентует цели курса и семинара;
\item Презентует краткий план семинара и что студент научится делать.
\end{itemize}
\end{itemize}
\subsubsection*{Quiz}
\begin{itemize}
\item \textbf{Цель этапа:} Вовлечение аудитории в обратную связь.
\item \textbf{Тайминг:} 5--7 минут (4 вопроса, по минуте на ответ).
\item \textbf{Действия преподавателя:}
\begin{itemize}
\item Преподаватель задаёт вопросы викторины, представленные на слайдах презентации;
\item через 30 секунд демонстрирует слайд-подсказку и ожидает ответов.
\end{itemize}
\item \textbf{Вопросы и ответы:}
\begin{enumerate}
\item Свойства окна, такие как размер и заголовок возможно задать (2)
\begin{enumerate}
\item написанием методов в классе-наследнике
\item вызовом методов в конструкторе
\item созданием констант в первых строках класса
\end{enumerate}
\item Для выполнения кода по нажатию кнопки на интерфейсе нужно (1)
\begin{enumerate}
\item создать обработчик кнопки и вписать код в него
\item переопределить метод нажатия у компонента кнопки
\item написать код непосредственно в методе создания кнопки
\end{enumerate}
\item Что такое Java Swing? (1)
\begin{enumerate}
\item набор библиотек для создания реактивных GUI;
\item среда разработки для Java;
\item язык программирования.
\end{enumerate}
\item Какая библиотека используется для создания графических интерфейсов на Java? (3)
\begin{enumerate}
\item Java Swing;
\item JavaFX;
\item обе библиотеки.
\end{enumerate}
\end{enumerate}
\end{itemize}
\subsubsection*{Рассмотрение ДЗ}
\begin{itemize}
\item \textbf{Цель этапа:} Пояснить не очевидные моменты в формулировке ДЗ с лекции, синхронизировать прочитанный на лекции материал к началу семинара.
\item \textbf{Тайминг:} 15-20 минут.
\item \textbf{Действия преподавателя:}
\begin{itemize}
\item Преподаватель демонстрирует свой вариант решения домашнего задания из лекции;
\item возможно, по предварительному опросу, демонстрирует и разбирает вариант решения одного из студентов.
\end{itemize}
\item \textbf{Домашнее задание из лекции:}
\begin{itemize}
\item Полностью разобраться с кодом -- это задание не нужно обсуждать, возможно просто спросить, кто действительно сел, взял в руки карандаш и блокнот, и попытался разобраться с кодом с лекции. Похвалить тех, кто это действительно сделал.
\item Переделать проверку победы, чтобы она не была реализована просто набором условий.
\textbf{Вариант решения}
На уроке был написан код, который, очевидно, никуда не годится
\begin{lstlisting}[language=Java,style=JCodeStyle]
private boolean checkWin(int c) {
if (field[0][0]==c && field[0][1]==c && field[0][2]==c) return true;
if (field[1][0]==c && field[1][1]==c && field[1][2]==c) return true;
if (field[2][0]==c && field[2][1]==c && field[2][2]==c) return true;
if (field[0][0]==c && field[1][0]==c && field[2][0]==c) return true;
if (field[0][1]==c && field[1][1]==c && field[2][1]==c) return true;
if (field[0][2]==c && field[1][2]==c && field[2][2]==c) return true;
if (field[0][0]==c && field[1][1]==c && field[2][2]==c) return true;
if (field[0][2]==c && field[1][1]==c && field[2][0]==c) return true;
return false;
}
\end{lstlisting}
Задание подразумевало переписывание изменяющейся индексации в циклы, но, поскольку для игры в крестики-нолики нужно также иметь возможность работать на любом поле с любым числом фигур подряд для победы -- имеет смысл рассматривать сразу следующее задание. Выполнивших это задание отдельно следует похвалить за точность выполнения задания.
\item Попробовать переписать логику проверки победы, чтобы она работала для поля 5х5 и количества фигур 4.
\textbf{Вариант решения}
\href{https://log.iovchinnikov.ru/tic-tac-toe-hint}{более подробное объяснение алгоритма по ссылке}
Задание в первую очередь подразумевало необходимость составить алгоритм действий, прежде, чем писать какой-то код. Важно напомнить студентам о необходимости проектирования перед разработкой. Реализованный в качестве варианта решения алгоритм подразумевает проход по каждой клетке поля с проверкой на наличие линии по четырём направлениям (пошаговая проверка линии реализована методом \code{checkLine} с указанием координат начала проверки, направления и проверяемого символа) с проверкой на выход за пределы поля.
Сначала учимся проверять одну линию в одном направлении. Если начинаем проверку от последнего поставленного символа -- алгоритм сразу становится сложнее, потому что считать надо в 8 сторон, да ещё и учитывать, не был ли последний Х в середине линии, но в итоге такой алгоритм на больших полях будет отрабатывать быстрее. Добавляем -- из каких координат, какой символ, в каком направлении смотрим и проверки на выход за пределы поля (благо поле это глобальные переменные)
\begin{lstlisting}[language=Java,style=JCodeStyle]
private boolean checkLine(int x, int y, int vx, int vy, int len, int c) {
final int far_x = x + (len - 1) * vx;
final int far_y = y + (len - 1) * vy;
if (!isValidCell(far_x, far_y)) return false;
for (int i = 0; i < len; i++) {
if (field[y + i * vy][x + i * vx] != c) return false;
}
return true;
}
\end{lstlisting}
Затем осуществляем проверки линий вправо, вниз, вправо-вниз и вправо-вверх из каждой клетки поля, но с проверкой на выход за пределы.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private boolean checkWin(int c) {
for (int i = 0; i < fieldSizeX; i++) {
for (int j = 0; j < fieldSizeY; j++) {
if (checkLine(i, j, 1, 0, winLength, c)) return true;
if (checkLine(i, j, 1, 1, winLength, c)) return true;
if (checkLine(i, j, 0, 1, winLength, c)) return true;
if (checkLine(i, j, 1, -1, winLength, c)) return true;
}
}
return false;
}
\end{lstlisting}
\item ** Доработать искусственный интеллект, чтобы он мог примитивно блокировать ходы игрока, и примитивно пытаться выиграть сам.
\textbf{Вариант решения}
На лекции был представлен код, при исполнении которого, компьютер ходит в случайную клетку.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private void aiTurn() {
int x, y;
do {
x = RANDOM.nextInt(fieldSizeX);
y = RANDOM.nextInt(fieldSizeY);
} while (!isEmptyCell(x, y));
field[y][x] = DOT_AI;
}
\end{lstlisting}
Для того, чтобы компьютер мог проверить, может ли он победить и может ли он помешать человеку победить, нужно перед тем как делать ход случайным образом, добавить две проверки, которые должны завершиться выходом из метода хода компьютера, в случае успеха (то есть ход закончен, если мы успешно поставили выигрышную фигуру или успешно предотвратили один из вариантов победы игрока).
\begin{lstlisting}[language=Java,style=JCodeStyle]
if (turnAIWinCell()) return;
if (turnHumanWinCell()) return;
\end{lstlisting}
Чтобы попытаться выиграть самому компьютер не будет делать ничего сложного -- просто пройдёт по всему полю, попробует поставить последовательно в каждую клетку нолик и проверить, не выигрывает ли он. Если да -- возвращает истину, если не выигрывает -- стирает нолик и продолжает проверку.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private boolean turnAIWinCell() {
for (int i = 0; i < fieldSizeY; i++) {
for (int j = 0; j < fieldSizeX; j++) {
if (isEmptyCell(j, i)) {
field[i][j] = DOT_AI;
if (checkWin(DOT_AI)) return true;
field[i][j] = DOT_EMPTY;
}
}
}
return false;
}
\end{lstlisting}
Чтобы попытаться предотвратить победу человека -- компьютер действует по тому же принципу -- подставляет в каждую клетку крестик и если на поле победа человека -- заменяет его на свой нолик и возвращает истину, считая, что успешно помешал человеку. Если же победы человека не происходит -- продолжает проверку.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private boolean turnHumanWinCell() {
for (int i = 0; i < fieldSizeY; i++) {
for (int j = 0; j < fieldSizeX; j++) {
if (isEmptyCell(j, i)) {
field[i][j] = DOT_HUMAN;
if (checkWin(DOT_HUMAN)) {
field[i][j] = DOT_AI;
return true;
}
field[i][j] = DOT_EMPTY;
}
}
}
return false;
}
\end{lstlisting}
\end{itemize}
\end{itemize}
\subsubsection*{Вопросы и ответы}
\begin{itemize}
\item \textbf{Ценность этапа} Вовлечение аудитории в обратную связь, пояснение неочевидных моментов в материале лекции и другой проделанной работе.
\item \textbf{Тайминг} 5-15 минут
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Преподаватель ожидает вопросов по теме прошедшей лекции, викторины и продемонстрированной работы;
\item Если преподаватель затрудняется с ответом, необходимо мягко предложить студенту ответить на его вопрос на следующем семинаре (и не забыть найти ответ на вопрос студента!);
\item Предложить и показать пути самостоятельного поиска студентом ответа на заданный вопрос;
\item Посоветовать литературу на тему заданного вопроса;
\item Дополнительно указать на то, что все сведения для выполнения домашнего задания, прохождения викторины и работы на семинаре были рассмотрены в методическом материале к этому или предыдущим урокам.
\end{itemize}
\end{itemize}
\subsubsection*{Задание 1}
\begin{itemize}
\item \textbf{Ценность этапа} Создание окна настроек игры в крестики-нолики, создание и компоновка элементов управления
\item \textbf{Тайминг} 15-20 мин
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Выдать задание студентам;
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
\end{itemize}
\item \textbf{Задание}:
\begin{itemize}
\item На лекции был написан фрейм, содержащий одну кнопку -- начать игру и расположением самого окна настроек автоматически, относительно игрового окна.
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class SettingsWindow extends JFrame {
private static final int WINDOW_HEIGHT = 230;
private static final int WINDOW_WIDTH = 350;
JButton btnStart = new JButton("Start new game");
SettingsWindow(GameWindow gameWindow) {
setLocationRelativeTo(gameWindow);
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
gameWindow.startNewGame(0, 3, 3, 3);
setVisible(false);
}
});
add(btnStart);
}
}
\end{lstlisting}
Первое задание -- добавить на экран компоновщик-сетку с одним столбцом и добавить над существующей кнопкой следующие компоненты в заданном порядке: \code{JLabel} с заголовком «Выберите режим игры», сгруппированные в \code{ButtonGroup} переключаемые \code{JRadioButton} с указанием режимов «Человек против компьютера» и «Человек против человека», \code{JLabel} с заголовком «Выберите размеры поля», \code{JLabel} с заголовком «Установленный размер поля:», \code{JSlider} со значениями 3..10, \code{JLabel} с заголовком «Выберите длину для победы», \code{JLabel} с заголовком «Установленная длина:», \code{JSlider} со значениями 3..10.
\textbf{Вариант решения}
Решение может быть выполнено сколь угодно гибко. Минимальное жизнеспособное решение приведено в листинге ниже.
\begin{lstlisting}[language=Java,style=JCodeStyle]
setLayout(new GridLayout(10,1));
add(new JLabel("Выберите режим игры"));
ButtonGroup bg = new ButtonGroup();
JRadioButton pvc = new JRadioButton("Человек против компьютера");
JRadioButton pvp = new JRadioButton("Человек против человека");
bg.add(pvc);
bg.add(pvp);
add(pvc);
add(pvp);
add(new JLabel("Выберите размеры поля"));
add(new JLabel("Установленный размер поля:"));
add(new JSlider(3, 10, 3));
add(new JLabel("Выберите длину для победы"));
add(new JLabel("Установленная длина:"));
add(new JSlider(3, 10, 3));
add(btnStart);
\end{lstlisting}
Если было получено такое решение, важно указать студентам, что мы не сможем снять показания с созданных таким образом компонентов и то, что будет использоваться, например, по кнопке, должно иметь более широкую область видимости, например, объявленным в классе
\item [$*_1$] Сгруппировать объявление компонентов в два метода по смыслу -- регулирование режима игры, регулирование параметров поля. Объявить компоненты так, чтобы они оказались доступны для обработчика кнопки.
\textbf{Вариант решения}
Код ниже. Общий смысл в том, чтобы получить доступные в классе компоненты и сделать конструктор не таким большим. Соответственно, методы должны быть вызваны из конструктора окна.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private JRadioButton humVSAI;
private JRadioButton humVShum;
private JSlider slideWinLen;
private JSlider slideFieldSize;
private void addGameModeControls() {
add(new JLabel("Выберите режим игры"));
humVSAI = new JRadioButton("Человек против компьютера");
humVShum = new JRadioButton("Человек против человека");
humVSAI.setSelected(true);
ButtonGroup gameMode = new ButtonGroup();
gameMode.add(humVSAI);
gameMode.add(humVShum);
add(humVSAI);
add(humVShum);
}
private void addFieldControls() {
JLabel lbFieldSize = new JLabel("Установленный размер поля:");
JLabel lbWinLength = new JLabel("Установленная длина:");
slideFieldSize = new JSlider(3, 10, 3);
slideWinLen = new JSlider(3, 10, 3);
add(new JLabel("Выберите размеры поля"));
add(lbFieldSize);
add(slideFieldSize);
add(new JLabel("Выберите длину для победы"));
add(lbWinLength);
add(slideWinLen);
}
\end{lstlisting}
\item [$*_2$] Вынести неизменяемую часть изменяемых сообщений (подписи к слайдерам) в константы класса. Вынести размеры, применяемые в слайдерах в константы класса (избавиться от магических чисел). Вынести обработчик кнопки в отдельный метод.
\textbf{Вариант решения}
В результате должны появиться классовые константы, измениться начало метода \code{addFieldControls} и добавиться метод для обработчика кнопки, все изменения показаны в листинге
\begin{lstlisting}[language=Java,style=JCodeStyle]
//constants
private static final int MIN_WIN_LENGTH = 3;
private static final int MIN_FIELD_SIZE = 3;
private static final int MAX_FIELD_SIZE = 10;
private static final String FIELD_SIZE_PREFIX = "Установленный размер поля: ";
private static final String WIN_LENGTH_PREFIX = "Установленная длина: ";
//addFieldControls
private void addFieldControls() {
JLabel lbFieldSize = new JLabel(FIELD_SIZE_PREFIX);
JLabel lbWinLength = new JLabel(WIN_LENGTH_PREFIX);
slideFieldSize = new JSlider(MIN_FIELD_SIZE, MAX_FIELD_SIZE, MIN_FIELD_SIZE);
slideWinLen = new JSlider(MIN_WIN_LENGTH, MAX_FIELD_SIZE, MIN_FIELD_SIZE);
//constructor
private final GameWindow gameWindow;
SettingsWindow(GameWindow gameWindow) {
this.gameWindow = gameWindow;
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
btnStartDelegate();
}
});
//click delegate
private void btnStartDelegate() {
gameWindow.startNewGame(0, 3, 3, 3);
setVisible(false);
}
\end{lstlisting}
\end{itemize}
\end{itemize}
\subsubsection*{Задание 2}
\begin{itemize}
\item \textbf{Ценность этапа} Создание окна настроек игры в крестики-нолики, связь компонентов и сбор сведений о состоянии компонента
\item \textbf{Тайминг} 15-20 мин
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Выдать задание студентам;
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
\end{itemize}
\item \textbf{Задание}:
\begin{itemize}
\item Добавить компонентам интерактивности, а именно, при перемещении ползунка слайдера в соответствующих лейблах должны появляться текущие значения слайдеров. Для этого необходимо добавить к слайдеру слушателя изменений (как это было сделано для действия кнопки).
\textbf{Вариант решения}
Важно не забыть поменять начальные значения у лейблов, чтобы с видимостью экрана сразу становились видимы текущие настройки.
\begin{lstlisting}[language=Java,style=JCodeStyle]
JLabel lbFieldSize = new JLabel(FIELD_SIZE_PREFIX + MIN_FIELD_SIZE);
JLabel lbWinLength = new JLabel(WIN_LENGTH_PREFIX + MIN_FIELD_SIZE);
slideWinLen.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
lbWinLength.setText(WIN_LENGTH_PREFIX + slideWinLen.getValue());
}
});
slideFieldSize.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
lbFieldSize.setText(FIELD_SIZE_PREFIX + slideFieldSize.getValue());
}
});
\end{lstlisting}
\item [$*_1$] Добавить автоматическое регулирование максимального значения у слайдера выигрышной длины при изменении значения слайдера размера поля.
\begin{lstlisting}[language=Java,style=JCodeStyle]
slideFieldSize.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
int currentValue = slideFieldSize.getValue();
lbFieldSize.setText(FIELD_SIZE_PREFIX + currentValue);
slideWinLen.setMaximum(currentValue);
}
});
\end{lstlisting}
\textbf{Вариант решения}
\item [$*_2$] Добавить центрирование окна настроек относительно главного (родительского) окна. То есть, в центре родительского окна должен быть не левый верхний угол окна настроек (как это сделано сейчас), а также его центр.
\textbf{Вариант решения}
В решении важно увидеть отказ от использования метода, который использовался на лекции, потому что при помощи него решить задачу не удастся. Забираем у родительского окна его границы в виде прямоугольника, ищем центр этого прямоугольника по осям и отнимаем от полученных координат половину ширины и высоты окна настроек, соответственно, потому что метод \code{setLocation} будет устанавливать верхний-левый угол окна настроек, а не центр.
\begin{lstlisting}[language=Java,style=JCodeStyle]
Settings(GameWindow mainWindow) {
this.mainWindow = mainWindow;
setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
//setLocationRelativeTo(mainWindow);
Rectangle gameWindowBounds = mainWindow.getBounds();
int posX = (int) gameWindowBounds.getCenterX() - WINDOW_WIDTH / 2;
int posY = (int) gameWindowBounds.getCenterY() - WINDOW_HEIGHT / 2;
setLocation(posX, posY);
setResizable(false);
//...
}
\end{lstlisting}
\end{itemize}
\end{itemize}
\subsubsection*{Задание 3}
\begin{itemize}
\item \textbf{Ценность этапа} Передача данных с компонентов в окно игры
\item \textbf{Тайминг} 10-15 мин
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Выдать задание студентам;
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
\end{itemize}
\item \textbf{Задание}:
\begin{itemize}
\item В методе обработчика нажатия кнопки необходимо заменить константы в аргументе вызова метода старта игры на текущие показания компонентов (какая радио-кнопка активна, значение слайдера размеров поля, значение слайдера выигрышной длины).
\textbf{Вариант решения}
При решении этого задания важно понимать, что любое невозможное состояние компонента нельзя игнорировать и должно быть выведено сообщение об ошибке, идеально, если будет выброшено исключение. Необходимо пояснить студентам, что это делается для самого же разработчика, который спустя условные полгода не вспомнит, куда именно нужно добавлять код при разработке новой функциональности, например, нового режима игры, а исключение подскажет конкретную строку. Также в представленном ниже варианте решения используются константы, которые нужно добавить в класс \code{Map}.Почему именно в \code{Map}? потому что именно этот класс занимается логикой игры и если мы хотим добавить новый режим, например, игру в крестики-нолики втроём, то всю логику этого режима будет знать \code{Map}, а, следовательно, и константы с режимами должен задавать он.
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class Map extends JPanel {
public static final int MODE_HVA = 0;
2024-05-12 20:23:51 +03:00
public static final int MODE_HVH = 1;
2023-05-23 22:25:16 +03:00
//...
}
public class SettingsWindow extends JFrame {
//...
private void btnStartDelegate() {
int gameMode;
if (humVSAI.isSelected()) {
gameMode = Map.MODE_HVA;
} else if (humVShum.isSelected()) {
gameMode = Map.MODE_HVH;
} else {
throw new RuntimeException("Unknown game mode");
}
int fieldSize = slideFieldSize.getValue();
int winLength = slideWinLen.getValue();
gameWindow.startNewGame(gameMode, fieldSize, fieldSize, winLength);
setVisible(false);
}
\end{lstlisting}
\end{itemize}
\end{itemize}
\subsubsection*{Задание 4}
\begin{itemize}
\item \textbf{Ценность этапа} Создание окна управления сервером
2024-05-12 20:23:51 +03:00
\item \textbf{Тайминг} 10-15 мин
2023-05-23 22:25:16 +03:00
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Выдать задание студентам;
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
\end{itemize}
\item \textbf{Задание}:
\begin{itemize}
\item Создать простейшее окно управления сервером (по сути, любым), содержащее две кнопки (\code{JButton}) -- запустить сервер и остановить сервер. Кнопки должны просто логировать нажатие (имитировать запуск и остановку сервера, соответственно) и выставлять внутри интерфейса соответствующее булево \code{isServerWorking}.
\textbf{Вариант решения}
В этом задании нет никаких подводных камней, просто экран, две кнопки, два обработчика
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class ServerWindow extends JFrame {
private static final int POS_X = 500;
private static final int POS_Y = 550;
private static final int WIDTH = 200;
private static final int HEIGHT = 100;
private final JButton btnStart = new JButton("Start");
private final JButton btnStop = new JButton("Stop");
private boolean isServerWorking;
public static void main(String[] args) {
new ServerWindow();
}
private ServerWindow() {
isServerWorking = false;
btnStop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
isServerWorking = false;
System.out.println("Server stopped " + isServerWorking);
}
});
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
isServerWorking = true;
System.out.println("Server started " + isServerWorking);
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(POS_X, POS_Y, WIDTH, HEIGHT);
setResizable(false);
setTitle("Chat server");
setAlwaysOnTop(true);
setLayout(new GridLayout(1, 2));
add(btnStart);
add(btnStop);
setVisible(true);
}
}
\end{lstlisting}
\item [$*_1$] Если сервер не запущен, кнопка остановки должна сообщить, что сервер не запущен и более ничего не делать. Если сервер запущен, кнопка старта должна сообщить, что сервер работает и более ничего не делать.
\textbf{Вариант решения}
Это задание также без подводных камней, важно не забыть, что после выдачи сообщения в проверке нужно прекратить выполнение обработчика.
\begin{lstlisting}[language=Java,style=JCodeStyle]
btnStop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!isServerWorking) {
System.out.println("already stopped");
return;
}
isServerWorking = false;
System.out.println("Server stopped " + isServerWorking);
}
});
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (isServerWorking) {
System.out.println("already working");
return;
}
isServerWorking = true;
System.out.println("Server started " + isServerWorking);
}
});
\end{lstlisting}
\item [$*_2$] Добавить на окно компонент \code{JtextArea} и выводить сообщения сервера в него, а не в терминал.
\textbf{Вариант решения}
В данном задании важно, что при добавлении текстовой области изменяется лейаут окна (обратно на стандартный), а кнопки довольно логично смотрятся сверху или снизу на отдельной панели с компоновкой сеткой. Также все выводы в консоль заменяются на вызов метода \code{log.append()}, и это поведение желательно убрать в приватный метод, что-то вроде \code{putLog(String msg)}, потому что сейчас мы хотим логировать так, а дальше захотим иначе и не надо будет искать во всём коде -- что и как логируется.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
private final JTextArea log = new JTextArea();
private ServerWindow() {
isServerWorking = false;
btnStop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!isServerWorking) {
log.append("already stopped\n");
return;
}
isServerWorking = false;
log.append("Server stopped " + isServerWorking + "\n");
}
});
btnStart.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (isServerWorking) {
log.append("already working\n");
return;
}
isServerWorking = true;
log.append("Server started " + isServerWorking + "\n");
}
});
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(POS_X, POS_Y, WIDTH, HEIGHT);
setResizable(false);
setTitle("Chat server");
setAlwaysOnTop(true);
JPanel panelTop = new JPanel(new GridLayout(1, 2));
log.setEditable(false);
log.setLineWrap(true);
JScrollPane scrollLog = new JScrollPane(log);
panelTop.add(btnStart);
panelTop.add(btnStop);
add(panelTop, BorderLayout.NORTH);
add(scrollLog, BorderLayout.CENTER);
setVisible(true);
}
\end{lstlisting}
\end{itemize}
\end{itemize}
\subsubsection*{Задание 5}
\begin{itemize}
\item \textbf{Ценность этапа} Создание окна клиентской части текстового чата
2024-05-12 20:23:51 +03:00
\item \textbf{Тайминг} 20-25 мин
2023-05-23 22:25:16 +03:00
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Выдать задание студентам;
\item Подробно объяснить, что именно требуется от студентов, избегая упоминания конкретных языковых конструкций;
\item Если группа студентов справилась с заданием, а времени осталось более 5 минут, выдавать группе задания «со звёздочкой».
\end{itemize}
\item \textbf{Задание}:
\begin{itemize}
\item Создать окно клиента чата. Окно должно содержать \code{JtextField} для ввода логина, пароля, IP-адреса сервера, порта подключения к серверу, область ввода сообщений, \code{JTextArea} область просмотра сообщений чата и \code{JButton} подключения к серверу и отправки сообщения в чат. Желательно сразу сгруппировать компоненты, относящиеся к серверу сгруппировать на \code{JPanel} сверху экрана, а компоненты, относящиеся к отправке сообщения -- на \code{JPanel} снизу.
\textbf{Вариант решения}
К решению, как и к проверке желательно подойти последовательно, сначала создать пустое окно с размерами и со стандартными элементами вроде заголовка и обработки закрытия окна на крестик
\begin{lstlisting}[language=Java,style=JCodeStyle]
public class ClientGUI extends JFrame {
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
ClientGUI() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setSize(WIDTH, HEIGHT);
setTitle("Chat Client");
setVisible(true);
}
public static void main(String[] args) {
new ClientGUI();
}
}
\end{lstlisting}
Далее добавить компоненты, касающиеся сервера и панель, например, верхнюю. В конструкторе, добавить эти элементы на эту панель и саму панель на экран.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private final JPanel panelTop = new JPanel(new GridLayout(2, 3));
private final JTextField tfIPAddress = new JTextField("127.0.0.1");
private final JTextField tfPort = new JTextField("8189");
private final JTextField tfLogin = new JTextField("ivan_igorevich");
private final JPasswordField tfPassword = new JPasswordField("123456");
private final JButton btnLogin = new JButton("Login");
// constructor
panelTop.add(tfIPAddress);
panelTop.add(tfPort);
panelTop.add(tfLogin);
panelTop.add(tfPassword);
panelTop.add(btnLogin);
add(panelTop, BorderLayout.NORTH);
\end{lstlisting}
Дальше делаем нижнюю панельку и также добавляем её на экран.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private final JPanel panelBottom = new JPanel(new BorderLayout());
private final JTextField tfMessage = new JTextField();
private final JButton btnSend = new JButton("Send");
// constructor
panelBottom.add(tfMessage, BorderLayout.CENTER);
panelBottom.add(btnSend, BorderLayout.EAST);
add(panelBottom, BorderLayout.SOUTH);
\end{lstlisting}
И как без внимания оставить лог. Его нужно сделать не редактируемым и прокручиваемым. для этого вызовем метод \code{setEditable} и добавим в \code{ScrollBox}.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private final JTextArea log = new JTextArea();
// constructor
log.setEditable(false);
JScrollPane scrollLog = new JScrollPane(log);
add(scrollLog);
\end{lstlisting}
\item [$*_1$] Добавить на экран компонент \code{JList<String>} -- имитацию списка пользователей, заполнить этот список несколькими выдуманными именами пользователей чата. Подсказка: компонент не может добавлять или убирать элементы списка, он работает с методом \code{setListData()}, изучите его аргументы.
\textbf{Вариант решения}
В самом создании списка нет ничего необычного, кроме использования метода из подсказки. Также достаточно важно, что список необходимо поместить в область прокрутки, поскольку список пользователей чата может не поместиться на экран.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private final JList<String> userList = new JList<>();
// constructor
JScrollPane scrollUsers = new JScrollPane(userList);
add(scrollUsers, BorderLayout.EAST);
String users[] = {"user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8", "user9", "user10"};
userList.setListData(users);
\end{lstlisting}
если какой-то из пользователей внезапно выберет достаточно длинное имя, оно может перекрыть область вывода сообщений, это обусловлено приоритетом компоновки (центр имеет наименьший приоритет). Для того, чтобы этого избежать нужно для области прокрутки выставить предпочтительный размер (например, ширина 100 и высота не важна).
\begin{lstlisting}[language=Java,style=JCodeStyle]
scrollUsers.setPreferredSize(new Dimension(100, 0));
\end{lstlisting}
\end{itemize}
\end{itemize}
\subsubsection*{Домашнее задание}
\begin{itemize}
\item \textbf{Ценность этапа} Задать задание для самостоятельного выполнения между занятиями.
\item \textbf{Тайминг} 5-10 минут.
\item \textbf{Действия преподавателя}
\begin{itemize}
\item Пояснить студентам в каком виде выполнять и сдавать задания
\item Уточнить кто будет проверять работы (преподаватель или ревьювер)
\item Объяснить к кому обращаться за помощью и где искать подсказки
\item Объяснить где взять проект заготовки для дз
\end{itemize}
\item \textbf{Задания}
\begin{enumerate}
\item [5-25 мин] Выполнить все задания семинара, если они не были решены, без ограничений по времени;
\textbf{Все варианты решения приведены в тексте семинара выше}
\item [15 мин] 1. Отправлять сообщения из текстового поля сообщения в лог по нажатию кнопки или по нажатию клавиши Enter на поле ввода сообщения.
\begin{lstlisting}[language=Java,style=JCodeStyle]
// constructor
btnSend.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
sendMsg();
}
});
tfMessage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
sendMsg();
}
});
private void sendMsg() {
String msg = tfMessage.getText(); // получили сообщение
String username = tfLogin.getText(); // получили имя
if ("".equals(msg)) return; // сравнили на пустое
log.append(username + ": " + msg + "\n"); // добавили
tfMessage.setText(null); // очищаем
tfMessage.requestFocusInWindow(); // сфокусируемся
}
\end{lstlisting}
\item [10-15 мин] 2. Продублировать импровизированный лог (историю) чата в файле.
\begin{lstlisting}[language=Java,style=JCodeStyle]
private void sendMsg() {
//...
try (FileWriter out = new FileWriter("log.txt", true)) {
out.write(username + ": " + msg + "\n");
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
\end{lstlisting}
\item [15-20 мин] 3. При запуске клиента чата заполнять поле истории из файла, если он существует. Обратите внимание, что чаще всего история сообщений хранится на сервере и заполнение истории чата лучше делать при соединении с сервером, а не при открытии окна клиента.
\begin{lstlisting}[language=Java,style=JCodeStyle]
// constructor
try (FileInputStream fis = new FileInputStream("log.txt")) {
int b;
while ((b = fis.read()) != -1) {
log.append(Character.valueOf((char) b).toString());
}
} catch (IOException e) {
e.printStackTrace();
}
\end{lstlisting}
\end{enumerate}
\end{itemize}
\subsubsection*{Рефлексия и завершение семинара}
\begin{itemize}
\item \textbf{Цель этапа:} Привести урок к логическому завершению, посмотреть что студентам удалось, что было сложно и над чем нужно еще поработать
\item \textbf{Тайминг:} 5-10 минут
\item \textbf{Действия преподавателя:}
\begin{itemize}
\item Запросить обратную связь от студентов.
\item Подчеркните то, чему студенты научились на занятии.
\item Дайте рекомендации по решению заданий, если в этом есть необходимость
\item Дайте краткую обратную связь студентам.
\item Поделитесь ощущением от семинара.
\item Поблагодарите за проделанную работу.
\end{itemize}
\end{itemize}
\newpage
\end{document}