Введение в JavaFX – Модель шахмат - Программа «Шахматы»

ОГЛАВЛЕНИЕ

Программа «Шахматы»

После создания окна следующим шагом является создание шахматной доски. Мы создадим пользовательский компонент шахматной доски для графического пользовательского интерфейса на JavaFX. Если бы мы делали это в Java, наша шахматная доска расширяла бы JPanel и мы бы добавили эту JPanel к нашей JFrame. В JavaFX наша шахматная доска будет расширять Scene и будет добавлена к нашему объекту Stage.

Перед созданием класса Board extends (расширяет) Scene нужно рассказать немного о доске, которую мы хотим создать. При создании программы на JavaFX мы решили использовать преимущество, предоставляемое большими возможностями нового языка для работы с графикой. В этом случае нам стоит использовать графику SVG для наших шахматных фигур, чтобы они хорошо выглядели в любом разрешении. Пользователь должен иметь возможность изменять размер шахматной доски, и она должна выглядеть хорошо независимо от расположения границ главного окна приложения. Следующий скриншот объясняет, как должна выглядеть доска при изменении размеров главного окна.

Шахматная доска изменяет свои размеры и положение в зависимости от размеров главного окна

Доска пытается занять как можно большую часть окна, насколько это возможно при сохранении квадратной формы. Если ширина окна больше, чем его высота, или его высота больше, чем ширина, то доска центрируется в окне. Если окно имеет квадратную форму, доска не займет окно полностью, но займет большую часть окна. При этом вокруг доски всегда остается небольшая рамка. Ширина и высота этой рамки по крайней мере равняются размеру одного квадрата на шахматной доске. Так что доска имеет следующие 3 атрибута:

  • squareSize – Размер одного квадрата на шахматной доске
  • xOffset – Если ширина окна больше, чем его высота, этот атрибут говорит о том, насколько далеко нужно передвинуть доску, чтобы расположить ее по центру окна
  • yOffset – Если высота окна больше, чем ширина, этот атрибут говорит о том, насколько далеко нужно передвинуть доску, чтобы расположить ее по центру окна.

Связывание в JavaFX

Связывание – это новая возможность в JavaFX, позволяющая связать значение одной переменной с другой переменной или связать значение метода с другой переменной. Связывание выполняется с помощью ключевого слова bind. Так как значения атрибутов squareSize, xOffset и yOffset зависят от размера шахматной доски, свяжем их значения с размером шахматной доски. Ниже приводится код для шахматной доски:

Шахматная доска использует "bind" для динамического изменения своего размера и положения

import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;

public class Board extends Scene {
    def LIGHT_COLOR: Color = Color.web("lemonchiffon");
    def DARK_COLOR: Color = Color.web("brown");

    public-read var squareSize = bind {
        if(width > height) {
            height / 10;
        } else {
            width / 10;
        }
    }

    public-read var xOffset = bind {
        if (width > height) {
            (width - height) / 2;
        } else }
            0;
        }
    }

    public-read var yOffset = bind {
        if (width > height) {
            0;
        } else {
            (height - width) / 2;
        }
    }

    def board = [ Coord.A8, Coord.B8, Coord.C8, Coord.D8,
            Coord.E8, Coord.F8, Coord.G8, Coord.H8,
                  Coord.A7, Coord.B7, Coord.C7, Coord.D7,
            Coord.E7, Coord.F7, Coord.G7, Coord.H7,
                  Coord.A6, Coord.B6, Coord.C6, Coord.D6,
            Coord.E6, Coord.F6, Coord.G6, Coord.H6,
                  Coord.A5, Coord.B5, Coord.C5, Coord.D5,
            Coord.E5, Coord.F5, Coord.G5, Coord.H5,
                  Coord.A4, Coord.B4, Coord.C4, Coord.D4,
            Coord.E4, Coord.F4, Coord.G4, Coord.H4,
                  Coord.A3, Coord.B3, Coord.C3, Coord.D3,
            Coord.E3, Coord.F3, Coord.G3, Coord.H3,
                  Coord.A2, Coord.B2, Coord.C2, Coord.D2,
            Coord.E2, Coord.F2, Coord.G2, Coord.H2,
                  Coord.A1, Coord.B1, Coord.C1, Coord.D1,
            Coord.E1, Coord.F1, Coord.G1, Coord.H1 ];

    postinit {
        for (square in board) {
            def i: Integer = indexof square;
            insert Rectangle {
                fill: if (square.getIsWhite()) LIGHT_COLOR else DARK_COLOR
                x: bind xOffset + ((i mod 8) + 1) * squareSize
                y: bind yOffset + ((i / 8) + 1) * squareSize
                width: bind squareSize
                height: bind squareSize
            } into content;
        }
    }
}

Coord – это просто enum (перечисление), обеспечивающий более легкое размещение шахматных фигур. Coord также отслеживает, является ли квадрат с теми координатами темным или ярко окрашенным квадратом. В шахматах королева всегда помещается на ее цвет, и нижний левый квадрат доски всегда темный. Это помогает не допустить помещения короля или королевы не на то место доски. JavaFX не может создавать enum, поэтому класс Coord написан на Java. Вот почему вам необходимы компиляторы Java и JavaFX для компоновки этого проекта. Нужно скомпоновать файл Coord.java, используя компилятор Java, перед компоновкой файлов на JavaFX с помощью компилятора JavaFX.

Этот класс использует множество связываний. Первое связывание применяется для атрибута класса class squareSize. squareSize в данном случае связывается с выражением. Если значение выражения изменяется, значение атрибута squareSize будет автоматически изменяться на соответствующее новому значению выражение. Выражение, с которым связан этот атрибут, является блоком кода. Значение этого блока кода равняется последней строке, выполненной в блоке кода. В данном случае, последняя выполненная строка зависит от значений атрибутов width и height объекта Scene. Значение squareSize будет установлено в height / 10; или width / 10;.

Класс Board будет помещен в класс Stage (главное окно приложения). Board будет заполнять всю клиентскую область Stage, обычно это целое окно, исключая рамку. Если размер окна изменяется, то атрибуты width и height класса Board будут изменяться. Когда их значения изменяются, среда выполнения JavaFX будет автоматически изменять значение атрибута squareSize.

Аналогично, атрибуты xOffset и yOffset связаны со значениями width и height.

postinit вместо конструкторов

Следующие связывания можно увидеть в блоке кода postinit. Как мы говорили раньше, классы JavaFX не имеют конструкторов. Однако всегда нужно выполнять какой-то код при первоначальном создании объекта. JavaFX позволяет определить код, который будет выполняться при создании объекта, с помощью использования ключевого слова postinit. Код, помещенный в блок кода postinit, будет автоматически выполняться при создании объекта, подобно вызову конструктора в Java.

В блоке Boards postinit создается Rectangle (прямоугольник) для каждого квадрата на шахматной доске. Местоположение и размер каждого из Rectangles на доске связаны со значениями squareSize, xOffset и yOffset. Это делается с помощью помещения ключевого слова bind после атрибутов x, y, width и height каждого из Rectangles, используемых для каждого из квадратов, и помещения выражения, с которым атрибуты должны быть связаны, после ключевого слова bind.

JavaFX имеет новый набор модификаторов доступа

Класс Board использует модификатор доступа, который не существует в языке программирования Java, public-read. JavaFX использует новый набор модификаторов доступа, описанный ниже:

  • default (по умолчанию) – Если вы не используете модификатор доступа для переменной или функции, по умолчанию устанавливается доступ только для сценария (script-only). Это означает, что можно получить доступ к переменной/функции только в пределах текущего файла сценария. JavaFX не имеет модификатора доступа private, модификатор доступа по умолчанию в JavaFX эквивалентен private в Java. (Замечание: мы используем термины «переменная» и «функция», потому что в JavaFX не требуется использовать классы, однако если вы используете класс, лучше применять термины «атрибут» и «метод».)
  • package – Этот модификатор делает переменную или функцию доступной для любого другого кода в этом же пакете. Это аналогично модификатору доступа по умолчанию в Java (Java не использует ключевое слово package как модификатор доступа).
  • protected - Этот модификатор делает переменную или функцию доступной для любого другого кода в этом же пакете и также для всех подклассов. Java использует такое же ключевое слово, имеющее такое же значение.
  • public - Этот модификатор делает переменную или функцию доступной в любом месте для доступа с целью чтения или записи. Java использует такое же ключевое слово, имеющее такое же значение.
  • public-read – Этот модификатор доступа используется в классе Board. Этот модификатор доступа можно применять только к переменным, его нельзя применять к функциям. Он открывает доступ к переменной с целью чтения из любого места, ограничивая доступ с целью записи рамками данного сценария. Java не имеет подобного модификатора доступа. Можно получить такую же функциональность в Java, объявив атрибут private и предоставив метод public, извлекающий значение атрибута. Пример этого показан в коде ниже.
  • public-init – Переменная, обозначенная как public-init, может записываться из любого места при первоначальном создании объекта. После создания объекта ее можно прочитать из любого места, но запись в переменную возможна только с помощью сценария, в котором определяется переменная. Как и модификатор доступа public-read, модификатор доступа public-init можно использовать только для переменной, но нельзя использовать для функции. Эквивалентный модификатор доступа не существует в Java. Вы можете получить такую же функциональность в Java, объявив атрибут private и предоставив метод public, извлекающий значение атрибута, и позволив устанавливать начальное значение атрибута через параметр, передаваемый конструктору. Пример этого дается в коде ниже:

Код "public-read" and "public-init" на JavaFX, преобразованный в код Java

// код JavaFX
public-read size: Integer;
public-init height: Integer;

// Такой же код на Java
public class MyClass {
    // только этот класс может записывать в атрибут size
    private int size;
    private int height;

    // другие классы могут записывать только в атрибут height с помощью этого конструктора
    // после создания объекта только этот класс может записывать в атрибут height
    public MyClass(int initHeight) {
        height = initHeight;
    }

    // любой может прочитать значение атрибута size
    public int getSize() {
        return size;
    }

    // любой может прочитать значение атрибута height
    public int getHeight() {
        return height;
    }
}

Есть одна вещь, которую мы можем сделать в Java, но которую не смогли сделать в JavaFX. В Java мы всегда можем объявить атрибут класса как final, в JavaFX можем это сделать, используя ключевое слово def. Однако, в Java можно позволить любому устанавливать начальное значение этого атрибута, передавая значение в конструктор. В JavaFX этого сделать нельзя. Мы пытались объявить переменную как public-init def, но это вызывает ошибку, если не присвоить значение переменной в том же месте.

В JavaFX вы не можете инициализировать переменную "def" (то есть final) из другого класса

// ниже приведен корректный код Java, для которого
// не существует аналога в JavaFX
public class Test {
    public final int i;

    public Test(int initI) {
        i = initI;
    }
}

// следующий код в JavaFX не будет компилироваться
public class Test {
    public-init def i: Integer;
}

def test: Test = Test { i: 7 }

// здесь есть сообщение об ошибке
Test.fx:2: The 'def' of 'i' must be initialized with a value here.
                Perhaps you meant to use 'var'?
    public-init def i: Integer

1 error

// если устанавливать значение атрибута i, получается еще больше сообщений об ошибке
class Test {
    public-init def i: Integer = 4;
}

def test: Test = Test { i: 7 }

// здесь есть сообщения об ошибке
Test.fx:2: modifier public-init not allowed on a def
    public-init def i: Integer = 4;

Test.fx:5: You cannot change the value(s) of 'i'
    because it was declared as a 'def', perhaps it should be a 'var'?
    def test: Test = Test { i: 7 }

2 errors

Полезной окажется возможность объявить переменную как public-init def. Мы хотели использовать это для шахматных фигур, объявляя фигуру как черную или белую, когда мы создали фигуру. Когда цвет фигуры установлен, его больше нельзя изменить, поэтому мы хотели объявить этот параметр как def, но не смогли этого сделать. Мы вынуждены объявить его как public-init var и проверять, что он не изменяется после первоначального присвоения значения. Это второе решение создателей JavaFX, которое кажется нам неправильным. (Первым было решение объявить атрибуты x, y, width и height класса Stage как NumbersIntegers.) вместо