Е. Буткевич Пишем программы и игры



страница7/15
Дата22.06.2019
Размер2.79 Mb.
ТипКраткое содержание
1   2   3   4   5   6   7   8   9   10   ...   15
Глава 7. Сохранение данных и параметров приложения

  • static String[] listRecordStoresO — возвращает массив имен всех хранилищ
    мидлета;

  • voidsetRecorddntrecordld, byte[] newData. int offset. intnumBytes) —изменить
    данные, содержащиеся в записи с номером recordld; остальные параметры иден­
    тичны параметрам метода addRecord.

Доступ к данным хранилища предоставляет метод byte[] getRecorddnt reccrdID), где recordID — уникальный номер необходимой записи. То есть, чтобы получить запись, нужно точно знать ее номер. На этом этапе возникает небольшая пробле­ма: как запоминать и где хранить эти номера. Вариант сохранения номеров запи­сей в самом хранилище отпадает — по той же самой причине. На выручку прихо­дит нумератор — интерфейс RecordEnumerat i on, осуществляющий перечисление всех записей в хранилище.

RecordEnumeration — нумератор списка записей

Используя метод enumerateRecords(RecordFiIter f i 1 ter, RecordComparator comparator, boolean keepUpdated), можно получить список записей конкретного хранилища. Навигация по списку номеров осуществляется с помощью методов нумератора nextRecordldO) и previousRecordldO, которые возвращают по порядку все номера записей хранилища, или с помощью методов nextRecordO и previousRecordO, ко­торые возвращают непосредственно записи в формате bytet].

Для наглядности приведем небольшой пример кода программы, которая записы­вает в хранилище пронумерованные строки, а затем считывает их:

1mport javax.microedition.rms. RecordStore; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordStoreException: try { String str;

// открыть хранилище записей с именем "Test Storage" RecordStore recordStore = RecordStore.openRecordStore

("Test Storage", true): // получить нумератор списка записей

RecordEnumeration re - recordStore.enumerateRecords(null. null, false): // добавить в хранилище 5 записей, содержащих пронумерованные строки fordnt 1-1;1<-5:1.-м-) { str = "String no " + i;

int id = recordStore.addRecord(str.getBytes(),0.str.lengthO): System.out.println(str+" was stored with recordID = "+id):

}

// перестроить список

re.rebuildO:

// получить все записи хранилища по их номерам while (re.hasNextElementO) {

RecordEnumeration — нумератор списка записей

77


int id - re.nextRecordldO;

str = new String(recordStore.getRecord(id)):

System.out.printlnC'RecordID - "+id+": "+str);

// вернуть нумератор в исходное положение re.resetО:

// получить все записи хранилища

for (int i-1: i<=re.numRecords() : i++) {

str = new String(re.previousRecordO);

System.out.println("Record: "+str);

// освободить ресурсы нумератора re. destroyО;

// закрыть хранилище записей recordStore.closeRecordStore():

} catch(RecordStoreException rse){

System.out.println( rse.getMessageOJ; 1 Результат работы примера выглядит так (рис. 7.2).



vr J.'Mi Wireless Toolkit - 4li.|.sl,i.w






no 1 was stored with

no 2 was stored with recordID

String no 3 was stored with recordID

String no 4 was stored with recordID

String no S usa stored with

RecordID • 5: String no 5

Re c or dID = 4: String no 4 - 3: String no 3

RecordID • 2: String no 2

RecordID • 1: String no I String no 1

Record: String no 2

Record: String no 3

Record: String no 4

Record: String no S

(NtwRns|eei..:;;

Рис. 7.2. Пример операций с записями хранилища

78 Глава 7. Сохранение данных и параметров приложения

Рассмотрим методы нумератора, использованные в примере:



  • byte[ ] nextRecord() — возвращает копию следующей записи в списке. Любые дей­
    ствия с полученным массивом не повлияют на запись, содержащуюся в храни­
    лище. После вызова функции происходит продвижение по списку, то есть сле­
    дующий вызов вернет уже другую запись. Первый вызов функции возвращает
    первую в списке запись;

  • byte[] previousRecordO — возвращает копию предыдущей записи в списке. Пер­
    вый вызов функции возвращает последнюю в списке запись;

  • int nextRecordldC) — возвращает номер следующей записи в списке по тому же
    принципу, что и метод nextRecord;

  • intpreviousRecordldC) — возвращает номер предыдущей записи в списке;

  • boolean hasNextE lement O — возвращает значение true, если список содержит
    следующую запись, то есть возможно продвижение с использованием nextRecord;

  • boolean hasPreviousElementO> — возвращает значение true, если список содер­
    жит предыдущую запись, то есть возможно продвижение с использованием
    previousRecord;

  • int numRecords() — получить количество записей, содержащихся в списке;

  • void reset О — возвращает нумератор в исходное положение;

  • void rebuild() — перестроить список после того, как записи были удалены или
    добавлены в хранилище;

  • void destroy О — освобождает все ресурсы, задействованные нумератором.

RecordFilter и RecordComparator — фильтр и компаратор

Вернемся к вышеприведенному примеру. При получении списка записей в мето­де enumera teRecords в качестве параметров RecordFiHer и RecordComparator мы пере­дали nul l, тем самым получив список всех без исключения записей хранилища. На самом деле, можно отделить зерна на этапе получения списка и выбрать толь­ко необходимые записи в определенном порядке. Для достижения этих целей слу­жат два интерфейса RMS — фильтр и компаратор.

Начнем с фильтра. Чтобы выбрать из хранилища только интересующие нас за­писи, необходимо реализовать интерфейс RecordFilter и переписать его метод boolean matches (byte[] candidate), который должен проверить запись на соответ­ствие установленному нами критерию и вернуть true в положительном случае.

Компаратор — интерфейс сравнения записей, позволяющий отсортировать спи­сок в заданном нами порядке. Для этих целей необходимо реализовать интерфейс RecordComparator и переписать его метод intcompare(byte[] reel. byte[] rec2), кото­рый возвращает константы:

a RecordComparator. PRECEDES — если первая запись (reel) предшествует второй записи (гес2) согласно нашей логике;

■ RecordComparator. FOLLOWS — если первая запись следует за второй согласно ус­


тановленному нами критерию;

RecordFilter и RecordComparator фильтр и компаратор 79

■ RecordComparator. EQUIVALENT — если с точки зрения упорядочивания записи равны. Объекты классов фильтра и компаратора передаются в качестве аргументов в ме­тод хранилища записей enumerateRecords(RecordFilter filter. RecordComparator comparator. bool ean keepUpdated). Обратим внимание на третий аргумент. Разработчи­ки утверждают, что если передать значение true, то список будет автоматически пе­рестраиваться во время работы с хранилищем после добавления или удаления запи­сей. Предупреждение гласит, что использование этой опции может существенно повлиять на производительность приложения. В приведенном примере для целей синхронизации списка и хранилища был использован метод rebuildC). Эксперимен­ты с параметром можете провести сами.

Для примера использования фильтра и компаратора допустим, что хранилище содержит картотеку с информацией о высоких незамужних блондинках, а пер­вый байт каждой записи представляет их возраст. Ставим перед собой задачу: выбрать всех представительниц моложе тридцати лет в порядке возрастания. Для этого реализуем два класса, MyRecordFiHer и MyRecordComparator:

// класс фильтра

private class MyRecordFiHer implements RecordFilter {

public boolean matches(byte [] candidate)

{

// проверяем первый байт записи if(candidate[0]<30) return true: else return false;



// класс компаратора

private class MyRecordComparator implements RecordCornparator

public int compare(byte[] reel. byte[] rec2)

{

// устанавливаем порядок записей по первому байту if(recl[0]rec2[0]) return RecordComparator.FOLLOWS: return RecordComparator.EQUIVALENT:



// открыть хранилище записей с именем "Blondes"

RecordStore recordStore - RecordStore.openRecordStoreC'Blondes", true); // создать объект фильтра MyRecordFiHer filter = new MyRecordFiiterO; // создать объект компаратора

MyRecordComparator comparator = new MyRecordComparatorO ; // получить список записей согласно критерию в порядке возрастания RecordEnumeration re = recordStore.enumerateRecords (filter, comparator, false):

80 Глава 7. Сохранение данных и параметров приложения

Таким образом, мы получили список интересующих нас кандидатур, с которы­ми теперь мы можем производить любые действия, как было показано в первом

примере.

Record Listener — лови момент

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

Для того чтобы создать блок прослушивания, нужно реализовать интерфейс RecordListener, в котором объявлены следующие методы:


  • recordAddecKRecordStore recordStore, int recordld) — вызывается сразу после
    добавления в хранилище recordStore новой записи с номером recordld;

  • recordChanged(RecordStore recordStore, int recordld) — вызывается сразу после
    того, как запись с номером recordld была изменена;

  • recordDeleted(RecordStore recordStore, int recordld) — вызывается после того,
    как запись с номером recordld была удалена. Функция вызывается после уда­
    ления, поэтому не стоит пытаться получить в ней запись с номером recordld —
    в этом случае будет сформировано исключение Inval idRecordlDException.

Процедура взаимосвязи блока прослушивания и конкретного хранилища запи­сей осуществляется с помощью следующих методов хранилища: addRecord Li stener (RecordLi stener l i stener), removeRecordLi stener(RecordLi stener l i stener). Один блок прослушивания может обслуживать одновременно несколько хранилищ.

RecordStoreException — возможные проблемы

Остановимся на обработке исключительных ситуаций, которые могут возникнуть при работе с системой управления записями. Все виды исключений RMS насле­дованы от одного класса RecordStoreException, поэтому в try-блоке можно отсле­живать только объект этого класса, как и было сделано в примере.

Если же мы хотим получить более подробную информацию о возникшей ошибке или предпринять особые действия для устранения возникшей проблемы, то сле­дует добавить обработку следующих исключений:


  • Inval idRecordlDException — формируется в случае невозможности выполнения
    операции из-за некорректного номера записи;

  • RecordStoreFulIException — операция не может быть завершена, так как храни­
    лище переполнено, то есть памяти, выделенной конкретной моделью, оказа­
    лось недостаточно;

High Score — таблица рекордов 81

  • RecordStoreNotFoundException —формируется при попытке открыть хранилище
    с несуществующим именем;

  • RecordStoreNotOpenException — возникает при попытке работы с неоткрытым
    хранилищем.

Таким образом, большинства из возможных проблем можно избежать, грамотно проектируя приложение. Следует внимательно следить за своевременным откры­тием и закрытием хранилища, актуальностью номеров записей. Распространен­ная ошибка — работа со списком записей, полученным до удаления или добавле­ния некоторых записей, поэтому незабываем про метод rebuiId().

High Score — таблица рекордов

Вернемся к нашим змеям и попробуем усовершенствовать игру, применив выше­изложенное на практике. В долговременном хранилище записей будем хранить запись, состоящую из двух байт, первый из которых будет содержать текущий рекорд, а второй — уровень игры, от которого будет зависеть скорость передвиже­ния змейки. Для реализации наших идей нам потребуется импортировать пакеты поддержки хранилища данных, а также завести новые поля в классе Snake: import javax.microedition.rms. RecordStore; import javax.microedition.rms.RecordEnumeration: import javax.microedition.rms.RecordStoreException;

private RecordStore recordStore; // хранилище данных

private int recordID; // ID записи параметров

private byte level; // уровень сложности игры

private byte speed: // коэффициент вычисления задержки треда

private byte highScore: // текущий рекорд

В конструкторе класса Snake добавим считывание параметров из хранилища дан­ных или создание новой записи для первого запуска игры: // параметры для долговременного хранения // первый байт - текущий рекорд // второй байт - уровень сложности игры byte buff[] = {0.5}; try {



// открыть хранилище записей с именем "SNAKE"

recordStore = RecordStore.openRecordStore("SNAKE", true);

// получить список записей хранилища

RecordEnumeration re = recordStore.enumerateRecordstnull, null, false):

// если хранилище пусто

if (re.numRecords()==0)

// добавить новую запись параметров игры recordID = recordStore.addRecord(buff. 0. 2);

else

// получить id записи параметров игры

82 Глава 7. Сохранение данных и параметров приложения

recordID = re.nextRecprdldO:

// считать запись параметров игры

buff = recordStore.getRecord(recordID);

// первый байт - текущий рекорд

highScore = buff[0]:

// второй байт - уровень сложности игры

level = buff[l];

// установить коэффициент вычисления задержки

speed - 100;

) catch(RecordStoreException rse) { 1

Теперь при завершении игры будем сравнивать набранный результат с текущим рекордом. Сообщение о новом рекорде будем выводить красным цветом, а резуль­тат записывать в хранилище данных (рис. 7.3).

Добавим эти изменения в метод paint О:

// если поднят флаг конца игры if ( gameOverFlag ) {

// вывести сообщение конца игры

g.drawString C'GAME OVER".width/2.height/2-10.g.HCENTERfg. TOP); // строка сообщения счета игры String scoreStr;

// массив параметров для долговременного хранения byte buff[] - new byte[2]; // если текущий рекорд меньше нового результата if(highScore < snakeLen) { highScore = (byte)snakeLen: // инициализировать массив с новыми параметрами biiffTO] - highScore; buff[l] = level: try {

// записать новый рекорд в хранилище recordStore.setRecordtrecordID.buff.0.2): } catch(RecordStoreException rse) {

}

// установить цвет текста красным g.setColor(OxffOOOO):



// сформировать строку с новым рекордом scoreStr - new StringC"HIGH SCORE: "+snakeLen) ; } else

// сформировать строку со счетом scoreStr - new StringC'YOUR SCORE: "+snakeLen); U вывести счет

g.drawString(scoreStr.width/2.height/2+10.g.HCENTER|g.TOP); } else

High Score — таблица рекордов

83




Готово, можно уже запускать! Правда, играть стало гораздо интереснее? Теперь можно ста- Ffe вить новые рекорды. Именно этим параметром завлекают многие и многие игрушки, как ком­пьютерные, так и мобильные. Вспомните, как в детстве, раз за разом, вы запускали игрушку, даже такую тупую, как «Ну, погоди!» или «Тай­ны океана», и старались изо всех сил вписать свое имя в таблицу славы, побив чужой рекорд. Самые хитрые, естественно, ничего не прохо­дили, а просто вскрывали программу и припи­сывали себе любые результаты. Как это делает­ся, мы рассмотрим в одной из заключительных глав.

Заметим, что для хранения текущего рекорда у нас отведен всего один байт, то есть максималь­ный рекорд, который можно поставить, ограни­чивается длиной змеи, равной 255 элементам. Хотя с такой реализацией увеличения скорос­ти после каждого «съеденного» сердечка нам это не грозит. При изменении этой логики, возмож­но, следует добавить памяти для хранения теку­щего рекорда. При проектировании приложения всегда стоит задуматься, отвечает ли заведенный тип данных характеру хранимых данных. Как мы уже видели, тип byte может принимать значения от 0 до 255.



Рис. 7.3. Сообщение о новом рекорде

В данный момент мы реализовали хранение

только одного рекорда, а хотелось бы, конечно,

иметь таблицу лучшей десятки с их именами.

Всему свое время: пока что нам не хватает знаний о реализации ввода текста

и работе со строками, но и до этого мы обязательно доберемся.

* * *

В этой главе мы достаточно подробно рассмотрели систему управления записями RMS, которая представляет собой простую абстракцию базы данных (RecordStore), связанную с записями. Записи не имеют определенного типа, а хранятся как мас­сив байтов. Записи можно получать по уникальному идентификатору записи (ID), если он известен, или же получить список всех записей. Фильтр записей (Record Fi H er) реализует механизм запросов, с помощью которого можно выбирать толь­ко соответствующие каким-то критериям записи. Компаратор записей (Record Comparator) определяет порядок извлечения записей из списка и реализует сорти­ровку записей.

Стоит отметить, что производительность RMS даже в современных моделях достаточно низка, поэтому стоит использовать RMS только в тех случаях, когда


84 Глава 7. Сохранение данных и параметров приложения

это действительно необходимо. Есть мнение, что производительность может увеличиваться, если не обновлять некоторые элементы, а переписать полно­стью все содержимое хранилища.

Чтобы оценить производительность написанной программы, лучше воспользо­ваться реальным аппаратом, поскольку эмулятор в этом случае может не отобра­зить реальной картины. Вот, собственно, и все. Мы еще вернемся к системе уп­равления записями в одном из следующих примеров.

Глава 8

Стандартные средства пользовательского интерфейса

Если вы смогли оторваться от игры, побив все мыслимые рекорды по откармли­ванию змей, то продолжим уроки мобильного программирования. Посмотрим кри­тическим взглядом на нашу игру и подумаем, чего в ней еще не хватает. Уважа­ющий себя театр должен начинаться с гардероба, а приложение для мобильного телефона, как и ресторан, начинается с меню. Меню мы изобретать не будем, а воспользуемся готовым пользовательским интерфейсом высокого уровня.



Класс List

Простейший вариант организации меню — использование готового интерфейса высокого уровня, предоставляющего все необходимые функции навигации и вы­бора одного из пунктов. Меню представлено классом Li st, который наследован от класса Screen, то есть является объектом, готовым к демонстрации через менед­жер дисплея. Создается объект меню с помощью одного из конструкторов:

ListCString title, int listType) или

List(String title, int listType, String[] stringElements, Image[] imageElements)

Первый конструктор создает пустой объект, в который можно будет добавить не­обходимые пункты, второй же принимает пункты меню в виде массива строк stringElements. Параметр imageElements определяет массив изображений, которые можно поставить в соответствие каждому пункту меню. Массив stri ngEl ements, как и каждый его элемент, не должн быть пустым. Длина массива определяет ко­личество пунктов меню. Массив imageElements может иметь значение nul1 или со­держать такое же количество элементов, как и массив stringElements.

Параметр title задает заголовок меню, а 11stType определяет один из трех типов меню, представленных следующими константами класса Li st:

■ IMPLICIT — каждое перемещение по пунктам меню вызывает немедленное опове­щение через блок прослушивания команд. Класс Li st содержит особую команду Command SELECT_COMMAND, которая формируется при выборе нового пункта и пере­дается в качестве аргумента в метод блока прослушивания commandAction(Command, Displayable);



86 Глава 8. Стандартные средства пользовательского интерфейса

  • EXCLUSIVE — позволяет выбор единственного пункта меню; оповещения прило­
    жения не генерируются;

  • MULTIPLE — позволяет выбор нескольких пунктов меню в любом сочетании; опо­
    вещения не генерируются.

Каждый из пунктов меню может находиться в двух состояниях — выбранный или невыбранный. В режимах работы IMPLICIT и EXCLUSIVE выбранным может быть толь­ко один пункт меню, в режиме MULTIPLE выбранными могут быть несколько пунк­тов меню одновременно.

Класс List расширяет класс Screen и реализует интерфейс Choice. Все его методы определяются реализуемым интерфейсом.



Interface Choice

Интерфейс Choice определяет набор методов для элементов пользовательского интерфейса верхнего уровня, предоставляющих возможность выбора из несколь­ких предложенных вариантов (класс List и класс ChoiceGroup). Каждый из вари­антов представляется текстовой строкой и необязательным графическим изобра­жением — пиктограммой.

Для организации работы с меню интерфейс Choice определяет следующие мето­ды, реализованные в классе List:

■ int size() — возвращает количество пунктов меню. Все пункты меню индек­


сируются целыми числами от0 до size( )-l;

• int appencKString stringPart, Image imagePart) —добавляет новый пункт меню, представленный строкой stringPart и картинкой imagePart, в конец списка и возвращает присвоенный элементу индекс;



  • void insert(int elementNum, String stringPart, Image imagePart) — аналогично
    предыдущему методу, вставляет новый пункт меню в позицию списка, задан­
    ную параметром elementNum;

  • void deletednt elementNum) — удаляет пункт меню с индексом elementNum;

  • void set(int elementNum, String stringPart, Image imagePart) — изменяет пункт
    меню с индексом el ementNum в соответствии с параметрами stringPart и imagePart;

  • String getStri ngdnt elementNum) —возвращает строку, представляющую пункт
    меню с индексом elementNum;

и Image getlmage(int elementNum) — возвращает картинку, связанную с пунктом меню, имеющим индекс elementNum;

  • boolean isSelected(int elementNum) — возвращаетзначениеtrue,еслипунктменю
    с индексом elementNum является выделенным;

  • int getSe1ectedIndex() — возвращает индекс выделенного пункта меню. В ре­
    жиме MULTIPLE всегда возвращает значение -1;

  • void setSelectedlndexUnt elementNum, boolean selected) — в режиме MULTIPLE
    устанавливает пункт меню с индексом elementNum в состояние, соответству­
    ющее параметру selected. В остальных режимах параметр selected должен иметь
    значение true, при этом соответствующий пункт меню переходит в выделен-

Класс SnakeGame 87

ное состояние. Пункт, бывший до этого в выделенном состоянии, переходит в невыделенное состояние. В режиме IMPLICIT никаких неявных вызовов не происходит;



  • int getSeiectedFlags(boolean[] selectedArray_return) — в параметр selected
    Array_return возвращается массив флагов, соответствующих пунктам меню.
    Элемент массива содержит значение true, если выделен соответствующий пункт
    меню. В режиме MULTIPLE значение true может содержать произвольное число
    элементов массива, в остальных режимах — только один элемент. Сам же ме­
    тод возвращает количество выделенных элементов;

  • void setSelectedFlags(boolean[] selectedArray) — устанавливает пункты меню
    в выделенное состояние в соответствии с массивом флагов selectedArray.
    В режимах EXCLUSIVE и IMPLICIT только один элемент массива selectedArray дол­
    жен иметь значение true. Если таких элементов нет, то выделенным становит­
    ся первый пункт меню. Если элементов больше, чем один, то первый соответ­
    ствующий пункт принимает выделенное состояние, остальные игнорируются.

Класс SnakeGame

Добавим в игру стартовое меню, которое будет иметь несколько команд: начать новую игру, установить уровень сложности игры и посмотреть текущий рекорд. При запуске игры сначала будем демонстрировать меню, а по окончании игры возвращаться туда же, а не зависать намертво, как мы делали до этого. Как мы уже выяснили, для воплощения наших идей нам потребуется создать объект класса List, который будет представлять стартовое меню игры, а также реализовать ин­терфейс CommandLi stener для прослушивания команд.

Для начала нам потребуется подключить несколько пакетов, реализующих необ­ходимые классы;

import javax.microedition.lcdui.CommandLi stener: import javax.microedition.lcdui. Command: import javax.microedition.lcdui.Displayable: import javax.microedition.lcdui.List:

Интерфейс CommandListener мы реализуем в основном классе мидлета SnakeGame. Его декларация теперь будет выглядеть следующим образом:

public class SnakeGame extends MIDlet implements CommandListener

Также нам потребуются два новых члена класса, представляющих собственно объект меню, а также команда для выбора пункта меню:

private List menu; // стартовое меню

private Command ok; // команда выбора пункта меню

Дальнейшие изменения произведем в методе startAppO, из которого уберем со­здание объекта змейки и старт нового треда, а добавим создание меню и отобра­жение его на экране. Теперь стартовый метод мидлета будет выглядеть так:

public void startAppO {

// массив строк с названиями пунктов меню

String menuOptions[] - {"New Game","Set Level "."High Score"}:

88


Каталог: Техника -> Информационные%20технологии
Информационные%20технологии -> Методические рекомендации по построению систем защиты узлов интернет 1 требования к системе защиты узла интернет 2
Техника -> Учебная программа для специальности: 1-23 01 73 Средства массовой информации
Техника -> Ремонт китайских телефонов
Техника -> Для профилактики и лечения насморка Зачем промывать нос во время насморка?
Техника -> Учебная программа для специальности: 1-23 01 73 Средства массовой информации


Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   10   ...   15


База данных защищена авторским правом ©vossta.ru 2019
обратиться к администрации

    Главная страница