Пишем тетрис под Symbian UIQ3

Прежде, чем начать, желательно прочитать вот эту
статью. Она даст базовые понятия о структуре Symbian приложений.
Естественно, нужно скачать и установить UIQ3.0 SDK, например, отсюда,
и какой-нибудь IDE, чтобы не компилировать проект из
командной строки. Мы будем использовать Carbide.c++ v1.2. Эта IDE
сама генерирует большую часть кода и имеет множество других удобных функций. Естественно, Вам портебуются  знание С и хотя бы основ C++, а также желание, и наличие свободного времени..

Итак, приступим.…Запускаем Carbide.c++. Создаем новый
проект File->New->Project. В появившемся окне выбираем Symbian
OS C++ Project
.


 




Жмем Next. Попадаем в следующее окошко. Там будут перечислены
некоторые стандартные “шаблоны” проектов, в зависимости от
установленных у Вас SDK. В нашем случае необходимо выбрать 3.x GUI
Application
(UIQ3.0 SDK должен быть уже установлен).

 




В следующем окне вводим имя проекта (в нашем случае: Tetris).

 






На следующей странице – выбор конфигураций запуска Launch configurations. Для простоты, выбираем все.
Хотя, можно ограничится WINSCW и GCCE.

 




Ваш список конфигураций может отличаться от вышеприведенного.



Далее, необходимо “придумать” UID для своего приложения (ну или пару
раз нажать random. Недопустимо, чтобы
несколько приложений, установленных в телефон имели одинаковые UID’ы (В
данном случае подразумевается UID3), иначе, эти приложения просто не
будут запускаться.*)

Далее, введите имя автора (свое) и копирайт (если есть). Эти два поля являются необязательными.




В принципе, уже
можно нажать Finish. Итак, Carbide сгенерировал нам
простейшее GUI приложение, которое уже можно компилировать
и запускать в эмуляторе или на телефоне. Стоит отметить, что при
сборке проекта под эмулятор (Emulator Debug), программа автоматически
установится, и будет отображаться в главном меню эмулятора. Итак,
запустим наше приложение.… Для начала, его надо “собрать”. Делается это
при помощи кнопки build (с молоточком, на панели задач). Щелкнем на
стрелочке возле этой кнопки, и в выпадающем списке выберем Emulator
Debug
. Подождем несколько секунд. Чтобы занять себя чем-нибудь, можно
почитать сообщения в окошке “Console”, расположенном внизу. “Сборка”
проекта должна завершиться без ошибок. Чтобы запустить приложение в
эмуляторе, необходимо нажать Run (кнопка с синей стрелкой). Ждите, пока
запустится эмулятор (это достаточно долгий процесс).
Ищем наше приложение в списке установленных и запускаем
его (думаю, разобраться несложно). Итак, мы видим на экране эмулятора
стандартные элементы UIQ приложения. По нажатию на “возврат”,
приложение сворачивается, как и должно. Осталось всего чуть-чуть:
разработать “движок” нашей игры и улучшить графический интерфейс.

Все предварительные приготовления оконены. Можно
приступать непосредственно к написанию нашего приложения.



Вместо предисловия:


GUI приложение Symbian 9.x состоит из 4-х основных классов: Application, Document, AppUI, View. Каждый из них играет свою роль.


Небольшой обзор назначения файлов, которые создал нам Carbide.


Каталог data:


Tetris.rls. В нем
определены символические идентификаторы для строк, использующихся в
программе. Лучше всего строки определять именно в .rls файлах, т.к. в
этом случае, локализация программы намного упрощается.


Tetris.rss. Cодержит описание ресурсов.


Tetris_loc.rls и Tetris_loc.rss.
Их содержимое совпадает с файлами соответственного расширения, с той
лишь разницей, что там хранятся данные для “представления” приложения в
системе (название в меню, иконка и т.п.)


Tetris_reg.rss Содержит данные для “регистрации” приложения в системе.


Каталог Group:


В нем содержатся файлы, необходимые непосредственно для сборки приложения.


Каталог Images:

Содержит изображения

Каталоги Inc, Src

Содержат *.h файлы и *.cpp файлы основных классов приложения.
Кроме того, Tetris.hrh содержит идентификаторы команд, общих для кода и
ресурсов (рассмотрим позже).


Каталог sis


Содержит pkg файл, в котором описана необходимая информация для сборки sis файла.



Структура данных


Перед тем, как приступить непосредственно к написанию кода, нужно
определиться, какие классы у нас будут представлены, за что они будут
отвечать, и как они будут связаны между собой. Решим так: помимо основных, объявим еще два класса:

 

 

  1. Содержит отдельный “блок” тетриса. Назовем его TBlock**
  2. Представляет “сетку” вместе с уже “упавшими” блоками. Размеры пусть будут 20x10 блоков. Назовем его TGrid.

В классе CTetrisView, помимо “стандартных” будут содержаться:


-Объект типа TGrid, содержащий в себе всю игровую сцену


-Объект типа TBlock содержит в себе падающий в данный момент блок.


-Объект типа TPoint***. Отвечает за текущее положение падающего блока.


Начнем с описания класса TGrid:


Создаем заголовочный файл. Для этого надо щелкнуть на папке inc
слева. Выбрать из контекстного меню New->Header File. Ввести имя файла, например
"grid.h". Carbide создаст для нас уже готовый
заголовочный файл. Нам остается наполнить его содержанием.


Код:

#include <e32std.h>    //TTFixedArray

#include "block.h"

//размеры игрового поля

const TInt KGridX=10;

const TInt KGridY=20;

class TGrid

{

public:

  TFixedArray<TUint16, KGridY> iMask;

  TFixedArray<TFixedArray<TInt8, KGridX>, KGridY> iContent;

  TGrid();

  TBool DoesCollide(const TBlock &aBlock, const TPoint &aPoint) const;

  void PutBlock(const TBlock &aBlock, const TPoint &aPoint);

  void Clear();

};




Думаю, назначение методов понятно из названия. Единственное, что может быть неясно - класс TFixedArray.
Его использование ничем не отличается от обычных массивов. Различия лишь в том, что внутри класса происходит проверка диапазона, что обезопасит
нас от лишних ошибок.


Таким же способом создаем заголовочный файл для описания класса TBlock.


Код:
#include <e32std.h>

class TBlock

{

public:

  TBlock() :iType(0), iRot(0) {}

  static TInt BlockCount();

  static TBlock Block(TInt aId);

  static TBlock RandomBlock(TInt64 &aSeed);

  void Rotate(TInt aDirection);

  TInt8 RowMask(TInt aNr) const;

  TInt8 Color() const;

protected:

  TBlock(TInt aType, TInt aRot) :iType(aType), iRot(aRot) { }

private:

  TInt8 iType;

  TInt8 iRot;

};



Думаю, тут тоже все понятно. Приступим к реализации этих классов.
Необходимо в проект добавить .cpp файлы. Правой кнопкой на каталог src,
меню New->Source File. Удобство этого способа заключается в том, что нужные .cpp файлы автоматически добавляются в .mmp файл... Вводим имя -"block.cpp". Вводим:


Код:
#include <e32math.h>  //Rand

#include "block.h"

//количество блоков

const TInt NumBlocks=7;

//непосредственно сами блоки+поворот

const TUint16 bl_types[4][NumBlocks]=

//  ####     ###      ### 
   ##     ##      ## 
    ###

//             #   
  #      ##       ## 
   ##       #

  {{0x4444, 0x0e20, 0x0740, 0x06c0, 0x0c60, 0x6600, 0xe400},

   {0x0f00, 0x0644, 0x4460, 0x4620, 0x2640, 0x6600, 0x8c80},

   {0x4444, 0x0470, 0x02e0, 0x06c0, 0x0c60, 0x6600, 0x04e0},

   {0x0f00, 0x2260, 0x0622, 0x4620, 0x2640, 0x6600, 0x2620},

  };

//возвращает количество

TInt TBlock::BlockCount()

{

  return NumBlocks;

}

//конструктор

TBlock TBlock::Block(TInt aId)

{

  return TBlock(aId, 0);

}

//возвращает случайный блок

TBlock TBlock::RandomBlock(TInt64 &aSeed)

{

  return Block(Math::Rand(aSeed)%NumBlocks);

}

//поворот

void TBlock::Rotate(TInt aDir)

{

  if (dir>0) iRot++;

  if (dir<0) iRot+=3;

  iRot%=4;

}

//маска для ряда

TInt8 TBlock::RowMask(TInt aNr) const

{

  return (bl_types[iRot][iType]>>(4*aNr))&0xf;

}

//цвет фигуры

TInt8 TBlock::Color() const

{

  return iType+1;

}



Думаю, понятно. Теперь таким же образом создаем .cpp файл для реализации TGrid. Файл назовем "grid.cpp"


Код:
#include "grid.h"

//конструктор

TGrid::TGrid()

{

  Clear();

}

void TGrid::Clear()

{

  for (int i=0; i<KGridY; i++)

  {

    iMask[i]=0x003f;  // 111111b

    for (int j=0; j<KGridX; j++)

      iContent[i][j]=0;

  }

}

//возвращает истину, если блок соприкасается с другими или со стенками

bool TGrid::DoesCollide(const TBlock &aBlock, const TPoint &aPoint) const

{

  TInt i;

  for (i=aPoint.iY; i<aPoint.iY+4; i++)

  {

    if (i<0)

    {

      if ((static_cast<TUint32>(aBlock.RowMask(i-aPoint.iY))<<(12-aPoint.iX))&0xf003f)

        return ETrue;

    } else

    if (i>=KGridY)

    {

      if (aBlock.RowMask(i-aPoint.iY)) return true;

    } else

    {

      if (iMask[i]&(aBlock.RowMask(i-aPoint.iY)<<(12-aPoint.iX))) return true;

      if (aPoint.iX<0 && ((aBlock.RowMask(i-aPoint.iY)>>(4+aPoint.iX)))) return true;

    }

  }

  return EFalse;

}

void TGrid::PutBlock(const TB
lock &aBlock, const TPoint &aPoint)

{

  TInt i, j;

  TInt c=aBlock.Color();

  for (i=aPoint.iY; i<aPoint.iY+4; i++)

  {

    if (i<0) continue;

    if (i>=KGridY) break;

    TUint16 mask=aBlock.RowMask(i-aPoint.iY);

    iMask[i]|=mask<<(12-aPoint.iX);

    for (j=aPoint.iX; j<aPoint.iX+4; j++)

      if (mask&(1<<(3-j+aPoint.iX)))

        iContent[i][j]=c;

  }

}



 

Пользовательский интерфейс

Собственно, в этом разделе будем создавать пользовательский
интерфейс. Т.е. то, что пользователь программы будет видеть перед
глазами. Заодно можно проверить действие структур данных из предыдущего
раздела. Клавишами ‘4’ и ‘6’ можно двигать блоки по сторонам. ‘8’ –
фиксирует блок, ‘5’ – вращение.


Теперь посмотрим, как это можно реализовать:

Для начала, думаю, графику можно реализовать просто разноцветными
квадратиками:) Итак, приступим. Переопределим метод Draw() в нашем
CTetrisView. Именно в нем мы будем рисовать наше игровое поле.
Использовать будем методы класса TGrid, чтобы получить тип блока, и
методы класса CWindowGC: DrawLine() и DrawRect(). Необходимо получить
графический контекст текущего устройства, с помощью функции SystemGc();
Пишем
в TetrisVew.h:


Код:
protected:

     void Draw(const TRect& aRect) const;



TetrisView.cpp:


Код:

const TInt KCellSize=13;      //размер ячейки

const TInt KBoardOffset=2;   //смещение игрового поля   

const TUint32 KColors[10]=   //цвета для различных блоков

     {0xFFFFFF, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF00FF,

      0xCC00DD, 0xADBEEF, 0x000000, 0xFFFF00, 0xAAAAAA};

void CTetrisView::Draw(const TRect& /*aRect*/) const

{

//получаем графический контекст

 CWindowGc &gc=SystemGc();

 
TInt i, j;

//в массиве содержится один ряд

 TFixedArray<TInt8, KGridX> arr;

//устанавливаем цвет пера и цвет заливки

 gc.SetPenColor(TRgb(0));

 gc.SetBrushStyle(CWindowGc::ESolidBrush);

 for (i=0; i<KGridY; i++)

  {

    GetRowContent(i, arr);      

    for (j=0; j<KGridX; j++)      //проходим по ячейкам ряда

    {

      gc.SetBrushColor(KColors[arr[j]]);

      if (arr[j])

        gc.DrawRect(TRect(KBoardOffset+KCellSize*j, KBoardOffset+KCellSize*i,

              KBoardOffset+KCellSize*(j+1)+1, KBoardOffset+KCellSize*(i+1)+1));

    }

  }

}



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


В TetrisView.h, раздел public:


Код:
//получает содержимое ряда под номером [i]aNr[/i] в массив [i]aRow[/i]

void GetRowContent(int aNr, TFixedArray<TInt8, KGridX> &aRow) const;

TInt CheckRows();

//создает новый блок

void NewBlock();

//сброс

void Reset();

TBool IsBlock(const TPoint &aPoint) const;

TBool MoveBlock(const TPoint &aPoint);

TBool RotateBlock(TInt aDir);

TBool FixBlock();

     

TGrid iGrid;

TBlock iCurrBlock;

TPoint iBlockPos;

TInt64 iSeed;




TPoint-стандартный класс, реализующий удобную работу с координатами. Остальное, думаю, понятно.


Приступим к руализации:

TetrisView.cpp:


Код:


void CTetrisView::Reset()

{

  iGrid.Clear();

  iBlockPos=TPoint(3, -4);

  iCurrBlock=TBlock::RandomBlock(iSeed);

}

void CTetrisView::NewBlock()

{

  iCurrBlock=TBlock::RandomBlock(iSeed);

  iBlockPos=TPoint(3,-4);

}

TBool CTetrisView::MoveBlock(const TPoint &aPoint)

{

  if (iGrid.DoesCollide(iCurrBlock, aPoint)) return EFalse;

  iBlockPos=aPoint;

  DrawDeferred();

  return ETrue;

}

TBool CTetrisView::FixBlock()

{

  TInt i;

  for (i=0; i<-iBlockPos.iY; i++)

    if (iCurrBlock.RowMask(i))

      return EFalse;

  iGrid.PutBlock(iCurrBlock, iBlockPos);

  return ETrue;

}

TInt CTetrisView::CheckRows()

{

 
TInt offset=0, i, j;

  for (i=KGridY-1; i>=0; i--)

  {

    if (iGrid.iMask[i]==0xffffU)

    {

      offset++;

      continue;

    }

    if (offset>0)

    {

      iGrid.iMask[i+offset]=iGrid.iMask[i];

      for (j=0; j<KGridX; j++)

        iGrid.iContent[i+offset][j]=iGrid.iContent[i][j];

    }

  }

  for (i=0; i<offset; i++)

  {

    iGrid.iMask[i]=0x003f;

    for (j=0; j<KGridX; j++)

      iGrid.iContent[i][j]=0;

  }

  if (offset>0) DrawDeferred();

  return offset;

}

TBool CTetrisView::RotateBlock(int aDirection)

{

  iCurrBlock.Rotate(aDirection);

  if (iGrid.DoesCollide(iCurrBlock, iBlockPos))

   {

   iCurrBlock.Rotate(-aDirection);

   return EFalse;

  &nb
sp;}

  DrawDeferred();

  return ETrue;

}

void CTetrisView::GetRowContent(int aNr,  TFixedArray<TInt8, KGridX> &aRow) const

{

  TInt i;

  for (i=0; i<KGridX; i++)

  {

    if (IsBlock(TPoint(i, aNr))) aRow[i]=iCurrBlock.Color();

      else row[i]=iGrid.iContent[aNr][i];

  }

}

TBool CTetrisView::IsBlock(const TPoint &aPoint) const

{

if (aPoint.iX>=iBlockPos.iX && aPoint.iX<iBlockPos.iX+4 &&

    aPoint.iY>=iBlockPos.iY && aPoint.iY<iBlockPos.iY+4)

      return (iCurrBlock.RowMask(aPoint.iY-iBlockPos.iY)&(1<<(3-aPoint.iX+iBlockPos.iX)))>0;

return EFalse;

}



Здесь подробнее стоит остановиться на процедуре DrawDeferred().
Собственно, этот метод производит перерисовку экрана нашего устройства.
Использование такого способа обусловлено тем, что перед тем, как что-то
рисовать, необходима некоторая предварительная инициализация оконного
сервера(Window Server). Из-за этого недопустимо просто вызвать
процедуру Draw().


В CTetrisView::ConstructL() добавим:


Код:
TTime time;

time.HomeTime();

iSeed=time.Int64();

Reset();



Это сделано для "рандомизации блоков".

Далее, большинство игр идут в полноэкранном виде. Чем же наша игра
хуже? Класс, который содержит в себе информацию о текущем наборе
различных экранных опций – TQikViewMode.. Изменения режимов происходит
с помощью методов: SetAppTitleBar(), SetButtonOrSoftkeyBar(),
SetFullScreen(), SetNormal(), SetStatusBar(), SetToolBar(). После
изменения режима нам необходимо “зафиксировать” изменения. За это
отвечает CQikViewBase::SetViewMode(). Итак, думаю, что надо “убрать”
Application Toolbar и StatusBar.


Пишем в CTetrisView::ConstructL():


Код:
TQikViewMode vm;

vm.SetAppTitleBar(EFalse);

vm.SetStatusBar(EFalse);

vm.SetButtonOrSoftkeyBar(ETrue);

SetViewModeL(vm);




Думаю, все очевидно: получаем объект класса TQikViewMode, устанавливаем различные опции, устанавливаем новый режим.


Ну вот. Наше приложение уже более-менее похоже на игру. Но есть еще пару основных моментов:

  1. Нет никакой динамики(движения)
  2. Пользователь не принимает никакого участия в процессе.


Исправим это:

Первая задача решается с помощью добавления "движка" в наше приложение. Нам необходимо позаботиться о том, чтобы блоки
смещались вниз через определенный промежуток времени. Если опускать
блок некуда, пробуем фиксировать его и пускаем новый. Если фиксировать
не получается, значит игровая область заполнена. Показываем сообщение о
проигрыше, останавливаем работу движка. Подумаем, как это можно
реализовать. Просто заключить все эти действия в один цикл и в конце
задержку поставить не получится в силу нескольких причин. Главная
причина - при таком подходе, наше приложение не сможет обрабатывать
нажатие клавиш, и наша игра теряет весь смысл. Значит, нам надо
использовать что-то другое. Этим классом является CTimer. Так как этот класс является потомком класса CActive,
то есть является активным объектом (Active Object), то нажатие
клавиш будет обрабатываться правильно. Итак, создаем новый
заголовочный файл. Назовем "TetrisEngine.h". В нем пишем:


Код:
#include <e32base.h>

class CTetrisView;

class CTetrisEngine : public CTimer

{

public:

   //стандартный "двух-фазовый" конструктор

   static CTetrisEngine* NewLC(CTetrisView *aView);

   static CTetrisEngine* NewL(CTetrisView *aView);

   

   //управление блоками

   void MoveLeft();

   void MoveRight();

   void Rotate(TInt aDirection);

   void Drop();

   

   //сброс параметров

   void Reset();

   

   //интервал между "тиками" таймера

   TInt iInterval;

   

   //множество состояний движка.

   enum TEngineState

     {

       EGameOver=0,

       EPaused,

       ERunning

     };

   TEngineState iState;

protected:

   //указатель на наш View

   CTetrisView *iView;

   //конструктор

   CTetrisEngine(CTetrisView *aView)

      :CTimer(EPriorityStandard), iInterval(500000), iView(aView) { }

   //см. ниже

   void RunL();

   void ConstructL();

};




Здесь необходимо остановиться на структуре классов активных объектов. Обязательная составляющая всех классов, наследованных от CActive-процедура RunL().
Именно она и выполняется при каждом "тике". В ней и будут происходить
основные действия (двигаем блок, проверяем на проигрыш и т.п.).
Приступим к реализации нашего движка. Создаем .cpp файл с именем
"TetrisEngine.cpp". В нем пишем:


Код:
#include <e32base.h>

#include "TetrisEngine.h"

#include "TetrisView.h"

CTetrisEngine *CTetrisEngine::NewLC(CTetrisView *aView)

   {

   CTetrisEngine *self=new (ELeave)CTetrisEngine(aView);

   CleanupStack::PushL(self);

   self->ConstructL();

   return self;

   }

CTetrisEngine *CTetrisEngine::NewL(CTetrisView *aView)

   {

   CTetrisEngine *self=CTetrisEngine::NewLC(aView);

   CleanupStack::Pop(self);

   return self;

   }

void CTetrisEn
gine::ConstructL()

   {

   CTimer::ConstructL();

   CActiveScheduler::Add(this);

   After(iInterval);

   iState=ERunning;

   }

//смещаем блоки просто прибавляя(отнимая) единицу от какой-либо координаты.

void CTetrisEngine::MoveLeft()

   {

   iView->MoveBlock(iView->iBlockPos-TPoint(1,0));

   }

void CTetrisEngine::MoveRight()

   {

   iView->MoveBlock(iView->iBlockPos+TPoint(1,0));

   }

void CTetrisEngine::Drop()

   {

   //двигаем блок вниз, пока он не зафиксируется

   while (iView->MoveBlock(iView->iBlockPos+TPoint(0,1)));

   }

void CTetrisEngine::Rotate(TInt aDirection)

   {

   iView->RotateBlock(aDirection);

   }

void CTetrisEngine::Reset()

   {

   if (iState==ERunning)

       Cancel();

   }

void CTetrisEngine::RunL()

   {

   if (!iView->MoveBlock(iView->iBlockPos+TPoint(0,1)))

      {

      //если двигать некуда, пробуем зафиксировать блок

      if (!iView->FixBlock())

         {

         //и не фиксируется. мы проиграли:(

         //выводим сообщение об ошибке

         User::InfoPrint(_L("Sorry, you lose"));

         

         iState=EGameOver;

         return;

         }

      //зафиксировали, выпускаем следующий

      iView->NewBlock();

      }

     iView->CheckRows();

     After(iInterval);

   }



Так как мы имеем Active Object, то необходимо его по особому
инициализировать. Для начала мы вызываем метод CTimer::ConstructL() для
базовой инициализации. Затем, нам необходимо добавить наш класс в
очередь "планировщика". Делается это с помощью CActiveShedulder::Add(),
куда в качестве параметра передается указатель на наш класс. Затем,
необходимо установить интервал, через который будет вызываться
процедура RunL(). "Остановка" движка происходит при помощи метода
Cancel(). Для того, чтобы иметь доступ к движку, необходимо создать
копию класса нашнго движка. Для этого воспользуемся реализованным нами
методом CTetrisEngine::NewL(), куда в качетве параметра нужно передать указатель на класс CTetrisView. Добавим в CTetrisView указатель на наш класс CTetrisEngine.


Код:
CTetrisEngine *iEngine



В CTetrisView::ConstructL()


Код:
iEngine=CTetrisEngine::NewL(this);




Раз мы создали копию, то память выделили. При закрытии приложения вся
память должна освобождаться. Насчет системных классов можно не
беспокоиться. За нас всю грязную работу продедывает Application
FrameWork. А с пользовательскими классами так уже не получится.
Необходимо вручную освобождать память. Обычно, это делается в
деструкторе класса, в котором память выделяется. В CTetrisView::~CTetrisView добавляем:


Код:
delete iEngine;




ОК, блок у нас падает. Но игра до сих пор неинтерсна по той простой
причине, что пользователь не может принимать участие в процессе.
Исправим это.


Каждое нажатие клавиш обрабатывается в методе OfferKeyEventL(), куда в
качестве параметра передаются объекты структур TKeyEvent и TEventCode.
Эта процедура должна возвращать один из двух элементов множества TKeyResponce: EKeyWasConsumed и EKeyWasNotConsumed. Назначения всего этого предлагаю посмотреть в SDK Documenation самостоятельно. Итак, нам необходимо переопределить метод OfferKeyEventL в классе CTetrisView. В TetrisView.h добавляем:


Код:
public:

TKeyResponse OfferKeyEventL(const TKeyEvent & aEvent, TEventCode aType);



TetrisView.cpp:


Код:
TKeyResponse CTetrisView::OfferKeyEventL(const TKeyEvent & aEvent, TEventCode aType)

{

if (aType == EEventKey) 

   {

   TInt code=aEvent.iCode;

   if (code=='5') iEngine->Rotate(1);

   if (code=='6') iEngine->MoveRight();

   if (code=='4') iEngine->MoveLeft();

   if (code=='8') iEngine->Drop();

   }

return EKeyWasConsumed;

}




Здесь, думаю, понятно. Если нажата клавиша, получаем код нажатой
клавиши. И в зависимости от него выполняем определенные действия.


Теперь можно пересобрать проект для эмулятора. Если все собралось без
ошибок, запускаем эмулятор, находим наш тетрис, запускаем его. Как мы
видим, у нас есть вполне рабочее приложение, в которое можно немного
поиграть:) Но видно невооруженым глазом, что нашему приложению еще
далеко до релиза. Чего стоит только надпись "Tetris" самом
центре экрана. Вобщем, дорабатывать еще много. Оставим это следующий
раз;) Удачи!

Если будут вопросы, предложения, замечания, идеи - пишите.

По материалам сайта www.mobilab.ru

Вот так будет выглядеть наша игра на эмуляторе.

.h и .cpp файлы к статье.


*Для выпуска публичной версии приложения, лучше всего будет получить гарантированно уникальный UID у SymbianSigned.com

**Почему в начале некоторых классов стоит T, а в некоторых C и
многое другое, можно узнать из “SDK Documentation”, раздел “Name
conventions”


***
Описание стандартных классов/функций и т.п можно посмотреть в “SDK Documentation”


Автор: Иван Кряк

Оригинал документа: forum.se-zone.ru