/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Напишем хорошо известную игру для тренировки памяти. На игровом поле расположены перевернутые картинки. Переворачивая по две, необходимо найти среди них одинаковые. Найденная пара исчезает с поля.
По задумке в программе должно быть:
Создаем новый проект Memoria.
Для игрового поля будем использовать элемент GridView. GridView - это представление данных (то есть их отображение на экране). А сами данные хранятся и передаются в View через адаптер (BaseAdapter), который связан с этим GridView. Стандартный адаптер нам не подойдет, поэтому будем писать свой. Им будет класс GridAdapter, унаследованный от стандартного класса адаптеров BaseAdapter.
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<GridView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/field"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
Создаем в проекте файл GridAdapter.java. Создаем в нем класс GridAdapter. Унаследованные от BaseAdapter классы должны определять четыре метода:
GridAdapter.java
package ru.alexanderklimov.memoria;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
public class GridAdapter extends BaseAdapter {
private Context mContext;
private Integer mCols, mRows;
public GridAdapter(Context context, int cols, int rows) {
mContext = context;
mCols = cols;
mRows = rows;
}
@Override
public int getCount() {
return mCols * mRows;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView view; // для вывода картинки
if (convertView == null)
view = new ImageView(mContext);
else
view = (ImageView) convertView;
view.setImageResource(R.drawable.ic_launcher);
return view;
}
}
Теперь в основном классе связываем наш адаптер с таблицей:
package ru.alexanderklimov.memoria;
import android.app.Activity;
import android.os.Bundle;
import android.widget.GridView;
public class MemoriaActivity extends Activity {
private GridView mGrid;
private GridAdapter mAdapter;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mGrid = (GridView)findViewById(R.id.field);
mGrid.setNumColumns(6);
mGrid.setEnabled(true);
mAdapter = new GridAdapter(this, 6, 6);
mGrid.setAdapter(mAdapter);
}
}
Запускаем проект и получаем таблицу 6*6 с одинаковой картинкой (обратная сторона) в каждой ячейке.
Нам нужны разные картинки, причем каждая картинка должна встречаться два раза. Так как наборы картинок можно будет менять, для удобства в каждом таком наборе назовем файлы следующим образом: animal0.png, animal1.png, …. animal17.png или people0.png, people1.png, …. people17.png. Теперь наборы картинок отличаются префиксом, а внутри набора цифрой. Для обращения к картинке используется его идентификатор, например R.drawable.picture. Для того чтобы выбирать картинку динамически, воспользуемся классом Resources. Для инициализации экземпляра класса используется метод getResources() из класса Context:
getIdentifier(String name, String defType, String defPackage)
где:
Таким образом записи:
Integer identifierID = R.drawable.picture;
и
Resources mRes = mContext.getResources(); Integer identifierID = mRes.getIdentifier("picture", "drawable", mContext.getPackageName());
по сути одинаковые и возвращают идентификатор файла picture.png из директории drawable.
Картинки в таблице должны располагаться в случайном порядке, для этого удобно использовать метод shuffle() класса ArrayList. ArrayList — массив для хранения данных любого типа. Добавим в массив название каждой картинки по два раза, а затем вызывем метод shuffle(), который перемешает все элементы.
package ru.alexanderklimov.memoria;
import java.util.ArrayList;
import java.util.Collections;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
public class GridAdapter extends BaseAdapter {
private Context mContext;
private Integer mCols, mRows;
private ArrayList<String> arrPict; // массив картинок
private String PictureCollection; // Префикс набора картинок
private Resources mRes; // Ресурсы приложени
public GridAdapter(Context context, int cols, int rows) {
mContext = context;
mCols = cols;
mRows = rows;
arrPict = new ArrayList<String>();
// Пока определяем префикс так, позже он будет браться из настроек
PictureCollection = "animal";
// Получаем все ресурсы приложения
mRes = mContext.getResources();
// Метод заполняющий массив vecPict
makePictArray();
}
private void makePictArray () {
// очищаем вектор
arrPict.clear();
// добавляем
for (int i = 0; i < ((mCols * mRows) / 2); i++)
{
arrPict.add (PictureCollection + Integer.toString (i));
arrPict.add (PictureCollection + Integer.toString (i));
}
// перемешиваем
Collections.shuffle(arrPict);
}
@Override
public int getCount() {
return mCols * mRows;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView view; // для вывода картинки
if (convertView == null)
view = new ImageView(mContext);
else
view = (ImageView) convertView;
// Получаем идентификатор ресурса для картинки,
// которая находится в векторе vecPict на позиции position
Integer drawableId = mRes.getIdentifier(arrPict.get(position), "drawable", mContext.getPackageName());
//view.setImageResource(R.drawable.ic_launcher);
view.setImageResource(drawableId);
return view;
}
}
Запускаем. Видим вот такой вот веселенький зоопарк :)
Каждая ячейка может быть в одном из трех состояний: открытая, закрытая и удаленая (если две открытые картинки совпали, они убираются с поля). Для удобства добавим в GridAdapter константы для этих состояний:
private static final int CELL_CLOSE = 0; private static final int CELL_OPEN = 1; private static final int CELL_DELETE = -1;
Мы сделаем их в виде enum (он красивее смотрится). В адаптере создадим массив для хранения состояния каждой ячейки, вначале игры всем ячейкам проставляется статус закрыта (CELL_CLOSE). И немного исправим метод getView чтобы картинка рисовалась в зависимости от статуса:
private static enum Status {CELL_OPEN, CELL_CLOSE, CELL_DELETE};
private ArrayList<Status> arrStatus; // состояние ячеек
public GridAdapter(Context context, int cols, int rows) {
mContext = context;
mCols = cols;
mRows = rows;
arrPict = new ArrayList<String>();
// Пока определяем префикс так, позже он будет браться из настроек
PictureCollection = "animal";
// Получаем все ресурсы приложения
mRes = mContext.getResources();
// Метод заполняющий массив vecPict
makePictArray();
// Метод устанавливающий всем ячейкам статус CELL_CLOSE
closeAllCells();
}
private void closeAllCells() {
arrStatus.clear();
for (int i = 0; i < mCols * mRows; i++)
arrStatus.add(Status.CELL_CLOSE);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ImageView view; // для вывода картинки
if (convertView == null)
view = new ImageView(mContext);
else
view = (ImageView) convertView;
switch (arrStatus.get(position)) {
case CELL_OPEN:
// Получаем идентификатор ресурса для картинки,
// которая находится в векторе vecPict на позиции position
Integer drawableId = mRes.getIdentifier(arrPict.get(position),
"drawable", mContext.getPackageName());
view.setImageResource(drawableId);
break;
case CELL_CLOSE:
view.setImageResource(R.drawable.close);
break;
default:
view.setImageResource(R.drawable.none);
}
return view;
}
Теперь надо обработать нажатие на ячейку. По нажатию будем делать следующее:
Можно проверять открытые картинки после нажатия, но тогда они будут закрываться (или удаляться) слишком быстро, что не удобно.
Нажатие на ячейку в таблице обрабатывает метод setOnItemClickListener класса GridView:
mGrid.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v,
int position, long id) {
mAdapter.checkOpenCells();
mAdapter.openCell(position);
if (mAdapter.checkGameOver())
Toast.makeText(getApplicationContext(), "Игра закончена",
Toast.LENGTH_SHORT).show();
}
});
В методе setOnItemClickListener мы дергаем три метода из GridAdapter, они достаточно банальны:
public void checkOpenCells() {
int first = arrStatus.indexOf(Status.CELL_OPEN);
int second = arrStatus.lastIndexOf(Status.CELL_OPEN);
if (first == second)
return;
if (arrPict.get(first).equals (arrPict.get(second)))
{
arrStatus.set(first, Status.CELL_DELETE);
arrStatus.set(second, Status.CELL_DELETE);
}
else
{
arrStatus.set(first, Status.CELL_CLOSE);
arrStatus.set(second, Status.CELL_CLOSE);
}
return;
}
public void openCell(int position) {
if (arrStatus.get(position) != Status.CELL_DELETE)
arrStatus.set(position, Status.CELL_OPEN);
notifyDataSetChanged();
return;
}
public boolean checkGameOver() {
if (arrStatus.indexOf(Status.CELL_CLOSE) < 0)
return true;
return false;
}
Метод notifyDataSetChanged() класса BaseAdapter (мы его вызываем в public void openCell(int position)) сообщает GridView, что данные изменились и таблица перерисовывается. Казалось бы, что notifyDataSetChanged() надо вызвать и в checkOpenCells(), потому что там тоже меняется статус ячеек, но смысла в этом мало, потому что после checkOpenCells() всегда будет вызываться openCell() в которой таблица и перерисуется.
Все! Можно запускать и играть. Пока игра не ведет счета и после окончания игры просто показывает черный экран без возможности запуска новой игры. Все это мы сделаем в следующий раз.
Источник: Программирование для android - Игра для тренировки памяти