JavaRush /Java блог /Архив info.javarush /Игра на java для начинающих
timurnav
21 уровень

Игра на java для начинающих

Статья из группы Архив info.javarush
Привет, друзья и будущие коллеги! Игра на java для начинающих - 1Совсем недавно я проходил тестирование для участия в реальном проекте, прошел его, но так уж сложилось, что я по личным обстоятельствам не смог принять участие в самом РП. После таких интересных задач, как тест на РП, обычные задачи курса стали менее привлекательным времяпрепровождением, тем более, большую часть я уже решил. Поэтому чтобы талант не пропадал зря продолжать обучение, я решил создать многопользовательскую веб-игру. Ссылки на другие игры:
  1. продолжение этой статьи
  2. 2048
Самой простой игрой мне показались крестики нолики, я решил разбить задачу на ряд подзадач:
  1. Консольное приложение для отработки игровой логики
  2. Мультиплеер
  3. Прикручивание базы данных игроков к консольному приложению
  4. Создание дизайна фронтенда, написание шаблонов страниц, игрового интерфейса
  5. Сборка "всего" воедино
Есть вероятность, что меня поругают, за такую последовательность, и скорей всего все серьезные проекты строятся в совсем иной последовательности, отвечу сразу, напишите об этом пост "для начинающих", чтобы все (и я в том числе) научились этому :) Чтож.. приступим к написанию консольного приложения! Я пойду по тем же шагам, что и в больших задачах 20-х уровней. Что есть в игре крестики-нолики?!
  1. поле
  2. два игрока, которые ходят по очереди, один ставит крестик, второй нолик. всё просто.
Поле делаем стандартное поле 3х3. В чем можно хранить такое поле? первый вариант — двумерный массив. Какие элементы должны содержаться в этом массиве? ответ – нужно подумать, что мы будем делать с этими элементами, это вывод на экран и сравнение для поиска победителя. Если бы мы их только выводили на экран, то логично было бы держать их в виде строки, тогда сам массив и вывод на экран в цикле выглядели бы как-то так:

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 комбинации, в зависимости от ячейки, см. рисунок: Игра на java для начинающих - 2теперь нужно придумать, как определить какую комбинацию нужно запустить? Вот тут я понял, что использовать двухмерный массив не очень удобно. Я решил рассмотреть еще варианты. Сначала я придумал, что поле можно держать в девятизначной цифре, например, то самое поле, которое мы вывели на экран можно записать так 220012111, объясню на пальцах что это ... Шифр прежний, 1 – "Х", 2 – "О", 0 – " ", значит 220012111 = "OO__XOXXX", или если после каждой третьей цифрой воткнуть перенос строки и добавить пробелы для наглядности:

О О _
_ Х О
Х Х Х
вот опять, удобно для хранения, приспособу для отображения придумали, но неудобно для сравнения! Решение нашлось когда я пронумеровал ячейки 1-9, потом подумал, ведь в программировании отсчет начинается с 0 и пронумеровал как на картинке Игра на java для начинающих - 3Не заметили никаких особенностей? если посмотреть на картинку выше, то станет ясно, что решения имеющие 2 комбинации, имеют нечетный порядковый номер, 4 комбинации - это порядковый номер 4, 3 комбинации – остальные. так я и пришел к тому, что нужно держать игровое поле в обычном массиве чисел: простая итерация между числами, возможность сравнения по алгоритму, который был выбран, простой вывод на экран. Что касается самого алгоритма сравнения. идем по порядку: во всех вариантах есть проверка строки и столбца, проверяем только их. если поиск не дел результатов, проверяем номер ячейки на чёт/нечёт, если нечетная то возвращаемся к игре, нет смысла проверять дальше, если четная, проверяем лежит ли на левой диагонали эта ячейка, номера этой диагонали при делении на 4 в остатке имеют 0. Если она лежит проверяем на совпадения, если совпадений не найдено, то проверяем на цифру 4, если нет – возврат в игру, если да идем дальше по коду и возвращаем результат проверки последней диагонали. Вероятно, для неподготовленного человека, это сложно понять прочитав набор букв выше, а кто-то может сказать, что много букв и в самом коде, что можно проще, буду рад обсудить это. С полем разобрались, теперь нужно разобраться с двумя пользователями, которые ходят по очереди и у каждого из них свой знак, Х или О. На первом этапе у нас нет никакой многопользовательности, значит проще всего будет использовать значки по очереди. Первый ход делает всегда Х, второй всегда О, потом снова Х и так далее. Напрашивается поставить флажок (true/false), и если true – то текущий игрок X, если false – то О и вначале каждого хода флажок=!флажок Осталось как-то принимать сигнал от игроков, о том какую ячейку они выбирают. Тут нам пригодится наш незабвенный BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); Игроки будут вводить номера ячеек, в консоль, и по нажатию Enter будет производиться ход. Ячейка соответствующая введенному номеру, будет менять значение с 0 на 1 или 2, в зависимости от текущего состояния флажка, который обсуждался абзацем выше. Вот тут важно сделать валидацию ввода, чтобы никто не смог поменять Х на О, когда ячейка уже заполнена :) Что может ввести в консоль игрок?
  1. пустая строка
  2. буквы, знаки препинания, скобки.. одним словом неЦифры
  3. некорректные цифры - отрицательные или находящиеся за пределами размеров массива, занятые ячейки.
Стандартный метод получения цифры из строки это статический метод parseInt класса Integer 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;
    }
}
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Игорь Уровень 34
6 ноября 2021
если бы еще ресурсы не забыл закрыть, было бы огонь
Anonymous #2361757 Уровень 38
7 августа 2020
Если никто не выиграл, то что с этого ? Игра продолжается.
Bender Уровень 20
3 ноября 2019
Понял решение только после того как Прочёл код , тяжело просто по тексту представить все в голове, крутое решение в целом , надеюсь что я тоже когда-нибудь смогу создавать что то подобное`, всем добра!
Vova Krulevsky Уровень 1
11 января 2019
надо написать игру дуель роботов штоб в консоли виводилось робот которий стриляет в другово и отнималось hp кто поможет
Vova Krulevsky Уровень 1
11 января 2019
кто может помочь?
XmaynS Уровень 0
6 октября 2018
Извините, не могли бы вы дать ссылку на пост с крестиками ноликами но с классами И да спасибо хорошо и понятно обьясняете! Ну или помогите решить проблему Ошибка: главный метод не найден в классе com.company.Main, пожалуйста, определите основной метод как:     public static void main (String [] args) или класс приложения JavaFX должен расширять javafx.application.Application
vovka_squid Уровень 17
29 апреля 2015
Молодец, но почему так мало классов?
Вынеси игроков и саму игру в разные классы и оперируй объектами. Мне кажется, тогда легче будет вводить новый функционал.
Kashey Уровень 11
29 апреля 2015
Молодец, классная игра!