Привет, друзья и будущие коллеги!
Совсем недавно я проходил тестирование для участия в реальном проекте, прошел его, но так уж сложилось, что я по личным обстоятельствам не смог принять участие в самом РП.
После таких интересных задач, как тест на РП, обычные задачи курса стали менее привлекательным времяпрепровождением, тем более, большую часть я уже решил. Поэтому чтобы талант не пропадал зря продолжать обучение, я решил создать многопользовательскую веб-игру.
Ссылки на другие игры:
Самой простой игрой мне показались крестики нолики, я решил разбить задачу на ряд подзадач:
Самый первый алгоритм — это проверка всего поля
- Консольное приложение для отработки игровой логики
- Мультиплеер
- Прикручивание базы данных игроков к консольному приложению
- Создание дизайна фронтенда, написание шаблонов страниц, игрового интерфейса
- Сборка "всего" воедино
- поле
- два игрока, которые ходят по очереди, один ставит крестик, второй нолик. всё просто.
String[][] strings = {{"O", "O", "_"},
{"_", "X", "O"},
{"X", "X", "X"},
for (String [] ss : strings){
for (String s : ss) System.out.print(s + " ");
System.out.println(); //для перевода строки
}
на экране бы отобразилось:
O O _
_ X O
X X X
Но кроме отображения, у нас есть еще сравнение значений, а тут уже возможны варианты.
Можно сравнивать строки, можно создать специальный класс-перечисление (enum
), но я предпочел бы сравнивать числа, а на "Х" и "О" заменять их только при выводе на экран. Пусть будет, например, 1 - "Х", 2 - "О", 0 - "_".
итак, как же проверять поле на тройное совпадение Х или О?Самый первый алгоритм — это проверка всего поля
int[][] canvas = {{00, 01, 02},
{10, 11, 12},
{20, 21, 22}}
Комбинации для выигрыша:
00-01-02, 10-11-12, 20-21-22, 00-10-20, 01-11-21, 02-12-22, 00-11-22, 20-11-02 — всего 8.
Проверка сравнением цифр, но это получается нужно каждый раз проверять ВСЁ поле, все 8 комбинаций. Конечно же, это не много, это не поиск чисел Армстронга в интервале от 0 до 1 млрд, здесь вычислений чуть более чем нет совсем, но всё равно хочется что-то более оптимальное, чем проверка всего поля.
Вторая идея которая меня посетила, это проверять только ячейку, которую отметили на предыдущем ходе, так еще можно определить победителя, ведь мы будем знать кто сделал этот ход.
Таким образом, вместо всех 8 комбинаций мы получаем всего 2, 3 или 4 комбинации, в зависимости от ячейки, см. рисунок:
теперь нужно придумать, как определить какую комбинацию нужно запустить? Вот тут я понял, что использовать двухмерный массив не очень удобно. Я решил рассмотреть еще варианты.
Сначала я придумал, что поле можно держать в девятизначной цифре, например, то самое поле, которое мы вывели на экран можно записать так 220012111, объясню на пальцах что это ...
Шифр прежний, 1 – "Х", 2 – "О", 0 – " ", значит 220012111 = "OO__XOXXX", или если после каждой третьей цифрой воткнуть перенос строки и добавить пробелы для наглядности:
О О _
_ Х О
Х Х Х
вот опять, удобно для хранения, приспособу для отображения придумали, но неудобно для сравнения!
Решение нашлось когда я пронумеровал ячейки 1-9, потом подумал, ведь в программировании отсчет начинается с 0 и пронумеровал как на картинке
Не заметили никаких особенностей? если посмотреть на картинку выше, то станет ясно, что решения имеющие 2 комбинации, имеют нечетный порядковый номер, 4 комбинации - это порядковый номер 4, 3 комбинации – остальные. так я и пришел к тому, что нужно держать игровое поле в обычном массиве чисел: простая итерация между числами, возможность сравнения по алгоритму, который был выбран, простой вывод на экран.
Что касается самого алгоритма сравнения. идем по порядку: во всех вариантах есть проверка строки и столбца, проверяем только их. если поиск не дел результатов, проверяем номер ячейки на чёт/нечёт, если нечетная то возвращаемся к игре, нет смысла проверять дальше, если четная, проверяем лежит ли на левой диагонали эта ячейка, номера этой диагонали при делении на 4 в остатке имеют 0. Если она лежит проверяем на совпадения, если совпадений не найдено, то проверяем на цифру 4, если нет – возврат в игру, если да идем дальше по коду и возвращаем результат проверки последней диагонали. Вероятно, для неподготовленного человека, это сложно понять прочитав набор букв выше, а кто-то может сказать, что много букв и в самом коде, что можно проще, буду рад обсудить это.
С полем разобрались, теперь нужно разобраться с двумя пользователями, которые ходят по очереди и у каждого из них свой знак, Х или О.
На первом этапе у нас нет никакой многопользовательности, значит проще всего будет использовать значки по очереди. Первый ход делает всегда Х, второй всегда О, потом снова Х и так далее. Напрашивается поставить флажок (true/false), и если true – то текущий игрок X, если false – то О и вначале каждого хода флажок=!флажок
Осталось как-то принимать сигнал от игроков, о том какую ячейку они выбирают. Тут нам пригодится наш незабвенный BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Игроки будут вводить номера ячеек, в консоль, и по нажатию Enter будет производиться ход. Ячейка соответствующая введенному номеру, будет менять значение с 0 на 1 или 2, в зависимости от текущего состояния флажка, который обсуждался абзацем выше.
Вот тут важно сделать валидацию ввода, чтобы никто не смог поменять Х на О, когда ячейка уже заполнена :)
Что может ввести в консоль игрок?
- пустая строка
- буквы, знаки препинания, скобки.. одним словом неЦифры
- некорректные цифры - отрицательные или находящиеся за пределами размеров массива, занятые ячейки.
Integer.parseInt("2");
Он бросает исключение NumberFormatException
, если не может получить цифру из заданной строки, защиту от первых двух пунктов мы сможем обеспечить перехватом этого исключения.
Для третьего пункта я бы создал еще один метод, который проверяет введенное значение, но правильнее всего будет вынести запрос строки в отдельный метод, в котором будет производиться валидация, а возвращать он будет только число.
Резюмируем, мы создали поле, сделали метод, который его отображает, сделали метод, который производит проверку "а не победил ли этот игрок часом?", сделали валидацию вводимых чисел.
Осталось совсем немного, сделать проверку на ничью - отдельный метод, который пробегает по массиву и ищет 0, и отображение результатов игры.
На этом всё, код готов, игра получилась небольшой, всего один класс, поэтому жесткие копипастеры могут не разбираясь, просто всё скопировать в свой проект и запустить его у себя, я и сам таким был, но сейчас стараюсь уже так не делать и никому не советую :)
Всем удачи в изучении JAVA!
p.s. остальные пункты — мультиплейер и БД будут позже, я уже начал изучение материала :)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class GameField {
static int [] canvas = {0,0,0,
0,0,0,
0,0,0};
//012, 345, 678, 036, 147, 258, 048, 246
public static void main(String[] args){
boolean b;
boolean isCurrentX = false;
do {
isCurrentX = !isCurrentX;
drawCanvas();
System.out.println("mark " + (isCurrentX ? "X" : "O"));
int n = getNumber();
canvas[n] = isCurrentX ? 1 : 2;
b = !isGameOver(n);
if (isDraw()){
System.out.println("Draw");
return;
}
} while (b);
drawCanvas();
System.out.println();
System.out.println("The winner is " + (isCurrentX ? "X" : "O") + "!");
}
static int getNumber(){
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true){
try {
int n = Integer.parseInt(reader.readLine());
if (n >= 0 && n < canvas.length && canvas[n]==0){
return n;
}
System.out.println("Choose free cell and enter its number");
} catch (NumberFormatException e) {
System.out.println("Please enter the number");
} catch (IOException e) {
}
}
}
static boolean isGameOver(int n){
// 0 1 2
// 3 4 5
// 6 7 8
//поиск совпадений по горизонтали
int row = n-n%3; //номер строки - проверяем только её
if (canvas[row]==canvas[row+1] &&
canvas[row]==canvas[row+2]) return true;
//поиск совпадений по вертикали
int column = n%3; //номер столбца - проверяем только его
if (canvas[column]==canvas[column+3])
if (canvas[column]==canvas[column+6]) return true;
//мы здесь, значит, первый поиск не положительного результата
//если значение n находится на одной из граней - возвращаем false
if (n%2!=0) return false;
//проверяем принадлежит ли к левой диагонали значение
if (n%4==0){
//проверяем есть ли совпадения на левой диагонали
if (canvas[0] == canvas[4] &&
canvas[0] == canvas[8]) return true;
if (n!=4) return false;
}
return canvas[2] == canvas[4] &&
canvas[2] == canvas[6];
}
static void drawCanvas(){
System.out.println(" | | ");
for (int i = 0; i < canvas.length; i++) {
if (i!=0){
if (i%3==0) {
System.out.println();
System.out.println("_____|_____|_____");
System.out.println(" | | ");
}
else
System.out.print("|");
}
if (canvas[i]==0) System.out.print(" " + i + " ");
if (canvas[i]==1) System.out.print(" X ");
if (canvas[i]==2) System.out.print(" O ");
}
System.out.println();
System.out.println(" | | ");
}
public static boolean isDraw() {
for (int n : canvas) if (n==0) return false;
return true;
}
}
Вынеси игроков и саму игру в разные классы и оперируй объектами. Мне кажется, тогда легче будет вводить новый функционал.