06b done, 06w idea saved

This commit is contained in:
Ivan I. Ovchinnikov 2023-02-05 23:50:05 +03:00
parent 891c1e64cc
commit ee2e91c954
2 changed files with 74 additions and 88 deletions

36
jtd1-06-workshop.tex Normal file
View File

@ -0,0 +1,36 @@
% СЕМИНАР: Давайте пилить стартовое окно.
% Объявим константы, заботливо подобранные заранее, ширина окна 350 высота 230, минимальная выигрышная длина 3, минимальный размер поля 3, и максимальные размеры 10, 10. эти значения нам нужны будут для создания слайдеров. И давайте сразу объявим переменную типа GameWindow gameWindow, и зададим ей значение из конструктора. this.gameWindow = gameWindow. установим ширину и длину. Теперь давайте, чтобы сделать красиво и ровно посередине, воспользуемся ещё одним новым на сегодня классом, Rectangle, который представляет собой абстрактный прямоугольник. назовём его gameWindowBounds и создадим, = gameWindow.getBounds(), сразу передав в него значения границ нашего окна. у окон есть и такой метод, getBounds(), да. В общем это просто удобный класс для работы с прямоугольниками, то есть с окнами например. И теперь делаем немного математики, задаём переменную
% int pos_x = (int)gameWindowBounds.getCenterX() - WIN_WIDTH / 2;
% int pos_y = (int)gameWindowBounds.getCenterY() - WIN_HEIGHT / 2;
% setLocation(pos_x, pos_y);
% setResizeable(false);
% setBorder(new EmptyBorder(3, 3, 6, 3));
% Сделаем заголовок, СОЗДАНИЕ НОВОЙ ИГРЫ. расположение сделаем сеткой setLayout(new GridLayout(10, 1)); Дальше нам надо добавить сюда много-много разных управляшек, и эти однотипные по сути действия мы для удобства вынесем в отдельный метод void addGameControlsMode(){};
% Для начала добавим надпись, для этого существует класс JLabel, пишем add(new JLabel(“Choose gaming mode”)); в конструктор можем передать собственно надпись, а можем ничего не передавать и задать её потом, но для этого надо будет поместить надпись в какой-то именованный контейнер, для нас сейчас это, понятно, лишнее.
% Дальше нужны будут две радиокнопки, которые пригодятся не только в этом методе, поэтому объявление вынесем вверх, а присваивание значений сделаем в нашем методе, который всё добавляет. выносим
% private JRadioButton humVSAI;
% private JRadioButton humVShum;
% и присвоим humVSAI = new JRadioButton(“Play VS Computer”); с надписью переданной в конструктор, и вторую также. добавим на наше окно и увидим что всё неправильно работает. add(hva); add(hvh);
% Чтобы всё заработало как мы ожидаем есть класс который связывает радиобатоны в группы, что видно из его названия ButtonGroup gameMode = newBG(); gmode.add(hvm); gmode.add(hvh); и саму радиогруппу никуда класть не надо, она просто объединит. и чтобы одна из них была выбрана по умолчанию, используем перегруженный конструктор радио кнопки, (“play vs comp”, true);
% ***** Слайдеры
% пишем следующий метод addGameControlsFieldWinLen(){} в котором будем описывать наши слайдеры, то есть понятно, что это всё ещё конструктор, просто мы делаем его чисто внешне менее жирным. Ну и чтобы логически разграничить, потому что код уже получается достаточно непростой.
% Напишем некий подзаголовок add(new JLabel(“Выбери длину”)); и пишем новенький лейбл на котором будем писать выигрышную длину
% JLabel lbl_win_len = new JLabel(“WIN_LEN” + WIN_LEN));
% add(lbl_win_len); этот видите мы создаём в два действия, потому что цифру на нём мы будем менять, а не задавать из программы. Давайте объявим слайдеры где-то рядом с JRadioButton'ами.
% private JSlider sField_size;
% JSlider sWin_len; и в методе также создадим. в конструкторе слайдеру можно передать три значения, мин, макс и текущее пишем
% sWin_len = new Slider(MIN_WIN, MIN_FIELD, MIN_WIN);
% на слайдер мы тоже вешаем слушателя изменений, только тут он называется ChangeListener пишем sWin_len.addChangeListener(new ChangeListener)); и в нём делаем изменение текста для нашего лейбла лбл_вин_лен.сетТекст(“WIN_LEN” + sWin_len.getValue);
% и добавим его на окно после лейбла. add(sWin_len); дальше создаём такой-же лейбл с размерами поля и аналогично ставим ему текст, добавим на окно. Теперь поинтереснее, создаём слайдер для поля, тоже аналогично, пишем чендж листнер и начинаем прикалываться, введём ещё одну переменную, current_field_size = sField_size.getValue() и будем использовать его в двух местах, во первых нам надо значения на лейбле менять, а во вторых, максимальное значение выигрышной длины тоже менять. лейбл.сетТекст(ФИЛД + каррент); и сВинЛен.сетМаксимум(каррент); и давайте аккуратно по порядку добавим слайдеры и надписи. Вот теперь вы никак не сможете ошибиться и сделать выигрышную длину больше чем размер поля.
% ***** И осталась нам только кнопочка “начать игру”
% в конце конструктора пишем JButton btnStartGame = new JButton(“New Game”); add(btnSG); вот у нас и получился один столбец и десять строчек. Осталось только оживить кнопочку, для этого напишем последний на сегодня метод назову его btnStart_onClick(),
% в кнопке пишем btnSG.addActionListener(new ActionListener()); и внутри любезно предоставленного шаблона просто вызовем наш метод, чтобы много не писать. А обработчик очень интересный, в нём нам надо вызвать метод startNewGame из основного окошка, но чтобы сделать это нам надо наполнить сигнатуру разными параметрами, которые мы возьмём с наших управляшек.
% Давайте в классе Map объявим пару констант, раз мы не изучали с вами перечисления. GAME_MODE_H_V_A = 0, GAME_MODE_H_V_H = 1; и в классе в котором работаем создадим новую переменную инт game_mode;
% пишем if(rb_hva.isSelected()) значит если кнопка выделена - game_mode = Map.HVA иначе rb_hvh.isSelected() game_mode = Map.HVH; ну и совсем иначе генерим исключение. мало-ли мы через пол года захотим ещё режимов надобавлять, батон добавим, а сюда забудем дописать, или пользователю не отобразится радиобатон, throw new RuntimeException(“No Buttons Selected”); и программа нам подскажет где искать.
% также с размером поля, которое пока что у нас будет квадратным, хоть мы и архитектурно заложили, что оно может быть не квадратным. эти показания мы просто снимем со слайдеров и передадим в конструктор slider.getValue(); ну и перед тем как закончить обработчик нам надо наше окошко спрятать от глаз пользователя до следующего раза, setVisible(false);
% ну вот мы выполнили план на сегодня, хоть мы и немного отошли от методички, но мы сделали хороший задел для нашего последнего занятия, нам там надо будет только написать логику, и расчертить полянку
% ***** Домашняя работа
% 1. Полностью разобраться с кодом (попробовать полностью самостоятельно переписать одно из окон)
% 2. Составить список вопросов и приложить в виде комментария к домашней работе
% 3. * Раертить панель Map на поле для игры, согласно fieldSize

View File

@ -154,7 +154,7 @@
06-25 & Итак нам понадобится генератор псевдослучайных чисел, потому что мы не будем на лекции создавать очень умного противника, символы, которыми мы будем обозначать на поле игрока, компьютер и пустую ячейку, собственно поле и его размеры. Размеры - на будущее. 06-25 & Итак нам понадобится генератор псевдослучайных чисел, потому что мы не будем на лекции создавать очень умного противника, символы, которыми мы будем обозначать на поле игрока, компьютер и пустую ячейку, собственно поле и его размеры. Размеры - на будущее.
%% 2 %% 2
Метод инициализации поля - просто создаём новый массив и заполняем его пустотой. Метод инициализации поля - просто создаём новый массив и заполняем его пустотой. Его вызов будет логично сразу разместить в метод старта новой игры.
%% 3 %% 3
Когда кто-то, мы или компьютер, будет совершать ход, нам будет важно, попал ли игрок ввобще в какую-то ячейку поля и пустота ли там, потому что, насколько я помню, нельзя ставить крестик поверх нолика и наоборот Когда кто-то, мы или компьютер, будет совершать ход, нам будет важно, попал ли игрок ввобще в какую-то ячейку поля и пустота ли там, потому что, насколько я помню, нельзя ставить крестик поверх нолика и наоборот
@ -163,98 +163,48 @@
Компьютер, как я и сказал, будем делать очень примитивный, он просто будет делать ход в случайные места на карте. Перед тем, как я покажу следующий слайд я прошу слабонервных удалиться от экранов и напоминаю, что мы делаем программу в учебных целях Компьютер, как я и сказал, будем делать очень примитивный, он просто будет делать ход в случайные места на карте. Перед тем, как я покажу следующий слайд я прошу слабонервных удалиться от экранов и напоминаю, что мы делаем программу в учебных целях
%% 5 %% 5
как вы видите, учебные цели предполагают не только демонстрацию того, как надо делать, но также и демонстрацию того как делать не надо. все трюки выполнены профессионалами, ни один тимлид после ревью кода не пострадал. на работе никогда так не делайте, всегда пишите с помощью циклов, потому что стоит нам захотеть поиграть на поле 4х4 или 5х5 - этот метод будет расти в геометрической прогрессии. как вы видите, учебные цели предполагают не только демонстрацию того, как надо делать, но также и демонстрацию того как делать не надо. все трюки выполнены профессионалами, ни один тимлид после ревью кода не пострадал. на работе никогда так не делайте, всегда пишите с помощью циклов, потому что стоит нам захотеть поиграть на поле 4х4 или 5х5 - этот метод будет расти в геометрической прогрессии. метод принимает на вход символ, который нужно проверить и проверяет - не победил ли он.
%% 6 %% 6
вот, например, достаточно хороший метод проверки поля на состояние ничьей. напомню, ничья в крестиках ноликах - это когда никто не победил и пустых клеток не осталось. \\ \hline а вот, например, достаточно хороший метод проверки поля на состояние ничьей. напомню, ничья в крестиках ноликах - это когда никто не победил и пустых клеток не осталось. \\ \hline
& ход игрока, рисование крестика и нолика \\ \hline 06-26 & Итак, теперь наша программа не просто умеет быть красивым окошком, но и может понять ряд интересных вещей, таких как, верный ли сделан ход, наступила ли ничья или кто-то победил. Всё, что мы будем делать дальше, будет сконцентрировано на методах обновления игрового состояния и отрисовки игрового поля. Ну что, нашли мы кликнутую ячейку, давайте в неё ходить, только нам для начала надо проверить, валидная-ли ячейка, и можно-ли туда ходить, вот и проверим: (если не валидная ячейка по координатам (cellX, cellY) или не пустая ячейка по тем же координатам) return; то есть если что-то пошло не так, игнорим клик, выходим из метода, ну а если всё хорошо - просто ходим филд[селХ][селУ] = humandot;
& конец игры \\ \hline %% 2
И добавим в метод отрисовки ещё немного логики. сделаем обход всех ячеек поля и внутри пишем, если ячейка пустая - просто идём дальше, ничего не делаем, дальше пишем маленькую заготовку: если в ячейке крестик - что-то сделаем, если нолик - сделаем что-то другое и в противном случае выбросим новый рантайм эксепшн с текстом (не пойму что в ячейке + и,ж), мало ли что мы там перекодили и допилили, может решили сделать баттл для трёх игроков?
%% 3
И теперь пришли к отрисовке. хотите, можете сюда картинку вставлять, хотите закрашенные квадратики, хотите рисуйте крестики-нолики, я для простоты буду пользоваться методом объекта графики g.fillOval() и буду рисовать кружки. ему в сигнатуре передаётся левая верхняя координата прямоугольника, в который потом будет вписан овал, и его ширина и высота соответственно. тем, кто хоть раз рисовал в пэинте - знакома эта техника рисования кружков. ну и чтобы придать ему цвета - перед тем как рисовать изменим цвет объекта graphics, g.setColor(Color.BLUE); Итак я предлагаю для человека рисовать синие кружки, а для компа красные. и чтобы сделать небольшой отступ от границ ячейки сделаем следующее, заведём константу DOTSPADDING = 5 пикселя. и первые координаты смещаем на + пэддинг вторые на - 2 пэддинга. Так посимпатичнее.
%% 4
по сути, нам осталось сделать две вещи - прикрутить так называемую бизнес логику, то есть в правильном порядке повызывать методы с логикой игры, избавиться от пары выпадающих исключений и нарисовать сообщение об окончании игры. начнём с конца. с конца игры. для того чтобы вывести результат, мы не будем придумывать ещё одно окно, это не слишком интересно, лучше напишем поверх игрового поля сообщение, а для этого нам надо как-то передать информацию о том, что игра завершена, для этого придумаем состояние окончания игры и опишем его вариации. \\ \hline
06-27 & Получается, теперь осталось это только оживить и нарисовать. Идём в метод апдейт и уточняем, что когда пользователь поставил точку, надо проверить состояние поля на наличие победы или ничьей, дать возможность компу поставить точку и сделать тоже самое
%% 2
дальше в принципе нам только и надо что нарисовать все красотульки. значит идём в метод рендер и думаем там. как только мы вывели поле, и если игра закончилась нам надо вывести сообщение с одним из вариантов исхода. так и запишем if (gameOver) шоуМессаджГеймовер(g); вот просто перевели наши слова на английский. теперь смотрим, чего у нас для этого не хватает в коде, как минимум метода шоумессаджгеймовер() и переменной признака конца игры. да не вопрос, мыжпрограммисты, напишем войд шоумессаджгеймовер(Graphics g), и где-то там вверху private boolean окончена ли игра. и поехали наполнять эти штуки смыслом. метод окончания игры рисует тёмно серый прямоугольник с жёлтой надписью о победе одного из игроков или ничьей в зависимости от геймоверстейта. если вдруг не поняли какой стейт у геймовера - кинули исключение.
%% 3
Осталось куда-то деть булево с окончанием игры и понять что за исключение у нас возникает при старте приложения. Почитав текст исключения мы понимаем, что оно возникает, когда программа не может что-то поделить на ноль. А что и когда она не может поделить на ноль? размеры поля до их инициализации, поэтому нам понадобиться ещё одна булева переменная - инициализирована ли игра.
В конструкторе панели, очевидно, поле ещё не инициализировано
В апдейте, очевидно, что нет смысла обрабатывать клики по неинициализированному полю или полю на котором закончилась игра
В старте новой игры, очевидно, игра перестаёт быть законченой, а поле становится инициализированным
Рендерить на неинициализированном поле нам тоже ничего не нужно.
А вот там, где мы проверяли не победил ли кто, нужно добавить истинность булевой с геймовером. \\ \hline
06-28 & Результат наших трудов вы можете видеть на экране, мне кажется, что получилось неплохо, да и с графическими интерфейсами короткое знакомство провели. \\ \hline
06-29 & На этой лекции мы познакомились с тем, как создавать окна, поговорили о менеджерах размещений, элементах графического интерфейса, и обработчиках событий. Многое осталось за границей этой лекции, во многом из-за того, что мы ещё не изучили такие интересные темы, как многопоточность и программные интерфейсы, но обязательно изучим. Изучение графических фреймворков не должно становиться для вас целью, если не хотите связать своё будущее с фронтенд разработкой, а раз уж вы здесь, думаю, вы уже сделали свой выбор. Разработка графического интерфейса подобного рода должна стать утилитарной задачей, то есть, решаться по мере поступления каких-то вопросов, поэтому мы оставим вопрос разработки графических интерфейсов на будущее самостоятельное изучение или даже лучше - просто периодически будем к ним возвращаться и делать наши приложения более привлекательными визуально. \\ \hline
06-29 & В качестве домашнего задания именно по графическим интерфейсам нужно будет полностью и досконально разобраться с кодом. Не бойтесь записывать свои вопросы и задавать их на семинарах и наставникам. А чтобы попрактиковаться в программировании нужно переделать метод проверки победы, чтобы на него не было больно смотреть. Будет классно, если получится сделать метод проверки победы для поля любого размера и любого числа крестиков-ноликов для победы, то есть, например, чтобы победить на поле 5х5 нужно собрать линию из 4х крестиков. Ну и если останется время, я отметил это задание аж двумя звёздочками, подумайте, как сделать компьютер немного умнее, чем просто случайным. специально отмечу в формулировке задания слово примитивно, потому что никто не ожидает от вас каких-то разборов графовых алгоритмов, минимакса и прочей теории игр. \\ \hline
06-30 & На сегодня это всё. Напоследок соглашусь с Иоганном Вольфгангом фон Гёте, если не очень нравится что-то делать, научиться этому будет довольно трудно. а если трудно, то правильно ли выбрано направление? Любите то, чему учитесь, знания будут сами собой оставаться в голове, без усилий. \\ \hline
\end{longtable} \end{longtable}
\end{document} \end{document}
***** Ход игрока
private static void humanTurn() {
int x, y;
do {
System.out.print("Введите координаты X и Y через пробел>> ");
x = SCANNER.nextInt() - 1;
y = SCANNER.nextInt() - 1;
} while (!isValidCell(x, y) || !isEmptyCell(x, y));
field[y][x] = HUMAN_DOT;
}
***** Игровой цикл
public static void main(String[] args) {
initMap(7, 5, 4);
printMap();
while (true) {
humanTurn();
printMap();
if (checkWin(HUMAN_DOT)) {
System.out.println("Выиграл игрок!!!");
break;
}
if (isMapFull()) {
System.out.println("Ничья!!!");
break;
}
aiTurn();
printMap();
if (checkWin(AI_DOT)) {
System.out.println("Выиграл компьютер!!!");
break;
}
if (isMapFull()) {
System.out.println("Ничья!!!");
break;
}
}
SCANNER.close();
}
***** берём наш код от крестиков-ноликов с третьего занятия
свой я вам выкладывал, так что если не готовы воспользоваться своим - возьмите мой. и копипастим его от мэйна до метода humanTurn(). всё, где логика и нет работы с консолью. вот вам пример необходимости писать методы, которые ничего лишнего не выводят. код не переписываем, а приложение уже совсем другое. Ну и видим, что нам не хватает кое каких констант. объявим private static final int EMPTY_DOT = 0, HUMAN_DOT = 1, AI_DOT = 2; убираем статики из методов, и чиним наш проект дальше.
***** Ну что, нашли мы кликнутую ячейку, давайте в неё ходить,
только нам для начала надо проверить, валидная-ли ячейка, и можно-ли туда ходить, вот и проверим: (!isValid(cellX, cellY) || !isEmptyCell(cellX, cellY)) return; игнорим клик, выходим из метода
ну а если всё хорошо - просто ходим филд[селХ][селУ] = hum_dot;
И добавить в наш метод отрисовки ещё немного логики. сделаем двойной массив по всем ячейкам нашего поля фори(филдСайзУ) фори(филдСайзХ) и внутри пишем
if(isEmptyCell(ж, и)) continue; то есть если ячейка пустая - просто идём дальше, ничего не делаем, дальше пишем маленькую заготовку:
if(field[i][j] == H_D) {} else if (field[i][j] == A_D) {} else throw new RTE (не пойму что в ячейке + и,ж) мало ли что мы там перекодили и допилили, может решили сделать баттл для трёх игроков?
И теперь пришли к отрисовке. хотите, можете сюда картинку вставлять, хотите закрашенные квадратики, хотите рисуйте крестики-нолики, я для простоты буду пользоваться методом g.fillOval() и буду рисовать кружки. ему в сигнатуре передаётся левая верхняя координата прямоугольника, в который потом будет вписан овал, и его ширина и высота соответственно. тем, кто хоть раз рисовал в пэинте - знакома эта техника рисования кружков. ну и чтобы придать ему цвета - перед тем как рисовать изменим цвет объекта graphics, g.setColor(Color.BLUE); или задать интами от 0 до 255 в формате РГБ new Color(RGB);
Итак я предлагаю для человека рисовать синие кружки, а для компа красные. и после проверки собственно его рисовать, в проверках пишем setColor(); а после проверок пишем g.fillOval(j*CellW, i*CellH, CW, CH); и чтобы сделать небольшой отступ сделаем следующее, заведём классовую константу DOTS_PAGGING = 5 пикселя. и первые координаты смещаем на + пэддинга вторые на - 2 пэддинга.
Так посимпатичнее.
***** Давайте завязывать с этим безумием
объявим константы которые будут содержать исходы игры, псфи DRAW = 0; HUM_WIN = 1; AI_WIN = 2; и нужна переменная stateGameOver в которой будет храниться статус.
Значит теперь в методе апдейт мы поставили точку, перерисовали и жедаем по старому сценарию
if(checkWin(H_D)) stGO = H_W; return;
if (isMapFull()) stGO = DR; return;
aiTurn();
repaint();
if(checkWin(A_D)) stGO = A_W; return;
if (isMapFull()) stGO = DR;
***** дальше в принципе нам только и надо что нарисовать все красотульки.
значит идём в метод рендер и думаем там. как только мы вывели поле, и если игра закончилась нам надо вывести сообщение с одним из вариантов исхода. так и запишем if (gameOver) showOverMessage(g); вот просто перевели наши слова на английский. теперь смотрим, чего у нас для этого не хватает в коде, как минимум метода showOverMessage() и переменной признака конца игры. да не вопрос, мыжпрограммисты, напишем войд sOW(Graphics g), private boolean gameOver. и поехали наполнять эти штуки смыслом:
в методе начала новой игры нам, очевидно, надо сказать что геймовер = фолс. и закончим на этом метод СНГ.
идём в метод апдейт и в самом начале пишем, что if(gameover) return; очевидно, что если геймовер случился - надо игнорировать вообще все клики. ну а если кто-то выиграл или ничья - геймовер также становится тру. и на этом закончим с методом апдейт и останется только дописать собственно метод показывающий, что у нас закончилась игра.
Ну и для того чтобы нарисовать в классе графики какой-то текст есть метод g.drawString() с выводимой строкой и координатами по х и по у, внимание, координата по у это координата нижней части букв, как строчка в тетради. Ну и для того чтобы этот текст кастомизировать да и вообще для работы со шрифтами есть класс не поверите Font. создадим классовый private final Font который назовём не поверите font = new Font( и на вход конструктор внезапно принимает название шрифта, стиль и размер “Times new roman”, Font.BOLD, 48);
будьте осторожны при работе с такими неочевидными классами. и теперь наш метод с отрисовкой строки немного дополняем, setColor(); setFont(font). вот так он и выводится. Давайте создадим классовые константы, в которых будем хранить наши сообщения о победах. psfS MSG_DRAW= “DRAW”; MSG_HW; MSG_AW;
***** в showGameOver
пишем свич (стейтГеймОвер) и кейсы ДРО, Х_В, А_В. дефолт throw new RTE(unexpected GO message);
теперь если дро, g.drawString(MSG_DRAW, 180, getHeight/2);
если H_W, g.drawString(HW, 70, gH/2);
если A_W (20);
и перед ним пишем setColor(YELLOW) setFont(font);
ну и чтобы было виднее, я предлагаю рисовать тёмно серый прямоугольник в качестве фона работает он также как fillOval только рисует прямоугольник пишем fillRect(0, 200, getWidth, 70); естественно надо все магические цифры либо вывести в константы, либо как-то динамически считать). и БУМ! всё классно, мы закончили.
***** Домашнее задание
1. Полностью разобраться с кодом;
2. Переделать проверку победы, чтобы она не была реализована просто набором условий.
3. * Попробовать переписать логику проверки победы, чтобы она работала для поля 5х5 и количества фишек 4.
4. *** Доработать искусственный интеллект, чтобы он мог блокировать ходы игрока, и пытаться выиграть сам.