Текст книги "Создание игр для мобильных телефонов"
Автор книги: Майкл Моррисон
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 26 (всего у книги 35 страниц)
Холст игры Connect 4 находится в классе C4Canvas, который в значительной степени отвечает за работу игры. Ниже приведены переменные этого класса:
private Display display;
private boolean sleeping;
private long frameDelay;
private Image[] piece = new Image[2];
private Sprite arrowSprite;
private Player legalPlayer;
private Player illegalPlayer;
private Player winPlayer;
private Player losePlayer;
private C4State gameState; //Класс C4State содержит большую часть логики игры Connect 4, включая положения фишек на игровой доске
private C4Client client;
private C4Server server;
private boolean isServer; //Эта переменная показывает, является ли данный экземпляр игры сервером
private String status = "";
private boolean gameOver;
private boolean myMove;
private int curSlot;
Переменная piece хранит изображения фишек, используемых в игре (одну красного цвета и одну синего). Переменная arrowSprite – это спрайт с изображением стрелки, который выводится на игровом поле и указывает на текущую выбранную колонку. Переменные типа Player – это проигрыватели, сопровождающие звуком события, например, ход игрока, невозможный ход, победа или поражение.
В копилку Игрока
Невозможный ход в игре Connect 4 – это попытка поставить фишку в уже заполненную колонку.
Переменная gameState очень важна, ее назначение следует объяснить подробно. Вы узнаете о ней намного больше в следующей главе, а пока важно понять, что эта переменная отражает состояние игры Connect 4 в любой момент времени, включая положение фишек на доске, счет, таблицу выигрышных комбинаций, которая используется для определения победителя.
Переменные client и server представляют клиентскую и серверную сетевые компоненты. Важно понять, что в каждом запущенном образе игры используется лишь одна из указанных переменных. Иначе говоря, если игра работает в режиме сервера, то используется переменная server, в противном случае – client. Переменная isServer отслеживает, работает ли программа в режиме сервера.
Переменная status содержит текст, выводимый в строке состояния, а переменная gameOver говорит, закончена игра или нет. Переменная myMove определяет, может ли игрок совершить ход, или следует ожидать хода соперника. И наконец, переменная curSlot хранит номер текущего выбранного столбца на игровой доске.
Переменные класса C4Canvas впервые появляются в методе start(), код которого приведен в листинге 15.5.
Листинг 15.5. Метод start() класса C4Canvas начинается с инициализации переменных игры и активации сервиса клиент/сервер
public void start() {
// установить вывод на экран
display.setCurrent(this);
// инициализация изображений фишек
try {
piece[0] = Image.createImage("/RedPiece.png");
piece[1] = Image.createImage("/BluePiece.png");
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
// инициализация спрайта стрелки
try {
// Create the arrow sprite
arrowSprite = new Sprite(Image.createImage("/Arrow.png"), 18, 16);
arrowSprite.setFrame(isServer ? 0 : 1); //Спрайт стрелки имеет два фрейма (синий и красный), каждый из которых используется в определенном режиме работы
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
// инициализация проигрователей
try {
InputStream is = getClass().getResourceAsStream("Legal.wav");
legalPlayer = Manager.createPlayer(is, "audio/X-wav");
legalPlayer.prefetch();
is = getClass().getResourceAsStream("Illegal.wav");
illegalPlayer = Manager.createPlayer(is, "audio/X-wav");
illegalPlayer.prefetch();
is = getClass().getResourceAsStream("Win.wav");
winPlayer = Manager.createPlayer(is, "audio/X-wav");
winPlayer.prefetch();
is = getClass().getResourceAsStream("Lose.wav");
losePlayer = Manager.createPlayer(is, "audio/X-wav");
losePlayer.prefetch();
}
catch (IOException ioe) {
}
catch (MediaException me) {
}
// инициализация переменных игры
gameOver = true;
myMove = !isServer; // клиент всегда ходит первым
curSlot = 0;
gameState = new C4State();
// запуск сетевого сервиса
if (isServer) { //Начиная с этой точки мидлет работает в режиме сервера или клиента
server = new C4Server(this);
server.start();
}
else {
client = new C4Client(this);
client.start();
}
// запуск потока анимации
sleeping = false;
Thread t = new Thread(this);
t.start();
}
В методе start() выполняется ряд важных инициализаций, например, изображений фишек и стрелки. Спрайт стрелки состоит из двух фреймов – синей и красной стрелок, цвет стрелки выбирается в соответствие с режимом работы игры (клиент или сервер). Затем выполняется инициализация проигрывателей, после чего инициализируются четыре основные игровые переменные (gameOver, myMove, curSlot и gameState). В зависимости от значения переменной isServer запускается нужный сетевой сервис (клиент или сервер). Значение этой переменной устанавливается при запуске конструктора C4Canvas().
Хотя метод start() очень важен для инициализации приложения, метод update(), приведенный в листинге 15.6, – это метод, в котором обрабатывается ввод и преобразуется в игровые события, передаваемые по сети.
Листинг 15.6. Метод update() класса C4Canvas отвечает на нажатия клавиш и отправляет игровые сообщения
private void update() {
// проверить, перезапущена ли игра
if (gameOver) {
int keyState = getKeyStates();
if ((keyState & FIRE_PRESSED) != 0) {
// начать новую игру
newGame();
// отправить сообщение о новой игре оппоненту
if (isServer)
server.sendMessage("NewGame"); //Оповестить другого игрока о начале игры
else
client.sendMessage("NewGame");
}
// игра окончена, обновление не требуется
return;
}
// обработка нажатия клавиш
if (!gameOver && myMove) {
// обработка пользовательского ввода
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED) != 0) {
if (–curSlot < 0) //Переместить маркер колонки влево
curSlot = 0;
}
else if ((keyState & RIGHT_PRESSED) != 0) {
if (++curSlot > 6) //Переместить маркер колонки вправо
curSlot = 6;
}
else if ((keyState & FIRE_PRESSED) != 0) {
if (makeMove(isServer ? 0 : 1, curSlot)) {
myMove = false; //Ход игрока окончен
// отправить сообщение другому игроку
if (isServer) //Передать информацию о ходе другому устройству
server.sendMessage(Integer.toString(curSlot));
else
client.sendMessage(Integer.toString(curSlot));
}
}
// обновить положение стрелки
arrowSprite.setPosition( //Изменить положение маркера колонки в соответствии с текущей выбранной колонкой
getWidth() * (curSlot + 1) / 8 – arrowSprite.getWidth() / 2, 21);
}
}
Метод update() начинается с проверки завершения игры и, если это так, то начинается новая игра. Обратите внимание, что перезапуск игры выполняется методом newGame(), а также отправкой сообщения NewGame другому образу игры. Если новая игра не начата, то проверяются нажатия клавиш Влево, Вправо и Огонь. Обратите внимание, что нажатия клавиш обрабатываются, пока игра запущена.
Стрелки Влево и Вправо изменяют значение переменной curSlot в соответствии с выбранной колонкой игровой доски. Код для обработки нажатия клавиши Огонь намного интереснее, ее нажатие говорит о совершении хода, в результате вызывается метод makeMove(). Об этом методе вы узнаете чуть позже. Независимо от нажатия клавиши Огонь в конце метода update() выполняется обновление спрайта стрелки в соответствии с изменениями переменной curSlot.
В листинге 15.7 приведен код метода draw(), который отвечает за графику мидлета Connect 4.
Листинг 15.7. Метод draw() класса C4Canvas отвечает за графику мидлета Connect 4
private void draw(Graphics g) {
// заполнить фон
g.setColor(128, 128, 128); // серый
g.fillRect(0, 0, getWidth(), getHeight());
// вывести статусное сообщение
g.setColor(0, 0, 0); // черные
g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM));
g.drawString(status, getWidth() / 2, 2, Graphics.TOP | Graphics.HCENTER); //Строка статусных сообщений располагается в верхней части игрового экрана
if (!gameOver && myMove) { //Вывести стрелку, если игра продолжается и ход принадлежит игроку
// вывести стрелку
arrowSprite.paint(g);
}
// вывести фишку
for (int i = 0; i < 7; i++)
for (int j = 0; j < 6; j++)
switch(gameState.board[i][j]) {
case 0:
g.drawImage(piece[0],
(getWidth() * (i + 1)) / 8 – (piece[0].getWidth() / 2), //Вывести фишки игрока сервера
((getHeight() – 33) * (6 – j)) / 7 – (piece[0].getHeight() / 2) + 33,
Graphics.TOP | Graphics.LEFT);
break;
case 1:
g.drawImage(piece[1],
(getWidth() * (i + 1)) / 8 – (piece[0].getWidth() / 2), //Вывести фишки игрока клиента
((getHeight() – 33) * (6 – j)) / 7 – (piece[1].getHeight() / 2) + 33,
Graphics.TOP | Graphics.LEFT);
break;
default:
g.setColor(255, 255, 255); // белый //Вывести свободные ячейки
g.fillArc((getWidth() * (i + 1)) / 8 – (piece[0].getWidth() / 2),
((getHeight() – 33) * (6 – j)) / 7 – (piece[0].getHeight() / 2) + 33,
piece[0].getWidth(), piece[0].getHeight(), 0, 360);
break;
}
// вывести графику на экран
flushGraphics();
}
Метод draw() начинается с заливки фона игрового экрана. Затем в нижней части экрана появляется игровое статусное сообщение. Если игра запущена и ход принадлежит игроку, то спрайт стрелки выводится под строкой статуса. Оставшаяся часть метода draw() выводит фишки и пустые ячейки на игровой доске. Значение 0 на игровой доске соответствует фишке красного цвета – игрока серверного приложения, а значение 1 – фишке синего цвета, принадлежащей игроку клиентского приложения.
Метод newGame() вызывается для запуска новой игры, его задача – инициализировать игровые переменные и обновить строку состояния. В листинге 15.8 приведен код этого метода.
Листинг 15.8. Метод newGame() класса C4Canvas запускает новую игру Connect 4
public void newGame() {
// Initialize the game variables
gameOver = false;
curSlot = 0;
gameState = new C4State();
// Update the status message
status = myMove ? "Your turn." : "Waiting for player's move...";
}
Этот код вполне очевидный, переменной gameOver присваивается значение false, переменной curSlot – 0, игровая доска обновляется при создании переменной gameState(). Затем обновляется сообщение в строке статуса в соответствии с очередностью хода.
Вы уже несколько раз видели вызов метода receiveMessage() (листинг 15.9), который отвечает за получение и обработку сообщений.
Листинг 15.9. Метод receiveMessage() класса C4Canvas получает и обрабатывает сообщения, переданные по сети
public void receiveMessage(String message) {
if (gameOver) {
// проверка сообщения о запуске новой игры
if (message.equals("NewGame")) //Если получено сообщение NewGame, то начать новую игру
newGame();
}
else {
if (!myMove) {
// попытка получить сообщение с информацией о ходе
try {
// отобразить ход соперника
int slot = Integer.parseInt(message);
if (slot >= 0 && slot <= 6) { //Проверить, что сообщение содержит допустимое значение колонки (от 0 до 6), а затем выполнить ход
if (makeMove(isServer ? 1 : 0, slot))
myMove = true;
}
}
catch (NumberFormatException nfe) {
}
}
}
}
Этот метод вызывается как клиентом, так и сервером. Он обрабатывает сообщения, отправленные соперником.
Сетевое сообщение всегда содержит один из возможных типов информации – сообщение о начале новой игры, или сообщение о номере столбца, в который соперник поставил фишку. Если получено сообщение NewGame, то запускается новая игра. Если получен номер столбца, в который был сделан ход, то он передается в метод makeMove().
Метод makeMove() – это последний интересный метод класса C4 Canvas. В листинге 15.10 приведен его код. Этот метод реализует большую часть логики мидлета Connect 4.
Листинг 15.10. Метод makeMove() класса C4Canvas отображает ходы, сделанные в игре
private boolean makeMove(int player, int slot) {
// бросить фишку
if (gameState.dropPiece(player, slot) == -1) { //Попытаться бросить фишку в колонку, в случае неудачи возвратить значение false
// воспроизвести звук неправильного хода
try {
illegalPlayer.start();
}
catch (MediaException me) {
}
return false;
}
// воспроизвести звук корректного хода
try {
legalPlayer.start();
}
catch (MediaException me) {
}
// проверить, закончена ли игра
if (gameState.isWinner(player)) {
if ((isServer && (player == 0)) || (!isServer && (player == 1))) { //Проверить, выиграл ли игрок
// воспроизвести звук победы
try {
winPlayer.start();
}
catch (MediaException me) {
}
status = "You won!";
}
else { //Игрок проиграл
// воспроизвести звук поражения
try {
losePlayer.start();
}
catch (MediaException me) {
}
status = "You lost!";
}
gameOver = true;
}
else if (gameState.isTie()) { //Проверить, закончилась ли игра ничьей
// воспроизвести звук ничьей
try {
losePlayer.start();
}
catch (MediaException me) {
}
status = "The game ended in a tie!";
gameOver = true;
}
else {
// обновить сообщение о статусе
status = myMove ? "Waiting for other player..." : "Your turn.";
}
return true;
}
Я знаю, что метод makeMove() достаточно запутан, однако он не такой сложный, как это может показаться. Во-первых, важно отметить, что два параметра, принимаемых методом, – это игрок и номер столбца, в который совершается ход. Метод начинается с вызова dropPiece(), в который передается переменная gameState. Этот метод пытается поместить фишку в выбранный столбец. Я говорю «пытается», потому что ход может быть невозможным из-за того, что столбец уже полон. В этом случае метод возвращает значение false, которое говорит о том, что ход сделать нельзя.
Если ход возможен, то метод makeMove() воспроизводит звуковой файл и проверяет, завершает ли этот ход игру. В вызываемый метод isWinner() передается объект, описывающий состояние игры, он проверяет, победил ли игрок, сделавший ход. Если да, то следует проверка, кто победил – игрок или его соперник. Затем обновляется статусное сообщение, а переменной gameOver присваивается значение true.
Игра Connect 4 может закончиться, когда на игровом поле не остается свободной ячейки, а никто из игроков не смог выставить 4 фишки в ряд. Чтобы определить ничью, достаточно вызвать метод isTie() класса C4Canvas. Метод makeMove() вызывает метод isTie() и проверяет, окончилась ли игра ничьей.
Последний фрагмент головоломки с названием Connect 4 – это класс, описывающий детали игры Connect 4, например, положение фишек на игровой доске. Класс C4State моделирует текущее состояние игры Connect 4, он содержит следующие переменные:
private static boolean[][][] map;
private int[][] score = new int[2][winPlaces];
public static final int winPlaces = 69, maxPieces = 42, Empty = 2;
private int numPieces;
public int[][] board = new int[7][6];
Чтобы упростить разбор класса C4State, давайте начнем разговор с рассмотрения переменных winPlaces, maxPieces и Empty типа static final. Такое объявление говорит о том, что эти члены класса являются константами. Приведенное ниже уравнение используется для подсчета значения константы winPlaces, определяющей число возможных выигрышных комбинаций на доске:
winPlaces = 4*w*h – 3*w*n – 3*h*n + 3*h – 4*n + 2*n*n;
Это общее уравнение, которое можно применить к любой игре типа Connect X. В этом уравнении: w и h – ширина и высота доски в ячейках соответственно, а n – число фишек, которое необходимо выставить для победы. Поскольку в игре Connect 4 используется доска размером 7 6, при этом для победы необходимо выставить 4 фишки, то вы легко можете посчитать значение winPlaces и получите число 69. Как раз именно это значение и присваивается в классе C4State.
Переменная maxPieces определяет максимальное число фишек, которое можно поставить на доску. Приведенное ниже уравнение позволяет вычислить это значение:
maxPieces = w*h;
Результат используется для определения ничьей. Ничья возникает, если все ячейки на доске заняты, а ни один из игроков не одержал победу.
Другая константа класса – это Empty, она соответствует свободному пространству на доске. Каждая ячейка на доске может содержать число 0, 1 или значение константы Empty – 2.
Возвращаясь к приведенному выше списку констант, переменная map – это трехмерный массив булевского типа, который содержит таблицу выигрышных положений. Чтобы лучше понять, как устроен массив map, представьте его двумерным массивом размером, равным размеру игровой доски. А теперь добавьте к нему третье измерение, присоединив к каждой ячейке массив выигрышных положений.
Каждая отдельная выигрышная комбинация в игре имеет свое уникальное положение в массиве (длинна массива выигрышных положений равна значению переменной winPlaces). Каждая ячейка массива содержит значение true или false в зависимости от того, лежит ли ячейка на пересечении выигрышной комбинации.
Давайте рассмотрим небольшой пример, чтобы понять, как работает массив map. Взгляните на верхний левый угол игровой доски (рис. 15.1). Давайте назовем это положение (0,0). Теперь представьте различные выигрышные комбинации, включающие это положение. Сдаетесь? Посмотрите на рис. 15.4.
Как вы видите, в положении (0,0) на доске имеет три выигрышных комбинации. Следовательно, массив для положения (0,0) отразит эти комбинации, установив значения соответствующих ячеек true, при этом значения во всех остальных ячейках будут false. Если выигрышные положения, показанные на рис. 15.4, будут в положениях 11–13, массив map инициализировался бы так:
…
map[0][0][9] = false;
map[0][0][10] = false;
map[0][0][11] = true;
map[0][0][12] = true;
map[0][0][13] = true;
map[0][0][14] = false;
map[0][0][15] = false;
…
После того как массив map создан, вы можете использовать его для проверки выигрышных комбинаций и определить, кто из игроков победил.
Переменная board – это целочисленный массив размером 7 6, он отражает состояние игры. Каждая ячейка может содержать одно из значений: 0, 1 (в зависимости от игрока) или Empty.
Переменная score – это двумерный целочисленный массив, хранящий счет игры. Основной массив в переменной score содержит массив для каждого из игроков длиной winPlaces. Эти массивы содержат информацию, описывающую, насколько близок каждый из игроков к потенциальной выигрышной комбинации, а также количество фишек, входящих в последовательность. Этот массив используется так: если в выигрышной комбинации нет ни одной фишки, то значение соответствующей ячейки массива равно 0. Если в комбинации появляются фишки, то значение ячейки массива становится равным 2 в степени m, где m – число фишек. Если в ячейке массива появляется число 16, это означает, что игрок одержал победу.
Совет Разработчику
Не важно полностью понимать, как класс C4State подсчитывает очки и определяет победителя. Основная цель этого примера – продемонстрировать работу сетевой мобильной игры. Хитрый код подсчета очков – это неотъемлемая часть кода, помогающая определить победителя в игре Connect 4, но она не столь важна для сетевых аспектов мидлета.
Вот и все, что касается переменных класса C4State. Теперь вы, вероятно, понимаете переменные класса и что они моделируют. Давайте перейдем к рассмотрению методов этого класса.
Конструктор класса C4State инициализирует массив map, игровую доску и массивы счета (листинг 15.11).
Листинг 15.11. Конструктор C4State() инициализирует массив map игры Connect 4 и игровую доску
public C4State() {
// инициализация map
int i, j, k, count = 0;
if (map == null) {
map = new boolean[7][6][winPlaces];
for (i = 0; i < 7; i++)
for (j = 0; j < 6; j++)
for (k = 0; k < winPlaces; k++)
map[i][j][k] = false;
// установить горизонтальные выигрышные комбинации
for (i = 0; i < 6; i++)
for (j = 0; j < 4; j++) {
for (k = 0; k < 4; k++)
map[j + k][i][count] = true;
count++;
}
// установить вертикальные выигрышные комбинации
for (i = 0; i < 7; i++)
for (j = 0; j < 3; j++) {
for (k = 0; k < 4; k++)
map[i][j + k][count] = true;
count++;
}
// установить прямые диагональные комбинации
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++) {
for (k = 0; k < 4; k++)
map[j + k][i + k][count] = true;
count++;
}
// установить обратные диагональные комбинации
for (i = 0; i < 3; i++)
for (j = 6; j >= 3; j–) {
for (k = 0; k < 4; k++)
map[j – k][i + k][count] = true;
count++;
}
}
// инициализировать доску
for (i = 0; i < 7; i++) //В начале игры на доске нет фишек
for (j = 0; j < 6; j++)
board[i][j] = Empty;
// инициализировать счет
for (i = 0; i < 2; i++)
for (j = 0; j < winPlaces; j++)
score[i][j] = 1;
numPieces = 0;
}
Несмотря на то что конструктор содержит большой фрагмент кода, в нем выполняется лишь инициализация массива возможных победных комбинаций.
Метод isWinner() класса C4State (листинг 15.12) проверяет, победил ли игрок.
Листинг 15.12. Метод isWinner() класса C4State проверяет, одержал ли игрок победу
public boolean isWinner(int player) {
// проверить, победил ли игрок
for (int i = 0; i < winPlaces; i++)
if (score[player][i] == 16) //Число 16 в массиве счета говорит о победе
return true;
return false;
}
Метод isWinner() определяет победу, проверяя элементы массива score на равенство 16.
Метод isTie() проверяет ничью в игре, для чего он просто сравнивает значения переменных numPieces и maxPieces. Если они равны, это означает, что доска заполнена. Код метода isTie() приведен в листинге 15.13.
Листинг 15.13. Метод isTie() класса C4State проверяет, закончилась ли игра ничьей
public boolean isTie() {
// проверить ничью
return (numPieces == maxPieces);
}
Метод dropPiece() помещает фишку в колонку на доске (листинг 15.14).
Листинг 15.14. Метод dropPiece() класса C4Stste размещает фрагмент в указанном месте игрового поля
public int dropPiece(int player, int xPos) {
// проверить, есть ли в колонке место
int yPos = 0;
while ((board[xPos][yPos] != Empty) && (++yPos < 6))
;
// колонка заполнена
if (yPos == 6) //Колонка заполнена, поэтому возвратить значение, соответствующее ошибке (-1)
return -1;
// в колонке есть место
board[xPos][yPos] = player;
numPieces++;
updateScore(player, xPos, yPos);
return yPos;
}
Метод dropPiece() в качестве параметра принимает координату X колонки. Сначала он проверяет, что в указанной колонке есть свободное место. Вы могли заметить, что игровая доска хранится в переменной board вверх тормашками. Такая инверсия облегчает процесс добавления фишки в колонку. Если ход возможен, то элементу массива board присваивается значение, соответствующее игроку, а значение переменной numPieces увеличивается на 1. Затем обновляется массив score, для чего вызывается метод updateScore().
Метод updateScore() класса C4State обновляет элементы массива score (листинг 15.15).
Листинг 15.15. Метод updateScore() класса C4State обновляет элементы массива score
private void updateScore(int player, int x, int y) {
// Update the score for the specified piece
for (int i = 0; i < winPlaces; i++)
if (map[x][y][i]) {
score[player][i] <<= 1;
score[1 – player][i] = 0;
}
}
Метод updateScore() устанавливает нужные значения элементов массива score в соответствие со сделанным ходом (ход определяется координатами х, y).
На этом заканчивается код класса C4Sate и код игры Connect 4. Если вы не поняли некоторые фрагменты приведенного кода, не переживайте. Цель этого примера – не научить вас определять победителя в игре Connect 4, а продемонстрировать, как построить беспроводную сетевую игру.
Совет Разработчику
Играть одновременно на клиентском и серверном устройстве очень неудобно. Однако, к счастью, игра Connect 4 – пошаговая, поэтому у вас есть время, чтобы тщательно обдумать каждый ход. В играх в реальном времени тестирование – это большая проблема, при этом возникает необходимость в помощи.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.